From 865eee9ef40bd13e66a91407fbe94721a044442a Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Sat, 2 Mar 2024 16:28:08 +0100 Subject: [PATCH] Rewrite in ocaml --- .env.loca | 83 ++++++++++++++++ .env.local | 83 ++++++++++++++++ .ocamlformat | 0 bin/Color.ml | 23 +++++ bin/Color.re | 19 ---- bin/Main.ml | 44 +++++++++ bin/Main.re | 86 ---------------- bin/ODiffBin.ml | 88 +++++++++++++++++ bin/ODiffBin.re | 161 ------------------------------ bin/Print.ml | 53 ++++++++++ bin/Print.re | 55 ----------- bin/dune | 5 +- io/ODiffIO.ml | 4 + io/ODiffIO.re | 4 - io/bmp/Bmp.ml | 30 ++++++ io/bmp/Bmp.mli | 3 + io/bmp/Bmp.re | 38 ------- io/bmp/Bmp.rei | 3 - io/bmp/ReadBmp.ml | 169 +++++++++++++++++++++++++++++++ io/bmp/ReadBmp.mli | 3 + io/bmp/ReadBmp.re | 208 --------------------------------------- io/bmp/ReadBmp.rei | 3 - io/config/discover.ml | 27 +++++ io/config/discover.re | 34 ------- io/config/dune | 2 +- io/jpg/Jpg.ml | 28 ++++++ io/jpg/Jpg.mli | 3 + io/jpg/Jpg.re | 56 ----------- io/jpg/Jpg.rei | 3 - io/jpg/ReadJpg.ml | 8 ++ io/jpg/ReadJpg.re | 11 --- io/png/Png.ml | 29 ++++++ io/png/Png.re | 37 ------- io/png/ReadPng.c | 28 ++---- io/png/ReadPng.ml | 6 ++ io/png/ReadPng.re | 9 -- io/png_write/WritePng.ml | 6 ++ io/png_write/WritePng.re | 6 -- io/tiff/ReadTiff.c | 27 +++-- io/tiff/ReadTiff.ml | 8 ++ io/tiff/ReadTiff.re | 11 --- io/tiff/Tiff.ml | 28 ++++++ io/tiff/Tiff.mli | 3 + io/tiff/Tiff.re | 56 ----------- io/tiff/Tiff.rei | 3 - package.json | 9 +- src/Antialiasing.ml | 89 +++++++++++++++++ src/Antialiasing.re | 124 ----------------------- src/ColorDelta.ml | 36 +++++++ src/ColorDelta.re | 48 --------- src/Diff.ml | 109 ++++++++++++++++++++ src/Diff.re | 159 ------------------------------ src/ImageIO.ml | 14 +++ src/ImageIO.re | 18 ---- src/PerfTest.ml | 11 +++ src/PerfTest.re | 21 ---- src/dune | 3 +- test/RunTests.ml | 1 + test/RunTests.re | 1 - test/TestFramework.ml | 5 + test/TestFramework.re | 7 -- test/Test_Core.ml | 82 +++++++++++++++ test/Test_Core.re | 104 -------------------- test/Test_IO_BMP.ml | 42 ++++++++ test/Test_IO_BMP.re | 57 ----------- test/Test_IO_JPG.ml | 42 ++++++++ test/Test_IO_JPG.re | 57 ----------- test/Test_IO_PNG.ml | 39 ++++++++ test/Test_IO_PNG.re | 54 ---------- test/Test_IO_TIFF.ml | 44 +++++++++ test/Test_IO_TIFF.re | 57 ----------- test/dune | 12 +-- 72 files changed, 1279 insertions(+), 1560 deletions(-) create mode 100644 .env.loca create mode 100644 .env.local create mode 100644 .ocamlformat create mode 100644 bin/Color.ml delete mode 100644 bin/Color.re create mode 100644 bin/Main.ml delete mode 100644 bin/Main.re create mode 100644 bin/ODiffBin.ml delete mode 100644 bin/ODiffBin.re create mode 100644 bin/Print.ml delete mode 100644 bin/Print.re create mode 100644 io/ODiffIO.ml delete mode 100644 io/ODiffIO.re create mode 100644 io/bmp/Bmp.ml create mode 100644 io/bmp/Bmp.mli delete mode 100644 io/bmp/Bmp.re delete mode 100644 io/bmp/Bmp.rei create mode 100644 io/bmp/ReadBmp.ml create mode 100644 io/bmp/ReadBmp.mli delete mode 100644 io/bmp/ReadBmp.re delete mode 100644 io/bmp/ReadBmp.rei create mode 100644 io/config/discover.ml delete mode 100644 io/config/discover.re create mode 100644 io/jpg/Jpg.ml create mode 100644 io/jpg/Jpg.mli delete mode 100644 io/jpg/Jpg.re delete mode 100644 io/jpg/Jpg.rei create mode 100644 io/jpg/ReadJpg.ml delete mode 100644 io/jpg/ReadJpg.re create mode 100644 io/png/Png.ml delete mode 100644 io/png/Png.re create mode 100644 io/png/ReadPng.ml delete mode 100644 io/png/ReadPng.re create mode 100644 io/png_write/WritePng.ml delete mode 100644 io/png_write/WritePng.re create mode 100644 io/tiff/ReadTiff.ml delete mode 100644 io/tiff/ReadTiff.re create mode 100644 io/tiff/Tiff.ml create mode 100644 io/tiff/Tiff.mli delete mode 100644 io/tiff/Tiff.re delete mode 100644 io/tiff/Tiff.rei create mode 100644 src/Antialiasing.ml delete mode 100644 src/Antialiasing.re create mode 100644 src/ColorDelta.ml delete mode 100644 src/ColorDelta.re create mode 100644 src/Diff.ml delete mode 100644 src/Diff.re create mode 100644 src/ImageIO.ml delete mode 100644 src/ImageIO.re create mode 100644 src/PerfTest.ml delete mode 100644 src/PerfTest.re create mode 100644 test/RunTests.ml delete mode 100644 test/RunTests.re create mode 100644 test/TestFramework.ml delete mode 100644 test/TestFramework.re create mode 100644 test/Test_Core.ml delete mode 100644 test/Test_Core.re create mode 100644 test/Test_IO_BMP.ml delete mode 100644 test/Test_IO_BMP.re create mode 100644 test/Test_IO_JPG.ml delete mode 100644 test/Test_IO_JPG.re create mode 100644 test/Test_IO_PNG.ml delete mode 100644 test/Test_IO_PNG.re create mode 100644 test/Test_IO_TIFF.ml delete mode 100644 test/Test_IO_TIFF.re diff --git a/.env.loca b/.env.loca new file mode 100644 index 00000000..cbf64daa --- /dev/null +++ b/.env.loca @@ -0,0 +1,83 @@ +AWS_ACCESS_KEY_ID=ASIAUFBYGHAIVIGXC27X +AWS_SECRET_ACCESS_KEY=1nGe9PNVkeQgF+HrWxaCGM6nV03NFfgrJtXkZOQU +AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEGsaCXVzLXdlc3QtMiJHMEUCIQD66rqo1KuXmQC/PuudXy2RekUCvSYzk7xEcCmpOVCSAQIgGYOBOAy/ouQxKSrqgVsfT2bS+2n+HAMEfSNw6ogqGC8q+AEI8///////////ARABGgwyODU3MzMxNzMyNjUiDFBd0qll58dF9vnliSrMAQggb+mVHPuiEZkxpGMJlaq7i6f3S7Q3AeMJEQcrvpimCrzBZ68i35B8hTmnlpAKSDYMaCYDYBC9Gbwm6XP8k3818UFhdUeR3ZE9CW04pniMc/mvxaGsZ/ZlmmtRn2Uy0nZJim6e8XCXYJjCUVubi5vJtzwUD0l9GF+SEPYMaMAd9Z3z4RyE+z4JXFUri+aMo3/v+/O85Up0CCtCn7mGJPOYU7rcmaIPNgLGE10orKyjVgnnwCFi0JIb9xT/I+Z9oLyE4SUuJv5deBThDzCVv5KSBjqYARVKRIrvvb7XhxiUi2O/qTpeFisiNYfYfhqFJQjgkUFd01sTu39NjHp0ZEW1uo8+O/8CAjOlTZ8/E0h+1j16mPsSe5tUEgnlrJ1JEMWN7GmJs0Mnq31s0jtt8eS+zn5Cd0hrHFr2erCI6RPGJ5vP38SjJUlslYUxvL8dzl6mDGymfngDrfSmM/eENKDMFq3U5GB1EYZGM7NP⏎ +BAT_THEME=Catppuccin-frappe +CAML_LD_LIBRARY_PATH=/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__cmdliner-opam__c__1.0.4-eb9fea1e/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__cmdliner-opam__c__1.0.4-eb9fea1e/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dune-opam__c__3.14.0-63ffdb60/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dune-opam__c__3.14.0-63ffdb60/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dune_configurator-opam__c__3.14.0-867484e2/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dune_configurator-opam__c__3.14.0-867484e2/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__merlin-opam__c__4.13-414-721030e9/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__merlin-opam__c__4.13-414-721030e9/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocaml_lsp_server-opam__c__1.17.0-dfe36634/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocaml_lsp_server-opam__c__1.17.0-dfe36634/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__odoc-opam__c__2.4.1-3c904b27/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__odoc-opam__c__2.4.1-3c904b27/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__reason-opam__c__3.9.0-73961b0c/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__reason-opam__c__3.9.0-73961b0c/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__console-6b692be4/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__console-6b692be4/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__pastel-58dd5ddf/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__pastel-58dd5ddf/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__rely-de50f06c/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__rely-de50f06c/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libjpeg-a362f00b/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libjpeg-a362f00b/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libspng-87abf33f/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libspng-87abf33f/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libtiff-24b66513/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libtiff-24b66513/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_zlib-13398823/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_zlib-13398823/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/ocaml-4.14.1000-f3f27a93/lib/ocaml/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/ocaml-4.14.1000-f3f27a93/lib/ocaml:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/refmterr-be19216b/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/refmterr-be19216b/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_ocaml__s__substs-0.0.1-f9e9eafc/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_ocaml__s__substs-0.0.1-f9e9eafc/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__base_threads-opam__c__base-78e4ca60/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__base_threads-opam__c__base-78e4ca60/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__base_unix-opam__c__base-2a6b9323/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__base_unix-opam__c__base-2a6b9323/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__csexp-opam__c__1.5.2-6261b495/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__csexp-opam__c__1.5.2-6261b495/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dot_merlin_reader-opam__c__4.9-ab014144/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dot_merlin_reader-opam__c__4.9-ab014144/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__merlin_lib-opam__c__4.13-414-94f097db/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__merlin_lib-opam__c__4.13-414-94f097db/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__yojson-opam__c__2.1.2-fde6c42e/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__yojson-opam__c__2.1.2-fde6c42e/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__astring-opam__c__0.8.5-9efd7913/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__astring-opam__c__0.8.5-9efd7913/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__camlp_streams-opam__c__5.0.1-078f8a05/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__camlp_streams-opam__c__5.0.1-078f8a05/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__chrome_trace-opam__c__3.14.0-665b45df/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__chrome_trace-opam__c__3.14.0-665b45df/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dune_build_info-opam__c__3.14.0-345e6189/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dune_build_info-opam__c__3.14.0-345e6189/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dune_rpc-opam__c__3.14.0-97b20380/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dune_rpc-opam__c__3.14.0-97b20380/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dyn-opam__c__3.14.0-806832a1/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__dyn-opam__c__3.14.0-806832a1/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__fiber-opam__c__3.7.0-9ab4f248/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__fiber-opam__c__3.7.0-9ab4f248/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocamlc_loc-opam__c__3.14.0-16e89124/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocamlc_loc-opam__c__3.14.0-16e89124/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocamlformat_rpc_lib-opam__c__0.26.1-81e2509e/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocamlformat_rpc_lib-opam__c__0.26.1-81e2509e/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ordering-opam__c__3.14.0-55c959b9/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ordering-opam__c__3.14.0-55c959b9/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__pp-opam__c__1.2.0-58434b96/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__pp-opam__c__1.2.0-58434b96/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ppx__yojson__conv__lib-opam__c__v0.16.0-950c81a1/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ppx__yojson__conv__lib-opam__c__v0.16.0-950c81a1/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__re-opam__c__1.11.0-4823eb2b/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__re-opam__c__1.11.0-4823eb2b/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__spawn-opam__c__v0.15.1-c7bf0b39/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__spawn-opam__c__v0.15.1-c7bf0b39/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__stdune-opam__c__3.14.0-64329914/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__stdune-opam__c__3.14.0-64329914/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__uutf-opam__c__1.0.3-90ca56ef/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__uutf-opam__c__1.0.3-90ca56ef/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__xdg-opam__c__3.14.0-d7576697/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__xdg-opam__c__3.14.0-d7576697/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__cppo-opam__c__1.6.9-0e8c541e/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__cppo-opam__c__1.6.9-0e8c541e/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__crunch-opam__c__3.2.0-2f34d817/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__crunch-opam__c__3.2.0-2f34d817/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__fmt-opam__c__0.9.0-55b29939/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__fmt-opam__c__0.9.0-55b29939/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__fpath-opam__c__0.7.3-e7040396/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__fpath-opam__c__0.7.3-e7040396/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__odoc_parser-opam__c__2.4.1-32c8280a/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__odoc_parser-opam__c__2.4.1-32c8280a/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__result-opam__c__1.5-70b48049/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__result-opam__c__1.5-70b48049/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__tyxml-opam__c__4.6.0-eb1e22e2/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__tyxml-opam__c__4.6.0-eb1e22e2/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__fix-opam__c__20230505-f6c2f871/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__fix-opam__c__20230505-f6c2f871/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__menhir-opam__c__20231231-bdda99e8/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__menhir-opam__c__20231231-bdda99e8/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__merlin_extend-opam__c__0.6.1-3a98042d/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__merlin_extend-opam__c__0.6.1-3a98042d/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocamlfind-opam__c__1.9.6-05e3f8a8/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocamlfind-opam__c__1.9.6-05e3f8a8/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ppx__derivers-opam__c__1.2.1-363971b9/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ppx__derivers-opam__c__1.2.1-363971b9/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ppxlib-opam__c__0.32.1~5.2preview-8bce98d2/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ppxlib-opam__c__0.32.1~5.2preview-8bce98d2/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__stdlib_shims-opam__c__0.3.0-88dca3a2/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__stdlib_shims-opam__c__0.3.0-88dca3a2/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__cli-c68c7bf9/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__cli-c68c7bf9/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__file_context_printer-5d062a5a/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/reason_native__s__file_context_printer-5d062a5a/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_cmake-3684ff51/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_cmake-3684ff51/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__atdgen-opam__c__2.12.0-19db3bce/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__atdgen-opam__c__2.12.0-19db3bce/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__seq-opam__c__base-1b5a7835/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__seq-opam__c__base-1b5a7835/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocamlbuild-opam__c__0.14.3-49cc2d3a/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocamlbuild-opam__c__0.14.3-49cc2d3a/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__topkg-opam__c__1.0.7-65c779a4/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__topkg-opam__c__1.0.7-65c779a4/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ptime-opam__c__1.1.0-91680209/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ptime-opam__c__1.1.0-91680209/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__menhircst-opam__c__20231231-6e48f448/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__menhircst-opam__c__20231231-6e48f448/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__menhirlib-opam__c__20231231-84bc4cd0/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__menhirlib-opam__c__20231231-84bc4cd0/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__menhirsdk-opam__c__20231231-d69ff41a/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__menhirsdk-opam__c__20231231-d69ff41a/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocaml_compiler_libs-opam__c__v0.12.4-ba1a963a/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__ocaml_compiler_libs-opam__c__v0.12.4-ba1a963a/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__sexplib0-opam__c__v0.16.0-ab84cf56/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__sexplib0-opam__c__v0.16.0-ab84cf56/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__atd-opam__c__2.12.0-d03f2d0b/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__atd-opam__c__2.12.0-d03f2d0b/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__atdgen_runtime-opam__c__2.15.0-e53ca11c/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__atdgen_runtime-opam__c__2.15.0-e53ca11c/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__biniou-opam__c__1.2.2-80c0de2b/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__biniou-opam__c__1.2.2-80c0de2b/lib/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__easy_format-opam__c__1.3.4-4a178906/stublibs:/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/opam__s__easy_format-opam__c__1.3.4-4a178906/lib/stublibs:/Users/dmtrkovalenko/.config/yarn/global/node_modules/esy/3/i/esy-f489fcc4/stublibs:/Users/dmtrkovalenko/.config/yarn/global/node_modules/esy/3/i/esy-f489fcc4/lib/stublibs:/Users/dmtrkovalenko/.opam/default/lib/stublibs:/Users/dmtrkovalenko/.opam/default/lib/ocaml/stublibs:/Users/dmtrkovalenko/.opam/default/lib/ocaml +CFLAGS=-I/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libjpeg-a362f00b/include -I/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libspng-87abf33f/include -I/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libtiff-24b66513/include -I/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_zlib-13398823/include +COLORTERM=truecolor +COMMAND_MODE=unix2003 +CPATH=/opt/homebrew/include +DUNE_BUILD_DIR=/Users/dmtrkovalenko/dev/odiff/_esy/default/store/b/odiff-2efe6d04 +DUNE_STORE_ORIG_SOURCE_DIR=true +ESY__ROOT_PACKAGE_CONFIG_PATH=/Users/dmtrkovalenko/dev/odiff/package.json +FZF_DEFAULT_OPTS=--cycle --preview-window 'right:57%' --preview 'bat --style=numbers --color=always --line-range :300 {}' --bind 'ctrl-u:preview-page-up,ctrl-d:preview-page-down,k:up,j:down,ctrl-j:half-page-down,ctrl-k:half-page-up' +FZF_DEFAUL_OPTIONS=--cycle +FZF_DEFAUL_OPTS=--cycle +HOME=/Users/dmtrkovalenko +JPEG_INCLUDE_PATH=/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libjpeg-a362f00b/include +JPEG_LIB_PATH=/Users/dmtrkovalenko/.esy/3__________________________________________________________/i/esy_libjpeg-a362f00b/lib +KITTY_INSTALLATION_DIR=/Applications/kitty.app/Contents/Resources/kitty +KITTY_LISTEN_ON=unix:/tmp/kitty-47052 +KITTY_PID=47052 +KITTY_PUBLIC_KEY=1:?r>=^0!K#>Q`Z@+rr^PiR4Ylj=^0!K#>Q`Z@+rr^PiR4Ylj ( + let short = len = 4 in + let r' = + match short with true -> String.sub s 1 1 | false -> String.sub s 1 2 + in + let g' = + match short with true -> String.sub s 2 1 | false -> String.sub s 3 2 + in + let b' = + match short with true -> String.sub s 3 1 | false -> String.sub s 5 2 + in + let r = int_of_string_opt ("0x" ^ r') in + let g = int_of_string_opt ("0x" ^ g') in + let b = int_of_string_opt ("0x" ^ b') in + + match (r, g, b) with + | 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) + | _ -> None diff --git a/bin/Color.re b/bin/Color.re deleted file mode 100644 index abe35c89..00000000 --- a/bin/Color.re +++ /dev/null @@ -1,19 +0,0 @@ -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; - }; diff --git a/bin/Main.ml b/bin/Main.ml new file mode 100644 index 00000000..64ea0852 --- /dev/null +++ b/bin/Main.ml @@ -0,0 +1,44 @@ +open Odiff.ImageIO +open Odiff.Diff + +let getIOModule filename = + match Filename.extension filename with + | ".png" -> (module ODiffIO.Png.IO : ImageIO) + | ".jpg" | ".jpeg" -> (module ODiffIO.Jpg.IO : ImageIO) + | ".bmp" -> (module ODiffIO.Bmp.IO : ImageIO) + | ".tiff" -> (module ODiffIO.Tiff.IO : ImageIO) + | f -> failwith ("This format is not supported: " ^ f) + +type 'output diffResult = { exitCode : int; diff : 'output option } + +let main img1Path img2Path diffPath threshold outputDiffMask failOnLayoutChange + diffColorHex stdoutParsableString antialiasing ignoreRegions diffLines = + let module IO1 = (val getIOModule img1Path) in + let module IO2 = (val getIOModule img2Path) in + let module Diff = MakeDiff (IO1) (IO2) in + let img1 = IO1.loadImage img1Path in + let img2 = IO2.loadImage img2Path in + let { diff; exitCode } = + Diff.diff img1 img2 ~outputDiffMask ~threshold ~failOnLayoutChange + ~antialiasing ~ignoreRegions ~diffLines + ~diffPixel: + (Color.ofHexString diffColorHex |> function + | Some col -> col + | None -> (255, 0, 0)) + () + |> Print.printDiffResult stdoutParsableString + |> function + | Layout -> { diff = None; exitCode = 21 } + | Pixel (diffOutput, diffCount, stdoutParsableString, _) when diffCount = 0 + -> + { exitCode = 0; diff = Some diffOutput } + | Pixel (diffOutput, diffCount, diffPercentage, _) -> + IO1.saveImage diffOutput diffPath; + { exitCode = 22; diff = Some diffOutput } + in + IO1.freeImage img1; + IO2.freeImage img2; + (match diff with + | Some output when outputDiffMask -> IO1.freeImage output + | _ -> ()); + exit exitCode diff --git a/bin/Main.re b/bin/Main.re deleted file mode 100644 index 255b8d5b..00000000 --- a/bin/Main.re +++ /dev/null @@ -1,86 +0,0 @@ -open Odiff.ImageIO; -open Odiff.Diff; - -let getIOModule = filename => - Filename.extension(filename) - |> ( - fun - | ".png" => ((module ODiffIO.Png.IO): (module ImageIO)) - | ".jpg" - | ".jpeg" => ((module ODiffIO.Jpg.IO): (module ImageIO)) - | ".bmp" => ((module ODiffIO.Bmp.IO): (module ImageIO)) - | ".tiff" => ((module ODiffIO.Tiff.IO): (module ImageIO)) - | f => failwith("This format is not supported: " ++ f) - ); - -type diffResult('output) = { - exitCode: int, - diff: option('output), -}; - -let main = - ( - img1Path, - img2Path, - diffPath, - threshold, - outputDiffMask, - failOnLayoutChange, - diffColorHex, - stdoutParsableString, - antialiasing, - ignoreRegions, - diffLines, - ) => { - module IO1 = (val getIOModule(img1Path)); - module IO2 = (val getIOModule(img2Path)); - - module Diff = MakeDiff(IO1, IO2); - - let img1 = IO1.loadImage(img1Path); - let img2 = IO2.loadImage(img2Path); - - let {diff, exitCode} = - Diff.diff( - img1, - img2, - ~outputDiffMask, - ~threshold, - ~failOnLayoutChange, - ~antialiasing, - ~ignoreRegions, - ~diffLines, - ~diffPixel= - Color.ofHexString(diffColorHex) - |> ( - fun - | Some(col) => col - | None => (255, 0, 0) // red - ), - (), - ) - |> Print.printDiffResult(stdoutParsableString) - |> ( - fun - | Layout => {diff: None, exitCode: 21} - | Pixel((diffOutput, diffCount, stdoutParsableString, _)) - when diffCount == 0 => { - exitCode: 0, - diff: Some(diffOutput), - } - | Pixel((diffOutput, diffCount, diffPercentage, _)) => { - IO1.saveImage(diffOutput, diffPath); - {exitCode: 22, diff: Some(diffOutput)}; - } - ); - - IO1.freeImage(img1); - IO2.freeImage(img2); - - switch (diff) { - | Some(output) when outputDiffMask => IO1.freeImage(output) - | _ => () - }; - - exit(exitCode); -}; diff --git a/bin/ODiffBin.ml b/bin/ODiffBin.ml new file mode 100644 index 00000000..11aaf8ab --- /dev/null +++ b/bin/ODiffBin.ml @@ -0,0 +1,88 @@ +open Cmdliner +open Term +open Arg + +let diffPath = + value & pos 2 string "" + & info [] ~docv:"DIFF" ~doc:"Diff output path (.png only)" + +let base = + value & pos 0 file "" & info [] ~docv:"BASE" ~doc:"Path to base image" + +let comp = + value & pos 1 file "" + & info [] ~docv:"COMPARING" ~doc:"Path to comparing image" + +let threshold = + value & opt float 0.1 + & info [ "t"; "threshold" ] ~docv:"THRESHOLD" + ~doc:"Color difference threshold (from 0 to 1). Less more precise." + +let diffMask = + value & flag + & info [ "dm"; "diff-mask" ] ~docv:"DIFF_IMAGE" + ~doc:"Output only changed pixel over transparent background." + +let failOnLayout = + value & flag + & info [ "fail-on-layout" ] ~docv:"FAIL_ON_LAYOUT" + ~doc: + "Do not compare images and produce output if images layout is \ + different." + +let parsableOutput = + value & flag + & info [ "parsable-stdout" ] ~docv:"PARSABLE_OUTPUT" + ~doc:"Stdout parsable output" + +let diffColor = + value & opt string "" + & info [ "diff-color" ] + ~doc: + "Color used to highlight different pixels in the output (in hex format \ + e.g. #cd2cc9)." + +let antialiasing = + value & flag + & info [ "aa"; "antialiasing" ] + ~doc: + "With this flag enabled, antialiased pixels are not counted to the \ + diff of an image" + +let diffLines = + value & flag + & info [ "output-diff-lines" ] + ~doc: + "With this flag enabled, output result in case of different images \ + will output lines for all the different pixels" + +let ignoreRegions = + value + & opt + (list ~sep:',' (t2 ~sep:'-' (t2 ~sep:':' int int) (t2 ~sep:':' int int))) + [] + & info [ "i"; "ignore" ] + ~doc: + "An array of regions to ignore in the diff. One region looks like \ + \"x1:y1-x2:y2\". Multiple regions are separated with a ','." + +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"; + ] + in + ( const Main.main $ base $ comp $ diffPath $ threshold $ diffMask + $ failOnLayout $ diffColor $ parsableOutput $ antialiasing $ ignoreRegions + $ diffLines, + Term.info "odiff" ~version:"3.0.0" ~doc:"Find difference between 2 images." + ~exits: + (Term.exit_info 0 ~doc:"on image match" + :: Term.exit_info 21 ~doc:"on layout diff when --fail-on-layout" + :: Term.exit_info 22 ~doc:"on image pixel difference" + :: Term.default_error_exits) + ~man ) + +let () = Term.eval cmd |> Term.exit diff --git a/bin/ODiffBin.re b/bin/ODiffBin.re deleted file mode 100644 index d64e23b9..00000000 --- a/bin/ODiffBin.re +++ /dev/null @@ -1,161 +0,0 @@ -open Cmdliner; - -let diffPath = - Arg.( - value - & pos(2, string, "") - & info([], ~docv="DIFF", ~doc="Diff output path (.png only)") - ); - -let base = - Arg.( - value - & pos(0, file, "") - & info([], ~docv="BASE", ~doc="Path to base image") - ); - -let comp = - Arg.( - value - & pos(1, file, "") - & info([], ~docv="COMPARING", ~doc="Path to comparing image") - ); - -let threshold = { - Arg.( - value - & opt(float, 0.1) - & info( - ["t", "threshold"], - ~docv="THRESHOLD", - ~doc="Color difference threshold (from 0 to 1). Less more precise.", - ) - ); -}; - -let diffMask = { - Arg.( - value - & flag - & info( - ["dm", "diff-mask"], - ~docv="DIFF_IMAGE", - ~doc="Output only changed pixel over transparent background.", - ) - ); -}; - -let failOnLayout = - Arg.( - value - & flag - & info( - ["fail-on-layout"], - ~docv="FAIL_ON_LAYOUT", - ~doc= - "Do not compare images and produce output if images layout is different.", - ) - ); - -let parsableOutput = - Arg.( - value - & flag - & info( - ["parsable-stdout"], - ~docv="PARSABLE_OUTPUT", - ~doc="Stdout parsable output", - ) - ); - -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 antialiasing = { - Arg.( - value - & flag - & info( - ["aa", "antialiasing"], - ~doc= - "With this flag enabled, antialiased pixels are not counted to the diff of an image", - ) - ); -}; - -let diffLines = { - Arg.( - value - & flag - & info( - ["output-diff-lines"], - ~doc= - "With this flag enabled, output result in case of different images will output lines for all the different pixels", - ) - ); -}; - -let ignoreRegions = { - Arg.( - value - & opt( - list( - ~sep=',', - t2(~sep='-', t2(~sep=':', int, int), t2(~sep=':', int, int)), - ), - [], - ) - & info( - ["i", "ignore"], - ~doc= - "An array of regions to ignore in the diff. One region looks like \"x1:y1-x2:y2\". Multiple regions are separated with a ','.", - ) - ); -}; - -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"), - ]; - - ( - Term.( - const(Main.main) - $ base - $ comp - $ diffPath - $ threshold - $ diffMask - $ failOnLayout - $ diffColor - $ parsableOutput - $ antialiasing - $ ignoreRegions - $ diffLines - ), - Term.info( - "odiff", - ~version="2.6.1", - ~doc="Find difference between 2 images.", - ~exits=[ - Term.exit_info(0, ~doc="on image match"), - Term.exit_info(21, ~doc="on layout diff when --fail-on-layout"), - Term.exit_info(22, ~doc="on image pixel difference"), - ...Term.default_error_exits, - ], - ~man, - ), - ); -}; - -let () = Term.eval(cmd) |> Term.exit; diff --git a/bin/Print.ml b/bin/Print.ml new file mode 100644 index 00000000..239c37c9 --- /dev/null +++ b/bin/Print.ml @@ -0,0 +1,53 @@ +open Odiff.Diff + +let printDiffResult makeParsableOutput result = + (match (result, makeParsableOutput) with + | Layout, true -> "" + | Layout, false -> + Pastel.createElement + ~children: + [ + (Pastel.createElement ~color:Red ~bold:true ~children:[ "Failure!" ] + () [@JSX]); + " Images have different layout.\n"; + ] + () [@JSX] + | Pixel (_output, diffCount, _percentage, _lines), true when diffCount == 0 -> + "" + | Pixel (_output, diffCount, _percentage, _lines), false when diffCount == 0 + -> + Pastel.createElement + ~children: + [ + (Pastel.createElement ~color:Green ~bold:true + ~children:[ "Success!" ] () [@JSX]); + " Images are equal.\n"; + (Pastel.createElement ~dim:true + ~children:[ "No diff output created." ] + () [@JSX]); + ] + () [@JSX] + | Pixel (_output, diffCount, diffPercentage, stack), true + when not (Stack.is_empty stack) -> + Int.to_string diffCount ^ ";" + ^ Float.to_string diffPercentage + ^ ";" + ^ (stack + |> Stack.fold (fun acc line -> (line |> Int.to_string) ^ "," ^ acc) "") + | Pixel (_output, diffCount, diffPercentage, _), true -> + Int.to_string diffCount ^ ";" ^ Float.to_string diffPercentage + | Pixel (_output, diffCount, diffPercentage, _lines), false -> + Pastel.createElement + ~children: + [ + (Pastel.createElement ~color:Red ~bold:true ~children:[ "Failure!" ] + () [@JSX]); + " Images are different.\n"; + "Different pixels: "; + (Pastel.createElement ~color:Red ~bold:true + ~children:[ Printf.sprintf "%i (%f%%)" diffCount diffPercentage ] + () [@JSX]); + ] + () [@JSX]) + |> Console.log; + result diff --git a/bin/Print.re b/bin/Print.re deleted file mode 100644 index 996e70b9..00000000 --- a/bin/Print.re +++ /dev/null @@ -1,55 +0,0 @@ -open Odiff.Diff; - -let printDiffResult = (makeParsableOutput, result) => { - ( - switch (result, makeParsableOutput) { - | (Layout, true) => "" - | (Layout, false) => - - "Failure! " - "Images have different layout.\n" - - - // SUCCESS - | (Pixel((_output, diffCount, _percentage, _lines)), true) - when diffCount === 0 => "" - | (Pixel((_output, diffCount, _percentage, _lines)), false) - when diffCount === 0 => - - "Success! " - "Images are equal.\n" - "No diff output created." - - - // FAILURE - | (Pixel((_output, diffCount, diffPercentage, stack)), true) when !Stack.is_empty(stack) => - Int.to_string(diffCount) - ++ ";" - ++ Float.to_string(diffPercentage) - ++ ";" - ++ ( - stack - |> Stack.fold( - (acc, line) => (line |> Int.to_string) ++ "," ++ acc, - "", - ) - ) - - | (Pixel((_output, diffCount, diffPercentage, _)), true) => - Int.to_string(diffCount) ++ ";" ++ Float.to_string(diffPercentage) - - | (Pixel((_output, diffCount, diffPercentage, _lines)), false) => - - "Failure! " - "Images are different.\n" - "Different pixels: " - - {Printf.sprintf("%i (%f%%)", diffCount, diffPercentage)} - - - } - ) - |> Console.log; - - result; -}; diff --git a/bin/dune b/bin/dune index 74d00c59..78ee6dc9 100644 --- a/bin/dune +++ b/bin/dune @@ -2,5 +2,6 @@ (name ODiffBin) (public_name ODiffBin) (package odiff) - (flags (:standard -w -27)) - (libraries console.lib pastel.lib odiff-core odiff-io cmdliner)) \ No newline at end of file + (flags + (:standard -w -27)) + (libraries console.lib pastel.lib odiff-core odiff-io cmdliner)) diff --git a/io/ODiffIO.ml b/io/ODiffIO.ml new file mode 100644 index 00000000..c8c70786 --- /dev/null +++ b/io/ODiffIO.ml @@ -0,0 +1,4 @@ +module Bmp = Bmp +module Png = Png +module Jpg = Jpg +module Tiff = Tiff diff --git a/io/ODiffIO.re b/io/ODiffIO.re deleted file mode 100644 index 0b231c16..00000000 --- a/io/ODiffIO.re +++ /dev/null @@ -1,4 +0,0 @@ -module Bmp = Bmp; -module Png = Png; -module Jpg = Jpg; -module Tiff = Tiff; diff --git a/io/bmp/Bmp.ml b/io/bmp/Bmp.ml new file mode 100644 index 00000000..7612ddc1 --- /dev/null +++ b/io/bmp/Bmp.ml @@ -0,0 +1,30 @@ +open Bigarray + +type data = (int32, int32_elt, c_layout) Array1.t + +module IO : Odiff.ImageIO.ImageIO = struct + type t = data + + let loadImage filename : t Odiff.ImageIO.img = + let width, height, data = ReadBmp.load filename in + { width; height; image = data } + + let readDirectPixel ~(x : int) ~(y : int) (img : t Odiff.ImageIO.img) = + let image : data = img.image in + Array1.unsafe_get image ((y * img.width) + x) + [@@inline] + + let setImgColor ~x ~y color (img : t Odiff.ImageIO.img) = + let image : data = img.image in + Array1.unsafe_set image ((y * img.width) + x) color + [@@inline] + + let saveImage (img : t Odiff.ImageIO.img) filename = + WritePng.write_png_bigarray filename img.image img.width img.height + + let freeImage (img : t Odiff.ImageIO.img) = () + + let makeSameAsLayout (img : t Odiff.ImageIO.img) = + let image = Array1.create int32 c_layout (Array1.dim img.image) in + { img with image } +end diff --git a/io/bmp/Bmp.mli b/io/bmp/Bmp.mli new file mode 100644 index 00000000..913c029c --- /dev/null +++ b/io/bmp/Bmp.mli @@ -0,0 +1,3 @@ +type data = (int32, Bigarray.int32_elt, Bigarray.c_layout) Bigarray.Array1.t + +module IO : Odiff.ImageIO.ImageIO diff --git a/io/bmp/Bmp.re b/io/bmp/Bmp.re deleted file mode 100644 index ff5371da..00000000 --- a/io/bmp/Bmp.re +++ /dev/null @@ -1,38 +0,0 @@ -open Bigarray; - -type data = Array1.t(int32, int32_elt, c_layout); - -module IO: Odiff.ImageIO.ImageIO = { - type t = data; - - let loadImage = (filename): Odiff.ImageIO.img(t) => { - let (width, height, data) = ReadBmp.load(filename); - - {width, height, image: data}; - }; - - [@inline] - let readDirectPixel = (~x: int, ~y: int, img: Odiff.ImageIO.img(t)) => { - let image: data = img.image; - Array1.unsafe_get(image, y * img.width + x); - }; - - [@inline] - let setImgColor = (~x, ~y, color, img: Odiff.ImageIO.img(t)) => { - let image: data = img.image; - Array1.unsafe_set(image, y * img.width + x, color); - }; - - let saveImage = (img: Odiff.ImageIO.img(t), filename) => { - WritePng.write_png_bigarray(filename, img.image, img.width, img.height); - }; - - let freeImage = (img: Odiff.ImageIO.img(t)) => { - (); - }; - - let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => { - let image = Array1.create(int32, c_layout, Array1.dim(img.image)); - {...img, image}; - }; -}; diff --git a/io/bmp/Bmp.rei b/io/bmp/Bmp.rei deleted file mode 100644 index 256a8d3f..00000000 --- a/io/bmp/Bmp.rei +++ /dev/null @@ -1,3 +0,0 @@ -type data = Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout); - -module IO: Odiff.ImageIO.ImageIO; diff --git a/io/bmp/ReadBmp.ml b/io/bmp/ReadBmp.ml new file mode 100644 index 00000000..e12061f2 --- /dev/null +++ b/io/bmp/ReadBmp.ml @@ -0,0 +1,169 @@ +open Bigarray + +type bicompression = BI_RGB | BI_RLE8 | BI_RLE4 | BI_BITFIELDS +type bibitcount = Monochrome | Color16 | Color256 | ColorRGB | ColorRGBA + +type bitmapfileheader = { + bfType : int; + bfSize : int; + bfReserved1 : int; + bfReserved2 : int; + bfOffBits : int; +} + +type bitmapinfoheader = { + biSize : int; + biWidth : int; + biHeight : int; + biPlanes : int; + biBitCount : bibitcount; + biCompression : bicompression; + biSizeImage : int; + biXPelsPerMeter : int; + biYPelsPerMeter : int; + biClrUsed : int; + biClrImportant : int; +} + +type bmp = { + bmpFileHeader : bitmapfileheader; + bmpInfoHeader : bitmapinfoheader; + bmpBytes : (int32, Bigarray.int32_elt, Bigarray.c_layout) Bigarray.Array1.t; +} + +let bytes_read = ref 0 + +let read_byte ic = + incr bytes_read; + input_byte ic + +let skip_byte ic = + incr bytes_read; + ignore (input_byte ic) + +let read_16bit ic = + let b0 = read_byte ic in + let b1 = read_byte ic in + (b1 lsl 8) + b0 + +let read_32bit ic = + let b0 = read_byte ic in + let b1 = read_byte ic in + let b2 = read_byte ic in + let b3 = read_byte ic in + (b3 lsl 24) + (b2 lsl 16) + (b1 lsl 8) + b0 + +let read_bit_count ic = + match read_16bit ic with + | 1 -> Monochrome + | 4 -> Color16 + | 8 -> Color256 + | 24 -> ColorRGB + | 32 -> ColorRGBA + | n -> failwith ("invalid number of colors in bitmap: " ^ string_of_int n) + +let read_compression ic = + match read_32bit ic with + | 0 -> BI_RGB + | 1 -> BI_RLE8 + | 2 -> BI_RLE4 + | 3 -> BI_BITFIELDS + | n -> failwith ("invalid compression: " ^ string_of_int n) + +let load_bitmapfileheader ic = + let bfType = read_16bit ic in + if bfType <> 19778 then failwith "Invalid bitmap file"; + let bfSize = read_32bit ic in + let bfReserved1 = read_16bit ic in + let bfReserved2 = read_16bit ic in + let bfOffBits = read_32bit ic in + { bfType; bfSize; bfReserved1; bfReserved2; bfOffBits } + +let load_bitmapinfoheader ic = + try + let biSize = read_32bit ic in + let biWidth = read_32bit ic in + let biHeight = read_32bit ic in + let biPlanes = read_16bit ic in + let biBitCount = read_bit_count ic in + let biCompression = read_compression ic in + let biSizeImage = read_32bit ic in + let biXPelsPerMeter = read_32bit ic in + let biYPelsPerMeter = read_32bit ic in + let biClrUsed = read_32bit ic in + let biClrImportant = read_32bit ic in + { + biSize; + biWidth; + biHeight; + biPlanes; + biBitCount; + biCompression; + biSizeImage; + biXPelsPerMeter; + biYPelsPerMeter; + biClrUsed; + biClrImportant; + } + with Failure s as e -> + prerr_endline s; + raise e + +let load_image24data bih ic = + let data = Array1.create int32 c_layout (bih.biWidth * bih.biHeight) in + let pad = (4 - (bih.biWidth * 3 mod 4)) land 3 in + for y = bih.biHeight - 1 downto 0 do + for x = 0 to bih.biWidth - 1 do + let b = (read_byte ic land 255) lsl 16 in + let g = (read_byte ic land 255) lsl 8 in + let r = (read_byte ic land 255) lsl 0 in + let a = 255 lsl 24 in + Array1.set data + ((y * bih.biWidth) + x) + (Int32.of_int (a lor b lor g lor r)) + done; + for _j = 0 to pad - 1 do + skip_byte ic + done + done; + data + +let load_image32data bih ic = + let data = Array1.create int32 c_layout (bih.biWidth * bih.biHeight) in + for y = bih.biHeight - 1 downto 0 do + for x = 0 to bih.biWidth - 1 do + let b = (read_byte ic land 255) lsl 16 in + let g = (read_byte ic land 255) lsl 8 in + let r = (read_byte ic land 255) lsl 0 in + let a = (read_byte ic land 255) lsl 24 in + Array1.set data + ((y * bih.biWidth) + x) + (Int32.of_int (a lor b lor g lor r)) + done + done; + data + +let load_imagedata bih ic = + match bih.biBitCount with + | ColorRGBA -> load_image32data bih ic + | ColorRGB -> load_image24data bih ic + | _ -> failwith "BMP has to be 32 or 24 bit" + +let skip_to ic n = + while !bytes_read <> n do + skip_byte ic + done + +let read_bmp ic = + bytes_read := 0; + let bmpFileHeader = load_bitmapfileheader ic in + let bmpInfoHeader = load_bitmapinfoheader ic in + skip_to ic bmpFileHeader.bfOffBits; + let bmpBytes = load_imagedata bmpInfoHeader ic in + { bmpFileHeader; bmpInfoHeader; bmpBytes } + +let load filename = + let ic = open_in_bin filename in + let bmp = read_bmp ic in + close_in ic; + (bmp.bmpInfoHeader.biWidth, bmp.bmpInfoHeader.biHeight, bmp.bmpBytes) diff --git a/io/bmp/ReadBmp.mli b/io/bmp/ReadBmp.mli new file mode 100644 index 00000000..da354d47 --- /dev/null +++ b/io/bmp/ReadBmp.mli @@ -0,0 +1,3 @@ +val load : + string -> + int * int * (int32, Bigarray.int32_elt, Bigarray.c_layout) Bigarray.Array1.t diff --git a/io/bmp/ReadBmp.re b/io/bmp/ReadBmp.re deleted file mode 100644 index ee7583d0..00000000 --- a/io/bmp/ReadBmp.re +++ /dev/null @@ -1,208 +0,0 @@ -open Bigarray; - -type bicompression = - | BI_RGB - | BI_RLE8 - | BI_RLE4 - | BI_BITFIELDS; - -type bibitcount = - | Monochrome - | Color16 - | Color256 - | ColorRGB - | ColorRGBA; - -type bitmapfileheader = { - bfType: int, - bfSize: int, - bfReserved1: int, - bfReserved2: int, - bfOffBits: int, -}; - -type bitmapinfoheader = { - biSize: int, - biWidth: int, - biHeight: int, - biPlanes: int, - biBitCount: bibitcount, - biCompression: bicompression, - biSizeImage: int, - biXPelsPerMeter: int, - biYPelsPerMeter: int, - biClrUsed: int, - biClrImportant: int, -}; - -type bmp = { - bmpFileHeader: bitmapfileheader, - bmpInfoHeader: bitmapinfoheader, - bmpBytes: Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout), -}; - -let bytes_read = ref(0); - -let read_byte = ic => { - incr(bytes_read); - input_byte(ic); -}; -let skip_byte = ic => { - incr(bytes_read); - ignore(input_byte(ic)); -}; - -let read_16bit = ic => { - let b0 = read_byte(ic); - let b1 = read_byte(ic); - - b1 lsl 8 + b0; -}; - -let read_32bit = ic => { - let b0 = read_byte(ic); - let b1 = read_byte(ic); - let b2 = read_byte(ic); - let b3 = read_byte(ic); - - b3 lsl 24 + b2 lsl 16 + b1 lsl 8 + b0; -}; - -let read_bit_count = ic => - switch (read_16bit(ic)) { - | 1 => Monochrome - | 4 => Color16 - | 8 => Color256 - | 24 => ColorRGB - | 32 => ColorRGBA - | n => failwith("invalid number of colors in bitmap: " ++ string_of_int(n)) - }; - -let read_compression = ic => - switch (read_32bit(ic)) { - | 0 => BI_RGB - | 1 => BI_RLE8 - | 2 => BI_RLE4 - | 3 => BI_BITFIELDS - | n => failwith("invalid compression: " ++ string_of_int(n)) - }; - -let load_bitmapfileheader = ic => { - let bfType = read_16bit(ic); - if (bfType != 19778) { - failwith("Invalid bitmap file"); - }; - let bfSize = read_32bit(ic); - let bfReserved1 = read_16bit(ic); - let bfReserved2 = read_16bit(ic); - let bfOffBits = read_32bit(ic); - {bfType, bfSize, bfReserved1, bfReserved2, bfOffBits}; -}; - -let load_bitmapinfoheader = ic => - try({ - let biSize = read_32bit(ic); - let biWidth = read_32bit(ic); - let biHeight = read_32bit(ic); - let biPlanes = read_16bit(ic); - let biBitCount = read_bit_count(ic); - let biCompression = read_compression(ic); - let biSizeImage = read_32bit(ic); - let biXPelsPerMeter = read_32bit(ic); - let biYPelsPerMeter = read_32bit(ic); - let biClrUsed = read_32bit(ic); - let biClrImportant = read_32bit(ic); - { - biSize, - biWidth, - biHeight, - biPlanes, - biBitCount, - biCompression, - biSizeImage, - biXPelsPerMeter, - biYPelsPerMeter, - biClrUsed, - biClrImportant, - }; - }) { - | Failure(s) as e => - prerr_endline(s); - raise(e); - }; - -let load_image24data = (bih, ic) => { - let data = Array1.create(int32, c_layout, bih.biWidth * bih.biHeight); - let pad = (4 - bih.biWidth * 3 mod 4) land 3; - - for (y in bih.biHeight - 1 downto 0) { - for (x in 0 to bih.biWidth - 1) { - let b = (read_byte(ic) land 0xFF) lsl 16; - let g = (read_byte(ic) land 0xFF) lsl 8; - let r = (read_byte(ic) land 0xFF) lsl 0; - let a = 0xFF lsl 24; - Array1.set( - data, - y * bih.biWidth + x, - Int32.of_int(a lor b lor g lor r), - ); - }; - for (_j in 0 to pad - 1) { - skip_byte(ic); - }; - }; - data; -}; - -let load_image32data = (bih, ic) => { - let data = Array1.create(int32, c_layout, bih.biWidth * bih.biHeight); - - for (y in bih.biHeight - 1 downto 0) { - for (x in 0 to bih.biWidth - 1) { - let b = (read_byte(ic) land 0xFF) lsl 16; - let g = (read_byte(ic) land 0xFF) lsl 8; - let r = (read_byte(ic) land 0xFF) lsl 0; - let a = (read_byte(ic) land 0xFF) lsl 24; - Array1.set( - data, - y * bih.biWidth + x, - Int32.of_int(a lor b lor g lor r), - ); - }; - }; - data; -}; - -let load_imagedata = (bih, ic) => { - switch (bih.biBitCount) { - | ColorRGBA => load_image32data(bih, ic) - | ColorRGB => load_image24data(bih, ic) - | _ => failwith("BMP has to be 32 or 24 bit") - }; -}; - -let skip_to = (ic, n) => { - while (bytes_read^ != n) { - skip_byte(ic); - }; -}; - -let read_bmp = ic => { - bytes_read := 0; - - let bmpFileHeader = load_bitmapfileheader(ic); - let bmpInfoHeader = load_bitmapinfoheader(ic); - - skip_to(ic, bmpFileHeader.bfOffBits); - let bmpBytes = load_imagedata(bmpInfoHeader, ic); - - {bmpFileHeader, bmpInfoHeader, bmpBytes}; -}; - -let load = filename => { - let ic = open_in_bin(filename); - let bmp = read_bmp(ic); - close_in(ic); - - (bmp.bmpInfoHeader.biWidth, bmp.bmpInfoHeader.biHeight, bmp.bmpBytes); -}; diff --git a/io/bmp/ReadBmp.rei b/io/bmp/ReadBmp.rei deleted file mode 100644 index 4738f862..00000000 --- a/io/bmp/ReadBmp.rei +++ /dev/null @@ -1,3 +0,0 @@ -let load: - string => - (int, int, Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout)); diff --git a/io/config/discover.ml b/io/config/discover.ml new file mode 100644 index 00000000..da8da519 --- /dev/null +++ b/io/config/discover.ml @@ -0,0 +1,27 @@ +module C = Configurator.V1 + +let _ = + C.main ~name:"odiff-c-lib-package-resolver" (fun _c -> + let spng_include_path = Sys.getenv "SPNG_INCLUDE_PATH" |> String.trim in + let spng_lib_path = Sys.getenv "SPNG_LIB_PATH" |> String.trim in + let libspng = spng_lib_path ^ "/libspng_static.a" in + let jpeg_include_path = Sys.getenv "JPEG_INCLUDE_PATH" |> String.trim in + let jpeg_lib_path = Sys.getenv "JPEG_LIB_PATH" |> String.trim in + let libjpeg = jpeg_lib_path ^ "/libjpeg.a" in + let tiff_include_path = Sys.getenv "TIFF_INCLUDE_PATH" |> String.trim in + let tiff_lib_path = Sys.getenv "TIFF_LIB_PATH" |> String.trim in + let libtiff = tiff_lib_path ^ "/libtiff.a" in + let z_lib_path = Sys.getenv "Z_LIB_PATH" |> String.trim in + let zlib = z_lib_path ^ "/libz.a" in + C.Flags.write_sexp "png_write_c_flags.sexp" [ "-I" ^ spng_include_path ]; + C.Flags.write_sexp "png_write_c_library_flags.sexp" [ libspng; zlib ]; + C.Flags.write_sexp "png_write_flags.sexp" [ "-cclib"; libspng ]; + C.Flags.write_sexp "png_c_flags.sexp" [ "-I" ^ spng_include_path ]; + C.Flags.write_sexp "png_c_library_flags.sexp" [ libspng; zlib ]; + C.Flags.write_sexp "png_flags.sexp" [ "-cclib"; libspng ]; + C.Flags.write_sexp "jpg_c_flags.sexp" [ "-I" ^ jpeg_include_path ]; + C.Flags.write_sexp "jpg_c_library_flags.sexp" [ libjpeg ]; + C.Flags.write_sexp "jpg_flags.sexp" [ "-cclib"; libjpeg ]; + C.Flags.write_sexp "tiff_c_flags.sexp" [ "-I" ^ tiff_include_path ]; + C.Flags.write_sexp "tiff_c_library_flags.sexp" [ libtiff; zlib ]; + C.Flags.write_sexp "tiff_flags.sexp" [ "-cclib"; libtiff ]) diff --git a/io/config/discover.re b/io/config/discover.re deleted file mode 100644 index c2814f92..00000000 --- a/io/config/discover.re +++ /dev/null @@ -1,34 +0,0 @@ -module C = Configurator.V1; - -C.main(~name="odiff-c-lib-package-resolver", _c => { - let spng_include_path = Sys.getenv("SPNG_INCLUDE_PATH") |> String.trim; - let spng_lib_path = Sys.getenv("SPNG_LIB_PATH") |> String.trim; - let libspng = spng_lib_path ++ "/libspng_static.a"; - - let jpeg_include_path = Sys.getenv("JPEG_INCLUDE_PATH") |> String.trim; - let jpeg_lib_path = Sys.getenv("JPEG_LIB_PATH") |> String.trim; - let libjpeg = jpeg_lib_path ++ "/libjpeg.a"; - - let tiff_include_path = Sys.getenv("TIFF_INCLUDE_PATH") |> String.trim; - let tiff_lib_path = Sys.getenv("TIFF_LIB_PATH") |> String.trim; - let libtiff = tiff_lib_path ++ "/libtiff.a"; - - let z_lib_path = Sys.getenv("Z_LIB_PATH") |> String.trim; - let zlib = z_lib_path ++ "/libz.a"; - - C.Flags.write_sexp("png_write_c_flags.sexp", ["-I" ++ spng_include_path]); - C.Flags.write_sexp("png_write_c_library_flags.sexp", [libspng, zlib]); - C.Flags.write_sexp("png_write_flags.sexp", ["-cclib", libspng]); - - C.Flags.write_sexp("png_c_flags.sexp", ["-I" ++ spng_include_path]); - C.Flags.write_sexp("png_c_library_flags.sexp", [libspng, zlib]); - C.Flags.write_sexp("png_flags.sexp", ["-cclib", libspng]); - - C.Flags.write_sexp("jpg_c_flags.sexp", ["-I" ++ jpeg_include_path]); - C.Flags.write_sexp("jpg_c_library_flags.sexp", [libjpeg]); - C.Flags.write_sexp("jpg_flags.sexp", ["-cclib", libjpeg]); - - C.Flags.write_sexp("tiff_c_flags.sexp", ["-I" ++ tiff_include_path]); - C.Flags.write_sexp("tiff_c_library_flags.sexp", [libtiff, zlib]); - C.Flags.write_sexp("tiff_flags.sexp", ["-cclib", libtiff]); -}); diff --git a/io/config/dune b/io/config/dune index d07fdf0f..bc2970ff 100644 --- a/io/config/dune +++ b/io/config/dune @@ -2,4 +2,4 @@ (name discover) (ocamlc_flags str.cma) (ocamlopt_flags str.cmxa) - (libraries dune-configurator)) \ No newline at end of file + (libraries dune-configurator)) diff --git a/io/jpg/Jpg.ml b/io/jpg/Jpg.ml new file mode 100644 index 00000000..73fc3439 --- /dev/null +++ b/io/jpg/Jpg.ml @@ -0,0 +1,28 @@ +open Bigarray + +type data = (int32, int32_elt, c_layout) Array1.t + +module IO = struct + type buffer + type t = { data : data; buffer : buffer } + + let loadImage filename : t Odiff.ImageIO.img = + let width, height, data, buffer = ReadJpg.read_jpeg_image filename in + { width; height; image = { data; buffer } } + + let readDirectPixel ~x ~y (img : t Odiff.ImageIO.img) = + Array1.unsafe_get img.image.data ((y * img.width) + x) + + let setImgColor ~x ~y color (img : t Odiff.ImageIO.img) = + Array1.unsafe_set img.image.data ((y * img.width) + x) color + + let saveImage (img : t Odiff.ImageIO.img) filename = + WritePng.write_png_bigarray filename img.image.data img.width img.height + + let freeImage (img : t Odiff.ImageIO.img) = + ReadJpg.cleanup_jpg img.image.buffer + + let makeSameAsLayout (img : t Odiff.ImageIO.img) = + let data = Array1.create int32 c_layout (Array1.dim img.image.data) in + { img with image = { data; buffer = img.image.buffer } } +end diff --git a/io/jpg/Jpg.mli b/io/jpg/Jpg.mli new file mode 100644 index 00000000..913c029c --- /dev/null +++ b/io/jpg/Jpg.mli @@ -0,0 +1,3 @@ +type data = (int32, Bigarray.int32_elt, Bigarray.c_layout) Bigarray.Array1.t + +module IO : Odiff.ImageIO.ImageIO diff --git a/io/jpg/Jpg.re b/io/jpg/Jpg.re deleted file mode 100644 index 126f53a8..00000000 --- a/io/jpg/Jpg.re +++ /dev/null @@ -1,56 +0,0 @@ -open Bigarray; - -type data = Array1.t(int32, int32_elt, c_layout); - -module IO = { - type buffer; - type t = { - data, - buffer, - }; - - let loadImage = (filename): Odiff.ImageIO.img(t) => { - let (width, height, data, buffer) = ReadJpg.read_jpeg_image(filename); - - { - width, - height, - image: { - data, - buffer, - }, - }; - }; - - let readDirectPixel = (~x, ~y, img: Odiff.ImageIO.img(t)) => { - Array1.unsafe_get(img.image.data, y * img.width + x); - }; - - let setImgColor = (~x, ~y, color, img: Odiff.ImageIO.img(t)) => { - Array1.unsafe_set(img.image.data, y * img.width + x, color); - }; - - let saveImage = (img: Odiff.ImageIO.img(t), filename) => { - WritePng.write_png_bigarray( - filename, - img.image.data, - img.width, - img.height, - ); - }; - - let freeImage = (img: Odiff.ImageIO.img(t)) => { - ReadJpg.cleanup_jpg(img.image.buffer); - }; - - let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => { - let data = Array1.create(int32, c_layout, Array1.dim(img.image.data)); - { - ...img, - image: { - data, - buffer: img.image.buffer, - }, - }; - }; -}; diff --git a/io/jpg/Jpg.rei b/io/jpg/Jpg.rei deleted file mode 100644 index 256a8d3f..00000000 --- a/io/jpg/Jpg.rei +++ /dev/null @@ -1,3 +0,0 @@ -type data = Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout); - -module IO: Odiff.ImageIO.ImageIO; diff --git a/io/jpg/ReadJpg.ml b/io/jpg/ReadJpg.ml new file mode 100644 index 00000000..43d7433f --- /dev/null +++ b/io/jpg/ReadJpg.ml @@ -0,0 +1,8 @@ +external read_jpeg_image : + string -> + int + * int + * (int32, Bigarray.int32_elt, Bigarray.c_layout) Bigarray.Array1.t + * 'a = "read_jpeg_file_to_tuple" + +external cleanup_jpg : 'a -> unit = "cleanup_jpg" [@@noalloc] diff --git a/io/jpg/ReadJpg.re b/io/jpg/ReadJpg.re deleted file mode 100644 index 70a645b3..00000000 --- a/io/jpg/ReadJpg.re +++ /dev/null @@ -1,11 +0,0 @@ -external read_jpeg_image: - string => - ( - int, - int, - Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout), - 'a, - ) = - "read_jpeg_file_to_tuple"; - -[@noalloc] external cleanup_jpg: 'a => unit = "cleanup_jpg"; diff --git a/io/png/Png.ml b/io/png/Png.ml new file mode 100644 index 00000000..b05b9bac --- /dev/null +++ b/io/png/Png.ml @@ -0,0 +1,29 @@ +open Bigarray +open Odiff.ImageIO + +type data = (int32, int32_elt, c_layout) Array1.t + +module IO : Odiff.ImageIO.ImageIO = struct + type t = data + + let readDirectPixel ~(x : int) ~(y : int) (img : t Odiff.ImageIO.img) = + let image : data = img.image in + Array1.unsafe_get image ((y * img.width) + x) + + let setImgColor ~x ~y color (img : t Odiff.ImageIO.img) = + let image : data = img.image in + Array1.unsafe_set image ((y * img.width) + x) color + + let loadImage filename : t Odiff.ImageIO.img = + let width, height, data, _buffer = ReadPng.read_png_image filename in + { width; height; image = data } + + let saveImage (img : t Odiff.ImageIO.img) filename = + WritePng.write_png_bigarray filename img.image img.width img.height + + let freeImage (img : t Odiff.ImageIO.img) = () + + let makeSameAsLayout (img : t Odiff.ImageIO.img) = + let image = Array1.create int32 c_layout (Array1.dim img.image) in + { img with image } +end diff --git a/io/png/Png.re b/io/png/Png.re deleted file mode 100644 index 52395b97..00000000 --- a/io/png/Png.re +++ /dev/null @@ -1,37 +0,0 @@ -open Bigarray; -open Odiff.ImageIO; - -type data = Array1.t(int32, int32_elt, c_layout); - -module IO: Odiff.ImageIO.ImageIO = { - type t = data; - - let readDirectPixel = (~x: int, ~y: int, img: Odiff.ImageIO.img(t)) => { - let image: data = img.image; - Array1.unsafe_get(image, y * img.width + x); - }; - - let setImgColor = (~x, ~y, color, img: Odiff.ImageIO.img(t)) => { - let image: data = img.image; - Array1.unsafe_set(image, y * img.width + x, color); - }; - - let loadImage = (filename): Odiff.ImageIO.img(t) => { - let (width, height, data, _buffer) = ReadPng.read_png_image(filename); - - {width, height, image: data}; - }; - - let saveImage = (img: Odiff.ImageIO.img(t), filename) => { - WritePng.write_png_bigarray(filename, img.image, img.width, img.height); - }; - - let freeImage = (img: Odiff.ImageIO.img(t)) => { - (); - }; - - let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => { - let image = Array1.create(int32, c_layout, Array1.dim(img.image)); - {...img, image}; - }; -}; diff --git a/io/png/ReadPng.c b/io/png/ReadPng.c index fc509338..661e2922 100644 --- a/io/png/ReadPng.c +++ b/io/png/ReadPng.c @@ -2,15 +2,13 @@ #include -#include #include -#include -#include #include +#include +#include +#include -CAMLprim value -read_png_file(value file) -{ +CAMLprim value read_png_file(value file) { CAMLparam1(file); CAMLlocal2(res, ba); @@ -21,15 +19,13 @@ read_png_file(value file) const char *filename = String_val(file); png = fopen(filename, "rb"); - if (png == NULL) - { + if (png == NULL) { caml_failwith("error opening input file"); } ctx = spng_ctx_new(0); - if (ctx == NULL) - { + if (ctx == NULL) { caml_failwith("spng_ctx_new() failed"); spng_ctx_free(ctx); free(out); @@ -49,8 +45,7 @@ read_png_file(value file) struct spng_ihdr ihdr; result = spng_get_ihdr(ctx, &ihdr); - if (result) - { + if (result) { caml_failwith("spng_get_ihdr() error!"); spng_ctx_free(ctx); free(out); @@ -58,21 +53,18 @@ read_png_file(value file) size_t out_size; result = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size); - if (result) - { + if (result) { spng_ctx_free(ctx); }; out = malloc(out_size); - if (out == NULL) - { + if (out == NULL) { spng_ctx_free(ctx); free(out); }; result = spng_decode_image(ctx, out, out_size, SPNG_FMT_RGBA8, 0); - if (result) - { + if (result) { spng_ctx_free(ctx); free(out); caml_failwith(spng_strerror(result)); diff --git a/io/png/ReadPng.ml b/io/png/ReadPng.ml new file mode 100644 index 00000000..fc894c3c --- /dev/null +++ b/io/png/ReadPng.ml @@ -0,0 +1,6 @@ +external read_png_image : + string -> + int + * int + * (int32, Bigarray.int32_elt, Bigarray.c_layout) Bigarray.Array1.t + * 'a = "read_png_file" diff --git a/io/png/ReadPng.re b/io/png/ReadPng.re deleted file mode 100644 index 43a979a7..00000000 --- a/io/png/ReadPng.re +++ /dev/null @@ -1,9 +0,0 @@ -external read_png_image: - string => - ( - int, - int, - Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout), - 'a, - ) = - "read_png_file"; diff --git a/io/png_write/WritePng.ml b/io/png_write/WritePng.ml new file mode 100644 index 00000000..84a811b8 --- /dev/null +++ b/io/png_write/WritePng.ml @@ -0,0 +1,6 @@ +open Bigarray + +external write_png_bigarray : + string -> (int32, int32_elt, c_layout) Array1.t -> int -> int -> unit + = "write_png_bigarray" +[@@noalloc] diff --git a/io/png_write/WritePng.re b/io/png_write/WritePng.re deleted file mode 100644 index 277c112e..00000000 --- a/io/png_write/WritePng.re +++ /dev/null @@ -1,6 +0,0 @@ -open Bigarray; - -[@noalloc] -external write_png_bigarray: - (string, Array1.t(int32, int32_elt, c_layout), int, int) => unit = - "write_png_bigarray"; diff --git a/io/tiff/ReadTiff.c b/io/tiff/ReadTiff.c index a09577dc..876c5320 100644 --- a/io/tiff/ReadTiff.c +++ b/io/tiff/ReadTiff.c @@ -2,17 +2,15 @@ #include -#include #include -#include -#include #include +#include +#include +#include #include -CAMLprim value -read_tiff_file_to_tuple(value file) -{ +CAMLprim value read_tiff_file_to_tuple(value file) { CAMLparam1(file); CAMLlocal2(res, ba); @@ -23,8 +21,7 @@ read_tiff_file_to_tuple(value file) TIFF *image; - if (!(image = TIFFOpen(filename, "r"))) - { + if (!(image = TIFFOpen(filename, "r"))) { caml_failwith("opening input file failed!"); } @@ -34,20 +31,20 @@ read_tiff_file_to_tuple(value file) int buffer_size = width * height; buffer = (uint32_t *)malloc(buffer_size * 4); - if (!buffer) - { + if (!buffer) { TIFFClose(image); caml_failwith("allocating TIFF buffer failed"); } - if (!(TIFFReadRGBAImageOriented(image, width, height, buffer, ORIENTATION_TOPLEFT, 0))) - { + if (!(TIFFReadRGBAImageOriented(image, width, height, buffer, + ORIENTATION_TOPLEFT, 0))) { TIFFClose(image); caml_failwith("reading input file failed"); } res = caml_alloc(4, 0); - ba = caml_ba_alloc_dims(CAML_BA_INT32 | CAML_BA_C_LAYOUT, 1, buffer, buffer_size); + ba = caml_ba_alloc_dims(CAML_BA_INT32 | CAML_BA_C_LAYOUT, 1, buffer, + buffer_size); Store_field(res, 0, Val_int(width)); Store_field(res, 1, Val_int(height)); @@ -59,9 +56,7 @@ read_tiff_file_to_tuple(value file) CAMLreturn(res); } -CAMLprim value -cleanup_tiff(value buffer) -{ +CAMLprim value cleanup_tiff(value buffer) { CAMLparam1(buffer); free(Bp_val(buffer)); CAMLreturn(Val_unit); diff --git a/io/tiff/ReadTiff.ml b/io/tiff/ReadTiff.ml new file mode 100644 index 00000000..cf2a8fa4 --- /dev/null +++ b/io/tiff/ReadTiff.ml @@ -0,0 +1,8 @@ +external load : + string -> + int + * int + * (int32, Bigarray.int32_elt, Bigarray.c_layout) Bigarray.Array1.t + * 'a = "read_tiff_file_to_tuple" + +external cleanup_tiff : 'a -> unit = "cleanup_tiff" [@@noalloc] diff --git a/io/tiff/ReadTiff.re b/io/tiff/ReadTiff.re deleted file mode 100644 index 02a8650d..00000000 --- a/io/tiff/ReadTiff.re +++ /dev/null @@ -1,11 +0,0 @@ -external load: - string => - ( - int, - int, - Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout), - 'a, - ) = - "read_tiff_file_to_tuple"; - -[@noalloc] external cleanup_tiff: 'a => unit = "cleanup_tiff"; diff --git a/io/tiff/Tiff.ml b/io/tiff/Tiff.ml new file mode 100644 index 00000000..1a6cb962 --- /dev/null +++ b/io/tiff/Tiff.ml @@ -0,0 +1,28 @@ +open Bigarray + +type data = (int32, int32_elt, c_layout) Array1.t + +module IO : Odiff.ImageIO.ImageIO = struct + type buffer + type t = { data : data; buffer : buffer } + + let loadImage filename : t Odiff.ImageIO.img = + let width, height, data, buffer = ReadTiff.load filename in + { width; height; image = { data; buffer } } + + let readDirectPixel ~(x : int) ~(y : int) (img : t Odiff.ImageIO.img) = + Array1.unsafe_get img.image.data ((y * img.width) + x) + + let setImgColor ~x ~y color (img : t Odiff.ImageIO.img) = + Array1.unsafe_set img.image.data ((y * img.width) + x) color + + let saveImage (img : t Odiff.ImageIO.img) filename = + WritePng.write_png_bigarray filename img.image.data img.width img.height + + let freeImage (img : t Odiff.ImageIO.img) = + ReadTiff.cleanup_tiff img.image.buffer + + let makeSameAsLayout (img : t Odiff.ImageIO.img) = + let data = Array1.create int32 c_layout (Array1.dim img.image.data) in + { img with image = { data; buffer = img.image.buffer } } +end diff --git a/io/tiff/Tiff.mli b/io/tiff/Tiff.mli new file mode 100644 index 00000000..913c029c --- /dev/null +++ b/io/tiff/Tiff.mli @@ -0,0 +1,3 @@ +type data = (int32, Bigarray.int32_elt, Bigarray.c_layout) Bigarray.Array1.t + +module IO : Odiff.ImageIO.ImageIO diff --git a/io/tiff/Tiff.re b/io/tiff/Tiff.re deleted file mode 100644 index d4f29dbe..00000000 --- a/io/tiff/Tiff.re +++ /dev/null @@ -1,56 +0,0 @@ -open Bigarray; - -type data = Array1.t(int32, int32_elt, c_layout); - -module IO: Odiff.ImageIO.ImageIO = { - type buffer; - type t = { - data, - buffer, - }; - - let loadImage = (filename): Odiff.ImageIO.img(t) => { - let (width, height, data, buffer) = ReadTiff.load(filename); - - { - width, - height, - image: { - data, - buffer, - }, - }; - }; - - let readDirectPixel = (~x: int, ~y: int, img: Odiff.ImageIO.img(t)) => { - Array1.unsafe_get(img.image.data, y * img.width + x); - }; - - let setImgColor = (~x, ~y, color, img: Odiff.ImageIO.img(t)) => { - Array1.unsafe_set(img.image.data, y * img.width + x, color); - }; - - let saveImage = (img: Odiff.ImageIO.img(t), filename) => { - WritePng.write_png_bigarray( - filename, - img.image.data, - img.width, - img.height, - ); - }; - - let freeImage = (img: Odiff.ImageIO.img(t)) => { - ReadTiff.cleanup_tiff(img.image.buffer); - }; - - let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => { - let data = Array1.create(int32, c_layout, Array1.dim(img.image.data)); - { - ...img, - image: { - data, - buffer: img.image.buffer, - }, - }; - }; -}; diff --git a/io/tiff/Tiff.rei b/io/tiff/Tiff.rei deleted file mode 100644 index 256a8d3f..00000000 --- a/io/tiff/Tiff.rei +++ /dev/null @@ -1,3 +0,0 @@ -type data = Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout); - -module IO: Odiff.ImageIO.ImageIO; diff --git a/package.json b/package.json index d6f18bb0..dadd4b88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "odiff", - "version": "2.6.1", + "version": "3.0.0", "description": "The fastest image difference tool.", "license": "MIT", "esy": { @@ -10,6 +10,7 @@ "bin": { "odiff": "ODiffBin" }, + "rewritePrefix": true, "includePackages": [ "odiff" ] @@ -29,16 +30,16 @@ "process:readme": "esy node scripts/process-readme.js" }, "dependencies": { - "@opam/reason": "3.9.0", "@opam/cmdliner": "1.0.4", "@opam/dune": "< 4.0.0", "@opam/dune-configurator": "< 4.0.0", + "@opam/reason": "3.9.0", "@reason-native/console": "*", "@reason-native/pastel": "*", "@reason-native/rely": "*", - "esy-libtiff": "*", - "esy-libspng": "*", "esy-libjpeg": "*", + "esy-libspng": "*", + "esy-libtiff": "*", "esy-zlib": "*", "ocaml": "4.14.x" }, diff --git a/src/Antialiasing.ml b/src/Antialiasing.ml new file mode 100644 index 00000000..241cf505 --- /dev/null +++ b/src/Antialiasing.ml @@ -0,0 +1,89 @@ +open ImageIO + +module MakeAntialiasing (IO1 : ImageIO.ImageIO) (IO2 : ImageIO.ImageIO) = struct + let hasManySiblingsWithSameColor ~x ~y ~width ~height ~readColor = + if x <= width - 1 && y <= height - 1 then ( + let x0 = max (x - 1) 0 in + let y0 = max (y - 1) 0 in + let x1 = min (x + 1) (width - 1) in + let y1 = min (y + 1) (height - 1) in + let zeroes = + match x = x0 || x = x1 || y = y0 || y = y1 with + | true -> ref 1 + | false -> ref 0 + in + let baseColor = readColor ~x ~y in + for adj_y = y0 to y1 do + for adj_x = x0 to x1 do + if !zeroes < 3 && (x <> adj_x || y <> adj_y) then + let adjacentColor = readColor ~x:adj_x ~y:adj_y in + if baseColor = adjacentColor then incr zeroes + done + done; + !zeroes >= 3) + else false + + let detect ~x ~y ~baseImg ~compImg = + let x0 = max (x - 1) 0 in + let y0 = max (y - 1) 0 in + let x1 = min (x + 1) (baseImg.width - 1) in + let y1 = min (y + 1) (baseImg.height - 1) in + let minSiblingDelta = ref 0.0 in + let maxSiblingDelta = ref 0.0 in + let minSiblingDeltaCoord = ref (0, 0) in + let maxSiblingDeltaCoord = ref (0, 0) in + let zeroes = + ref + (match x = x0 || x = x1 || y = y0 || y = y1 with + | true -> 1 + | false -> 0) + in + + let baseColor = baseImg |> IO1.readDirectPixel ~x ~y in + for adj_y = y0 to y1 do + for adj_x = x0 to x1 do + if !zeroes < 3 && (x <> adj_x || y <> adj_y) then + let adjacentColor = + baseImg |> IO1.readDirectPixel ~x:adj_x ~y:adj_y + in + if baseColor = adjacentColor then incr zeroes + else + let delta = + ColorDelta.calculatePixelBrightnessDelta baseColor adjacentColor + in + if delta < !minSiblingDelta then ( + minSiblingDelta := delta; + minSiblingDeltaCoord := (adj_x, adj_y)) + else if delta > !maxSiblingDelta then ( + maxSiblingDelta := delta; + maxSiblingDeltaCoord := (adj_x, adj_y)) + done + done; + + if !zeroes >= 3 || !minSiblingDelta = 0.0 || !maxSiblingDelta = 0.0 then + (* + If we found more than 2 equal siblings or there are + no darker pixels among other siblings or + there are not brighter pixels among the siblings + *) + false + else + (* + If either the darkest or the brightest pixel has 3+ equal siblings in both images + (definitely not anti-aliased), this pixel is anti-aliased + *) + let minX, minY = !minSiblingDeltaCoord in + let maxX, maxY = !maxSiblingDeltaCoord in + (hasManySiblingsWithSameColor ~x:minX ~y:minY ~width:baseImg.width + ~height:baseImg.height + ~readColor:(IO1.readDirectPixel baseImg) + || hasManySiblingsWithSameColor ~x:maxX ~y:maxY ~width:baseImg.width + ~height:baseImg.height + ~readColor:(IO1.readDirectPixel baseImg)) + && (hasManySiblingsWithSameColor ~x:minX ~y:minY ~width:compImg.width + ~height:compImg.height + ~readColor:(IO2.readDirectPixel compImg) + || hasManySiblingsWithSameColor ~x:maxX ~y:maxY ~width:compImg.width + ~height:compImg.height + ~readColor:(IO2.readDirectPixel compImg)) +end diff --git a/src/Antialiasing.re b/src/Antialiasing.re deleted file mode 100644 index 432be68a..00000000 --- a/src/Antialiasing.re +++ /dev/null @@ -1,124 +0,0 @@ -open ImageIO; - -module MakeAntialiasing = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => { - let hasManySiblingsWithSameColor = (~x, ~y, ~width, ~height, ~readColor) => - if (x <= width - 1 && y <= height - 1) { - let x0 = max(x - 1, 0); - let y0 = max(y - 1, 0); - - let x1 = min(x + 1, width - 1); - let y1 = min(y + 1, height - 1); - - let zeroes = - x == x0 || x == x1 || y == y0 || y == y1 ? ref(1) : ref(0); - - let baseColor = readColor(~x, ~y); - - // go through 8 adjacent pixels - for (adj_y in y0 to y1) { - for (adj_x in x0 to x1) { - /* This is not the current pixel and we don't have our result */ - if (zeroes^ < 3 && (x != adj_x || y != adj_y)) { - let adjacentColor = readColor(~x=adj_x, ~y=adj_y); - if (baseColor == adjacentColor) { - incr(zeroes); - }; - }; - }; - }; - - zeroes^ >= 3; - } else { - false; - }; - - let detect = (~x, ~y, ~baseImg, ~compImg) => { - let x0 = max(x - 1, 0); - let y0 = max(y - 1, 0); - - let x1 = min(x + 1, baseImg.width - 1); - let y1 = min(y + 1, baseImg.height - 1); - - let minSiblingDelta = ref(0.0); - let maxSiblingDelta = ref(0.0); - - let minSiblingDeltaCoord = ref((0, 0)); - let maxSiblingDeltaCoord = ref((0, 0)); - - let zeroes = ref(x == x0 || x == x1 || y == y0 || y == y1 ? 1 : 0); - - let baseColor = baseImg |> IO1.readDirectPixel(~x, ~y); - - for (adj_y in y0 to y1) { - for (adj_x in x0 to x1) { - /* This is not the current pixel and we don't have our result */ - if (zeroes^ < 3 && (x != adj_x || y != adj_y)) { - let adjacentColor = - baseImg |> IO1.readDirectPixel(~x=adj_x, ~y=adj_y); - - if (baseColor == adjacentColor) { - incr(zeroes); - } else { - let delta = - ColorDelta.calculatePixelBrightnessDelta( - baseColor, - adjacentColor, - ); - - if (delta < minSiblingDelta^) { - minSiblingDelta := delta; - minSiblingDeltaCoord := (adj_x, adj_y); - } else if (delta > maxSiblingDelta^) { - maxSiblingDelta := delta; - maxSiblingDeltaCoord := (adj_x, adj_y); - }; - }; - }; - }; - }; - - // if we found more than 2 equal siblings or - // there are no darker pixels among the siblings or - // there are no brighter pixels among the siblings it's not anti-aliasing - if (zeroes^ >= 3 || minSiblingDelta^ == 0.0 || maxSiblingDelta^ == 0.0) { - false; - } else { - // if either the darkest or the brightest pixel has 3+ equal siblings in both images - // (definitely not anti-aliased), this pixel is anti-aliased - let (minX, minY) = minSiblingDeltaCoord^; - let (maxX, maxY) = maxSiblingDeltaCoord^; - ( - hasManySiblingsWithSameColor( - ~x=minX, - ~y=minY, - ~width=baseImg.width, - ~height=baseImg.height, - ~readColor=IO1.readDirectPixel(baseImg), - ) - || hasManySiblingsWithSameColor( - ~x=maxX, - ~y=maxY, - ~width=baseImg.width, - ~height=baseImg.height, - ~readColor=IO1.readDirectPixel(baseImg), - ) - ) - && ( - hasManySiblingsWithSameColor( - ~x=minX, - ~y=minY, - ~width=compImg.width, - ~height=compImg.height, - ~readColor=IO2.readDirectPixel(compImg), - ) - || hasManySiblingsWithSameColor( - ~x=maxX, - ~y=maxY, - ~width=compImg.width, - ~height=compImg.height, - ~readColor=IO2.readDirectPixel(compImg), - ) - ); - }; - }; -}; diff --git a/src/ColorDelta.ml b/src/ColorDelta.ml new file mode 100644 index 00000000..3482982c --- /dev/null +++ b/src/ColorDelta.ml @@ -0,0 +1,36 @@ +let blend color alpha = 255. +. ((color -. 255.) *. alpha) + +let blendSemiTransparentColor = function + | r, g, b, alpha when alpha < 255. -> + (blend r alpha, blend g alpha, blend b alpha, alpha /. 255.) + | colors -> colors + +let convertPixelToFloat pixel = + let pixel = pixel |> Int32.to_int in + let a = (pixel lsr 24) land 255 in + let b = (pixel lsr 16) land 255 in + let g = (pixel lsr 8) land 255 in + let r = pixel land 255 in + (Float.of_int r, Float.of_int g, Float.of_int b, Float.of_int a) + +let rgb2y (r, g, b, a) = + (r *. 0.29889531) +. (g *. 0.58662247) +. (b *. 0.11448223) + +let rgb2i (r, g, b, a) = + (r *. 0.59597799) -. (g *. 0.27417610) -. (b *. 0.32180189) + +let rgb2q (r, g, b, a) = + (r *. 0.21147017) -. (g *. 0.52261711) +. (b *. 0.31114694) + +let calculatePixelColorDelta _pixelA _pixelB = + let pixelA = _pixelA |> convertPixelToFloat |> blendSemiTransparentColor in + let pixelB = _pixelB |> convertPixelToFloat |> blendSemiTransparentColor in + let y = rgb2y pixelA -. rgb2y pixelB in + let i = rgb2i pixelA -. rgb2i pixelB in + let q = rgb2q pixelA -. rgb2q pixelB in + (0.5053 *. y *. y) +. (0.299 *. i *. i) +. (0.1957 *. q *. q) + +let calculatePixelBrightnessDelta pixelA pixelB = + let pixelA = pixelA |> convertPixelToFloat |> blendSemiTransparentColor in + let pixelB = pixelB |> convertPixelToFloat |> blendSemiTransparentColor in + rgb2y pixelA -. rgb2y pixelB diff --git a/src/ColorDelta.re b/src/ColorDelta.re deleted file mode 100644 index 9a8b94f5..00000000 --- a/src/ColorDelta.re +++ /dev/null @@ -1,48 +0,0 @@ -let blend = (color, alpha) => 255. +. (color -. 255.) *. alpha; - -let blendSemiTransparentColor = - fun - | (r, g, b, alpha) when alpha < 255. => ( - blend(r, alpha), - blend(g, alpha), - blend(b, alpha), - alpha /. 255., - ) - | colors => colors; - -let convertPixelToFloat = pixel => { - let pixel = pixel |> Int32.to_int; - let a = pixel lsr 24 land 0xFF; - let b = pixel lsr 16 land 0xFF; - let g = pixel lsr 8 land 0xFF; - let r = pixel land 0xFF; - - (Float.of_int(r), Float.of_int(g), Float.of_int(b), Float.of_int(a)); -}; - -let rgb2y = ((r, g, b, a)) => - r *. 0.29889531 +. g *. 0.58662247 +. b *. 0.11448223; - -let rgb2i = ((r, g, b, a)) => - r *. 0.59597799 -. g *. 0.27417610 -. b *. 0.32180189; - -let rgb2q = ((r, g, b, a)) => - r *. 0.21147017 -. g *. 0.52261711 +. b *. 0.31114694; - -let calculatePixelColorDelta = (_pixelA, _pixelB) => { - let pixelA = _pixelA |> convertPixelToFloat |> blendSemiTransparentColor; - let pixelB = _pixelB |> convertPixelToFloat |> blendSemiTransparentColor; - - let y = rgb2y(pixelA) -. rgb2y(pixelB); - let i = rgb2i(pixelA) -. rgb2i(pixelB); - let q = rgb2q(pixelA) -. rgb2q(pixelB); - - 0.5053 *. y *. y +. 0.299 *. i *. i +. 0.1957 *. q *. q; -}; - -let calculatePixelBrightnessDelta = (pixelA, pixelB) => { - let pixelA = pixelA |> convertPixelToFloat |> blendSemiTransparentColor; - let pixelB = pixelB |> convertPixelToFloat |> blendSemiTransparentColor; - - rgb2y(pixelA) -. rgb2y(pixelB); -}; diff --git a/src/Diff.ml b/src/Diff.ml new file mode 100644 index 00000000..9564b22f --- /dev/null +++ b/src/Diff.ml @@ -0,0 +1,109 @@ +let redPixel = (255, 0, 0) +let maxYIQPossibleDelta = 35215. + +type 'a diffVariant = Layout | Pixel of ('a * int * float * int Stack.t) + +let computeIgnoreRegionOffsets width = + List.map (fun ((x1, y1), (x2, y2)) -> + let p1 = (y1 * width) + x1 in + let p2 = (y2 * width) + x2 in + (p1, p2)) + +let isInIgnoreRegion offset = + List.exists (fun ((p1 : int), (p2 : int)) -> offset >= p1 && offset <= p2) + +module MakeDiff (IO1 : ImageIO.ImageIO) (IO2 : ImageIO.ImageIO) = struct + module BaseAA = Antialiasing.MakeAntialiasing (IO1) (IO2) + module CompAA = Antialiasing.MakeAntialiasing (IO2) (IO1) + + let compare (base : IO1.t ImageIO.img) (comp : IO2.t ImageIO.img) + ?(antialiasing = false) ?(outputDiffMask = false) ?(diffLines = false) + ?(diffPixel : int * int * int = redPixel) ?(threshold = 0.1) + ?(ignoreRegions = []) () = + let maxDelta = maxYIQPossibleDelta *. (threshold ** 2.) in + let diffOutput = + match outputDiffMask with + | true -> IO1.makeSameAsLayout base + | false -> base + in + let diffPixelQueue = Queue.create () in + let diffLinesStack = Stack.create () in + let countDifference x y = + diffPixelQueue |> Queue.push (x, y); + if + diffLines + && (diffLinesStack |> Stack.is_empty || diffLinesStack |> Stack.top < y) + then diffLinesStack |> Stack.push y + in + + let ignoreRegions = + ignoreRegions |> computeIgnoreRegionOffsets base.width + in + + let size = (base.height * base.width) - 1 in + let x = ref 0 in + let y = ref 0 in + + for offset = 0 to size do + (if !x >= comp.width || !y >= comp.height then ( + let alpha = + (Int32.to_int (IO1.readDirectPixel ~x:!x ~y:!y base) lsr 24) land 255 + in + if alpha <> 0 then countDifference !x !y) + else + let baseColor = IO1.readDirectPixel ~x:!x ~y:!y base in + let compColor = IO2.readDirectPixel ~x:!x ~y:!y comp in + if baseColor <> compColor then + let delta = + ColorDelta.calculatePixelColorDelta baseColor compColor + in + if delta > maxDelta then + let isIgnored = isInIgnoreRegion offset ignoreRegions in + if not isIgnored then + let isAntialiased = + if not antialiasing then false + else + BaseAA.detect ~x:!x ~y:!y ~baseImg:base ~compImg:comp + || CompAA.detect ~x:!x ~y:!y ~baseImg:comp ~compImg:base + in + if not isAntialiased then countDifference !x !y); + + if !x = base.width - 1 then ( + x := 0; + incr y) + else incr x + done; + + let diffCount = diffPixelQueue |> Queue.length in + (if diffCount > 0 then + let r, g, b = diffPixel in + let a = (255 land 255) lsl 24 in + let b = (b land 255) lsl 16 in + let g = (g land 255) lsl 8 in + let r = (r land 255) lsl 0 in + let diffPixel = Int32.of_int (a lor b lor g lor r) in + diffPixelQueue + |> Queue.iter (fun (x, y) -> + diffOutput |> IO1.setImgColor ~x ~y diffPixel)); + + let diffPercentage = + 100.0 *. Float.of_int diffCount + /. (Float.of_int base.width *. Float.of_int base.height) + in + (diffOutput, diffCount, diffPercentage, diffLinesStack) + + let diff (base : IO1.t ImageIO.img) (comp : IO2.t ImageIO.img) ~outputDiffMask + ?(threshold = 0.1) ?(diffPixel = redPixel) ?(failOnLayoutChange = true) + ?(antialiasing = false) ?(diffLines = false) ?(ignoreRegions = []) () = + if + failOnLayoutChange = true + && (base.width <> comp.width || base.height <> comp.height) + then Layout + else + let diffResult = + compare base comp ~threshold ~diffPixel ~outputDiffMask ~antialiasing + ~diffLines ~ignoreRegions () + in + + Pixel diffResult +end diff --git a/src/Diff.re b/src/Diff.re deleted file mode 100644 index c8ec74a7..00000000 --- a/src/Diff.re +++ /dev/null @@ -1,159 +0,0 @@ -let redPixel = (255, 0, 0); -let maxYIQPossibleDelta = 35215.; - -type diffVariant('a) = - | Layout - | Pixel(('a, int, float, Stack.t(int))); - -let computeIngoreRegionOffsets = width => { - List.map((((x1, y1), (x2, y2))) => { - let p1 = y1 * width + x1; - let p2 = y2 * width + x2; - (p1, p2); - }); -}; - -let isInIgnoreRegion = offset => { - List.exists(((p1: int, p2: int)) => offset >= p1 && offset <= p2); -}; - -module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => { - module BaseAA = Antialiasing.MakeAntialiasing(IO1, IO2); - module CompAA = Antialiasing.MakeAntialiasing(IO2, IO1); - - let compare = - ( - base: ImageIO.img(IO1.t), - comp: ImageIO.img(IO2.t), - ~antialiasing=false, - ~outputDiffMask=false, - ~diffLines=false, - ~diffPixel: (int, int, int)=redPixel, - ~threshold=0.1, - ~ignoreRegions=[], - (), - ) => { - let maxDelta = maxYIQPossibleDelta *. threshold ** 2.; - let diffOutput = outputDiffMask ? IO1.makeSameAsLayout(base) : base; - - let diffPixelQueue = Queue.create(); - let diffLinesStack = Stack.create(); - - let countDifference = (x, y) => { - diffPixelQueue |> Queue.push((x, y)); - - if (diffLines && (diffLinesStack |> Stack.is_empty || diffLinesStack |> Stack.top < y)) { - diffLinesStack |> Stack.push(y); - } - }; - - let ignoreRegions = - ignoreRegions |> computeIngoreRegionOffsets(base.width); - - let size = base.height * base.width - 1; - - let x = ref(0); - let y = ref(0); - - for (offset in 0 to size) { - if (x^ >= comp.width || y^ >= comp.height) { - let alpha = - Int32.to_int(IO1.readDirectPixel(~x=x^, ~y=y^, base)) - lsr 24 - land 0xFF; - - if (alpha != 0) { - countDifference(x^, y^); - }; - } else { - let baseColor = IO1.readDirectPixel(~x=x^, ~y=y^, base); - let compColor = IO2.readDirectPixel(~x=x^, ~y=y^, comp); - - if (baseColor != compColor) { - let delta = - ColorDelta.calculatePixelColorDelta(baseColor, compColor); - - if (delta > maxDelta) { - let isIgnored = isInIgnoreRegion(offset, ignoreRegions); - - if (!isIgnored) { - let isAntialiased = - if (!antialiasing) { - false; - } else { - BaseAA.detect(~x=x^, ~y=y^, ~baseImg=base, ~compImg=comp) - || CompAA.detect(~x=x^, ~y=y^, ~baseImg=comp, ~compImg=base); - }; - - if (!isAntialiased) { - countDifference(x^, y^); - }; - }; - }; - }; - }; - if (x^ == base.width - 1) { - x := 0; - incr(y); - } else { - incr(x); - }; - }; - - let diffCount = diffPixelQueue |> Queue.length; - - if (diffCount > 0) { - let (r, g, b) = diffPixel; - let a = (255 land 0xFF) lsl 24; - let b = (b land 0xFF) lsl 16; - let g = (g land 0xFF) lsl 8; - let r = (r land 0xFF) lsl 0; - let diffPixel = Int32.of_int(a lor b lor g lor r); - - diffPixelQueue - |> Queue.iter(((x, y)) => { - diffOutput |> IO1.setImgColor(~x, ~y, diffPixel) - }); - }; - - let diffPercentage = - 100.0 - *. Float.of_int(diffCount) - /. (Float.of_int(base.width) *. Float.of_int(base.height)); - - (diffOutput, diffCount, diffPercentage, diffLinesStack); - }; - - let diff = - ( - base: ImageIO.img(IO1.t), - comp: ImageIO.img(IO2.t), - ~outputDiffMask, - ~threshold=0.1, - ~diffPixel=redPixel, - ~failOnLayoutChange=true, - ~antialiasing=false, - ~diffLines=false, - ~ignoreRegions=[], - (), - ) => - if (failOnLayoutChange == true - && (base.width != comp.width || base.height != comp.height)) { - Layout; - } else { - let diffResult = - compare( - base, - comp, - ~threshold, - ~diffPixel, - ~outputDiffMask, - ~antialiasing, - ~diffLines, - ~ignoreRegions, - (), - ); - - Pixel(diffResult); - }; -}; diff --git a/src/ImageIO.ml b/src/ImageIO.ml new file mode 100644 index 00000000..38b52a33 --- /dev/null +++ b/src/ImageIO.ml @@ -0,0 +1,14 @@ +type 'a img = { width : int; height : int; image : 'a } + +exception ImageNotLoaded + +module type ImageIO = sig + type t + + val loadImage : string -> t img + val makeSameAsLayout : t img -> t img + val readDirectPixel : x:int -> y:int -> t img -> Int32.t + val setImgColor : x:int -> y:int -> Int32.t -> t img -> unit + val saveImage : t img -> string -> unit + val freeImage : t img -> unit +end diff --git a/src/ImageIO.re b/src/ImageIO.re deleted file mode 100644 index 62ae1aa6..00000000 --- a/src/ImageIO.re +++ /dev/null @@ -1,18 +0,0 @@ -type img('a) = { - width: int, - height: int, - image: 'a, -}; - -exception ImageNotLoaded; - -module type ImageIO = { - type t; - - let loadImage: string => img(t); - let makeSameAsLayout: img(t) => img(t); - let readDirectPixel: (~x: int, ~y: int, img(t)) => Int32.t; - let setImgColor: (~x: int, ~y: int, Int32.t, img(t)) => unit; - let saveImage: (img(t), string) => unit; - let freeImage: img(t) => unit; -}; diff --git a/src/PerfTest.ml b/src/PerfTest.ml new file mode 100644 index 00000000..581931c5 --- /dev/null +++ b/src/PerfTest.ml @@ -0,0 +1,11 @@ +let now (name : string) = (name, ref (Sys.time ())) + +let cycle (name, timepoint) ?(cycleName = "") () = + Printf.printf "'%s %s' executed for: %f ms \n" name cycleName + ((Sys.time () -. !timepoint) *. 1000.); + timepoint := Sys.time () + +let ifTimeMore amount (name, timepoint) = + (Sys.time () -. timepoint) *. 1000. > amount + +let cycleIf point predicate = if predicate point then cycle point () diff --git a/src/PerfTest.re b/src/PerfTest.re deleted file mode 100644 index 06fbd3d7..00000000 --- a/src/PerfTest.re +++ /dev/null @@ -1,21 +0,0 @@ -let now = (name: string) => (name, ref(Unix.gettimeofday())); - -let cycle = ((name, timepoint), ~cycleName="", ()) => { - Printf.printf( - "'%s %s' executed for: %f ms \n", - name, - cycleName, - (Unix.gettimeofday() -. timepoint^) *. 1000., - ); - - timepoint := Unix.gettimeofday() -}; - -let ifTimeMore = (amount, (name, timepoint)) => { - (Unix.gettimeofday() -. timepoint) *. 1000. > amount; -}; - -let cycleIf = (point, predicate) => - if (predicate(point)) { - cycle(point, ()); - }; \ No newline at end of file diff --git a/src/dune b/src/dune index a6336b7a..190fb24b 100644 --- a/src/dune +++ b/src/dune @@ -2,5 +2,4 @@ (name odiff) (public_name odiff-core) (flags - (-w -40 -w +26)) - ) + (-w -40 -w +26))) diff --git a/test/RunTests.ml b/test/RunTests.ml new file mode 100644 index 00000000..70c218cc --- /dev/null +++ b/test/RunTests.ml @@ -0,0 +1 @@ +OdiffTests.TestFramework.cli () diff --git a/test/RunTests.re b/test/RunTests.re deleted file mode 100644 index f10dc787..00000000 --- a/test/RunTests.re +++ /dev/null @@ -1 +0,0 @@ -OdiffTests.TestFramework.cli() \ No newline at end of file diff --git a/test/TestFramework.ml b/test/TestFramework.ml new file mode 100644 index 00000000..42715b50 --- /dev/null +++ b/test/TestFramework.ml @@ -0,0 +1,5 @@ +include Rely.Make (struct + let config = + Rely.TestFrameworkConfig.initialize + { snapshotDir = "test/__snapshots__"; projectDir = "." } +end) diff --git a/test/TestFramework.re b/test/TestFramework.re deleted file mode 100644 index e046fa79..00000000 --- a/test/TestFramework.re +++ /dev/null @@ -1,7 +0,0 @@ -include Rely.Make({ - let config = - Rely.TestFrameworkConfig.initialize({ - snapshotDir: "test/__snapshots__", - projectDir: "." - }); -}); \ No newline at end of file diff --git a/test/Test_Core.ml b/test/Test_Core.ml new file mode 100644 index 00000000..f58fca21 --- /dev/null +++ b/test/Test_Core.ml @@ -0,0 +1,82 @@ +open TestFramework +open ODiffIO +module PNG_Diff = Odiff.Diff.MakeDiff (Png.IO) (Png.IO) + +let _ = + describe "CORE: Antialiasing" (fun { test; _ } -> + let open Png.IO in + test "does not count anti-aliased pixels as different" + (fun { expect; _ } -> + let img1 = loadImage "test/test-images/aa/antialiasing-on.png" in + let img2 = loadImage "test/test-images/aa/antialiasing-off.png" in + let _, diffPixels, diffPercentage, _ = + PNG_Diff.compare img1 img2 ~outputDiffMask:false ~antialiasing:true + () + in + (expect.int diffPixels).toBe 38; + (expect.float diffPercentage).toBeCloseTo 0.095); + test "tests different sized AA images" (fun { expect; _ } -> + let img1 = loadImage "test/test-images/aa/antialiasing-on.png" in + let img2 = + loadImage "test/test-images/aa/antialiasing-off-small.png" + in + let _, diffPixels, diffPercentage, _ = + PNG_Diff.compare img1 img2 ~outputDiffMask:true ~antialiasing:true + () + in + (expect.int diffPixels).toBe 417; + (expect.float diffPercentage).toBeCloseTo 1.04)) + +let _ = + describe "CORE: Threshold" (fun { test; _ } -> + test "uses provided threshold" (fun { expect; _ } -> + let img1 = Png.IO.loadImage "test/test-images/png/orange.png" in + let img2 = + Png.IO.loadImage "test/test-images/png/orange_changed.png" + in + let _, diffPixels, diffPercentage, _ = + PNG_Diff.compare img1 img2 ~threshold:0.5 () + in + (expect.int diffPixels).toBe 222; + (expect.float diffPercentage).toBeCloseTo 0.19)) + +let _ = + describe "CORE: Ignore Regions" (fun { test; _ } -> + test "uses provided irgnore regions" (fun { expect; _ } -> + let img1 = Png.IO.loadImage "test/test-images/png/orange.png" in + let img2 = + Png.IO.loadImage "test/test-images/png/orange_changed.png" + in + let _diffOutput, diffPixels, diffPercentage, _ = + PNG_Diff.compare img1 img2 + ~ignoreRegions: + [ ((150, 30), (310, 105)); ((20, 175), (105, 200)) ] + () + in + (expect.int diffPixels).toBe 0; + (expect.float diffPercentage).toBeCloseTo 0.0)) + +let _ = + describe "CORE: Diff Color" (fun { test; _ } -> + test "creates diff output image with custom diff color" + (fun { expect; _ } -> + let img1 = Png.IO.loadImage "test/test-images/png/orange.png" in + let img2 = + Png.IO.loadImage "test/test-images/png/orange_changed.png" + in + let diffOutput, _, _, _ = + PNG_Diff.compare img1 img2 ~diffPixel:(0, 255, 0) () + in + let originalDiff = + Png.IO.loadImage "test/test-images/png/orange_diff_green.png" + in + let diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _ = + PNG_Diff.compare originalDiff diffOutput () + in + if diffOfDiffPixels > 0 then ( + Png.IO.saveImage diffOutput + "test/test-images/png/diff-output-green.png"; + Png.IO.saveImage diffMaskOfDiff + "test/test-images/png/diff-of-diff-green.png"); + (expect.int diffOfDiffPixels).toBe 0; + (expect.float diffOfDiffPercentage).toBeCloseTo 0.0)) diff --git a/test/Test_Core.re b/test/Test_Core.re deleted file mode 100644 index 538dfdab..00000000 --- a/test/Test_Core.re +++ /dev/null @@ -1,104 +0,0 @@ -open TestFramework; -open ODiffIO; - -module PNG_Diff = Odiff.Diff.MakeDiff(Png.IO, Png.IO); - -describe("CORE: Antialiasing", ({test, _}) => { - open Png.IO; - - test("does not count anti-aliased pixels as different", ({expect, _}) => { - let img1 = loadImage("test/test-images/aa/antialiasing-on.png"); - let img2 = loadImage("test/test-images/aa/antialiasing-off.png"); - - let (_, diffPixels, diffPercentage, _) = - PNG_Diff.compare( - img1, - img2, - ~outputDiffMask=false, - ~antialiasing=true, - (), - ); - - expect.int(diffPixels).toBe(38); - expect.float(diffPercentage).toBeCloseTo(0.095); - }); - - test("tests diffrent sized AA images", ({expect, _}) => { - let img1 = loadImage("test/test-images/aa/antialiasing-on.png"); - let img2 = loadImage("test/test-images/aa/antialiasing-off-small.png"); - - let (_, diffPixels, diffPercentage, _) = - PNG_Diff.compare( - img1, - img2, - ~outputDiffMask=true, - ~antialiasing=true, - (), - ); - - expect.int(diffPixels).toBe(417); - expect.float(diffPercentage).toBeCloseTo(1.04); - }); -}); - -describe("CORE: Threshold", ({test, _}) => { - test("uses provided threshold", ({expect, _}) => { - let img1 = Png.IO.loadImage("test/test-images/png/orange.png"); - let img2 = Png.IO.loadImage("test/test-images/png/orange_changed.png"); - - let (_, diffPixels, diffPercentage, _) = - PNG_Diff.compare(img1, img2, ~threshold=0.5, ()); - expect.int(diffPixels).toBe(222); - expect.float(diffPercentage).toBeCloseTo(0.19); - }) -}); - -describe("CORE: Ignore Regions", ({test, _}) => { - test("uses provided irgnore regions", ({expect, _}) => { - let img1 = Png.IO.loadImage("test/test-images/png/orange.png"); - let img2 = Png.IO.loadImage("test/test-images/png/orange_changed.png"); - - let (_diffOutput, diffPixels, diffPercentage, _) = - PNG_Diff.compare( - img1, - img2, - ~ignoreRegions=[ - ((150, 30), (310, 105)), - ((20, 175), (105, 200)), - ], - (), - ); - - expect.int(diffPixels).toBe(0); - expect.float(diffPercentage).toBeCloseTo(0.0); - }) -}); - -describe("CORE: Diff Color", ({test, _}) => { - test("creates diff output image with custom diff color", ({expect, _}) => { - let img1 = Png.IO.loadImage("test/test-images/png/orange.png"); - let img2 = Png.IO.loadImage("test/test-images/png/orange_changed.png"); - - let (diffOutput, _, _, _) = - PNG_Diff.compare(img1, img2, ~diffPixel=(0, 255, 0), ()); - - let originalDiff = - Png.IO.loadImage("test/test-images/png/orange_diff_green.png"); - let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _) = - PNG_Diff.compare(originalDiff, diffOutput, ()); - - if (diffOfDiffPixels > 0) { - Png.IO.saveImage( - diffOutput, - "test/test-images/png/diff-output-green.png", - ); - Png.IO.saveImage( - diffMaskOfDiff, - "test/test-images/png/diff-of-diff-green.png", - ); - }; - - expect.int(diffOfDiffPixels).toBe(0); - expect.float(diffOfDiffPercentage).toBeCloseTo(0.0); - }) -}); diff --git a/test/Test_IO_BMP.ml b/test/Test_IO_BMP.ml new file mode 100644 index 00000000..ce2bb67f --- /dev/null +++ b/test/Test_IO_BMP.ml @@ -0,0 +1,42 @@ +open TestFramework +open ODiffIO +module Diff = Odiff.Diff.MakeDiff (Bmp.IO) (Bmp.IO) +module Output_Diff = Odiff.Diff.MakeDiff (Png.IO) (Bmp.IO) + +let _ = + describe "IO: BMP" (fun { test; _ } -> + test "finds difference between 2 images" (fun { expect; _ } -> + let img1 = Bmp.IO.loadImage "test/test-images/bmp/clouds.bmp" in + let img2 = Bmp.IO.loadImage "test/test-images/bmp/clouds-2.bmp" in + let _, diffPixels, diffPercentage, _ = Diff.compare img1 img2 () in + (expect.int diffPixels).toBe 191; + (expect.float diffPercentage).toBeCloseTo 0.076); + test "Diff of mask and no mask are equal" (fun { expect; _ } -> + let img1 = Bmp.IO.loadImage "test/test-images/bmp/clouds.bmp" in + let img2 = Bmp.IO.loadImage "test/test-images/bmp/clouds-2.bmp" in + let _, diffPixels, diffPercentage, _ = + Diff.compare img1 img2 ~outputDiffMask:false () + in + let img1 = Bmp.IO.loadImage "test/test-images/bmp/clouds.bmp" in + let img2 = Bmp.IO.loadImage "test/test-images/bmp/clouds-2.bmp" in + let _, diffPixelsMask, diffPercentageMask, _ = + Diff.compare img1 img2 ~outputDiffMask:true () + in + (expect.int diffPixels).toBe diffPixelsMask; + (expect.float diffPercentage).toBeCloseTo diffPercentageMask); + test "Creates correct diff output image" (fun { expect; _ } -> + let img1 = Bmp.IO.loadImage "test/test-images/bmp/clouds.bmp" in + let img2 = Bmp.IO.loadImage "test/test-images/bmp/clouds-2.bmp" in + let diffOutput, _, _, _ = Diff.compare img1 img2 () in + let originalDiff = + Png.IO.loadImage "test/test-images/bmp/clouds-diff.png" + in + let diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _ = + Output_Diff.compare originalDiff diffOutput () + in + if diffOfDiffPixels > 0 then ( + Bmp.IO.saveImage diffOutput "test/test-images/bmp/_diff-output.png"; + Png.IO.saveImage diffMaskOfDiff + "test/test-images/bmp/_diff-of-diff.png"); + (expect.int diffOfDiffPixels).toBe 0; + (expect.float diffOfDiffPercentage).toBeCloseTo 0.0)) diff --git a/test/Test_IO_BMP.re b/test/Test_IO_BMP.re deleted file mode 100644 index f2fec7a6..00000000 --- a/test/Test_IO_BMP.re +++ /dev/null @@ -1,57 +0,0 @@ -open TestFramework; -open ODiffIO; - -module Diff = Odiff.Diff.MakeDiff(Bmp.IO, Bmp.IO); -module Output_Diff = Odiff.Diff.MakeDiff(Png.IO, Bmp.IO); - -describe("IO: BMP", ({test, _}) => { - test("finds difference between 2 images", ({expect, _}) => { - let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp"); - let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp"); - - let (_, diffPixels, diffPercentage, _) = Diff.compare(img1, img2, ()); - - expect.int(diffPixels).toBe(191); - expect.float(diffPercentage).toBeCloseTo(0.076); - }); - - test("Diff of mask and no mask are equal", ({expect, _}) => { - let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp"); - let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp"); - - let (_, diffPixels, diffPercentage, _) = - Diff.compare(img1, img2, ~outputDiffMask=false, ()); - - let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp"); - let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp"); - - let (_, diffPixelsMask, diffPercentageMask, _) = - Diff.compare(img1, img2, ~outputDiffMask=true, ()); - - expect.int(diffPixels).toBe(diffPixelsMask); - expect.float(diffPercentage).toBeCloseTo(diffPercentageMask); - }); - - test("Creates correct diff output image", ({expect, _}) => { - let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp"); - let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp"); - - let (diffOutput, _, _, _) = Diff.compare(img1, img2, ()); - - let originalDiff = - Png.IO.loadImage("test/test-images/bmp/clouds-diff.png"); - let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _) = - Output_Diff.compare(originalDiff, diffOutput, ()); - - if (diffOfDiffPixels > 0) { - Bmp.IO.saveImage(diffOutput, "test/test-images/bmp/_diff-output.png"); - Png.IO.saveImage( - diffMaskOfDiff, - "test/test-images/bmp/_diff-of-diff.png", - ); - }; - - expect.int(diffOfDiffPixels).toBe(0); - expect.float(diffOfDiffPercentage).toBeCloseTo(0.0); - }); -}); diff --git a/test/Test_IO_JPG.ml b/test/Test_IO_JPG.ml new file mode 100644 index 00000000..a052a60c --- /dev/null +++ b/test/Test_IO_JPG.ml @@ -0,0 +1,42 @@ +open TestFramework +open ODiffIO +module Diff = Odiff.Diff.MakeDiff (Jpg.IO) (Jpg.IO) +module Output_Diff = Odiff.Diff.MakeDiff (Png.IO) (Jpg.IO) + +let _ = + describe "IO: JPG / JPEG" (fun { test; _ } -> + test "finds difference between 2 images" (fun { expect; _ } -> + let img1 = Jpg.IO.loadImage "test/test-images/jpg/tiger.jpg" in + let img2 = Jpg.IO.loadImage "test/test-images/jpg/tiger-2.jpg" in + let _, diffPixels, diffPercentage, _ = Diff.compare img1 img2 () in + (expect.int diffPixels).toBe 7586; + (expect.float diffPercentage).toBeCloseTo 1.14); + test "Diff of mask and no mask are equal" (fun { expect; _ } -> + let img1 = Jpg.IO.loadImage "test/test-images/jpg/tiger.jpg" in + let img2 = Jpg.IO.loadImage "test/test-images/jpg/tiger-2.jpg" in + let _, diffPixels, diffPercentage, _ = + Diff.compare img1 img2 ~outputDiffMask:false () + in + let img1 = Jpg.IO.loadImage "test/test-images/jpg/tiger.jpg" in + let img2 = Jpg.IO.loadImage "test/test-images/jpg/tiger-2.jpg" in + let _, diffPixelsMask, diffPercentageMask, _ = + Diff.compare img1 img2 ~outputDiffMask:true () + in + (expect.int diffPixels).toBe diffPixelsMask; + (expect.float diffPercentage).toBeCloseTo diffPercentageMask); + test "Creates correct diff output image" (fun { expect; _ } -> + let img1 = Jpg.IO.loadImage "test/test-images/jpg/tiger.jpg" in + let img2 = Jpg.IO.loadImage "test/test-images/jpg/tiger-2.jpg" in + let diffOutput, _, _, _ = Diff.compare img1 img2 () in + let originalDiff = + Png.IO.loadImage "test/test-images/jpg/tiger-diff.png" + in + let diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _ = + Output_Diff.compare originalDiff diffOutput () + in + if diffOfDiffPixels > 0 then ( + Jpg.IO.saveImage diffOutput "test/test-images/jpg/_diff-output.png"; + Png.IO.saveImage diffMaskOfDiff + "test/test-images/jpg/_diff-of-diff.png"); + (expect.int diffOfDiffPixels).toBe 0; + (expect.float diffOfDiffPercentage).toBeCloseTo 0.0)) diff --git a/test/Test_IO_JPG.re b/test/Test_IO_JPG.re deleted file mode 100644 index 92c66d26..00000000 --- a/test/Test_IO_JPG.re +++ /dev/null @@ -1,57 +0,0 @@ -open TestFramework; -open ODiffIO; - -module Diff = Odiff.Diff.MakeDiff(Jpg.IO, Jpg.IO); -module Output_Diff = Odiff.Diff.MakeDiff(Png.IO, Jpg.IO); - -describe("IO: JPG / JPEG", ({test, _}) => { - test("finds difference between 2 images", ({expect, _}) => { - let img1 = Jpg.IO.loadImage("test/test-images/jpg/tiger.jpg"); - let img2 = Jpg.IO.loadImage("test/test-images/jpg/tiger-2.jpg"); - - let (_, diffPixels, diffPercentage, _) = Diff.compare(img1, img2, ()); - - expect.int(diffPixels).toBe(7586); - expect.float(diffPercentage).toBeCloseTo(1.14); - }); - - test("Diff of mask and no mask are equal", ({expect, _}) => { - let img1 = Jpg.IO.loadImage("test/test-images/jpg/tiger.jpg"); - let img2 = Jpg.IO.loadImage("test/test-images/jpg/tiger-2.jpg"); - - let (_, diffPixels, diffPercentage, _) = - Diff.compare(img1, img2, ~outputDiffMask=false, ()); - - let img1 = Jpg.IO.loadImage("test/test-images/jpg/tiger.jpg"); - let img2 = Jpg.IO.loadImage("test/test-images/jpg/tiger-2.jpg"); - - let (_, diffPixelsMask, diffPercentageMask, _) = - Diff.compare(img1, img2, ~outputDiffMask=true, ()); - - expect.int(diffPixels).toBe(diffPixelsMask); - expect.float(diffPercentage).toBeCloseTo(diffPercentageMask); - }); - - test("Creates correct diff output image", ({expect, _}) => { - let img1 = Jpg.IO.loadImage("test/test-images/jpg/tiger.jpg"); - let img2 = Jpg.IO.loadImage("test/test-images/jpg/tiger-2.jpg"); - - let (diffOutput, _, _, _) = Diff.compare(img1, img2, ()); - - let originalDiff = - Png.IO.loadImage("test/test-images/jpg/tiger-diff.png"); - let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _) = - Output_Diff.compare(originalDiff, diffOutput, ()); - - if (diffOfDiffPixels > 0) { - Jpg.IO.saveImage(diffOutput, "test/test-images/jpg/_diff-output.png"); - Png.IO.saveImage( - diffMaskOfDiff, - "test/test-images/jpg/_diff-of-diff.png", - ); - }; - - expect.int(diffOfDiffPixels).toBe(0); - expect.float(diffOfDiffPercentage).toBeCloseTo(0.0); - }); -}); diff --git a/test/Test_IO_PNG.ml b/test/Test_IO_PNG.ml new file mode 100644 index 00000000..4c8f83e8 --- /dev/null +++ b/test/Test_IO_PNG.ml @@ -0,0 +1,39 @@ +open TestFramework +open ODiffIO +module Diff = Odiff.Diff.MakeDiff (Png.IO) (Png.IO) + +let _ = + describe "IO: PNG" (fun { test; _ } -> + let open Png.IO in + test "finds difference between 2 images" (fun { expect; _ } -> + let img1 = loadImage "test/test-images/png/orange.png" in + let img2 = loadImage "test/test-images/png/orange_changed.png" in + let _, diffPixels, diffPercentage, _ = Diff.compare img1 img2 () in + (expect.int diffPixels).toBe 1430; + (expect.float diffPercentage).toBeCloseTo 1.20); + test "Diff of mask and no mask are equal" (fun { expect; _ } -> + let img1 = loadImage "test/test-images/png/orange.png" in + let img2 = loadImage "test/test-images/png/orange_changed.png" in + let _, diffPixels, diffPercentage, _ = + Diff.compare img1 img2 ~outputDiffMask:false () + in + let img1 = loadImage "test/test-images/png/orange.png" in + let img2 = loadImage "test/test-images/png/orange_changed.png" in + let _, diffPixelsMask, diffPercentageMask, _ = + Diff.compare img1 img2 ~outputDiffMask:true () + in + (expect.int diffPixels).toBe diffPixelsMask; + (expect.float diffPercentage).toBeCloseTo diffPercentageMask); + test "Creates correct diff output image" (fun { expect; _ } -> + let img1 = loadImage "test/test-images/png/orange.png" in + let img2 = loadImage "test/test-images/png/orange_changed.png" in + let diffOutput, _, _, _ = Diff.compare img1 img2 () in + let originalDiff = loadImage "test/test-images/png/orange_diff.png" in + let diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _ = + Diff.compare originalDiff diffOutput () + in + if diffOfDiffPixels > 0 then ( + saveImage diffOutput "test/test-images/png/diff-output.png"; + saveImage diffMaskOfDiff "test/test-images/png/diff-of-diff.png"); + (expect.int diffOfDiffPixels).toBe 0; + (expect.float diffOfDiffPercentage).toBeCloseTo 0.0)) diff --git a/test/Test_IO_PNG.re b/test/Test_IO_PNG.re deleted file mode 100644 index 0c859cbd..00000000 --- a/test/Test_IO_PNG.re +++ /dev/null @@ -1,54 +0,0 @@ -open TestFramework; -open ODiffIO; - -module Diff = Odiff.Diff.MakeDiff(Png.IO, Png.IO); - -describe("IO: PNG", ({test, _}) => { - open Png.IO; - - test("finds difference between 2 images", ({expect, _}) => { - let img1 = loadImage("test/test-images/png/orange.png"); - let img2 = loadImage("test/test-images/png/orange_changed.png"); - - let (_, diffPixels, diffPercentage, _) = Diff.compare(img1, img2, ()); - - expect.int(diffPixels).toBe(1430); - expect.float(diffPercentage).toBeCloseTo(1.20); - }); - - test("Diff of mask and no mask are equal", ({expect, _}) => { - let img1 = loadImage("test/test-images/png/orange.png"); - let img2 = loadImage("test/test-images/png/orange_changed.png"); - - let (_, diffPixels, diffPercentage, _) = - Diff.compare(img1, img2, ~outputDiffMask=false, ()); - - let img1 = loadImage("test/test-images/png/orange.png"); - let img2 = loadImage("test/test-images/png/orange_changed.png"); - - let (_, diffPixelsMask, diffPercentageMask, _) = - Diff.compare(img1, img2, ~outputDiffMask=true, ()); - - expect.int(diffPixels).toBe(diffPixelsMask); - expect.float(diffPercentage).toBeCloseTo(diffPercentageMask); - }); - - test("Creates correct diff output image", ({expect, _}) => { - let img1 = loadImage("test/test-images/png/orange.png"); - let img2 = loadImage("test/test-images/png/orange_changed.png"); - - let (diffOutput, _, _, _) = Diff.compare(img1, img2, ()); - - let originalDiff = loadImage("test/test-images/png/orange_diff.png"); - let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _) = - Diff.compare(originalDiff, diffOutput, ()); - - if (diffOfDiffPixels > 0) { - saveImage(diffOutput, "test/test-images/png/diff-output.png"); - saveImage(diffMaskOfDiff, "test/test-images/png/diff-of-diff.png"); - }; - - expect.int(diffOfDiffPixels).toBe(0); - expect.float(diffOfDiffPercentage).toBeCloseTo(0.0); - }); -}); diff --git a/test/Test_IO_TIFF.ml b/test/Test_IO_TIFF.ml new file mode 100644 index 00000000..1deb0b35 --- /dev/null +++ b/test/Test_IO_TIFF.ml @@ -0,0 +1,44 @@ +open TestFramework +open ODiffIO +module Diff = Odiff.Diff.MakeDiff (Tiff.IO) (Tiff.IO) +module Output_Diff = Odiff.Diff.MakeDiff (Png.IO) (Tiff.IO) + +let _ = + describe "IO: TIFF" (fun { test; _ } -> + test "finds difference between 2 images" (fun { expect; _ } -> + let img1 = Tiff.IO.loadImage "test/test-images/tiff/laptops.tiff" in + let img2 = Tiff.IO.loadImage "test/test-images/tiff/laptops-2.tiff" in + let _, diffPixels, diffPercentage, _ = Diff.compare img1 img2 () in + (expect.int diffPixels).toBe 8569; + (expect.float diffPercentage).toBeCloseTo 3.79); + + test "Diff of mask and no mask are equal" (fun { expect; _ } -> + let img1 = Tiff.IO.loadImage "test/test-images/tiff/laptops.tiff" in + let img2 = Tiff.IO.loadImage "test/test-images/tiff/laptops-2.tiff" in + let _, diffPixels, diffPercentage, _ = + Diff.compare img1 img2 ~outputDiffMask:false () + in + let img1 = Tiff.IO.loadImage "test/test-images/tiff/laptops.tiff" in + let img2 = Tiff.IO.loadImage "test/test-images/tiff/laptops-2.tiff" in + let _, diffPixelsMask, diffPercentageMask, _ = + Diff.compare img1 img2 ~outputDiffMask:true () + in + (expect.int diffPixels).toBe diffPixelsMask; + (expect.float diffPercentage).toBeCloseTo diffPercentageMask); + test "Creates correct diff output image" (fun { expect; _ } -> + let img1 = Tiff.IO.loadImage "test/test-images/tiff/laptops.tiff" in + let img2 = Tiff.IO.loadImage "test/test-images/tiff/laptops-2.tiff" in + let diffOutput, _, _, _ = Diff.compare img1 img2 () in + let originalDiff = + Png.IO.loadImage "test/test-images/tiff/laptops-diff.png" + in + let diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _ = + Output_Diff.compare originalDiff diffOutput () + in + if diffOfDiffPixels > 0 then ( + Tiff.IO.saveImage diffOutput + "test/test-images/tiff/_diff-output.png"; + Png.IO.saveImage diffMaskOfDiff + "test/test-images/tiff/_diff-of-diff.png"); + (expect.int diffOfDiffPixels).toBe 0; + (expect.float diffOfDiffPercentage).toBeCloseTo 0.0)) diff --git a/test/Test_IO_TIFF.re b/test/Test_IO_TIFF.re deleted file mode 100644 index 56c2e238..00000000 --- a/test/Test_IO_TIFF.re +++ /dev/null @@ -1,57 +0,0 @@ -open TestFramework; -open ODiffIO; - -module Diff = Odiff.Diff.MakeDiff(Tiff.IO, Tiff.IO); -module Output_Diff = Odiff.Diff.MakeDiff(Png.IO, Tiff.IO); - -describe("IO: TIFF", ({test, _}) => { - test("finds difference between 2 images", ({expect, _}) => { - let img1 = Tiff.IO.loadImage("test/test-images/tiff/laptops.tiff"); - let img2 = Tiff.IO.loadImage("test/test-images/tiff/laptops-2.tiff"); - - let (_, diffPixels, diffPercentage, _) = Diff.compare(img1, img2, ()); - - expect.int(diffPixels).toBe(8569); - expect.float(diffPercentage).toBeCloseTo(3.79); - }); - - test("Diff of mask and no mask are equal", ({expect, _}) => { - let img1 = Tiff.IO.loadImage("test/test-images/tiff/laptops.tiff"); - let img2 = Tiff.IO.loadImage("test/test-images/tiff/laptops-2.tiff"); - - let (_, diffPixels, diffPercentage, _) = - Diff.compare(img1, img2, ~outputDiffMask=false, ()); - - let img1 = Tiff.IO.loadImage("test/test-images/tiff/laptops.tiff"); - let img2 = Tiff.IO.loadImage("test/test-images/tiff/laptops-2.tiff"); - - let (_, diffPixelsMask, diffPercentageMask, _) = - Diff.compare(img1, img2, ~outputDiffMask=true, ()); - - expect.int(diffPixels).toBe(diffPixelsMask); - expect.float(diffPercentage).toBeCloseTo(diffPercentageMask); - }); - - test("Creates correct diff output image", ({expect, _}) => { - let img1 = Tiff.IO.loadImage("test/test-images/tiff/laptops.tiff"); - let img2 = Tiff.IO.loadImage("test/test-images/tiff/laptops-2.tiff"); - - let (diffOutput, _, _, _) = Diff.compare(img1, img2, ()); - - let originalDiff = - Png.IO.loadImage("test/test-images/tiff/laptops-diff.png"); - let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _) = - Output_Diff.compare(originalDiff, diffOutput, ()); - - if (diffOfDiffPixels > 0) { - Tiff.IO.saveImage(diffOutput, "test/test-images/tiff/_diff-output.png"); - Png.IO.saveImage( - diffMaskOfDiff, - "test/test-images/tiff/_diff-of-diff.png", - ); - }; - - expect.int(diffOfDiffPixels).toBe(0); - expect.float(diffOfDiffPercentage).toBeCloseTo(0.0); - }); -}); diff --git a/test/dune b/test/dune index d1d9b628..e60f682d 100644 --- a/test/dune +++ b/test/dune @@ -6,16 +6,14 @@ ; you will want to depend on the library you are testing as well, however for ; the purposes of this example we are only depending on the test runner itself (libraries rely.lib odiff odiff-io) - (modules (:standard \ RunTests)) -) + (modules + (:standard \ RunTests))) + (executable ; the for the library is automatically detected because of the name, but we ; need to explicitly specify the package here (name RunTests) (package odiff) (public_name RunTests.exe) - (libraries - OdiffTests - ) - (modules RunTests) -) \ No newline at end of file + (libraries OdiffTests) + (modules RunTests))