Skip to content

Commit

Permalink
feat: Anti-aliasing detection (#27)
Browse files Browse the repository at this point in the history
* Add basic antialiasing support

* Remove cache

* Update tests / add tests

* Add antialiasing flag

* fix: remove rewritePrefix (#30)

Releases configured with `"esy.release.rewritePrefix": true` cannot be installed into deep filesystem locations (the limit is around 108 characters).

* Clip antialiasing check to correct dimensions

* feat: Return diffCount and diffPercentage in nodejs bindings (#32)

* Implement parsing results output for node bindings

* Update ts typings

* Use pattern matching over union

Co-authored-by: Torben Ewert <Torben@eWert-Online.com>

* Better error message

Co-authored-by: Torben Ewert <Torben@eWert-Online.com>

* Better error handling

* Add more tests and format package.json

Co-authored-by: Torben Ewert <Torben@eWert-Online.com>

* Bump version

* Update ts

* chore: Split code into multiple opam packages (#28) (#31)

* Split into multiple opam files

* generate packages with dune-project

* set "use_standard_c_and_cxx_flags" to false

* fix public name of bin

* Add opam release github action

* ci: checkout project sources before releasing

* ci: fetch tags

* Add synopsis

* Setup user and token

* Remove --dry-run

* feat: Return diffCount and diffPercentage in nodejs bindings (#32)

* Implement parsing results output for node bindings

* Update ts typings

* Use pattern matching over union

Co-authored-by: Torben Ewert <Torben@eWert-Online.com>

* Better error message

Co-authored-by: Torben Ewert <Torben@eWert-Online.com>

* Better error handling

* Add more tests and format package.json

Co-authored-by: Torben Ewert <Torben@eWert-Online.com>

* Bump version

* Update ts

* Split into multiple opam files

* generate packages with dune-project

* set "use_standard_c_and_cxx_flags" to false

* fix public name of bin

* Add opam release github action

* ci: checkout project sources before releasing

* ci: fetch tags

* Add synopsis

* Setup user and token

* Remove --dry-run

* ci: Update token location

* ci: try cloning opam repo

* Delete release-opam.yml

investigate automatic releases on a separate branch

Co-authored-by: Dmitriy Kovalenko <dmtr.kovalenko@outlook.com>

* Add basic antialiasing support

* Remove cache

* Update tests / add tests

* Add antialiasing flag

* Clip antialiasing check to correct dimensions

* Fix tests

* Make bigarray-first IO version

* WIP: add comparing debuggers

* Commit test file

* Fix compilation

* Implement flat bigarray based architecture for antialsing

* Fix antialiasing with --diff-mask

* More precise type checking for rowPointers fields

* switch to 32bit bigarray

Co-authored-by: Dmitriy Kovalenko <dmtr.kovalenko@outlook.com>
  • Loading branch information
eWert-Online and dmtrKovalenko committed May 22, 2021
1 parent 77964ba commit 31a1680
Show file tree
Hide file tree
Showing 26 changed files with 477 additions and 30 deletions.
Binary file added .DS_Store
Binary file not shown.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Expand Up @@ -26,6 +26,8 @@
"system_error": "c",
"tuple": "c",
"type_traits": "c",
"vector": "c"
"vector": "c",
"ios": "c",
"cstddef": "c"
}
}
11 changes: 8 additions & 3 deletions bin/Main.re
@@ -1,10 +1,13 @@
open Odiff.ImageIO;
open Odiff.Diff;

let getIOModule = filename =>
let getIOModule = (filename, ~antialiasing) =>
Filename.extension(filename)
|> (
fun
| ".png" when antialiasing => (
(module ODiffIO.PureC_IO_Bigarray.IO): (module ImageIO)
)
| ".png" => ((module ODiffIO.PureC_IO.IO): (module ImageIO))
| _ => ((module ODiffIO.CamlImagesIO.IO): (module ImageIO))
);
Expand All @@ -24,9 +27,10 @@ let main =
failOnLayoutChange,
diffColorHex,
stdoutParsableString,
antialiasing,
) => {
module IO1 = (val getIOModule(img1Path));
module IO2 = (val getIOModule(img2Path));
module IO1 = (val getIOModule(img1Path, ~antialiasing));
module IO2 = (val getIOModule(img2Path, ~antialiasing));

module Diff = MakeDiff(IO1, IO2);

Expand All @@ -40,6 +44,7 @@ let main =
~outputDiffMask,
~threshold,
~failOnLayoutChange,
~antialiasing,
~diffPixel=
Color.ofHexString(diffColorHex)
|> (
Expand Down
16 changes: 15 additions & 1 deletion bin/ODiffBin.re
Expand Up @@ -76,9 +76,22 @@ let diffColor =
& opt(string, "")
& info(
["diff-color"],
~doc="Color used to highlight different pixels in the output (in hex format e.g. #cd2cc9).",
~doc=
"Color used to highlight different pixels in the output (in hex format e.g. #cd2cc9).",
)
);

let antialiasing = {
Arg.(
value
& flag
& info(
["aa", "antialiasing"],
~doc=
"With this flag enabled, antialiased pixels are not counted to the diff of an image",
)
);
};

let cmd = {
let man = [
Expand All @@ -98,6 +111,7 @@ let cmd = {
$ failOnLayout
$ diffColor
$ parsableOutput
$ antialiasing
),
Term.info(
"odiff",
Expand Down
2 changes: 2 additions & 0 deletions bin/node-bindings/odiff.d.ts
Expand Up @@ -7,6 +7,8 @@ export type ODiffOptions = {
failOnLayoutDiff: boolean;
/** Color difference threshold (from 0 to 1). Less more precise. */
threshold: number;
/** If this is true, antialiased pixels are not counted to the diff of an image */
antialiasing: boolean;
};

declare function compare(
Expand Down
4 changes: 4 additions & 0 deletions bin/node-bindings/odiff.js
Expand Up @@ -41,6 +41,10 @@ function optionsToArgs(options) {
case "diffColor":
setArgWithValue("diff-color", value);
break;

case "antialiasing":
setArgWithValue("antialiasing", value);
break;
}
});

Expand Down
Binary file added images/2x2-ff0000ff.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/__debug.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions io/CamlImagesIO.re
Expand Up @@ -22,7 +22,7 @@ module IO: ImageIO.ImageIO = {
Png.save(filename, [], Images.Rgba32(img.image));
};

let readImgColor = (x, y, img: ImageIO.img(t)) => {
let readDirectPixel = (~x, ~y, img: ImageIO.img(Rgba32.t)) => {
let (bytes, position) = Rgba32.unsafe_access(img.image, x, y);

(
Expand All @@ -33,9 +33,12 @@ module IO: ImageIO.ImageIO = {
);
};

let readImgColor = (x, y, img: ImageIO.img(t)) =>
readDirectPixel(~x, ~y, img);

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);
Expand Down
9 changes: 7 additions & 2 deletions io/PureC_IO.re
@@ -1,5 +1,5 @@
module IO: Odiff.ImageIO.ImageIO = {
type t;
type t = int;
type row =
Bigarray.Array1.t(int, Bigarray.int8_unsigned_elt, Bigarray.c_layout);

Expand All @@ -16,7 +16,8 @@ module IO: Odiff.ImageIO.ImageIO = {
};

let loadImage = (filename): Odiff.ImageIO.img(t) => {
let (width, height, rowPointers) = ReadPng.read_png_image(filename);
let (width, height, _rowbytes, rowPointers) =
ReadPng.read_png_image(filename);

{width, height, image: rowPointers};
};
Expand All @@ -32,4 +33,8 @@ module IO: Odiff.ImageIO.ImageIO = {
let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => {
{...img, image: ReadPng.create_empty_img(img.height, img.width)};
};

let readDirectPixel = (~x, ~y, _img) => {
failwith("Direct pixel access is not allowed for imperative C IO");
};
};
70 changes: 70 additions & 0 deletions io/PureC_IO_Bigarray.re
@@ -0,0 +1,70 @@
open Odiff.ImageIO;

module IO: Odiff.ImageIO.ImageIO = {
type rowPointers = int;
type t = {
rowPointers,
bigarray: Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout),
};

type row = int;
let readDirectPixel = (~x: int, ~y: int, img) => {
let pixel = (img.image.bigarray).{y * img.width + x} |> Int32.to_int;

let a = pixel lsr 24 land 0xFF;
let r = pixel lsr 16 land 0xFF;
let g = pixel lsr 8 land 0xFF;
let b = pixel land 0xFF;

(r, g, b, a);
};

let readRow = (img: Odiff.ImageIO.img(t), y): row => y;
// row is always an int, so we can read pixel directly
let readImgColor = (x, row: row, img: Odiff.ImageIO.img(t)) =>
readDirectPixel(~x, ~y=row, img);

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

let loadImage = (filename): Odiff.ImageIO.img(t) => {
let (width, height, rowbytes, rowPointers) =
ReadPng.read_png_image(filename);

let bigarray =
ReadPng.row_pointers_to_bigarray(rowPointers, rowbytes, height, width);

{
width,
height,
image: {
bigarray,
rowPointers,
},
};
};

let saveImage = (img: Odiff.ImageIO.img(t), filename) => {
ReadPng.write_png_file(
img.image.rowPointers,
img.width,
img.height,
filename,
);
};

let freeImage = (img: Odiff.ImageIO.img(t)) => {
ReadPng.free_row_pointers(img.image.rowPointers, img.height);
};

let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => {
{
...img,
image: {
rowPointers: ReadPng.create_empty_img(img.height, img.width),
bigarray: img.image.bigarray,
},
};
};
};
33 changes: 27 additions & 6 deletions io/ReadPng.c
Expand Up @@ -82,10 +82,11 @@ read_png_file_to_tuple(value file)
png_read_update_info(png, info);

row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);
int rowbytes = png_get_rowbytes(png, info);

for (int y = 0; y < height; y++)
{
row_pointers[y] = (png_byte *)malloc(png_get_rowbytes(png,info));
row_pointers[y] = (png_byte *)malloc(rowbytes);
}

png_read_image(png, row_pointers);
Expand All @@ -94,23 +95,44 @@ read_png_file_to_tuple(value file)

png_destroy_read_struct(&png, &info, NULL);

res = caml_alloc(3, 0);
res = caml_alloc(4, 0);

Store_field(res, 0, Val_int(width));
Store_field(res, 1, Val_int(height));
Store_field(res, 2, row_pointers);
Store_field(res, 2, Val_int(rowbytes));
Store_field(res, 3, row_pointers);

CAMLreturn(res);
}

CAMLprim value
row_pointers_to_bigarray(png_bytep *row_pointers, value rowbytes_val, value height_val, value width_val)
{
CAMLparam3(rowbytes_val, height_val, width_val);

int width = Int_val(width_val);
int height = Int_val(height_val);
int rowbytes = Int_val(rowbytes_val);

unsigned char *total_pixels = malloc(height * rowbytes);

for (int y = 0; y < height; y++)
{
memcpy(total_pixels + y * rowbytes, row_pointers[y], rowbytes);
}

long dims[1] = {width * height};
CAMLreturn(caml_ba_alloc(CAML_BA_INT32 | CAML_BA_C_LAYOUT, 1, total_pixels, dims));
}

CAMLprim value
create_empty_img(value height_val, value width_val)
{
CAMLparam2(height_val, width_val);
int width = Int_val(width_val);
int height = Int_val(height_val);
png_bytep *row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);;

png_bytep *row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);

for (int y = 0; y < height; y++)
{
Expand All @@ -128,7 +150,6 @@ read_row(png_bytep *row_pointers, value y_val, value img_width_val)
int img_width = Int_val(img_width_val);

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

long dims[] = {img_width * 4};
CAMLreturn(caml_ba_alloc(CAML_BA_UINT8 | CAML_BA_C_LAYOUT, 1, row, dims));
Expand Down
6 changes: 3 additions & 3 deletions io/ReadPng.ml
@@ -1,5 +1,6 @@

external read_png_image: string -> int * int * 'a = "read_png_file_to_tuple"
external read_png_image: string -> int * int * int * 'b = "read_png_file_to_tuple"
external row_pointers_to_bigarray: 'b -> int -> int -> int-> 'c = "row_pointers_to_bigarray"

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

Expand All @@ -9,5 +10,4 @@ external write_png_file: 'a -> int -> int -> string -> unit = "write_png_file" [

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

external create_empty_img: int -> int -> 'a = "create_empty_img" [@@noalloc]

external create_empty_img: int -> int -> 'a = "create_empty_img" [@@noalloc]
2 changes: 1 addition & 1 deletion io/dune
Expand Up @@ -5,7 +5,7 @@
(-w -40 -w +26))
(foreign_stubs (language c) (names ReadPng) (flags (:include c_flags.sexp)))
(c_library_flags (:include c_library_flags.sexp))
(libraries odiff-core camlimages.png camlimages.jpeg camlimages.tiff camlimages.xpm)
(libraries odiff-core console.lib camlimages.png camlimages.jpeg camlimages.tiff camlimages.xpm)
)

(rule
Expand Down

0 comments on commit 31a1680

Please sign in to comment.