diff --git a/Cargo.lock b/Cargo.lock index 46d06d13..4402b26f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "ahash" version = "0.8.9" @@ -686,7 +692,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.2", "object", "rustc-demangle", ] @@ -985,6 +991,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.0" @@ -1129,15 +1141,6 @@ dependencies = [ "itertools 0.10.5", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -1464,6 +1467,16 @@ dependencies = [ "sqlparser 0.43.1", ] +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + [[package]] name = "deterministic-hash" version = "1.0.1" @@ -1493,40 +1506,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "dssim" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddc15a051da6e0d2611493ba1b5d56e3739d6d9df8a36ec2406c2b6f5145f33" -dependencies = [ - "crossbeam-channel", - "dssim-core", - "getopts", - "imgref", - "load_image", - "lodepng", - "rayon", - "rgb", -] - -[[package]] -name = "dssim-core" -version = "3.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fafad37c1f4f168243f3ac1b4cae0d358c528ac695670100337314e38d54b486" -dependencies = [ - "imgref", - "itertools 0.12.1", - "rayon", - "rgb", -] - -[[package]] -name = "dunce" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" - [[package]] name = "either" version = "1.10.0" @@ -1666,7 +1645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.7.2", ] [[package]] @@ -1684,33 +1663,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1853,15 +1805,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", -] - [[package]] name = "getrandom" version = "0.2.12" @@ -1875,6 +1818,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.1" @@ -2120,10 +2073,23 @@ dependencies = [ ] [[package]] -name = "imgref" -version = "1.10.1" +name = "image" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational 0.3.2", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] [[package]] name = "indexmap" @@ -2254,9 +2220,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" dependencies = [ "rayon", ] @@ -2297,29 +2263,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lcms2" -version = "6.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2249d276363ddd7591b2e049d61d3d2be5efe8acabd670f4e9480e5fecbb5a60" -dependencies = [ - "bytemuck", - "foreign-types", - "lcms2-sys", -] - -[[package]] -name = "lcms2-sys" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c5fe876968a1685074a0978632afd4629d7ba40f0ac563388c1a296ce12536" -dependencies = [ - "cc", - "dunce", - "libc", - "pkg-config", -] - [[package]] name = "lexical-core" version = "0.8.5" @@ -2408,22 +2351,6 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" -[[package]] -name = "load_image" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c73d7e075d05bdcc5ff4cf16b35b50b2be696b93c1be1b32201fb32c35a814" -dependencies = [ - "bytemuck", - "imgref", - "jpeg-decoder", - "lcms2", - "lodepng", - "quick-error", - "rexif", - "rgb", -] - [[package]] name = "lock_api" version = "0.4.11" @@ -2532,6 +2459,25 @@ dependencies = [ "unicase", ] +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2574,7 +2520,7 @@ dependencies = [ "num-complex", "num-integer", "num-iter", - "num-rational", + "num-rational 0.4.1", "num-traits", ] @@ -2618,6 +2564,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2931,6 +2888,15 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pixelmatch" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b15774a2acbbefbf12db95ef5582f80a3c9628dc48b6397e563c08640799764" +dependencies = [ + "image", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -2965,6 +2931,18 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + [[package]] name = "polling" version = "2.8.0" @@ -3192,12 +3170,6 @@ dependencies = [ "serde", ] -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quick-xml" version = "0.31.0" @@ -3414,15 +3386,6 @@ dependencies = [ "rand", ] -[[package]] -name = "rexif" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49352965b70522af9085d7a8c2a6df7494713c67ac58b9af02bcff7fb4ca1483" -dependencies = [ - "num", -] - [[package]] name = "rgb" version = "0.8.37" @@ -3609,6 +3572,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + [[package]] name = "scopeguard" version = "1.2.0" @@ -4049,6 +4018,17 @@ dependencies = [ "ordered-float 2.10.1", ] +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -4579,7 +4559,6 @@ dependencies = [ "datafusion-optimizer", "datafusion-physical-expr", "deterministic-hash", - "dssim", "env_logger", "float-cmp", "futures", @@ -4592,6 +4571,7 @@ dependencies = [ "num-traits", "object_store", "ordered-float 3.9.2", + "pixelmatch", "prost", "prost-types", "regex", @@ -4875,6 +4855,12 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "which" version = "4.4.2" diff --git a/vegafusion-runtime/Cargo.toml b/vegafusion-runtime/Cargo.toml index 482a0b96..6d0fe00f 100644 --- a/vegafusion-runtime/Cargo.toml +++ b/vegafusion-runtime/Cargo.toml @@ -42,7 +42,7 @@ futures-util = "0.3.21" rstest = "0.18.2" test-case = "3.1.0" base64 = "0.21.0" -dssim = "3.1.0" +pixelmatch = "0.1.0" rgb = "0.8.32" lodepng = "3.6.1" diff --git a/vegafusion-runtime/tests/test_image_comparison.rs b/vegafusion-runtime/tests/test_image_comparison.rs index 261919ae..62c8d359 100644 --- a/vegafusion-runtime/tests/test_image_comparison.rs +++ b/vegafusion-runtime/tests/test_image_comparison.rs @@ -754,8 +754,8 @@ mod test_vegalite_specs { case("vegalite/point_no_axis_domain_grid", 0.001), // (random function is non-deterministic so use higher tolerance) - case("vegalite/point_offset_random", 0.3), - case("vegalite/point_ordinal_bin_offset_random", 0.3), + case("vegalite/point_offset_random", 0.8), + case("vegalite/point_ordinal_bin_offset_random", 0.8), case("vegalite/point_ordinal_color", 0.001), case("vegalite/point_overlap", 0.001), diff --git a/vegafusion-runtime/tests/util/vegajs_runtime/mod.rs b/vegafusion-runtime/tests/util/vegajs_runtime/mod.rs index 1af83667..e32152bb 100644 --- a/vegafusion-runtime/tests/util/vegajs_runtime/mod.rs +++ b/vegafusion-runtime/tests/util/vegajs_runtime/mod.rs @@ -1,5 +1,4 @@ use datafusion_common::ScalarValue; -use dssim::{Dssim, DssimImage}; use regex::Regex; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{json, Value}; @@ -7,6 +6,7 @@ use std::collections::HashMap; use self::super::estree_expression::ESTreeExpression; use itertools::Itertools; +use pixelmatch::{pixelmatch, Options}; use std::io::{Read, Write}; use std::ops::Deref; use std::process::{Child, Command, Stdio}; @@ -471,51 +471,37 @@ impl ExportImage { Ok(path) } - pub fn to_dssim(&self, attr: &Dssim) -> Result> { - if !matches!(self, ExportImage::Png(_)) { - return Err(VegaFusionError::internal("Only PNG image supported")); + pub fn as_png_bytes(&self) -> Result> { + match self { + ExportImage::Png(png_b64) => { + #[allow(deprecated)] + let png_bytes = base64::decode(png_b64) + .external("Failed to decdode base64 encoded png image")?; + Ok(png_bytes) + } + _ => Err(VegaFusionError::internal("Only PNG image supported")), } - let tmpfile = tempfile::NamedTempFile::new().unwrap(); - let tmppath = tmpfile.path().to_str().unwrap(); - self.save(tmppath, false)?; - - let img = dssim::load_image(attr, tmppath) - .external("Failed to create DSSIM image for comparison")?; - Ok(img) } pub fn compare(&self, other: &Self) -> Result<(f64, Option>)> { - let mut attr = Dssim::new(); - attr.set_save_ssim_maps(1); - let this_img = self.to_dssim(&attr)?; - let other_img = other.to_dssim(&attr)?; - let (diff, ssim_maps) = attr.compare(&this_img, other_img); - // println!("ssim_map: {:?}", ssim_map); - - if diff > 0.0 { - let map_meta = ssim_maps[0].clone(); - let avgssim = map_meta.ssim as f32; - - let out: Vec<_> = map_meta - .map - .pixels() - .map(|ssim| { - let max = 1_f32 - ssim; - let maxsq = max * max; - rgb::RGBA8 { - r: to_byte(maxsq * 16.0), - g: to_byte(max * 3.0), - b: to_byte(max / ((1_f32 - avgssim) * 4_f32)), - a: 255, - } - }) - .collect(); - let png_res = - lodepng::encode32(&out, map_meta.map.width(), map_meta.map.height()).unwrap(); - Ok((diff.into(), Some(png_res))) - } else { - Ok((diff.into(), None)) - } + let self_png = self.as_png_bytes()?; + let other_png = other.as_png_bytes()?; + let mut img_out = Vec::new(); + let res = pixelmatch( + self_png.as_slice(), + other_png.as_slice(), + Some(&mut img_out), + None, + None, + Some(Options { + threshold: 0.1, + ..Default::default() + }), + ) + .expect("pixelmatch failed"); + + let diff_factor = (res as f64) / 10000.0; + Ok((diff_factor, Some(img_out))) } }