Skip to content

Commit

Permalink
feat: New option for custom diff color for output (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmtrKovalenko authored Feb 13, 2021
1 parent 777f77c commit 2bc9c67
Show file tree
Hide file tree
Showing 12 changed files with 109 additions and 34 deletions.
19 changes: 19 additions & 0 deletions bin/Color.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
let ofHexString = s =>
if (String.length(s) == 4 || String.length(s) == 7) {
let short = String.length(s) == 4;
let r' = short ? String.sub(s, 1, 1) : String.sub(s, 1, 2);
let g' = short ? String.sub(s, 2, 1) : String.sub(s, 3, 2);
let b' = short ? String.sub(s, 3, 1) : String.sub(s, 5, 2);

let r = int_of_string_opt("0x" ++ r');
let g = int_of_string_opt("0x" ++ g');
let b = int_of_string_opt("0x" ++ b');

switch (r, g, b) {
| (Some(r), Some(g), Some(b)) when short => Some((16 * r + r, 16 * g + g, 16 * b + b))
| (Some(r), Some(g), Some(b)) => Some((r, g, b))
| _ => None
};
} else {
None;
};
28 changes: 23 additions & 5 deletions bin/ODiffBin.re
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let main =
threshold,
outputDiffMask,
failOnLayoutChange,
diffColorHex,
) => {
open! Odiff.ImageIO;

Expand All @@ -36,6 +37,13 @@ let main =
~outputDiffMask,
~threshold,
~failOnLayoutChange,
~diffPixel=
Color.ofHexString(diffColorHex)
|> (
fun
| Some(col) => col
| None => (255, 0, 0) // red
),
(),
)
) {
Expand Down Expand Up @@ -76,9 +84,9 @@ let main =
IO2.freeImage(img2);

switch (diffOutput) {
| Some(output) when outputDiffMask => IO1.freeImage(output)
| _ => ()
}
| Some(output) when outputDiffMask => IO1.freeImage(output)
| _ => ()
};

exit(exitCode);
};
Expand Down Expand Up @@ -123,8 +131,7 @@ let diffMask = {
& info(
["dm", "diff-mask"],
~docv="DIFF_IMAGE",
~doc=
"Output only changed pixel over transparent background.",
~doc="Output only changed pixel over transparent background.",
)
);
};
Expand All @@ -141,6 +148,16 @@ let failOnLayout =
)
);

let diffColor =
Arg.(
value
& opt(string, "")
& info(
["diff-color"],
~doc="Color used to highlight different pixels in the output (in hex format e.g. #cd2cc9).",
)
);

let cmd = {
let man = [
`S(Manpage.s_description),
Expand All @@ -157,6 +174,7 @@ let cmd = {
$ threshold
$ diffMask
$ failOnLayout
$ diffColor
),
Term.info(
"odiff",
Expand Down
2 changes: 2 additions & 0 deletions bin/node-bindings/odiff.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type ODiffOptions = {
/** Color used to highlight different pixels in the output (in hex format e.g. #cd2cc9). */
diffColor: boolean;
/** Output full diff image. */
outputDiffMask: boolean;
/** Do not compare images and produce output if images layout is different. */
Expand Down
13 changes: 8 additions & 5 deletions bin/node-bindings/odiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ function optionsToArgs(options) {
let argArray = [];

const setArgWithValue = (name, value) => {
argArray.push(`--${name}`);
argArray.push(value.toString());
argArray.push(`--${name}=${value.toString()}`);
};

const setFlag = (name, value) => {
Expand All @@ -21,10 +20,10 @@ function optionsToArgs(options) {
};

Object.entries(options).forEach((optionEntry) => {
/**
* @type {[keyof import('./odiff').ODiffOptions, unknown]}
/**
* @type {[keyof import('./odiff').ODiffOptions, unknown]}
* @ts-expect-error */
const [option, value] = optionEntry
const [option, value] = optionEntry;

switch (option) {
case "failOnLayoutDiff":
Expand All @@ -38,6 +37,10 @@ function optionsToArgs(options) {
case "threshold":
setArgWithValue("threshold", value);
break;

case "diffColor":
setArgWithValue("diff-color", value);
break;
}
});

Expand Down
6 changes: 3 additions & 3 deletions io/CamlImagesIO.re
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ module IO: ImageIO.ImageIO = {
);
};

let setImgColor = (x, y, (r, g, b, a), img: ImageIO.img(t)) => {
let setImgColor = (x, y, (r, g, b), img: ImageIO.img(t)) => {
let (bytes, position) = Rgba32.unsafe_access(img.image, x, y);

Bytes.unsafe_set(bytes, position, r |> char_of_int);
Bytes.unsafe_set(bytes, position + 1, g |> char_of_int);
Bytes.unsafe_set(bytes, position + 2, b |> char_of_int);
Bytes.unsafe_set(bytes, position + 3, a |> char_of_int);
Bytes.unsafe_set(bytes, position + 3, 255 |> char_of_int);
};

let freeImage = _ => ();
Expand Down
11 changes: 4 additions & 7 deletions io/PureC_IO.re
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ module IO: Odiff.ImageIO.ImageIO = {
(row.{x * 4}, row.{x * 4 + 1}, row.{x * 4 + 2}, row.{x * 4 + 3});
};

let setImgColor = (x, y, _pixel, img: Odiff.ImageIO.img(t)) => {
ReadPng.set_pixel_data(img.image, x, y);
let setImgColor = (x, y, pixel, img: Odiff.ImageIO.img(t)) => {
ReadPng.set_pixel_data(img.image, x, y, pixel);
};

let loadImage = (filename): Odiff.ImageIO.img(t) => {
Expand All @@ -30,9 +30,6 @@ module IO: Odiff.ImageIO.ImageIO = {
};

let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => {
{
...img,
image: ReadPng.create_empty_img(img.height, img.width)
}
{...img, image: ReadPng.create_empty_img(img.height, img.width)};
};
};
};
12 changes: 6 additions & 6 deletions io/ReadPng.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,19 @@ read_row(png_bytep *row_pointers, value y_val, value img_width_val)
}

CAMLprim value
set_pixel_data(png_bytep *row_pointers, value x_val, value y_val)
set_pixel_data(png_bytep *row_pointers, value x_val, value y_val, value pixel_val)
{
CAMLparam2(x_val, y_val);
CAMLparam3(x_val, y_val, pixel_val);

int x = Int_val(x_val);
int y = Int_val(y_val);

png_bytep row = row_pointers[y];
png_bytep px = &(row[x * 4]);

px[0] = 255;
px[1] = 0;
px[2] = 0;
px[0] = Int_val(Field(pixel_val, 0));
px[1] = Int_val(Field(pixel_val, 1));
px[2] = Int_val(Field(pixel_val, 2));
px[3] = 255;

CAMLreturn(Val_unit);
Expand Down Expand Up @@ -218,4 +218,4 @@ free_row_pointers(png_bytep *row_pointers, value height_value)
free(row_pointers);

CAMLreturn(Val_unit);
}
}
2 changes: 1 addition & 1 deletion io/ReadPng.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ external read_png_image: string -> int * int * 'a = "read_png_file_to_tuple"

external read_row: 'a -> int -> int -> 'b = "read_row"

external set_pixel_data: 'a -> int -> int -> unit = "set_pixel_data" [@@noalloc]
external set_pixel_data: 'a -> int -> int -> int * int * int -> unit = "set_pixel_data" [@@noalloc]

external write_png_file: 'a -> int -> int -> string -> unit = "write_png_file" [@@noalloc]

Expand Down
10 changes: 7 additions & 3 deletions src/Diff.re
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let redPixel = (255, 0, 0, 255);
let redPixel = (255, 0, 0)

let maxYIQPossibleDelta = 35215.;

Expand All @@ -12,6 +12,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
base: ImageIO.img(IO1.t),
comp: ImageIO.img(IO2.t),
~outputDiffMask=false,
~diffPixel:(int, int, int)=redPixel,
~threshold=0.1,
(),
) => {
Expand All @@ -21,7 +22,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {

let countDifference = (x, y) => {
diffCount := diffCount^ + 1;
diffOutput |> IO1.setImgColor(x, y, redPixel);
diffOutput |> IO1.setImgColor(x, y, diffPixel);
};

for (y in 0 to base.height - 1) {
Expand Down Expand Up @@ -62,6 +63,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
comp: ImageIO.img(IO2.t),
~outputDiffMask,
~threshold=0.1,
~diffPixel=redPixel,
~failOnLayoutChange=true,
(),
) =>
Expand All @@ -70,7 +72,9 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
&& base.height != comp.height) {
Layout;
} else {
let diffResult = compare(base, comp, ~threshold, ~outputDiffMask, ());
let diffResult =
compare(base, comp, ~threshold, ~diffPixel, ~outputDiffMask, ());

Pixel(diffResult);
};
};
5 changes: 3 additions & 2 deletions src/ImageIO.re
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type img('a) = {
image: 'a,
};

type rgb_pixel = (int, int, int);
exception ImageNotLoaded;

module type ImageIO = {
Expand All @@ -13,8 +14,8 @@ module type ImageIO = {
let loadImage: string => img(t);
let readRow: (img(t), int) => row;
let readImgColor: (int, row, img(t)) => (int, int, int, int);
let setImgColor: (int, int, (int, int, int, int), img(t)) => unit;
let setImgColor: (int, int, rgb_pixel, img(t)) => unit;
let saveImage: (img(t), string) => unit;
let freeImage: img(t) => unit;
let makeSameAsLayout: img(t) => img(t);
};
};
35 changes: 33 additions & 2 deletions test/PngTests.re
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,40 @@ describe("Png comparing", ({test, _}) => {

if (diffOfDiffPixels > 0) {
PureC_IO.IO.saveImage(diffOutput, "test/test-images/diff-output.png");
PureC_IO.IO.saveImage(diffMaskOfDiff, "test/test-images/diff-of-diff.png");
PureC_IO.IO.saveImage(
diffMaskOfDiff,
"test/test-images/diff-of-diff.png",
);
};

expect.int(diffOfDiffPixels).toBe(0);
});
});

test(
"creates the right diff output image with custom diff color",
({expect, _}) => {
let img1 = PureC_IO.IO.loadImage("test/test-images/orange.png");
let img2 = PureC_IO.IO.loadImage("test/test-images/orange_changed.png");

let (diffOutput, _) =
Diff.compare(img1, img2, ~diffPixel=(0, 255, 0), ());

let originalDiff =
PureC_IO.IO.loadImage("test/test-images/orange_diff_green.png");
let (diffMaskOfDiff, diffOfDiffPixels) =
Diff.compare(originalDiff, diffOutput, ());

if (diffOfDiffPixels > 0) {
PureC_IO.IO.saveImage(
diffOutput,
"test/test-images/diff-output-green.png",
);
PureC_IO.IO.saveImage(
diffMaskOfDiff,
"test/test-images/diff-of-diff-green.png",
);
};

expect.int(diffOfDiffPixels).toBe(0);
});
});
Binary file added test/test-images/orange_diff_green.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 2bc9c67

Please sign in to comment.