diff --git a/cli/image_data.js b/cli/image_data.js new file mode 100644 index 00000000..7aea33bc --- /dev/null +++ b/cli/image_data.js @@ -0,0 +1,7 @@ +module.exports = class ImageData { + constructor(data, width, height) { + this.data = data; + this.width = width; + this.height = height; + } +}; diff --git a/cli/index.js b/cli/index.js new file mode 100644 index 00000000..3e871232 --- /dev/null +++ b/cli/index.js @@ -0,0 +1,100 @@ +const mozjpeg_dec = require("../codecs/mozjpeg/dec/mozjpeg_dec.js"); +const mozjpeg_enc = require("../codecs/mozjpeg/enc/mozjpeg_enc.js"); +const webp_enc = require("../codecs/webp/enc/webp_enc.js"); +const visdifModule = require("../codecs/visdif/visdif.js"); + +// Our decoders currently rely on this. +globalThis.ImageData = require("./image_data.js"); + +const fsp = require("fs").promises; + +async function decodeFile(file) { + const buffer = await fsp.readFile(file); + const rgba = (await mozjpeg_dec()).decode(new Uint8Array(buffer)); + return rgba; +} + +const butteraugliGoal = 1.4; +const maxRounds = 8; +async function optimize(bitmapIn, encode, decode) { + let quality = 50; + let inc = 25; + let butteraugliDistance = 2; + let attempts = 0; + let bitmapOut; + let binaryOut; + + const { visdif } = await visdifModule(); + do { + binaryOut = await encode(bitmapIn, quality); + bitmapOut = await decode(binaryOut); + console.log({ + butteraugliDistance, + quality, + attempts, + binaryOut, + bitmapOut + }); + butteraugliDistance = visdif( + bitmapIn.data, + bitmapOut.data, + bitmapIn.width, + bitmapIn.height + ); + if (butteraugliDistance > butteraugliGoal) { + quality += inc; + } else { + quality -= inc; + } + inc /= 2; + attempts++; + } while ( + Math.abs(butteraugliDistance - butteraugliGoal) > 0.1 && + attempts < maxRounds + ); + + return { + bitmap: bitmapOut, + binary: binaryOut, + quality, + attempts + }; +} + +async function main() { + const [, , inFile, outFile] = process.argv; + const bitmapIn = await decodeFile(inFile); + + const jpegEncModule = await mozjpeg_enc(); + const jpegEnc = async (bitmap, quality) => + jpegEncModule.encode(bitmap.data.buffer, bitmap.width, bitmap.height, { + quality, + baseline: false, + arithmetic: false, + progressive: true, + optimize_coding: true, + smoothing: 0, + color_space: 3 /*YCbCr*/, + quant_table: 3, + trellis_multipass: false, + trellis_opt_zero: false, + trellis_opt_table: false, + trellis_loops: 1, + auto_subsample: true, + chroma_subsample: 2, + separate_chroma_quality: false, + chroma_quality: 75 + }); + const jpegDecModule = await mozjpeg_dec(); + const jpegDec = async binary => jpegDecModule.decode(binary); + const jpegOut = await optimize(bitmapIn, jpegEnc, jpegDec); + await fsp.writeFile("out.jpg", jpegOut.binary); + //await webp_enc(); + //console.log(bitmapIn); +} + +main().catch(err => { + console.error("Something went wrong"); + console.error(err); + process.exit(1); +}); diff --git a/cli/package-lock.json b/cli/package-lock.json new file mode 100644 index 00000000..e84d7743 --- /dev/null +++ b/cli/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "cli", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "commander": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.0.0.tgz", + "integrity": "sha512-s7EA+hDtTYNhuXkTlhqew4txMZVdszBmKWSPEMxGr8ru8JXR7bLUFIAtPhcSuFdJQ0ILMxnJi8GkQL0yvDy/YA==" + } + } +} diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 00000000..f11643a7 --- /dev/null +++ b/cli/package.json @@ -0,0 +1,15 @@ +{ + "name": "cli", + "version": "0.0.1", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Surma ", + "license": "Apache-2.0", + "dependencies": { + "commander": "^6.0.0" + } +} diff --git a/codecs/visdif/Cargo.lock b/codecs/visdif/Cargo.lock deleted file mode 100644 index 65e52f7c..00000000 --- a/codecs/visdif/Cargo.lock +++ /dev/null @@ -1,445 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "bumpalo" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" - -[[package]] -name = "bytemuck" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7a1029718df60331e557c9e83a55523c955e5dd2a7bfeffad6bbd50b538ae9" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "console_error_panic_hook" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "crossbeam-deque" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg", - "cfg-if", - "lazy_static", -] - -[[package]] -name = "dssim-core" -version = "2.11.3" -dependencies = [ - "imgref", - "itertools", - "rayon", - "rgb", -] - -[[package]] -name = "either" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" - -[[package]] -name = "futures" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" - -[[package]] -name = "hermit-abi" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" -dependencies = [ - "libc", -] - -[[package]] -name = "imgref" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8767843be7909b5b3ffd9b52cdc042882b94e2cfe0c00abc4ce1c301cb1fcae" - -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - -[[package]] -name = "js-sys" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4b9172132a62451e56142bff9afc91c8e4a4500aa5b847da36815b63bfda916" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - -[[package]] -name = "memoffset" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid 0.1.0", -] - -[[package]] -name = "proc-macro2" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" -dependencies = [ - "unicode-xid 0.2.1", -] - -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - -[[package]] -name = "quote" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" -dependencies = [ - "proc-macro2 1.0.18", -] - -[[package]] -name = "rayon" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" -dependencies = [ - "crossbeam-deque", - "crossbeam-queue", - "crossbeam-utils", - "lazy_static", - "num_cpus", -] - -[[package]] -name = "rgb" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7466cad0eb3303798229ffab23bb8f598d185c71f3dfa17cd751d440e375782a" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "squoosh-visdiff" -version = "0.1.0" -dependencies = [ - "cfg-if", - "console_error_panic_hook", - "dssim-core", - "imgref", - "rgb", - "wasm-bindgen", - "wasm-bindgen-test", - "wee_alloc", -] - -[[package]] -name = "syn" -version = "1.0.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" -dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.7", - "unicode-xid 0.2.1", -] - -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" - -[[package]] -name = "wasm-bindgen" -version = "0.2.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a634620115e4a229108b71bde263bb4220c483b3f07f5ba514ee8d15064c4c2" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e53963b583d18a5aa3aaae4b4c1cb535218246131ba22a71f05b518098571df" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2 1.0.18", - "quote 1.0.7", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83420b37346c311b9ed822af41ec2e82839bfe99867ec6c54e2da43b7538771c" -dependencies = [ - "cfg-if", - "futures", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcfd5ef6eec85623b4c6e844293d4516470d8f19cd72d0d12246017eb9060b8" -dependencies = [ - "quote 1.0.7", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9adff9ee0e94b926ca81b57f57f86d5545cdcb1d259e21ec9bdd95b901754c75" -dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.7", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7b90ea6c632dd06fd765d44542e234d5e63d9bb917ecd64d79778a13bd79ae" - -[[package]] -name = "wasm-bindgen-test" -version = "0.2.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d9693b63a742d481c7f80587e057920e568317b2806988c59cd71618bc26c1" -dependencies = [ - "console_error_panic_hook", - "futures", - "js-sys", - "scoped-tls", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.2.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0789dac148a8840bbcf9efe13905463b733fa96543bfbf263790535c11af7ba5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", -] - -[[package]] -name = "web-sys" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863539788676619aac1a23e2df3655e96b32b0e05eb72ca34ba045ad573c625d" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if", - "libc", - "memory_units", - "winapi", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/codecs/visdif/Cargo.toml b/codecs/visdif/Cargo.toml deleted file mode 100644 index 6814f128..00000000 --- a/codecs/visdif/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "squoosh-visdiff" -version = "0.1.0" -authors = ["Surma "] -edition = "2018" -publish = false - -[lib] -#crate-type = ["cdylib", "rlib"] -crate-type = ["cdylib"] - -[features] -default = ["console_error_panic_hook", "wee_alloc"] - -[dependencies] -cfg-if = "0.1.2" -wasm-bindgen = "0.2.38" -imgref = "1.7.0" -rgb = "0.8.24" -#rayon-core = "1.7.1" - - -# The `console_error_panic_hook` crate provides better debugging of panics by -# logging them with `console.error`. This is great for development, but requires -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for -# code size when deploying. -console_error_panic_hook = { version = "0.1.1", optional = true } - -# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size -# compared to the default allocator's ~10K. It is slower than the default -# allocator, however. -# -# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. -wee_alloc = { version = "0.4.2", optional = true } - -[dependencies.dssim-core] -path = "/Users/surma/src/github.com/kornelski/dssim/dssim-core" -# Shim rayon API -default-features = false - -[dev-dependencies] -wasm-bindgen-test = "0.2" - -[profile.release] -# Tell `rustc` to optimize for small code size. -opt-level = "s" -lto = true diff --git a/codecs/visdif/Makefile b/codecs/visdif/Makefile new file mode 100644 index 00000000..faf1910d --- /dev/null +++ b/codecs/visdif/Makefile @@ -0,0 +1,30 @@ +CODEC_URL = https://github.com/google/butteraugli/archive/71b18b636b9c7d1ae0c1d3730b85b3c127eb4511.tar.gz +CODEC_DIR = node_modules/butteraugli + +OUT_JS = visdif.js +OUT_WASM = $(OUT_JS:.js=.wasm) + +.PHONY: all clean + +all: $(OUT_JS) + +%.js: $(CODEC_DIR) + $(CXX) \ + -I $(CODEC_DIR) \ + ${CXXFLAGS} \ + ${LDFLAGS} \ + --bind \ + --closure 1 \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s MODULARIZE=1 \ + -s 'EXPORT_NAME="$(basename $(@F))"' \ + -o $@ \ + visdif.cpp \ + $(CODEC_DIR)/butteraugli/butteraugli.cc + +$(CODEC_DIR): + mkdir -p $@ + curl -sL $(CODEC_URL) | tar xz --strip 1 -C $@ + +clean: + $(RM) $(OUT_JS) $(OUT_WASM) diff --git a/codecs/visdif/package-lock.json b/codecs/visdif/package-lock.json deleted file mode 100644 index ca1a498d..00000000 --- a/codecs/visdif/package-lock.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "resize", - "lockfileVersion": 1 -} diff --git a/codecs/visdif/package.json b/codecs/visdif/package.json index facaf7d4..19b0fc64 100644 --- a/codecs/visdif/package.json +++ b/codecs/visdif/package.json @@ -1,6 +1,6 @@ { - "name": "resize", + "name": "avif", "scripts": { - "build": "../build-rust.sh" + "build": "../build-cpp.sh" } } diff --git a/codecs/visdif/src/lib.rs b/codecs/visdif/src/lib.rs deleted file mode 100644 index a482adde..00000000 --- a/codecs/visdif/src/lib.rs +++ /dev/null @@ -1,48 +0,0 @@ -use cfg_if::cfg_if; -use wasm_bindgen::prelude::*; - -use dssim_core::{Dssim, ToRGBAPLU}; -use imgref; -use rgb::FromSlice; -//use rayon_core; - -mod utils; - -cfg_if! { - // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global - // allocator. - if #[cfg(feature = "wee_alloc")] { - extern crate wee_alloc; - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - } -} - - -#[wasm_bindgen] -#[no_mangle] -pub fn ssim(data_a: Vec, data_b: Vec, width: usize, height: usize) -> f64 { - let dssim = Dssim::new(); - - // FIXME: Creating new Dssim every time is wasteful. In the context - // of the CLI, one image is highly likely to remain static (i.e. the - // reference image). dssim_core has a `set_save_ssim_maps`, that should - // make subsequent comparisons a lot cheaper. - - let image_a = imgref::Img::new(data_a.as_slice().as_rgba().to_rgbaplu(), width, height); - let a = match dssim.create_image(&image_a) { - Some(v) => v, - _ => return -1.0, // FIXME: Use something more idiomatic - }; - - let image_b = imgref::Img::new(data_b.as_slice().as_rgba().to_rgbaplu(), width, height); - let b = match dssim.create_image(&image_b) { - Some(v) => v, - _ => return -1.0, // FIXME: Use something more idiomatic - }; - - //let b = (image_b.as_slice().to_rgbaplu(); - // TODO: Rearchitect entire thing to enable `set_save_ssim_maps` - // accrossm multiple SSIM calculations. - dssim.compare(&a, &b).0.into() -} diff --git a/codecs/visdif/src/utils.rs b/codecs/visdif/src/utils.rs deleted file mode 100644 index 24b802c6..00000000 --- a/codecs/visdif/src/utils.rs +++ /dev/null @@ -1,17 +0,0 @@ -use cfg_if::cfg_if; - -cfg_if! { - // When the `console_error_panic_hook` feature is enabled, we can call the - // `set_panic_hook` function at least once during initialization, and then - // we will get better error messages if our code ever panics. - // - // For more details see - // https://github.com/rustwasm/console_error_panic_hook#readme - if #[cfg(feature = "console_error_panic_hook")] { - use console_error_panic_hook; - pub use self::console_error_panic_hook::set_once as set_panic_hook; - } else { - #[inline] - pub fn set_panic_hook() {} - } -} diff --git a/codecs/visdif/visdif.cpp b/codecs/visdif/visdif.cpp new file mode 100644 index 00000000..7f2608a3 --- /dev/null +++ b/codecs/visdif/visdif.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +using namespace emscripten; +using namespace butteraugli; + +double visdif(std::string rgba1, std::string rgba2, int width, int height) { + const char* rgba1p = rgba1.c_str(); + const char* rgba2p = rgba2.c_str(); + // One Image8 for each color channel + alpha + std::vector img1; + img1.push_back(ImageF(width, height)); + img1.push_back(ImageF(width, height)); + img1.push_back(ImageF(width, height)); + img1.push_back(ImageF(width, height)); + std::vector img2; + img2.push_back(ImageF(width, height)); + img2.push_back(ImageF(width, height)); + img2.push_back(ImageF(width, height)); + img2.push_back(ImageF(width, height)); + // Convert from interleaved RGBA to separate channels + for (int y = 0; y < height; y++) { + float* const img1_row_r = img1[0].Row(y); + float* const img1_row_g = img1[1].Row(y); + float* const img1_row_b = img1[2].Row(y); + float* const img1_row_a = img1[3].Row(y); + float* const img2_row_r = img2[0].Row(y); + float* const img2_row_g = img2[1].Row(y); + float* const img2_row_b = img2[2].Row(y); + float* const img2_row_a = img2[3].Row(y); + for (int x = 0; x < width; x++) { + img1_row_r[x] = 255.0 * pow(rgba1p[y * width * 4 + x * 4 + 0] / 255.0, 2.2); + img1_row_g[x] = 255.0 * pow(rgba1p[y * width * 4 + x * 4 + 1] / 255.0, 2.2); + img1_row_b[x] = 255.0 * pow(rgba1p[y * width * 4 + x * 4 + 2] / 255.0, 2.2); + img1_row_a[x] = 255.0 * pow(rgba1p[y * width * 4 + x * 4 + 3] / 255.0, 2.2); + img2_row_r[x] = 255.0 * pow(rgba2p[y * width * 4 + x * 4 + 0] / 255.0, 2.2); + img2_row_g[x] = 255.0 * pow(rgba2p[y * width * 4 + x * 4 + 1] / 255.0, 2.2); + img2_row_b[x] = 255.0 * pow(rgba2p[y * width * 4 + x * 4 + 2] / 255.0, 2.2); + img2_row_a[x] = 255.0 * pow(rgba2p[y * width * 4 + x * 4 + 3] / 255.0, 2.2); + } + } + + ImageF diffmap; + double diffvalue; + if (!ButteraugliInterface(img1, img2, 1.0, diffmap, diffvalue)) { + return -1.0; + } + return diffvalue; +} + +EMSCRIPTEN_BINDINGS(my_module) { + function("visdif", &visdif); +} diff --git a/codecs/visdif/visdif.js b/codecs/visdif/visdif.js new file mode 100644 index 00000000..420802cd --- /dev/null +++ b/codecs/visdif/visdif.js @@ -0,0 +1,65 @@ + +var visdif = (function() { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; + if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; + return ( +function(visdif) { + visdif = visdif || {}; + + +var d;d||(d=typeof visdif !== 'undefined' ? visdif : {});var aa,ba;d.ready=new Promise(function(a,b){aa=a;ba=b});var r={},t;for(t in d)d.hasOwnProperty(t)&&(r[t]=d[t]);var u=!1,v=!1,ca=!1,da=!1;u="object"===typeof window;v="function"===typeof importScripts;ca="object"===typeof process&&"object"===typeof process.versions&&"string"===typeof process.versions.node;da=!u&&!ca&&!v;var w="",x,z,ea,fa; +if(ca)w=v?require("path").dirname(w)+"/":__dirname+"/",x=function(a,b){ea||(ea=require("fs"));fa||(fa=require("path"));a=fa.normalize(a);return ea.readFileSync(a,b?null:"utf8")},z=function(a){a=x(a,!0);a.buffer||(a=new Uint8Array(a));a.buffer||A("Assertion failed: undefined");return a},1=e);)++c;if(16f?e+=String.fromCharCode(f):(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else e+=String.fromCharCode(f)}return e} +function la(a,b,c){var e=G;if(0=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}if(127>=g){if(b>=c)break;e[b++]=g}else{if(2047>=g){if(b+1>=c)break;e[b++]=192|g>>6}else{if(65535>=g){if(b+2>=c)break;e[b++]=224|g>>12}else{if(b+3>=c)break;e[b++]=240|g>>18;e[b++]=128|g>>12&63}e[b++]=128|g>>6&63}e[b++]=128|g&63}}e[b]=0}}var ma="undefined"!==typeof TextDecoder?new TextDecoder("utf-16le"):void 0; +function na(a,b){var c=a>>1;for(var e=c+b/2;!(c>=e)&&H[c];)++c;c<<=1;if(32>1];if(0==f||c==b/2)return e;++c;e+=String.fromCharCode(f)}}function oa(a,b,c){void 0===c&&(c=2147483647);if(2>c)return 0;c-=2;var e=b;c=c<2*a.length?c/2:a.length;for(var f=0;f>1]=a.charCodeAt(f),b+=2;I[b>>1]=0;return b-e}function pa(a){return 2*a.length} +function qa(a,b){for(var c=0,e="";!(c>=b/4);){var f=K[a+4*c>>2];if(0==f)break;++c;65536<=f?(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023)):e+=String.fromCharCode(f)}return e}function ra(a,b,c){void 0===c&&(c=2147483647);if(4>c)return 0;var e=b;c=e+c-4;for(var f=0;f=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}K[b>>2]=g;b+=4;if(b+4>c)break}K[b>>2]=0;return b-e} +function sa(a){for(var b=0,c=0;c=e&&++c;b+=4}return b}var L,ta,G,I,H,K,M,ua,va;function wa(a){L=a;d.HEAP8=ta=new Int8Array(a);d.HEAP16=I=new Int16Array(a);d.HEAP32=K=new Int32Array(a);d.HEAPU8=G=new Uint8Array(a);d.HEAPU16=H=new Uint16Array(a);d.HEAPU32=M=new Uint32Array(a);d.HEAPF32=ua=new Float32Array(a);d.HEAPF64=va=new Float64Array(a)}var xa=d.INITIAL_MEMORY||16777216;d.wasmMemory?E=d.wasmMemory:E=new WebAssembly.Memory({initial:xa/65536,maximum:32768}); +E&&(L=E.buffer);xa=L.byteLength;wa(L);K[5740]=5266E3;function N(a){for(;0=b?"_"+a:a} +function Oa(a,b){a=Na(a);return(new Function("body","return function "+a+'() {\n "use strict"; return body.apply(this, arguments);\n};\n'))(b)}function Pa(a){var b=Error,c=Oa(a,function(e){this.name=a;this.message=e;e=Error(e).stack;void 0!==e&&(this.stack=this.toString()+"\n"+e.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(b.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message};return c} +var Sa=void 0;function W(a){throw new Sa(a);}var Ta=void 0;function Ua(a,b){function c(k){k=b(k);if(k.length!==e.length)throw new Ta("Mismatched type converter count");for(var h=0;h>2])}function Ya(a){if(null===a)return"null";var b=typeof a;return"object"===b||"array"===b||"function"===b?a.toString():""+a} +function Za(a,b){switch(b){case 2:return function(c){return this.fromWireType(ua[c>>2])};case 3:return function(c){return this.fromWireType(va[c>>3])};default:throw new TypeError("Unknown float type: "+a);}}function $a(a){var b=Function;if(!(b instanceof Function))throw new TypeError("new_ called with constructor type "+typeof b+" which is not a function");var c=Oa(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c} +function ab(a){for(;a.length;){var b=a.pop();a.pop()(b)}}function bb(a,b){var c=d;if(void 0===c[a].F){var e=c[a];c[a]=function(){c[a].F.hasOwnProperty(arguments.length)||W("Function '"+b+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+c[a].F+")!");return c[a].F[arguments.length].apply(this,arguments)};c[a].F=[];c[a].F[e.J]=e}} +function cb(a,b,c){d.hasOwnProperty(a)?((void 0===c||void 0!==d[a].F&&void 0!==d[a].F[c])&&W("Cannot register public name '"+a+"' twice"),bb(a,a),d.hasOwnProperty(c)&&W("Cannot register multiple overloads of a function with the same number of arguments ("+c+")!"),d[a].F[c]=b):(d[a]=b,void 0!==c&&(d[a].O=c))}function db(a,b){for(var c=[],e=0;e>2)+e]);return c} +function eb(a,b){a=T(a);var c=d["dynCall_"+a];for(var e=[],f=1;f>1]}:function(e){return H[e>>1]};case 2:return c?function(e){return K[e>>2]}:function(e){return M[e>>2]};default:throw new TypeError("Unknown integer type: "+a);}} +for(var kb=[null,[],[]],lb=Array(256),mb=0;256>mb;++mb)lb[mb]=String.fromCharCode(mb);La=lb;Sa=d.BindingError=Pa("BindingError");Ta=d.InternalError=Pa("InternalError");d.count_emval_handles=function(){for(var a=0,b=5;b>g])},G:null})},j:function(a,b){b=T(b);X(a,{name:b,fromWireType:function(c){var e=Y[c].value;4q&&W("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var y=null!==h[1]&&!1,C=!1,m=1;m>>k}}var h=-1!=b.indexOf("unsigned");X(a,{name:b,fromWireType:g,toWireType:function(n,p){if("number"!==typeof p&&"boolean"!==typeof p)throw new TypeError('Cannot convert "'+Ya(p)+'" to '+this.name);if(pf)throw new TypeError('Passing a number "'+ +Ya(p)+'" from JS side to C/C++ side to an argument of type "'+b+'", which is outside the valid range ['+e+", "+f+"]!");return h?p>>>0:p|0},argPackAdvance:8,readValueFromPointer:jb(b,l,0!==e),G:null})},a:function(a,b,c){function e(g){g>>=2;var l=M;return new f(L,l[g+1],l[g])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=T(c);X(a,{name:c,fromWireType:e,argPackAdvance:8,readValueFromPointer:e},{L:!0})},g:function(a,b){b=T(b);var c="std::string"=== +b;X(a,{name:b,fromWireType:function(e){var f=M[e>>2];if(c)for(var g=e+4,l=0;l<=f;++l){var k=e+4+l;if(l==f||0==G[k]){g=g?F(G,g,k-g):"";if(void 0===h)var h=g;else h+=String.fromCharCode(0),h+=g;g=k+1}}else{h=Array(f);for(l=0;l=q&&(q=65536+((q&1023)<<10)|f.charCodeAt(++p)&1023);127>=q?++n:n=2047>=q?n+2:65535>=q?n+3:n+4}return n}:function(){return f.length})(),k=nb(4+l+1);M[k>>2]=l;if(c&&g)la(f,k+4,l+1);else if(g)for(g=0;g>2],p=l(),q,y=h+4,C=0;C<=n;++C){var m=h+4+C*b;if(C==n||0==p[m>>k])y=e(y,m-y),void 0===q?q=y:(q+=String.fromCharCode(0),q+=y),y=m+b}Z(h);return q},toWireType:function(h,n){"string"!==typeof n&&W("Cannot pass non-string to C++ string type "+c);var p=g(n),q=nb(4+p+b);M[q>>2]=p>> +k;f(n,q+4,p+b);null!==h&&h.push(Z,q);return q},argPackAdvance:8,readValueFromPointer:Xa,G:function(h){Z(h)}})},l:function(a,b){b=T(b);X(a,{N:!0,name:b,argPackAdvance:0,fromWireType:function(){},toWireType:function(){}})},f:function(){A()},o:function(a,b,c){G.copyWithin(a,b,b+c)},c:function(a){a>>>=0;var b=G.length;if(2147483648=c;c*=2){var e=b*(1+.2/c);e=Math.min(e,a+100663296);e=Math.max(16777216,a,e);0>>16);wa(E.buffer);var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1},h:function(a,b,c,e){for(var f=0,g=0;g>2],k=K[b+(8*g+4)>>2],h=0;h>2]=f;return 0},memory:E,n:function(){},table:ia}; +(function(){function a(f){d.asm=f.exports;O--;d.monitorRunDependencies&&d.monitorRunDependencies(O);0==O&&(null!==Da&&(clearInterval(Da),Da=null),Q&&(f=Q,Q=null,f()))}function b(f){a(f.instance)}function c(f){return Ia().then(function(g){return WebAssembly.instantiate(g,e)}).then(f,function(g){B("failed to asynchronously prepare wasm: "+g);A(g)})}var e={a:ob};O++;d.monitorRunDependencies&&d.monitorRunDependencies(O);if(d.instantiateWasm)try{return d.instantiateWasm(e,a)}catch(f){return B("Module.instantiateWasm callback failed with error: "+ +f),!1}(function(){if(D||"function"!==typeof WebAssembly.instantiateStreaming||Fa()||Ea("file://")||"function"!==typeof fetch)return c(b);fetch(R,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,e).then(b,function(g){B("wasm streaming compile failed: "+g);B("falling back to ArrayBuffer instantiation");return c(b)})})})();return{}})(); +var Ja=d.___wasm_call_ctors=function(){return(Ja=d.___wasm_call_ctors=d.asm.r).apply(null,arguments)},nb=d._malloc=function(){return(nb=d._malloc=d.asm.s).apply(null,arguments)},Z=d._free=function(){return(Z=d._free=d.asm.t).apply(null,arguments)},hb=d.___getTypeName=function(){return(hb=d.___getTypeName=d.asm.u).apply(null,arguments)};d.___embind_register_native_and_builtin_types=function(){return(d.___embind_register_native_and_builtin_types=d.asm.v).apply(null,arguments)}; +d.dynCall_diiiii=function(){return(d.dynCall_diiiii=d.asm.w).apply(null,arguments)};d.dynCall_diiii=function(){return(d.dynCall_diiii=d.asm.x).apply(null,arguments)};d.dynCall_vi=function(){return(d.dynCall_vi=d.asm.y).apply(null,arguments)};d.dynCall_ii=function(){return(d.dynCall_ii=d.asm.z).apply(null,arguments)};d.dynCall_iiii=function(){return(d.dynCall_iiii=d.asm.A).apply(null,arguments)};d.dynCall_viiiiii=function(){return(d.dynCall_viiiiii=d.asm.B).apply(null,arguments)}; +d.dynCall_viiiii=function(){return(d.dynCall_viiiii=d.asm.C).apply(null,arguments)};d.dynCall_viiii=function(){return(d.dynCall_viiii=d.asm.D).apply(null,arguments)};d.dynCall_jiji=function(){return(d.dynCall_jiji=d.asm.E).apply(null,arguments)};var pb;Q=function qb(){pb||rb();pb||(Q=qb)}; +function rb(){function a(){if(!pb&&(pb=!0,d.calledRun=!0,!ja)){N(za);N(Aa);aa(d);if(d.onRuntimeInitialized)d.onRuntimeInitialized();if(d.postRun)for("function"==typeof d.postRun&&(d.postRun=[d.postRun]);d.postRun.length;){var b=d.postRun.shift();Ba.unshift(b)}N(Ba)}}if(!(0