diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index cd3a2cf8..00000000
Binary files a/.DS_Store and /dev/null differ
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..288c8bb9 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,23 +30,22 @@
"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"
},
"devDependencies": {
"ava": "^3.15.0",
"typescript": "^4.3.5",
- "simple-git-hooks": "^2.5.1",
"@opam/merlin": "*",
"@opam/ocaml-lsp-server": "*",
"@opam/odoc": "*",
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))