-
-
Notifications
You must be signed in to change notification settings - Fork 84
Draft: Add buffers as input #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,49 @@ | ||
open Odiff.ImageIO; | ||
open Odiff.Diff; | ||
|
||
let getIOModule = filename => | ||
let getTypeFromFilename = filename => | ||
Filename.extension(filename) | ||
|> ( | ||
fun | ||
| ".png" => ((module ODiffIO.Png.IO): (module ImageIO)) | ||
| ".png" => `png | ||
| ".jpg" | ||
| ".jpeg" => ((module ODiffIO.Jpg.IO): (module ImageIO)) | ||
| ".bmp" => ((module ODiffIO.Bmp.IO): (module ImageIO)) | ||
| ".tiff" => ((module ODiffIO.Tiff.IO): (module ImageIO)) | ||
| ".jpeg" => `jpg | ||
| ".bmp" => `bmp | ||
| ".tiff" => `tiff | ||
| f => failwith("This format is not supported: " ++ f) | ||
); | ||
|
||
let getIOModule = | ||
fun | ||
| `png => ((module ODiffIO.Png.IO): (module ImageIO)) | ||
| `jpg => ((module ODiffIO.Jpg.IO): (module ImageIO)) | ||
| `bmp => ((module ODiffIO.Bmp.IO): (module ImageIO)) | ||
| `tiff => ((module ODiffIO.Tiff.IO): (module ImageIO)); | ||
|
||
type diffResult('output) = { | ||
exitCode: int, | ||
diff: option('output), | ||
}; | ||
|
||
let readFromStdin = () => { | ||
/* We use 65536 because that is the size of OCaml's IO buffers. */ | ||
let chunk_size = 65536; | ||
let buffer = Buffer.create(chunk_size); | ||
let rec loop = () => { | ||
Buffer.add_channel(buffer, stdin, chunk_size); | ||
loop(); | ||
}; | ||
try(loop()) { | ||
| End_of_file => Buffer.contents(buffer) | ||
}; | ||
}; | ||
|
||
let main = | ||
( | ||
img1Path, | ||
img2Path, | ||
img1, | ||
img2, | ||
img1Type, | ||
img2Type, | ||
diffPath, | ||
threshold, | ||
outputDiffMask, | ||
|
@@ -31,13 +53,54 @@ let main = | |
antialiasing, | ||
ignoreRegions, | ||
) => { | ||
module IO1 = (val getIOModule(img1Path)); | ||
module IO2 = (val getIOModule(img2Path)); | ||
let img1Type = | ||
switch (img1Type) { | ||
| `auto when img1 == "_" => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should be named stdin instead of auto There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
failwith("--base-type has to be not auto, when using buffer as input") | ||
| `auto => getTypeFromFilename(img1) | ||
| `bmp => `bmp | ||
| `jpg => `jpg | ||
| `png => `png | ||
| `tiff => `tiff | ||
}; | ||
|
||
let img2Type = | ||
switch (img2Type) { | ||
| `auto when img2 == "_" => | ||
failwith( | ||
"--compare-type has to be not auto, when using buffer as input", | ||
) | ||
| `auto => getTypeFromFilename(img2) | ||
| `bmp => `bmp | ||
| `jpg => `jpg | ||
| `png => `png | ||
| `tiff => `tiff | ||
}; | ||
|
||
module IO1 = (val getIOModule(img1Type)); | ||
module IO2 = (val getIOModule(img2Type)); | ||
|
||
module Diff = MakeDiff(IO1, IO2); | ||
|
||
let img1 = IO1.loadImage(img1Path); | ||
let img2 = IO2.loadImage(img2Path); | ||
let img1 = | ||
switch (img1) { | ||
| "_" => | ||
if (!stdoutParsableString) { | ||
print_endline("Please provide the buffer for the base image:"); | ||
}; | ||
readFromStdin() |> IO1.loadImageFromBuffer; | ||
| path => IO1.loadImageFromPath(path) | ||
}; | ||
|
||
let img2 = | ||
switch (img2) { | ||
| "_" => | ||
if (!stdoutParsableString) { | ||
print_endline("Please provide the buffer for the compare image:"); | ||
}; | ||
readFromStdin() |> IO2.loadImageFromBuffer; | ||
| path => IO2.loadImageFromPath(path) | ||
}; | ||
|
||
let {diff, exitCode} = | ||
Diff.diff( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,21 @@ | ||
open Cmdliner; | ||
|
||
let supported_formats = [ | ||
("jpg", `jpg), | ||
("png", `png), | ||
("bmp", `bmp), | ||
("tiff", `tiff), | ||
]; | ||
|
||
let underscore_or = ((parse, print)) => { | ||
let parse = s => | ||
switch (s) { | ||
| "_" => `Ok(s) | ||
| s => parse(s) | ||
}; | ||
(parse, print); | ||
}; | ||
|
||
let diffPath = | ||
Arg.( | ||
value | ||
|
@@ -10,15 +26,55 @@ let diffPath = | |
let base = | ||
Arg.( | ||
value | ||
& pos(0, file, "") | ||
& info([], ~docv="BASE", ~doc="Path to base image") | ||
& pos(0, underscore_or(non_dir_file), "_") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are now able to add either a file or an |
||
& info( | ||
[], | ||
~docv="BASE", | ||
~doc= | ||
"Path to base image (or \"_\" when you want to provide the base image from stdin)", | ||
) | ||
); | ||
|
||
let baseType = | ||
Arg.( | ||
value | ||
& opt(enum([("auto", `auto), ...supported_formats]), `auto) | ||
& info( | ||
["base-type"], | ||
~docv="FORMAT", | ||
~doc= | ||
Printf.sprintf( | ||
"The type of the base image (required to be not auto when a buffer is used as input).\nSupported values are: auto,%s", | ||
supported_formats |> List.map(fst) |> String.concat(","), | ||
), | ||
) | ||
Comment on lines
+38
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are now able to explicitly set the type of the provided input. This is required for buffer inputs. |
||
); | ||
|
||
let comp = | ||
Arg.( | ||
value | ||
& pos(1, file, "") | ||
& info([], ~docv="COMPARING", ~doc="Path to comparing image") | ||
& pos(1, underscore_or(non_dir_file), "_") | ||
& info( | ||
[], | ||
~docv="COMPARING", | ||
~doc= | ||
"Path to comparing image (or \"_\" when you want to provide the comparing image from stdin)", | ||
) | ||
); | ||
|
||
let compType = | ||
Arg.( | ||
value | ||
& opt(enum([("auto", `auto), ...supported_formats]), `auto) | ||
& info( | ||
["compare-type"], | ||
~docv="FORMAT", | ||
~doc= | ||
Printf.sprintf( | ||
"The type of the comparing image (required to be not auto when a buffer is used as input).\nSupported values are: auto,%s", | ||
supported_formats |> List.map(fst) |> String.concat(","), | ||
), | ||
) | ||
); | ||
|
||
let threshold = { | ||
|
@@ -113,14 +169,16 @@ let cmd = { | |
let man = [ | ||
`S(Manpage.s_description), | ||
`P("$(tname) is the fastest pixel-by-pixel image comparison tool."), | ||
`P("Supported image types: .png, .jpg, .jpeg, .bitmap"), | ||
`P("Supported image types: .png, .jpg, .jpeg, .bmp, .tiff"), | ||
]; | ||
|
||
( | ||
Term.( | ||
const(Main.main) | ||
$ base | ||
$ comp | ||
$ baseType | ||
$ compType | ||
$ diffPath | ||
$ threshold | ||
$ diffMask | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
export type ODiffOptions = Partial<{ | ||
/** The image type of the base image. This has to be set to the corresponding image format when using a buffer as input */ | ||
baseImageType?: 'filepath' | 'jpg' | 'png' | 'bmp' | 'tiff' = 'filepath'; | ||
/** The image type of the compare image. This has to be set to the corresponding image format when using a buffer as input */ | ||
compareImageType?: 'filepath' | 'jpg' | 'png' | 'bmp' | 'tiff' = 'filepath'; | ||
Comment on lines
+2
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really like, that you are currently able to provide a buffer as input and at the same time add "filepath" as the image type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any way too quickly read codec info from buffer? I understand why this is done looks like it may hurt performance to add a lookup over all the codecs. I think this can be done by simply duplicating overrides like this declare function compare(
baseImage: Buffer,
compareImage: buffer,
baseCodec: "..",
compareCodec: ".."
)
declare function compare(
baseImage: string,
compareImage: string,
) and then in function check the type of baseImage && compareImage There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can of course try to read in the first 4 bytes and identify the magic number. Here is a list of the valid magic numbers for different image formats: https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5. TIFF and JPG do seem to have multiple valid ones, But I think it would be simple (and probably reasonably fast) to check it. |
||
/** Color used to highlight different pixels in the output (in hex format e.g. #cd2cc9). */ | ||
diffColor: string; | ||
/** Output full diff image. */ | ||
|
@@ -21,24 +25,24 @@ export type ODiffOptions = Partial<{ | |
}>; | ||
|
||
declare function compare( | ||
basePath: string, | ||
comparePath: string, | ||
baseImage: string | Buffer, | ||
compareImage: string | Buffer, | ||
diffPath: string, | ||
options?: ODiffOptions | ||
): Promise< | ||
| { match: true } | ||
| { match: false; reason: "layout-diff" } | ||
| { match: false; reason: 'layout-diff' } | ||
| { | ||
match: false; | ||
reason: "pixel-diff"; | ||
reason: 'pixel-diff'; | ||
/** Amount of different pixels */ | ||
diffCount: number; | ||
/** Percentage of different pixels in the whole image */ | ||
diffPercentage: number; | ||
} | ||
| { | ||
match: false; | ||
reason: "file-not-exists"; | ||
reason: 'file-not-exists'; | ||
/** Errored file path */ | ||
file: string; | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are reading from stdin until there is no more data provided.
If we wanted to support both base and compare image as buffer inputs, we would need to implement a check for some kind of separator in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that is basically why I didn't implement this from scratch. Looks incredibly tough to correctly read stdin and avoid memory copying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean the performance is not bad. The only issue is knowing when the first image ends and the second one starts.
I will need to see what I can come up with to solve this problem.