diff --git a/bin/Main.re b/bin/Main.re index 5f4a08d4..cc153364 100644 --- a/bin/Main.re +++ b/bin/Main.re @@ -6,11 +6,14 @@ let getIOModule = (filename, ~antialiasing) => |> ( fun | ".png" when antialiasing => ( - (module ODiffIO.PureC_IO_Bigarray.IO): (module ImageIO) + (module ODiffIO.Png.BigarrayIO): (module ImageIO) ) - | ".png" => ((module ODiffIO.PureC_IO.IO): (module ImageIO)) - | unsupportedFormat => - failwith("This format is not supported: " ++ unsupportedFormat) + | ".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) = { diff --git a/dune-project b/dune-project index abc5b83d..162f4838 100644 --- a/dune-project +++ b/dune-project @@ -41,7 +41,6 @@ dune conf-libpng odiff-core - (camlimages (= 5.4.0)) (reason (and (>= 3.6.0) (< 4.0.0))) (ocaml (and (>= 4.10.0) (< 4.11.0))) ) diff --git a/esy.lock/index.json b/esy.lock/index.json index 51f383f7..16de3f70 100644 --- a/esy.lock/index.json +++ b/esy.lock/index.json @@ -1,5 +1,5 @@ { - "checksum": "b2b6508a82304ba11c49ee08ec5747d3", + "checksum": "f3ecae1e054832e57ef4792158eeea14", "root": "odiff@link-dev:./package.json", "node": { "yargs-parser@20.2.9@d41d8cd9": { @@ -941,7 +941,7 @@ }, "overrides": [], "dependencies": [ - "path-parse@1.0.7@d41d8cd9", "is-core-module@2.4.0@d41d8cd9" + "path-parse@1.0.7@d41d8cd9", "is-core-module@2.5.0@d41d8cd9" ], "devDependencies": [] }, @@ -1052,7 +1052,7 @@ "dependencies": [ "type-fest@0.6.0@d41d8cd9", "parse-json@5.2.0@d41d8cd9", "normalize-package-data@2.5.0@d41d8cd9", - "@types/normalize-package-data@2.4.0@d41d8cd9" + "@types/normalize-package-data@2.4.1@d41d8cd9" ], "devDependencies": [] }, @@ -1553,19 +1553,21 @@ "dependencies": [ "ocaml@4.10.2000@d41d8cd9", "esy-zlib@github:eWert-Online/esy-zlib#9b0394f07551d9fde386e28a6ff269984b63ffcf@d41d8cd9", + "esy-libtiff@github:esy-packages/esy-libtiff#83b1f5c8f76ad42acb5c008f537b5b3f0902c066@d41d8cd9", "esy-libpng@github:eWert-Online/esy-libpng#485fec4990dbb0f6b0ecb3ced53f5bd7bc9a4506@d41d8cd9", - "@reason-native/rely@3.2.1@d41d8cd9", + "esy-libjpeg@github:eWert-Online/esy-libjpeg#76b287add2732e191a2c4a1c14a34bab63c4f74d@d41d8cd9", + "ava@3.15.0@d41d8cd9", "@reason-native/rely@3.2.1@d41d8cd9", "@reason-native/pastel@0.3.0@d41d8cd9", "@reason-native/console@0.1.0@d41d8cd9", - "@opam/dune-configurator@opam:2.8.5@428293ca", + "@opam/reason@opam:3.7.0@494dd52d", + "@opam/dune-configurator@opam:2.9.0@40e837e7", "@opam/dune@opam:2.9.0@489e77a9", - "@opam/cmdliner@opam:1.0.4@93208aac", - "@esy-ocaml/reason@3.7.0@d41d8cd9" + "@opam/cmdliner@opam:1.0.4@93208aac" ], "devDependencies": [ "typescript@4.3.5@d41d8cd9", "simple-git-hooks@2.5.1@d41d8cd9", "refmterr@3.3.2@d41d8cd9", "ava@3.15.0@d41d8cd9", - "@opam/odoc@opam:1.5.2@3d78163d", + "@opam/odoc@opam:1.5.3@29f53af8", "@opam/ocaml-lsp-server@opam:1.4.0@c9583433", "@opam/merlin@opam:3.5.0@f8ae5d56" ] @@ -2316,14 +2318,14 @@ "dependencies": [], "devDependencies": [] }, - "is-core-module@2.4.0@d41d8cd9": { - "id": "is-core-module@2.4.0@d41d8cd9", + "is-core-module@2.5.0@d41d8cd9": { + "id": "is-core-module@2.5.0@d41d8cd9", "name": "is-core-module", - "version": "2.4.0", + "version": "2.5.0", "source": { "type": "install", "source": [ - "archive:https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#sha1:8e9fc8e15027b011418026e98f0e6f4d86305cc1" + "archive:https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz#sha1:f754843617c70bfd29b7bd87327400cda5c18491" ] }, "overrides": [], @@ -2675,7 +2677,7 @@ "overrides": [], "dependencies": [ "slash@3.0.0@d41d8cd9", "merge2@1.4.1@d41d8cd9", - "ignore@5.1.8@d41d8cd9", "fast-glob@3.2.6@d41d8cd9", + "ignore@5.1.8@d41d8cd9", "fast-glob@3.2.7@d41d8cd9", "dir-glob@3.0.1@d41d8cd9", "array-union@2.1.0@d41d8cd9" ], "devDependencies": [] @@ -2882,20 +2884,20 @@ "dependencies": [ "reusify@1.0.4@d41d8cd9" ], "devDependencies": [] }, - "fast-glob@3.2.6@d41d8cd9": { - "id": "fast-glob@3.2.6@d41d8cd9", + "fast-glob@3.2.7@d41d8cd9": { + "id": "fast-glob@3.2.7@d41d8cd9", "name": "fast-glob", - "version": "3.2.6", + "version": "3.2.7", "source": { "type": "install", "source": [ - "archive:https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz#sha1:434dd9529845176ea049acc9343e8282765c6e1a" + "archive:https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz#sha1:fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" ] }, "overrides": [], "dependencies": [ "micromatch@4.0.4@d41d8cd9", "merge2@1.4.1@d41d8cd9", - "glob-parent@5.1.2@d41d8cd9", "@nodelib/fs.walk@1.2.7@d41d8cd9", + "glob-parent@5.1.2@d41d8cd9", "@nodelib/fs.walk@1.2.8@d41d8cd9", "@nodelib/fs.stat@2.0.5@d41d8cd9" ], "devDependencies": [] @@ -2930,6 +2932,24 @@ "dependencies": [], "devDependencies": [] }, + "esy-libtiff@github:esy-packages/esy-libtiff#83b1f5c8f76ad42acb5c008f537b5b3f0902c066@d41d8cd9": { + "id": + "esy-libtiff@github:esy-packages/esy-libtiff#83b1f5c8f76ad42acb5c008f537b5b3f0902c066@d41d8cd9", + "name": "esy-libtiff", + "version": + "github:esy-packages/esy-libtiff#83b1f5c8f76ad42acb5c008f537b5b3f0902c066", + "source": { + "type": "install", + "source": [ + "github:esy-packages/esy-libtiff#83b1f5c8f76ad42acb5c008f537b5b3f0902c066" + ] + }, + "overrides": [], + "dependencies": [ + "esy-zlib@github:eWert-Online/esy-zlib#9b0394f07551d9fde386e28a6ff269984b63ffcf@d41d8cd9" + ], + "devDependencies": [] + }, "esy-libpng@github:eWert-Online/esy-libpng#485fec4990dbb0f6b0ecb3ced53f5bd7bc9a4506@d41d8cd9": { "id": "esy-libpng@github:eWert-Online/esy-libpng#485fec4990dbb0f6b0ecb3ced53f5bd7bc9a4506@d41d8cd9", @@ -2948,6 +2968,22 @@ ], "devDependencies": [] }, + "esy-libjpeg@github:eWert-Online/esy-libjpeg#76b287add2732e191a2c4a1c14a34bab63c4f74d@d41d8cd9": { + "id": + "esy-libjpeg@github:eWert-Online/esy-libjpeg#76b287add2732e191a2c4a1c14a34bab63c4f74d@d41d8cd9", + "name": "esy-libjpeg", + "version": + "github:eWert-Online/esy-libjpeg#76b287add2732e191a2c4a1c14a34bab63c4f74d", + "source": { + "type": "install", + "source": [ + "github:eWert-Online/esy-libjpeg#76b287add2732e191a2c4a1c14a34bab63c4f74d" + ] + }, + "overrides": [], + "dependencies": [], + "devDependencies": [] + }, "esutils@2.0.3@d41d8cd9": { "id": "esutils@2.0.3@d41d8cd9", "name": "esutils", @@ -4175,14 +4211,14 @@ "dependencies": [], "devDependencies": [] }, - "@types/normalize-package-data@2.4.0@d41d8cd9": { - "id": "@types/normalize-package-data@2.4.0@d41d8cd9", + "@types/normalize-package-data@2.4.1@d41d8cd9": { + "id": "@types/normalize-package-data@2.4.1@d41d8cd9", "name": "@types/normalize-package-data", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "install", "source": [ - "archive:https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#sha1:e486d0d97396d79beedd0a6e33f4534ff6b4973e" + "archive:https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#sha1:d3357479a0fdfdd5907fe67e17e0a85c906e1301" ] }, "overrides": [], @@ -4510,6 +4546,40 @@ "ocaml@4.10.2000@d41d8cd9", "@opam/dune@opam:2.9.0@489e77a9" ] }, + "@opam/reason@opam:3.7.0@494dd52d": { + "id": "@opam/reason@opam:3.7.0@494dd52d", + "name": "@opam/reason", + "version": "opam:3.7.0", + "source": { + "type": "install", + "source": [ + "archive:https://opam.ocaml.org/cache/md5/7e/7eb8cbbff8565b93ebfabf4eca7254d4#md5:7eb8cbbff8565b93ebfabf4eca7254d4", + "archive:https://registry.npmjs.org/@esy-ocaml/reason/-/reason-3.7.0.tgz#md5:7eb8cbbff8565b93ebfabf4eca7254d4" + ], + "opam": { + "name": "reason", + "version": "3.7.0", + "path": "esy.lock/opam/reason.3.7.0" + } + }, + "overrides": [], + "dependencies": [ + "ocaml@4.10.2000@d41d8cd9", "@opam/result@opam:1.5@1c6a6533", + "@opam/ppx_derivers@opam:1.2.1@ecf0aa45", + "@opam/ocamlfind@opam:1.9.1@b748edf6", + "@opam/merlin-extend@opam:0.6@88755c91", + "@opam/menhir@opam:20210419@11c42419", + "@opam/fix@opam:20201120@0b212fb9", "@opam/dune@opam:2.9.0@489e77a9", + "@esy-ocaml/substs@0.0.1@d41d8cd9" + ], + "devDependencies": [ + "ocaml@4.10.2000@d41d8cd9", "@opam/result@opam:1.5@1c6a6533", + "@opam/ppx_derivers@opam:1.2.1@ecf0aa45", + "@opam/merlin-extend@opam:0.6@88755c91", + "@opam/menhir@opam:20210419@11c42419", + "@opam/fix@opam:20201120@0b212fb9", "@opam/dune@opam:2.9.0@489e77a9" + ] + }, "@opam/re@opam:1.9.0@9373f267": { "id": "@opam/re@opam:1.9.0@9373f267", "name": "@opam/re", @@ -4615,20 +4685,20 @@ "ocaml@4.10.2000@d41d8cd9", "@opam/dune@opam:2.9.0@489e77a9" ] }, - "@opam/odoc@opam:1.5.2@3d78163d": { - "id": "@opam/odoc@opam:1.5.2@3d78163d", + "@opam/odoc@opam:1.5.3@29f53af8": { + "id": "@opam/odoc@opam:1.5.3@29f53af8", "name": "@opam/odoc", - "version": "opam:1.5.2", + "version": "opam:1.5.3", "source": { "type": "install", "source": [ - "archive:https://opam.ocaml.org/cache/sha256/d2/d24463f2660bc28c72cda001478360158e953721c9e23fb361ec4783113c4871#sha256:d24463f2660bc28c72cda001478360158e953721c9e23fb361ec4783113c4871", - "archive:https://github.com/ocaml/odoc/releases/download/1.5.2/odoc-1.5.2.tbz#sha256:d24463f2660bc28c72cda001478360158e953721c9e23fb361ec4783113c4871" + "archive:https://opam.ocaml.org/cache/sha256/f2/f2b76f811658c4b52cb48ac4ffc2ec37cedd2a805111c7f8ec20f8f36b8bbf45#sha256:f2b76f811658c4b52cb48ac4ffc2ec37cedd2a805111c7f8ec20f8f36b8bbf45", + "archive:https://github.com/ocaml/odoc/releases/download/1.5.3/odoc-1.5.3.tbz#sha256:f2b76f811658c4b52cb48ac4ffc2ec37cedd2a805111c7f8ec20f8f36b8bbf45" ], "opam": { "name": "odoc", - "version": "1.5.2", - "path": "esy.lock/opam/odoc.1.5.2" + "version": "1.5.3", + "path": "esy.lock/opam/odoc.1.5.3" } }, "overrides": [], @@ -4979,20 +5049,20 @@ "ocaml@4.10.2000@d41d8cd9", "@opam/dune@opam:2.9.0@489e77a9" ] }, - "@opam/dune-configurator@opam:2.8.5@428293ca": { - "id": "@opam/dune-configurator@opam:2.8.5@428293ca", + "@opam/dune-configurator@opam:2.9.0@40e837e7": { + "id": "@opam/dune-configurator@opam:2.9.0@40e837e7", "name": "@opam/dune-configurator", - "version": "opam:2.8.5", + "version": "opam:2.9.0", "source": { "type": "install", "source": [ - "archive:https://opam.ocaml.org/cache/sha256/79/79011283fb74c7a27eb17ad752efbcc39b39633cbacc8d7be97e8ea869443629#sha256:79011283fb74c7a27eb17ad752efbcc39b39633cbacc8d7be97e8ea869443629", - "archive:https://github.com/ocaml/dune/releases/download/2.8.5/dune-2.8.5.tbz#sha256:79011283fb74c7a27eb17ad752efbcc39b39633cbacc8d7be97e8ea869443629" + "archive:https://opam.ocaml.org/cache/sha256/bb/bb217cfb17e893a0b1eb002ac83c14f96adc9d0703cb51ff34ed3ef5a639a41e#sha256:bb217cfb17e893a0b1eb002ac83c14f96adc9d0703cb51ff34ed3ef5a639a41e", + "archive:https://github.com/ocaml/dune/releases/download/2.9.0/dune-2.9.0.tbz#sha256:bb217cfb17e893a0b1eb002ac83c14f96adc9d0703cb51ff34ed3ef5a639a41e" ], "opam": { "name": "dune-configurator", - "version": "2.8.5", - "path": "esy.lock/opam/dune-configurator.2.8.5" + "version": "2.9.0", + "path": "esy.lock/opam/dune-configurator.2.9.0" } }, "overrides": [], @@ -5334,14 +5404,14 @@ ], "devDependencies": [ "ocaml@4.10.2000@d41d8cd9" ] }, - "@nodelib/fs.walk@1.2.7@d41d8cd9": { - "id": "@nodelib/fs.walk@1.2.7@d41d8cd9", + "@nodelib/fs.walk@1.2.8@d41d8cd9": { + "id": "@nodelib/fs.walk@1.2.8@d41d8cd9", "name": "@nodelib/fs.walk", - "version": "1.2.7", + "version": "1.2.8", "source": { "type": "install", "source": [ - "archive:https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz#sha1:94c23db18ee4653e129abd26fb06f870ac9e1ee2" + "archive:https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#sha1:e95737e8bb6746ddedf69c556953494f196fe69a" ] }, "overrides": [], diff --git a/esy.lock/opam/dune-configurator.2.8.5/opam b/esy.lock/opam/dune-configurator.2.9.0/opam similarity index 63% rename from esy.lock/opam/dune-configurator.2.8.5/opam rename to esy.lock/opam/dune-configurator.2.9.0/opam index aa5f74b7..d0fc2619 100644 --- a/esy.lock/opam/dune-configurator.2.8.5/opam +++ b/esy.lock/opam/dune-configurator.2.9.0/opam @@ -10,14 +10,14 @@ Among other things, dune-configurator allows one to: - import #define from OCaml header files - generate config.h file """ -maintainer: ["Jane Street Group, LLC"] -authors: ["Jane Street Group, LLC"] +maintainer: ["Jane Street Group, LLC "] +authors: ["Jane Street Group, LLC "] license: "MIT" homepage: "https://github.com/ocaml/dune" doc: "https://dune.readthedocs.io/" bug-reports: "https://github.com/ocaml/dune/issues" depends: [ - "dune" {>= "2.8"} + "dune" {>= "2.9"} "ocaml" {>= "4.03.0"} "result" "csexp" {>= "1.3.0"} @@ -37,11 +37,11 @@ build: [ "@doc" {with-doc} ] ] -x-commit-hash: "e84ba5230f6afacb12f022937138a752f1c301b6" +x-commit-hash: "641a95d2254ca7c51c97f07f2eed85b7a95db954" url { - src: "https://github.com/ocaml/dune/releases/download/2.8.5/dune-2.8.5.tbz" + src: "https://github.com/ocaml/dune/releases/download/2.9.0/dune-2.9.0.tbz" checksum: [ - "sha256=79011283fb74c7a27eb17ad752efbcc39b39633cbacc8d7be97e8ea869443629" - "sha512=4ef6cdea0768a29de0108cb61b04ef471cb494762c865265f20d7d15ed65a39557f7e34f2dbd466352a6567cce29d7ba21be6569afafbcfc2871720b9466dcae" + "sha256=bb217cfb17e893a0b1eb002ac83c14f96adc9d0703cb51ff34ed3ef5a639a41e" + "sha512=fcd8bc3ea7e9a14e6787a3b0384a488e45fa51e164a175ad1b147bebb3fbcccfd8f321b9b6a7665ac3dafeb18a2a6f8626d182af3b1796691f6ed79c166a5f44" ] } diff --git a/esy.lock/opam/odoc.1.5.2/opam b/esy.lock/opam/odoc.1.5.3/opam similarity index 58% rename from esy.lock/opam/odoc.1.5.2/opam rename to esy.lock/opam/odoc.1.5.3/opam index b3c74b37..bf1d503d 100644 --- a/esy.lock/opam/odoc.1.5.2/opam +++ b/esy.lock/opam/odoc.1.5.3/opam @@ -12,7 +12,7 @@ authors: [ "Anton Bachin " "Jon Ludlam " ] -maintainer: "Anton Bachin " +maintainer: "Jon Ludlam " dev-repo: "git+https://github.com/ocaml/odoc.git" synopsis: "OCaml documentation generator" @@ -27,27 +27,27 @@ depends: [ "cppo" {build & >= "1.1.0"} "dune" "fpath" - "ocaml" {>= "4.02.0" & < "4.13"} + "ocaml" {>= "4.02.0"} "result" "tyxml" {>= "4.3.0"} - "alcotest" {dev & >= "0.8.3"} - "markup" {dev & >= "1.0.0"} + "alcotest" {dev & >= "0.8.3" & with-test} + "markup" {dev & >= "1.0.0" & with-test} "ocamlfind" {dev} - "sexplib" {dev & >= "113.33.00"} + "sexplib" {dev & >= "113.33.00" & with-test} - "bisect_ppx" {dev & >= "1.3.0"} + "bisect_ppx" {with-test & >= "1.3.0"} ] build: [ ["dune" "subst"] {dev} ["dune" "build" "-p" name "-j" jobs] ] -x-commit-hash: "c0df8ce2171fa9645a41f371429aa3ddc16de5c1" url { - src: "https://github.com/ocaml/odoc/releases/download/1.5.2/odoc-1.5.2.tbz" + src: "https://github.com/ocaml/odoc/releases/download/1.5.3/odoc-1.5.3.tbz" checksum: [ - "sha256=d24463f2660bc28c72cda001478360158e953721c9e23fb361ec4783113c4871" - "sha512=e6c83630325de422f31cda8f88c038d213969f8b98e989593c057658f3956c0855860c9bc38f61b6479929516ca95aee689ddfba3ad8c47d821c4fdf54524cf9" + "sha256=f2b76f811658c4b52cb48ac4ffc2ec37cedd2a805111c7f8ec20f8f36b8bbf45" + "sha512=9e069590e0737c94813d25235b5cfe27feb5a0298a17ff9b9ee446c69827c3a0ea3b7da5d05b278639cd1f0202e0d83356707979edfaa2af73876fc000c23c4d" ] } +x-commit-hash: "8de4a36814533b25b461373fe5c0f54db55e5e7c" diff --git a/esy.lock/opam/reason.3.7.0/opam b/esy.lock/opam/reason.3.7.0/opam new file mode 100644 index 00000000..007aa5bc --- /dev/null +++ b/esy.lock/opam/reason.3.7.0/opam @@ -0,0 +1,31 @@ +opam-version: "2.0" +maintainer: "Jordan Walke " +authors: [ "Jordan Walke " ] +license: "MIT" +homepage: "https://github.com/facebook/reason" +doc: "http://reasonml.github.io/" +bug-reports: "https://github.com/facebook/reason/issues" +dev-repo: "git://github.com/facebook/reason.git" +tags: [ "syntax" ] +build: [ + ["dune" "build" "-p" name "-j" jobs] +] +depends: [ + "ocaml" {>= "4.03" & < "4.13"} + "dune" {>= "1.4"} + "ocamlfind" {build} + "menhir" {>= "20180523"} + "merlin-extend" {>= "0.6"} + "ppx_derivers" {< "2.0"} + "fix" + "result" +] +synopsis: "Reason: Syntax & Toolchain for OCaml" +description: """ +Reason gives OCaml a new syntax that is remniscient of languages like +JavaScript. It's also the umbrella project for a set of tools for the OCaml & +JavaScript ecosystem.""" +url { + src: "https://registry.npmjs.org/@esy-ocaml/reason/-/reason-3.7.0.tgz" + checksum: "md5=7eb8cbbff8565b93ebfabf4eca7254d4" +} diff --git a/io/ODiffIO.re b/io/ODiffIO.re new file mode 100644 index 00000000..0b231c16 --- /dev/null +++ b/io/ODiffIO.re @@ -0,0 +1,4 @@ +module Bmp = Bmp; +module Png = Png; +module Jpg = Jpg; +module Tiff = Tiff; diff --git a/io/ReadPng.ml b/io/ReadPng.ml deleted file mode 100644 index c4659624..00000000 --- a/io/ReadPng.ml +++ /dev/null @@ -1,15 +0,0 @@ - -external read_png_image: string -> int * int * int * 'b = "read_png_file_to_tuple" -external row_pointers_to_bigarray: 'b -> int -> int -> int-> 'c = "row_pointers_to_bigarray" - -external read_row: 'a -> int -> int -> 'b = "read_row" - -external set_pixel_data: 'a -> int -> int -> int * int * int -> unit = "set_pixel_data" [@@noalloc] - -external write_png_file: 'a -> int -> int -> string -> unit = "write_png_file" [@@noalloc] - -external write_png_buffer : string -> bytes -> int -> int -> unit = "write_png_buffer" - -external free_row_pointers: 'a -> int -> unit = "free_row_pointers" [@@noalloc] - -external create_empty_img: int -> int -> 'a = "create_empty_img" [@@noalloc] \ No newline at end of file diff --git a/io/bmp/Bmp.re b/io/bmp/Bmp.re new file mode 100644 index 00000000..0aa8a061 --- /dev/null +++ b/io/bmp/Bmp.re @@ -0,0 +1,48 @@ +open Bigarray; + +module IO: Odiff.ImageIO.ImageIO = { + type t = Array1.t(int32, int32_elt, c_layout); + type row = int; + + let loadImage = (filename): Odiff.ImageIO.img(t) => { + let (width, height, image) = ReadBmp.load(filename); + + {width, height, image}; + }; + + let readRow = (img: Odiff.ImageIO.img(t), y): row => y; + + let readDirectPixel = (~x, ~y, img: Odiff.ImageIO.img(t)) => { + (img.image).{y * img.width + x}; + }; + + let readImgColor = (x, row: row, img: Odiff.ImageIO.img(t)) => { + readDirectPixel(~x, ~y=row, img); + }; + + let setImgColor = (x, y, (r, g, b), img: Odiff.ImageIO.img(t)) => { + 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; + Array1.set( + img.image, + y * img.width + x, + Int32.of_int(a lor b lor g lor r), + ); + }; + + 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/ReadBmp.re b/io/bmp/ReadBmp.re new file mode 100644 index 00000000..ee7583d0 --- /dev/null +++ b/io/bmp/ReadBmp.re @@ -0,0 +1,208 @@ +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 new file mode 100644 index 00000000..4738f862 --- /dev/null +++ b/io/bmp/ReadBmp.rei @@ -0,0 +1,3 @@ +let load: + string => + (int, int, Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout)); diff --git a/io/bmp/dune b/io/bmp/dune new file mode 100644 index 00000000..fb308294 --- /dev/null +++ b/io/bmp/dune @@ -0,0 +1,6 @@ +(library + (name Bmp) + (public_name odiff-io.bmp) + (flags + (-w -40 -w +26)) + (libraries odiff-core WritePng)) diff --git a/io/config/discover.re b/io/config/discover.re index de259b64..a8e94ccf 100644 --- a/io/config/discover.re +++ b/io/config/discover.re @@ -3,15 +3,32 @@ module C = Configurator.V1; C.main(~name="odiff-c-lib-package-resolver", _c => { let png_include_path = Sys.getenv("PNG_INCLUDE_PATH") |> String.trim; let png_lib_path = Sys.getenv("PNG_LIB_PATH") |> String.trim; + let libpng16 = png_lib_path ++ "/libpng16.a"; - let z_lib_path = Sys.getenv("Z_LIB_PATH") |> String.trim; + 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"; - let libpng16 = png_lib_path ++ "/libpng16.a"; - C.Flags.write_sexp("c_flags.sexp", ["-I" ++ png_include_path]); + C.Flags.write_sexp("png_write_c_flags.sexp", ["-I" ++ png_include_path]); + C.Flags.write_sexp("png_write_c_library_flags.sexp", [libpng16, zlib]); + C.Flags.write_sexp("png_write_flags.sexp", ["-cclib", libpng16]); + + C.Flags.write_sexp("png_c_flags.sexp", ["-I" ++ png_include_path]); + C.Flags.write_sexp("png_c_library_flags.sexp", [libpng16, zlib]); + C.Flags.write_sexp("png_flags.sexp", ["-cclib", libpng16]); - C.Flags.write_sexp("c_library_flags.sexp", [libpng16, zlib]); + 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("flags.sexp", ["-cclib", libpng16]); + 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/dune b/io/dune index fceaa5d5..59b6a544 100644 --- a/io/dune +++ b/io/dune @@ -2,19 +2,5 @@ (name ODiffIO) (public_name odiff-io) (flags - (-w -40 -w +26) - (:include flags.sexp) - -verbose) - (foreign_stubs - (language c) - (names ReadPng) - (flags - (:include c_flags.sexp))) - (c_library_flags - (:include c_library_flags.sexp)) - (libraries odiff-core)) - -(rule - (targets flags.sexp c_flags.sexp c_library_flags.sexp) - (action - (run ./config/discover.exe))) + (-w -40 -w +26)) + (libraries odiff-io.png odiff-io.jpg odiff-io.bmp odiff-io.tiff)) diff --git a/io/jpg/Jpg.re b/io/jpg/Jpg.re new file mode 100644 index 00000000..f3b554bc --- /dev/null +++ b/io/jpg/Jpg.re @@ -0,0 +1,50 @@ +open Bigarray; + +module IO: Odiff.ImageIO.ImageIO = { + type t = Array1.t(int32, int32_elt, c_layout); + type row = int; + let buffer = ref(None); + + let loadImage = (filename): Odiff.ImageIO.img(t) => { + let (width, height, image, b) = ReadJpg.read_jpeg_image(filename); + buffer := Some(b); + + {width, height, image}; + }; + + let readRow = (img: Odiff.ImageIO.img(t), y): row => y; + + let readDirectPixel = (~x, ~y, img: Odiff.ImageIO.img(t)) => { + (img.image).{y * img.width + x}; + }; + + let readImgColor = (x, row: row, img: Odiff.ImageIO.img(t)) => { + readDirectPixel(~x, ~y=row, img); + }; + + let setImgColor = (x, y, (r, g, b), img: Odiff.ImageIO.img(t)) => { + 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; + Array1.set( + img.image, + y * img.width + x, + Int32.of_int(a lor b lor g lor r), + ); + }; + + 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)) => { + buffer^ |> Option.iter(ReadJpg.cleanup_jpg); + }; + + let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => { + let image = Array1.create(int32, c_layout, Array1.dim(img.image)); + + {...img, image}; + }; +}; diff --git a/io/jpg/ReadJpg.c b/io/jpg/ReadJpg.c new file mode 100644 index 00000000..bf3a7909 --- /dev/null +++ b/io/jpg/ReadJpg.c @@ -0,0 +1,89 @@ +#define CAML_NAME_SPACE + +#include + +#include + +#include +#include +#include +#include +#include + +CAMLprim value +read_jpeg_file_to_tuple(value file) +{ + CAMLparam1(file); + CAMLlocal2(res, ba); + + size_t size; + struct jpeg_error_mgr jerr; + struct jpeg_decompress_struct cinfo; + + jpeg_create_decompress(&cinfo); + cinfo.err = jpeg_std_error(&jerr); + + const char *filename = String_val(file); + FILE *fp = fopen(filename, "rb"); + if (!fp) { + caml_failwith("opening input file failed!"); + } + if (fseek(fp, 0, SEEK_END) < 0 || ((size = ftell(fp)) < 0) || fseek(fp, 0, SEEK_SET) < 0) { + caml_failwith("determining input file size failed"); + } + if (size == 0) { + caml_failwith("Input file contains no data"); + } + + jpeg_stdio_src(&cinfo, fp); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + uint32_t width = cinfo.output_width; + uint32_t height = cinfo.output_height; + uint32_t channels = cinfo.output_components; + + JDIMENSION stride = width * channels; + JSAMPARRAY temp_buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, stride, 1); + + int buffer_size = width * height; + uint8_t *image_buffer = (uint8_t*)malloc(buffer_size * 4); + + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, temp_buffer, 1); + + unsigned int k = (cinfo.output_scanline - 1) * 4 * width; + unsigned int j = 0; + for(unsigned int i = 0; i < 4 * width; i += 4) { + image_buffer[k + i] = temp_buffer[0][j]; + image_buffer[k + i + 1] = temp_buffer[0][j + 1]; + image_buffer[k + i + 2] = temp_buffer[0][j + 2]; + image_buffer[k + i + 3] = 255; + + j += 3; + } + } + + jpeg_finish_decompress(&cinfo); + + res = caml_alloc(4, 0); + ba = caml_ba_alloc_dims(CAML_BA_INT32 | CAML_BA_C_LAYOUT, 1, image_buffer, buffer_size); + + Store_field(res, 0, Val_int(width)); + Store_field(res, 1, Val_int(height)); + Store_field(res, 2, ba); + Store_field(res, 3, Val_bp(image_buffer)); + + jpeg_destroy_decompress(&cinfo); + fclose(fp); + + CAMLreturn(res); +} + +CAMLprim value +cleanup_jpg(value buffer) +{ + CAMLparam1(buffer); + free(Bp_val(buffer)); + CAMLreturn(Val_unit); +} diff --git a/io/jpg/ReadJpg.re b/io/jpg/ReadJpg.re new file mode 100644 index 00000000..70a645b3 --- /dev/null +++ b/io/jpg/ReadJpg.re @@ -0,0 +1,11 @@ +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/jpg/dune b/io/jpg/dune new file mode 100644 index 00000000..5517a6f3 --- /dev/null +++ b/io/jpg/dune @@ -0,0 +1,19 @@ +(library + (name Jpg) + (public_name odiff-io.jpg) + (flags + (-w -40 -w +26) + (:include jpg_flags.sexp)) + (foreign_stubs + (language c) + (names ReadJpg) + (flags + (:include jpg_c_flags.sexp))) + (c_library_flags + (:include jpg_c_library_flags.sexp)) + (libraries odiff-core WritePng)) + +(rule + (targets jpg_flags.sexp jpg_c_flags.sexp jpg_c_library_flags.sexp) + (action + (run ../config/discover.exe))) diff --git a/io/PureC_IO.re b/io/png/Png.re similarity index 90% rename from io/PureC_IO.re rename to io/png/Png.re index cea2fbc0..3db8052d 100644 --- a/io/PureC_IO.re +++ b/io/png/Png.re @@ -1,3 +1,5 @@ +module BigarrayIO = PureC_IO_Bigarray.IO; + module IO: Odiff.ImageIO.ImageIO = { type t = int; type row = Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout); @@ -22,7 +24,7 @@ module IO: Odiff.ImageIO.ImageIO = { }; let saveImage = (img: Odiff.ImageIO.img(t), filename) => { - ReadPng.write_png_file(img.image, img.width, img.height, filename); + WritePng.write_png_file(img.image, img.width, img.height, filename); }; let freeImage = (img: Odiff.ImageIO.img(t)) => { diff --git a/io/PureC_IO_Bigarray.re b/io/png/PureC_IO_Bigarray.re similarity index 98% rename from io/PureC_IO_Bigarray.re rename to io/png/PureC_IO_Bigarray.re index efe4716d..faee17c7 100644 --- a/io/PureC_IO_Bigarray.re +++ b/io/png/PureC_IO_Bigarray.re @@ -39,7 +39,7 @@ module IO: Odiff.ImageIO.ImageIO = { }; let saveImage = (img: Odiff.ImageIO.img(t), filename) => { - ReadPng.write_png_file( + WritePng.write_png_file( img.image.rowPointers, img.width, img.height, diff --git a/io/ReadPng.c b/io/png/ReadPng.c similarity index 57% rename from io/ReadPng.c rename to io/png/ReadPng.c index 721fe743..c41bc94b 100644 --- a/io/ReadPng.c +++ b/io/png/ReadPng.c @@ -79,6 +79,8 @@ read_png_file_to_tuple(value file) color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); + png_set_interlace_handling(png); + png_read_update_info(png, info); row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height); @@ -100,7 +102,7 @@ read_png_file_to_tuple(value file) Store_field(res, 0, Val_int(width)); Store_field(res, 1, Val_int(height)); Store_field(res, 2, Val_int(rowbytes)); - Store_field(res, 3, row_pointers); + Store_field(res, 3, Val_bp(row_pointers)); CAMLreturn(res); } @@ -121,8 +123,7 @@ row_pointers_to_bigarray(png_bytep *row_pointers, value rowbytes_val, value heig memcpy(total_pixels + y * rowbytes, row_pointers[y], rowbytes); } - long dims[1] = {width * height}; - CAMLreturn(caml_ba_alloc(CAML_BA_INT32 | CAML_BA_C_LAYOUT, 1, total_pixels, dims)); + CAMLreturn(caml_ba_alloc_dims(CAML_BA_INT32 | CAML_BA_C_LAYOUT, 1, total_pixels, width * height)); } CAMLprim value @@ -139,7 +140,7 @@ create_empty_img(value height_val, value width_val) row_pointers[y] = (png_byte *)malloc(width * 4); // we always use RGBA } - CAMLreturn(row_pointers); + CAMLreturn(Val_bp(row_pointers)); } CAMLprim value @@ -151,8 +152,7 @@ read_row(png_bytep *row_pointers, value y_val, value img_width_val) png_bytep row = row_pointers[y]; - long dims[] = {img_width}; - CAMLreturn(caml_ba_alloc(CAML_BA_INT32 | CAML_BA_C_LAYOUT, 1, row, dims)); + CAMLreturn(caml_ba_alloc_dims(CAML_BA_INT32 | CAML_BA_C_LAYOUT, 1, row, img_width)); } CAMLprim value @@ -174,57 +174,6 @@ set_pixel_data(png_bytep *row_pointers, value x_val, value y_val, value pixel_va CAMLreturn(Val_unit); } -CAMLprim value write_png_file(png_bytep *row_pointers, value width_value, value height_value, value filename_value) -{ - CAMLparam3(height_value, width_value, filename_value); - int height = Int_val(height_value); - int width = Int_val(width_value); - - FILE *fp = fopen(String_val(filename_value), "wb"); - if (!fp) - caml_failwith("Can not save the output :("); - - png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png) - caml_failwith("Can not save the output :("); - - png_infop info = png_create_info_struct(png); - if (!info) - caml_failwith("Can not save the output :("); - - if (setjmp(png_jmpbuf(png))) - caml_failwith("Can not save the output :("); - - png_init_io(png, fp); - - // Output is 8bit depth, RGBA format. - png_set_IHDR( - png, - info, - width, height, - 8, - PNG_COLOR_TYPE_RGBA, - PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, - PNG_FILTER_TYPE_DEFAULT); - - png_write_info(png, info); - - if (!row_pointers) - caml_failwith("Can not save the output :("); - - png_set_compression_level(png, 2); - png_set_filter(png, 0, PNG_FILTER_NONE); - png_write_image(png, row_pointers); - png_write_end(png, NULL); - - fclose(fp); - - png_destroy_write_struct(&png, &info); - - CAMLreturn(Val_unit); -} - CAMLprim value free_row_pointers(png_bytep *row_pointers, value height_value) { @@ -240,73 +189,3 @@ free_row_pointers(png_bytep *row_pointers, value height_value) CAMLreturn(Val_unit); } - -value write_png_buffer(value name, value buffer, value width, value height) -{ - CAMLparam4(name, buffer, width, height); - - FILE *fp; - png_structp png_ptr; - png_infop info_ptr; - - int w = Int_val(width); - int h = Int_val(height); - const char *buf = String_val(buffer); - - if (( fp = fopen(String_val(name), "wb")) == NULL ){ - caml_failwith("Can not save the output :("); - } - - if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL){ - fclose(fp); - caml_failwith("Can not save the output :("); - } - - if( (info_ptr = png_create_info_struct(png_ptr)) == NULL ){ - fclose(fp); - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); - caml_failwith("Can not save the output :("); - } - - /* error handling */ - if (setjmp(png_jmpbuf(png_ptr))) { - /* Free all of the memory associated with the png_ptr and info_ptr */ - png_destroy_write_struct(&png_ptr, &info_ptr); - fclose(fp); - /* If we get here, we had a problem writing the file */ - caml_failwith("Can not save the output :("); - } - - png_init_io(png_ptr, fp); - - png_set_IHDR( - png_ptr, info_ptr, w, h, - 8, - PNG_COLOR_TYPE_RGB_ALPHA, - PNG_INTERLACE_ADAM7, - PNG_COMPRESSION_TYPE_DEFAULT, - PNG_FILTER_TYPE_DEFAULT - ); - - png_write_info(png_ptr, info_ptr); - - png_bytep *row_pointers; - - row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * h); - int rowbytes = png_get_rowbytes(png_ptr, info_ptr); - - for (int y = 0; y < h; y++) - { - row_pointers[y] = (png_bytep)(buf + rowbytes * y); - } - - png_write_image(png_ptr, row_pointers); - free((void*)row_pointers); - - png_write_end(png_ptr, info_ptr); - png_destroy_write_struct(&png_ptr, &info_ptr); - - fclose(fp); - - CAMLreturn(Val_unit); -} diff --git a/io/png/ReadPng.re b/io/png/ReadPng.re new file mode 100644 index 00000000..99dfc7ab --- /dev/null +++ b/io/png/ReadPng.re @@ -0,0 +1,15 @@ +external read_png_image: string => (int, int, int, 'b) = + "read_png_file_to_tuple"; + +external row_pointers_to_bigarray: ('b, int, int, int) => 'c = + "row_pointers_to_bigarray"; + +external read_row: ('a, int, int) => 'b = "read_row"; + +[@noalloc] +external set_pixel_data: ('a, int, int, (int, int, int)) => unit = + "set_pixel_data"; + +[@noalloc] external free_row_pointers: ('a, int) => unit = "free_row_pointers"; + +[@noalloc] external create_empty_img: (int, int) => 'a = "create_empty_img"; diff --git a/io/png/dune b/io/png/dune new file mode 100644 index 00000000..a1746aad --- /dev/null +++ b/io/png/dune @@ -0,0 +1,19 @@ +(library + (name Png) + (public_name odiff-io.png) + (flags + (-w -40 -w +26) + (:include png_flags.sexp)) + (foreign_stubs + (language c) + (names ReadPng) + (flags + (:include png_c_flags.sexp))) + (c_library_flags + (:include png_c_library_flags.sexp)) + (libraries odiff-core WritePng)) + +(rule + (targets png_flags.sexp png_c_flags.sexp png_c_library_flags.sexp) + (action + (run ../config/discover.exe))) diff --git a/io/png_write/WritePng.c b/io/png_write/WritePng.c new file mode 100644 index 00000000..b9db9607 --- /dev/null +++ b/io/png_write/WritePng.c @@ -0,0 +1,150 @@ +#define CAML_NAME_SPACE +#include +#include +#include +#include +#include +#include +#include +#include + +CAMLprim value write_png_file(png_bytep *row_pointers, value width_value, value height_value, value filename_value) +{ + CAMLparam3(height_value, width_value, filename_value); + int height = Int_val(height_value); + int width = Int_val(width_value); + + FILE *fp = fopen(String_val(filename_value), "wb"); + if (!fp) + caml_failwith("Can not save the output :("); + + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) + caml_failwith("Can not save the output :("); + + png_infop info = png_create_info_struct(png); + if (!info) + caml_failwith("Can not save the output :("); + + if (setjmp(png_jmpbuf(png))) + caml_failwith("Can not save the output :("); + + png_init_io(png, fp); + + // Output is 8bit depth, RGBA format. + png_set_IHDR( + png, + info, + width, height, + 8, + PNG_COLOR_TYPE_RGBA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + png_write_info(png, info); + + if (!row_pointers) + caml_failwith("Can not save the output :("); + + png_set_compression_level(png, 2); + png_set_filter(png, 0, PNG_FILTER_NONE); + png_write_image(png, row_pointers); + png_write_end(png, NULL); + + fclose(fp); + + png_destroy_write_struct(&png, &info); + + CAMLreturn(Val_unit); +} + +void write_png(const char *name, const char *data, int w, int h) +{ + FILE *fp; + png_structp png_ptr; + png_infop info_ptr; + + if (( fp = fopen(name, "wb")) == NULL ){ + caml_failwith("Can not save the output :("); + } + + if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL){ + fclose(fp); + caml_failwith("Can not save the output :("); + } + + if( (info_ptr = png_create_info_struct(png_ptr)) == NULL ){ + fclose(fp); + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + caml_failwith("Can not save the output :("); + } + + /* error handling */ + if (setjmp(png_jmpbuf(png_ptr))) { + /* Free all of the memory associated with the png_ptr and info_ptr */ + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + /* If we get here, we had a problem writing the file */ + caml_failwith("Can not save the output :("); + } + + png_init_io(png_ptr, fp); + + png_set_IHDR( + png_ptr, info_ptr, w, h, + 8, + PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_ADAM7, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT + ); + + png_write_info(png_ptr, info_ptr); + + png_bytep *row_pointers; + + row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * h); + int rowbytes = png_get_rowbytes(png_ptr, info_ptr); + + for (int y = 0; y < h; y++) + { + row_pointers[y] = (png_bytep)(data + rowbytes * y); + } + + png_write_image(png_ptr, row_pointers); + free((void*)row_pointers); + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + fclose(fp); +} + +value write_png_bigarray(value name, value bigarray, value width, value height) +{ + CAMLparam4(name, bigarray, width, height); + + int w = Int_val(width); + int h = Int_val(height); + const char *buf = Caml_ba_data_val(bigarray); + const char *filename = String_val(name); + + write_png(filename, buf, w, h); + + CAMLreturn(Val_unit); +} + +value write_png_buffer(value name, value buffer, value width, value height) +{ + CAMLparam4(name, buffer, width, height); + + int w = Int_val(width); + int h = Int_val(height); + const char *buf = String_val(buffer); + const char *filename = String_val(name); + + write_png(filename, buf, w, h); + + CAMLreturn(Val_unit); +} diff --git a/io/png_write/WritePng.re b/io/png_write/WritePng.re new file mode 100644 index 00000000..955ff246 --- /dev/null +++ b/io/png_write/WritePng.re @@ -0,0 +1,17 @@ +[@noalloc] +external write_png_file: ('a, int, int, string) => unit = "write_png_file"; + +[@noalloc] +external write_png_buffer: (string, bytes, int, int) => unit = + "write_png_buffer"; + +[@noalloc] +external write_png_bigarray: + ( + string, + Bigarray.Array1.t(int32, Bigarray.int32_elt, Bigarray.c_layout), + int, + int + ) => + unit = + "write_png_bigarray"; diff --git a/io/png_write/dune b/io/png_write/dune new file mode 100644 index 00000000..cff8c632 --- /dev/null +++ b/io/png_write/dune @@ -0,0 +1,21 @@ +(library + (name WritePng) + (public_name odiff-io.png_write) + (flags + (-w -40 -w +26) + (:include png_write_flags.sexp)) + (foreign_stubs + (language c) + (names WritePng) + (flags + (:include png_write_c_flags.sexp))) + (c_library_flags + (:include png_write_c_library_flags.sexp))) + +(rule + (targets + png_write_flags.sexp + png_write_c_flags.sexp + png_write_c_library_flags.sexp) + (action + (run ../config/discover.exe))) diff --git a/io/tiff/ReadTiff.c b/io/tiff/ReadTiff.c new file mode 100644 index 00000000..b584e471 --- /dev/null +++ b/io/tiff/ReadTiff.c @@ -0,0 +1,69 @@ +#define CAML_NAME_SPACE + +#include + +#include +#include +#include +#include +#include + +#include + +CAMLprim value +read_tiff_file_to_tuple(value file) +{ + CAMLparam1(file); + CAMLlocal2(res, ba); + + const char *filename = String_val(file); + uint32_t *buffer = NULL; + int width; + int height; + + TIFF *image; + + + + if (!(image = TIFFOpen(filename, "r"))) { + caml_failwith("opening input file failed!"); + } + + TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width); + TIFFGetField(image, TIFFTAG_IMAGELENGTH, &height); + + + int buffer_size = width * height; + buffer = (uint32_t*)malloc(buffer_size * 4); + + if (!buffer) { + TIFFClose(image); + caml_failwith("allocating TIFF buffer failed"); + } + + + 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); + + Store_field(res, 0, Val_int(width)); + Store_field(res, 1, Val_int(height)); + Store_field(res, 2, ba); + Store_field(res, 3, Val_bp(buffer)); + + TIFFClose(image); + + CAMLreturn(res); +} + +CAMLprim value +cleanup_tiff(value buffer) +{ + CAMLparam1(buffer); + free(Bp_val(buffer)); + CAMLreturn(Val_unit); +} diff --git a/io/tiff/ReadTiff.re b/io/tiff/ReadTiff.re new file mode 100644 index 00000000..02a8650d --- /dev/null +++ b/io/tiff/ReadTiff.re @@ -0,0 +1,11 @@ +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.re b/io/tiff/Tiff.re new file mode 100644 index 00000000..b84321eb --- /dev/null +++ b/io/tiff/Tiff.re @@ -0,0 +1,52 @@ +open Bigarray; + +module IO: Odiff.ImageIO.ImageIO = { + type t = Array1.t(int32, int32_elt, c_layout); + type row = int; + + let buffer = ref(None); + + let loadImage = (filename): Odiff.ImageIO.img(t) => { + let (width, height, image, b) = ReadTiff.load(filename); + + buffer := Some(b); + + {width, height, image}; + }; + + let readRow = (img: Odiff.ImageIO.img(t), y): row => y; + + let readDirectPixel = (~x, ~y, img: Odiff.ImageIO.img(t)) => { + (img.image).{y * img.width + x}; + }; + + let readImgColor = (x, row: row, img: Odiff.ImageIO.img(t)) => { + readDirectPixel(~x, ~y=row, img); + }; + + let setImgColor = (x, y, (r, g, b), img: Odiff.ImageIO.img(t)) => { + 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; + Array1.set( + img.image, + y * img.width + x, + Int32.of_int(a lor b lor g lor r), + ); + }; + + 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)) => { + buffer^ |> Option.iter(ReadTiff.cleanup_tiff); + }; + + let makeSameAsLayout = (img: Odiff.ImageIO.img(t)) => { + let image = Array1.create(int32, c_layout, Array1.dim(img.image)); + + {...img, image}; + }; +}; diff --git a/io/tiff/dune b/io/tiff/dune new file mode 100644 index 00000000..45d5e506 --- /dev/null +++ b/io/tiff/dune @@ -0,0 +1,19 @@ +(library + (name Tiff) + (public_name odiff-io.tiff) + (flags + (-w -40 -w +26) + (:include tiff_flags.sexp)) + (foreign_stubs + (language c) + (names ReadTiff) + (flags + (:include tiff_c_flags.sexp))) + (c_library_flags + (:include tiff_c_library_flags.sexp)) + (libraries odiff-core WritePng)) + +(rule + (targets tiff_flags.sexp tiff_c_flags.sexp tiff_c_library_flags.sexp) + (action + (run ../config/discover.exe))) diff --git a/odiff-io.opam b/odiff-io.opam index b4859055..79d1d3d0 100644 --- a/odiff-io.opam +++ b/odiff-io.opam @@ -10,7 +10,6 @@ depends: [ "dune" {>= "2.8"} "conf-libpng" "odiff-core" - "camlimages" {= "5.4.0"} "reason" {>= "3.6.0" & < "4.0.0"} "ocaml" {>= "4.10.0" & < "4.11.0"} "odoc" {with-doc} diff --git a/package.json b/package.json index 6cb54ec2..2e4625b7 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,6 @@ "ODOC_SYNTAX": "re" } }, - "simple-git-hooks": { - "pre-push": "esy node process-readme.md" - }, "scripts": { "run": "esy x ODiffBin", "test": "esy x RunTests.exe", @@ -32,15 +29,18 @@ "process:readme": "esy node scripts/process-readme.js" }, "dependencies": { - "@esy-ocaml/reason": ">= 3.6.0 < 4.0.0", + "@opam/reason": ">= 3.6.0 < 4.0.0", "@opam/cmdliner": "1.0.4", "@opam/dune": "< 3.0.0", - "@opam/dune-configurator": "2.8.5", + "@opam/dune-configurator": "< 3.0.0", "@reason-native/console": "*", "@reason-native/pastel": "*", "@reason-native/rely": "^3.2.1", - "esy-libpng": "eWert-Online/esy-libpng#485fec4990dbb0f6b0ecb3ced53f5bd7bc9a4506", - "esy-zlib": "eWert-Online/esy-zlib#9b0394f07551d9fde386e28a6ff269984b63ffcf", + "ava": "^3.15.0", + "esy-libtiff": "*", + "esy-libpng": "*", + "esy-libjpeg": "*", + "esy-zlib": "*", "ocaml": "~4.10.0" }, "devDependencies": { @@ -52,6 +52,12 @@ "@opam/odoc": "*", "refmterr": "*" }, + "resolutions": { + "esy-libjpeg": "eWert-Online/esy-libjpeg#76b287add2732e191a2c4a1c14a34bab63c4f74d", + "esy-libpng": "eWert-Online/esy-libpng#485fec4990dbb0f6b0ecb3ced53f5bd7bc9a4506", + "esy-libtiff": "esy-packages/esy-libtiff#83b1f5c8f76ad42acb5c008f537b5b3f0902c066", + "esy-zlib": "eWert-Online/esy-zlib#9b0394f07551d9fde386e28a6ff269984b63ffcf" + }, "repository": { "type": "git", "url": "https://github.com/dmtrKovalenko/odiff" diff --git a/src/Helpers.re b/src/Helpers.re deleted file mode 100644 index 8b137891..00000000 --- a/src/Helpers.re +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test.h.gch b/test.h.gch deleted file mode 100644 index 8a1be905..00000000 Binary files a/test.h.gch and /dev/null differ diff --git a/test/PngTests.re b/test/PngTests.re deleted file mode 100644 index 4d918fea..00000000 --- a/test/PngTests.re +++ /dev/null @@ -1,156 +0,0 @@ -open TestFramework; -open ODiffIO; - -module Diff = Odiff.Diff.MakeDiff(PureC_IO.IO, PureC_IO.IO); -module AADiff = - Odiff.Diff.MakeDiff(PureC_IO_Bigarray.IO, PureC_IO_Bigarray.IO); - -describe("Png comparing", ({test, _}) => { - test("finds difference between 2 images", ({expect, _}) => { - let img1 = PureC_IO.IO.loadImage("test/test-images/orange.png"); - let img2 = PureC_IO.IO.loadImage("test/test-images/orange_changed.png"); - - let (_, diffPixels, diffPercentage) = Diff.compare(img1, img2, ()); - - expect.int(diffPixels).toBe(1430); - expect.float(diffPercentage).toBeCloseTo(1.20); - }); - - test("uses provided threshold", ({expect, _}) => { - let img1 = PureC_IO.IO.loadImage("test/test-images/orange.png"); - let img2 = PureC_IO.IO.loadImage("test/test-images/orange_changed.png"); - - let (_, diffPixels, diffPercentage) = - Diff.compare(img1, img2, ~threshold=0.5, ()); - expect.int(diffPixels).toBe(222); - expect.float(diffPercentage).toBeCloseTo(0.19); - }); - - test("uses provided irgnore regions", ({expect, _}) => { - let img1 = PureC_IO.IO.loadImage("test/test-images/orange.png"); - let img2 = PureC_IO.IO.loadImage("test/test-images/orange_changed.png"); - - let (_diffOutput, diffPixels, diffPercentage) = - Diff.compare( - img1, - img2, - ~ignoreRegions=[ - ((150, 30), (310, 105)), - ((20, 175), (105, 200)), - ], - (), - ); - - expect.int(diffPixels).toBe(0); - expect.float(diffPercentage).toBeCloseTo(0.0); - }); - - test("creates the right diff output image", ({expect, _}) => { - let img1 = PureC_IO.IO.loadImage("test/test-images/orange.png"); - let img2 = PureC_IO.IO.loadImage("test/test-images/orange_changed.png"); - - let (diffOutput, _, _) = Diff.compare(img1, img2, ()); - - let originalDiff = - PureC_IO.IO.loadImage("test/test-images/orange_diff.png"); - let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage) = - Diff.compare(originalDiff, diffOutput, ()); - - if (diffOfDiffPixels > 0) { - PureC_IO.IO.saveImage(diffOutput, "test/test-images/diff-output.png"); - PureC_IO.IO.saveImage( - diffMaskOfDiff, - "test/test-images/diff-of-diff.png", - ); - }; - - expect.int(diffOfDiffPixels).toBe(0); - expect.float(diffOfDiffPercentage).toBeCloseTo(0.0); - }); - - test( - "creates the right diff output image with custom diff color", - ({expect, _}) => { - let img1 = PureC_IO.IO.loadImage("test/test-images/orange.png"); - let img2 = PureC_IO.IO.loadImage("test/test-images/orange_changed.png"); - - let (diffOutput, _, _) = - Diff.compare(img1, img2, ~diffPixel=(0, 255, 0), ()); - - let originalDiff = - PureC_IO.IO.loadImage("test/test-images/orange_diff_green.png"); - let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage) = - Diff.compare(originalDiff, diffOutput, ()); - - if (diffOfDiffPixels > 0) { - PureC_IO.IO.saveImage( - diffOutput, - "test/test-images/diff-output-green.png", - ); - PureC_IO.IO.saveImage( - diffMaskOfDiff, - "test/test-images/diff-of-diff-green.png", - ); - }; - - expect.int(diffOfDiffPixels).toBe(0); - expect.float(diffOfDiffPercentage).toBeCloseTo(0.0); - }); - - test("does not count anti-aliased pixels as different", ({expect, _}) => { - let img1 = - PureC_IO_Bigarray.IO.loadImage("test/test-images/antialiasing-on.png"); - let img2 = - PureC_IO_Bigarray.IO.loadImage("test/test-images/antialiasing-off.png"); - - let (_, diffPixels, diffPercentage) = - AADiff.compare( - img1, - img2, - ~outputDiffMask=true, - ~antialiasing=true, - (), - ); - - expect.int(diffPixels).toBe(38); - expect.float(diffPercentage).toBeCloseTo(0.095); - }); - - test("Diff of mask and no mask are equal", ({expect, _}) => { - let img1 = PureC_IO.IO.loadImage("test/test-images/antialiasing-on.png"); - let img2 = PureC_IO.IO.loadImage("test/test-images/antialiasing-off.png"); - - let (_, diffPixels, diffPercentage) = - Diff.compare(img1, img2, ~outputDiffMask=false, ()); - - let img1 = PureC_IO.IO.loadImage("test/test-images/antialiasing-on.png"); - let img2 = PureC_IO.IO.loadImage("test/test-images/antialiasing-off.png"); - - let (_, diffPixelsMask, diffPercentageMask) = - Diff.compare(img1, img2, ~outputDiffMask=true, ()); - - expect.int(diffPixels).toBe(diffPixelsMask); - expect.float(diffPercentage).toBeCloseTo(diffPercentageMask); - }); - - test("tests diffrent sized AA images", ({expect, _}) => { - let img1 = - PureC_IO_Bigarray.IO.loadImage("test/test-images/antialiasing-on.png"); - let img2 = - PureC_IO_Bigarray.IO.loadImage( - "test/test-images/antialiasing-off-small.png", - ); - - let (_, diffPixels, diffPercentage) = - AADiff.compare( - img1, - img2, - ~outputDiffMask=true, - ~antialiasing=true, - (), - ); - - expect.int(diffPixels).toBe(417); - expect.float(diffPercentage).toBeCloseTo(1.04); - }); -}); diff --git a/test/Test_Core.re b/test/Test_Core.re new file mode 100644 index 00000000..6910b268 --- /dev/null +++ b/test/Test_Core.re @@ -0,0 +1,105 @@ +open TestFramework; +open ODiffIO; + +module PNG_Diff = Odiff.Diff.MakeDiff(Png.IO, Png.IO); +module PNG_BA_Diff = Odiff.Diff.MakeDiff(Png.BigarrayIO, Png.BigarrayIO); + +describe("CORE: Antialiasing", ({test, _}) => { + open Png.BigarrayIO; + + 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_BA_Diff.compare( + img1, + img2, + ~outputDiffMask=true, + ~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_BA_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.re b/test/Test_IO_BMP.re new file mode 100644 index 00000000..84eaf8a8 --- /dev/null +++ b/test/Test_IO_BMP.re @@ -0,0 +1,57 @@ +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.re b/test/Test_IO_JPG.re new file mode 100644 index 00000000..c6b7897c --- /dev/null +++ b/test/Test_IO_JPG.re @@ -0,0 +1,57 @@ +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.re b/test/Test_IO_PNG.re new file mode 100644 index 00000000..a6e2290e --- /dev/null +++ b/test/Test_IO_PNG.re @@ -0,0 +1,54 @@ +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.re b/test/Test_IO_TIFF.re new file mode 100644 index 00000000..8308d343 --- /dev/null +++ b/test/Test_IO_TIFF.re @@ -0,0 +1,57 @@ +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/test-images/antialiasing-off-small.png b/test/test-images/aa/antialiasing-off-small.png similarity index 100% rename from test/test-images/antialiasing-off-small.png rename to test/test-images/aa/antialiasing-off-small.png diff --git a/test/test-images/antialiasing-off.png b/test/test-images/aa/antialiasing-off.png similarity index 100% rename from test/test-images/antialiasing-off.png rename to test/test-images/aa/antialiasing-off.png diff --git a/test/test-images/antialiasing-on.png b/test/test-images/aa/antialiasing-on.png similarity index 100% rename from test/test-images/antialiasing-on.png rename to test/test-images/aa/antialiasing-on.png diff --git a/test/test-images/bmp/clouds-2.bmp b/test/test-images/bmp/clouds-2.bmp new file mode 100644 index 00000000..421b9a11 Binary files /dev/null and b/test/test-images/bmp/clouds-2.bmp differ diff --git a/test/test-images/bmp/clouds-diff.png b/test/test-images/bmp/clouds-diff.png new file mode 100644 index 00000000..deee3278 Binary files /dev/null and b/test/test-images/bmp/clouds-diff.png differ diff --git a/test/test-images/bmp/clouds.bmp b/test/test-images/bmp/clouds.bmp new file mode 100644 index 00000000..b31b58e0 Binary files /dev/null and b/test/test-images/bmp/clouds.bmp differ diff --git a/test/test-images/jpg/tiger-2.jpg b/test/test-images/jpg/tiger-2.jpg new file mode 100644 index 00000000..2d6b9829 Binary files /dev/null and b/test/test-images/jpg/tiger-2.jpg differ diff --git a/test/test-images/jpg/tiger-diff.png b/test/test-images/jpg/tiger-diff.png new file mode 100644 index 00000000..ea3ed4c2 Binary files /dev/null and b/test/test-images/jpg/tiger-diff.png differ diff --git a/test/test-images/jpg/tiger.jpg b/test/test-images/jpg/tiger.jpg new file mode 100644 index 00000000..11d4c5c3 Binary files /dev/null and b/test/test-images/jpg/tiger.jpg differ diff --git a/test/test-images/orange.png b/test/test-images/png/orange.png similarity index 100% rename from test/test-images/orange.png rename to test/test-images/png/orange.png diff --git a/test/test-images/orange_changed.png b/test/test-images/png/orange_changed.png similarity index 100% rename from test/test-images/orange_changed.png rename to test/test-images/png/orange_changed.png diff --git a/test/test-images/orange_diff.png b/test/test-images/png/orange_diff.png similarity index 100% rename from test/test-images/orange_diff.png rename to test/test-images/png/orange_diff.png diff --git a/test/test-images/orange_diff_green.png b/test/test-images/png/orange_diff_green.png similarity index 100% rename from test/test-images/orange_diff_green.png rename to test/test-images/png/orange_diff_green.png diff --git a/test/test-images/tiff/laptops-2.tiff b/test/test-images/tiff/laptops-2.tiff new file mode 100644 index 00000000..3faaaccc Binary files /dev/null and b/test/test-images/tiff/laptops-2.tiff differ diff --git a/test/test-images/tiff/laptops-diff.png b/test/test-images/tiff/laptops-diff.png new file mode 100644 index 00000000..d505b914 Binary files /dev/null and b/test/test-images/tiff/laptops-diff.png differ diff --git a/test/test-images/tiff/laptops.tiff b/test/test-images/tiff/laptops.tiff new file mode 100644 index 00000000..a9a24a5a Binary files /dev/null and b/test/test-images/tiff/laptops.tiff differ