From 4b6334a2125a14cf428c8f59ffb560d88e762ad5 Mon Sep 17 00:00:00 2001 From: Surma Date: Tue, 8 Sep 2020 00:18:01 +0100 Subject: [PATCH] Switch to Butteraugli and simpel CLI --- cli/image_data.js | 7 + cli/index.js | 100 +++++++ cli/package-lock.json | 13 + cli/package.json | 15 ++ codecs/visdif/Cargo.lock | 445 -------------------------------- codecs/visdif/Cargo.toml | 47 ---- codecs/visdif/Makefile | 30 +++ codecs/visdif/package-lock.json | 4 - codecs/visdif/package.json | 4 +- codecs/visdif/src/lib.rs | 48 ---- codecs/visdif/src/utils.rs | 17 -- codecs/visdif/visdif.cpp | 54 ++++ codecs/visdif/visdif.js | 65 +++++ codecs/visdif/visdif.wasm | Bin 0 -> 43484 bytes 14 files changed, 286 insertions(+), 563 deletions(-) create mode 100644 cli/image_data.js create mode 100644 cli/index.js create mode 100644 cli/package-lock.json create mode 100644 cli/package.json delete mode 100644 codecs/visdif/Cargo.lock delete mode 100644 codecs/visdif/Cargo.toml create mode 100644 codecs/visdif/Makefile delete mode 100644 codecs/visdif/package-lock.json delete mode 100644 codecs/visdif/src/lib.rs delete mode 100644 codecs/visdif/src/utils.rs create mode 100644 codecs/visdif/visdif.cpp create mode 100644 codecs/visdif/visdif.js create mode 100644 codecs/visdif/visdif.wasm 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(!(0wkGqW?9T*w62?>0bypaCIV1m@k$BoH9t4Qp#Pgd|925<(`E z5Hyz&_){#jVhBgHRg*yrf_m^$rHuk=ZPDT>STFRZQ7MO7tJvx(Ui1I{p0)Qo@8kxx zJ?HZ|pU;`(efQdHul1~FJ-79&XRWngZ^g!SzUO)V`-2m&@OSt-`0}m@cJLMM(6?uI zh4LuBQ^r!nP{1slv_my6a@0Ad5g}=(H^1a&fukuQy z-{b{+U+s1Ay~gr~Eoa1Pt+n(zuY;%8cs}3jy$avgTFDK9Sy{Jw-TKk%1Ajai9|u-> zY{ivpS5u-9{;U72UoQLQO4;}L%=Ns^pcaOn@A(xk;9B;BF5P*xnKSG2{f5`6;(=Ex zmxD^+dmUj|^?JP-GmZ-4p#SJHwaQ-T1=9jQKpH$hsQSt%`}S7~k2|Jb@n_GjdSPd0 z==)){JZ)BxguWL90S)ujTkDw9RStSinAhiZ^mNo})oQh?*5Orzr2cu8>C;~nf`I3h z4*Wa5UhgI0k`Qs&)jC{*EeDI$%il_$NLaMAe~gf7F8}8@)yTnAh)b z@*ea3&E7WuI`4zNzs0-P_pkTv@%=Y@f8_g__W|F(z*(n3QAo3 zMS+*y*O3JA>bUGhDpYTTy3`t85@hi(HMviG;LZ2_9f_CKhf|(=4WG0}{lo_%H}Uc0 zPV>F!1vmWD=!G?WqWTmFV;WP7T;k!BWXFa zV9aIfFwLZXwq--=4L8a>_cMP3CHyShu(->IQ=v)(BWajDa*z6zmX?wa62tH_%>K+W zFH{$^{U!=jsHKQP7H&@Y8q1!%#~;3MPEr!6dTNM1aF3ty0bI5{39j1!nd)Ig5jaqH zbgR{aNHu0RDxhU}P+1KbpjsN3+yQb9OfM?J(^}n+R3qyfP6I=j{)7l#>n?+`6biw) zQB6wpzrCnWQ890R5T~UsM7U}~86_16QlK)It_5S+z7P7tX^BRlzQZ2Ub@|yR=pUa0 z`&Y^0tS_kmISH5a4?EHG7kAN9KdCGVW~;+-DuF=&7g5YQHroi%mj>d9J?P6H(2Jxj zH59+H@WL)Hd+;7VF;OkU#O+WPa=~eT)Cp}kY)=uCauP0sGI@0$c6;>B&w>pSuX6UN z;V-cwCH3~QPtz?vbSv@DSqb+f?3OHag+juJCh@R@+RJ4{5Xp8BRgll2Ap*UHtz?u& z8xBV&X`o(*Q(Po)kYKau-~=wMA}YDant+tl?i9K6o!mkEAemYk>6IJ?(qD;_ns9Ph zR?^8~4%{{WJi=I>B4OqK8etSE44+>^80mDIFqYeek?!WgNLLRjjO3dzBI_blA&f}r z!GbnvB2bG$Hck4DAe~sT+?aS%m{8<|E^`BexPBhUvNc{6+|7m^d%hP$sv4hOPV#IHzIWmw&NvBrV}3l(J-) z;d~b~pfr@Vj4*yZWVM{uz3$@asDpCiQ@}M_h@TPFJch%UGK$9}G+t^{9m)FEMgQ0l zmT|wUTZcURdv<`4!23*Exgfdao`NSy<({;(I1>}c#~;~`$+nRZLD^qL0xYw72}IS? z>JnP@F5ECG5gK~;yY%NbclT&4^0OV6ip1W#XvLSL?Z;dj%nJb@^dm7cx|F*Z6K?>= zw+<_-npF2db?Z*H7(^9|nNj;J&>j&fg`v*3pW!Lg( zLoM}f${xEed+QdSBcI?}&ffmf?xh+SvY&nb#Y<5x*e3pq2+d)uWuSu?TrtslZiKx zcIBmN&E53H+nv;tPM)Ann?Qg`!YC%CcWjuOWLWJyoOVv^p+t>x9eegn?3qYw6QY}_ z;Y|pt`#1E%moL6}{`M~oObo65yD#2x@y%Zz*gY}iXGeeVim@k@Tz351UwQfEUsH1b z{pbc)~MtR zN{Xm^T7oyhqb7KN{CqInoqD^|8q$4FBMh2}2{{G|0lca7a+5ofKYz<`5{`DEltaI| z;pEj9&Uoj!yXjwb&#KJd*yYPVqsnuV{cQZM&)6OuL#^eznTWpTo}z zbFw@;5NxHefeS!^=wBv0*fg3S@novm3SyV?goKOit~=Xm$tITMzg5U}8r$ zr{M_p2B+epe%TM4Go%uVzIDs+rX<*40Xzm&M;n_d? z)dk8+YC~tQy|=#Nw-d;o)*u&U?4RhG{~LPz{SS?w^w%tu5Br|x0AUp_0mo2p~cM5Ah!p@6cZCn{R=q6VEXyBE*K@}bPM>BsN0c>;;| z`q5fva>w!T%Vu-OA1Q6nczLN=Bsr1$eam3dmyO#f7%WZw{y0s13={@kreZvZUnIk` z-MHspg0;ik;hvB$*Ka(KJ^m3BX{>J0WD0}uz;>n~3=)Zdj;C9@FlUfth1vG@e1^Yl zTYH8usk(w&+w&P$obijxhY>Ij)h>V!LFja8#&C+mGA&dFv4JAL^Ar6-=b5aCnyXPbxLz38Nb_EnR zYTj6C25>m@FH*@y;G7l9Z{0Qm1c%8DsEPGh_hMwh(F!lKK`>+@8byE##iUa9h$p(h zw5-V_o9;OfAT77gW^DK&C6}_9x#iWx2}{NKj0*QP_gZo5#XNgD*ZeZg`;tXL;_~J# z3g)@g{6)chms+qWSm06%7X=Gl>hwjy=`MBFqTno-I(JcUE*)!*G&IgOVQf-n!i8SY z1$sCId;Bp+0A!WNbTks#5DQaUM?~nrY%_6soYVCHLei5{Jk&^xCKa6Hp(jd(=XlbA zDXkRPgtT!ab}+4!Hd{C$d&O|pximV>!WHo`AnWI5B?(62f0bn+>k^X@lP8ND#cztD zWzNzNJPIuYBp)0_u^GP?AD#N~|H7;{#8oMKoiRmj-A{ZpnL&Y>fbbwwA*5)!#1hvo z{yjYp;F#4uJ{0D*>#6uuf!ZfKDq+`@JUQEhYi%6;$z0#^HW>ktVMXo6w z#)D`T5wIPUzOtNoQHrY#evpcBa4q5?x&hz%7B3bFm2ZWjUP`ugRIaXkU(Ip22$M^r zex*RMzu^T{hv$oDfM>u|5|r5;as8hY3#9#F$bMU}^YyJcn7s4w`0q#%!Q98!sglH*J*} z6N4hcqrj8!Vxn`K>@F~bX`^C7W!kbRff|MM#jLnfk*`kZ+xfqRzQtr*QpNPtrp?t6 z1qU?fq9k-6PCc8VDfU4Sm0n>R@{&jlMlT8E7g0;PN&+Xq;*6S0Ltrk;n27^_cB6wV z67{q+8KqJN6Ko4XxTq%6ToDM14}t1kvV8<9!Nqg9P(fIAA7Hp{`{$+9*5WR|m9F&1ez+f&zwpdw7D)25OJfet*1-zE@^RR+-gh>HY z`Y|{exFOKZ(^3BN7(IXrC_mvLcmT8Q{$TYFa`mSKT~B1(a`k5_3l@{=Kjj|v&+muZ z$k0#}LDCmC+vN{=TPkst5cbnYc&-GJV?AOp2@nhV8<_wQAd&yw3cXfGL?$9lG6N+U zIb=mrT2i8(hHX;n!a!4`w9UHIaTHAWEb?v|dQ15#6m1E_DKj&#RG!jb$c?vCypIyf|*Lgsk@JvCIUS!_fr4qw>bNpN06 zXuSkqfFS2B_e;$Id%Gos=dSzx3wE3d?Au6;?^Z0Nq+5z6b$p2I+6~#0_kHcY@ zhxfbAWZ%CpPmCv#W;#%nF)J-2UP*OXSCl<-pIMZU#`d`+Gwxxg$+cyK6v9hJyXY%W zPVsPOHgr`2TM#f)NtI{s^QCtZE-%HOlVISUpo5GYmvl+d%&bX&2IwNSANaU6=tIhQ zOiK`cjCy6?|06%cqxS06NJa9+k!;73E;O`JQMb+VX?#V{c1F$YzY?jf+1sl}b8a1(%=)&5pBzJ_#Nzo}5APYIf`7@qt zNi%5DTJY<%2G4zocrYpA9*~@fiyUnt-cAi;1_Zl|(E!4Xig<_$BU3e-KMXA+$!#vX zl3b&qmpx_ulIbBO7r}$I`d1j9piMm~Ao)g$(M$p&S`%m~rkIm2?s|wiWiJyqm1kNg zDZ*{16?v9YdCP7E?z69A?VQEZ^Izl^Cqy2cZ!NzSQas7U8RZ~*N?B&0@dV^{B%0ls z6|H2iGd~0(WW*Xl2$M^S05-0vTF`v3_LpwS{yHuzR1mZ*Fiwx> zVHYG@LYsd`g6ll!Z8?b88UczyD&%>X6GoGAF+()y7?Hciu$6SMdq9jRmW$1iZzCC0 zG-_dR9kt|w6)VQYig4lm=i|Mmd{Zldb(tBcSgPt0Wh}*pOnVB{i4Bd;K#8`E2pV(S zr!8zmFdw6Sh>ZxPwgq7eOCT)rykQ`1&2H2Hp8M?;F$Hs|vCVcpU}Llq!BnI!Tkk5W zMr-Z2ntM5JBLa8mRR!p9I%wcVFfmGTV;Ev_dm1|kQJOaHkQUSAL)HE3|DYe07wqd!X!TH^gR)^5xLoJyjZ%)Y$V!`s^sKM_gF?QNJF?@+`3 zk?bu3SjtXNaNW#zDf>-lF|t>p&@hZaahO0)nDr}$6>4Sp#K2bJ%?w()rd*!Y6xce% z*i#HL6q${{K^ZTrT1bp4D3d~OSSEZT#lg)q$(J&tkxM0ZK%Ouw?$#L#49Piha!H`0 zZ97S$ZRde_$o#f_=YeJm^vu|2u@1?lYzK15Me%2oORSLyj}Lb&$OTSNXfdB#vyRPd z5Zs?{Zhc7c#IiH86?1Fme(iH>qX9U~CA)3wLAzWsi?6iFWgfo~{?@UQ$tC8S{RRXD zwBLXhF*$Bkza^KYC3vEqlS@ilsa!72l$l)O(wkfwK@@<`sl=rw+YVf9HZW;5)vh6Q zpf3@Igt?;EqNGFY_T*pZRSb|b+z6Ugu|{$p;1VPA@`V_-n0%N9kYvL~F6o8)L}tsu zIPAR57CEOlc0n`gaP}E3oSU|&ZAL#)a8kl3aS5G$Ca$(U@vhsDVt36|=`iEmA>&-q z#vE7Jj59;7XyB&QKw_lVng+1UIFoO|Dem>4uXq4Xe%2$`KruMFh0)CwnDqt(PA0{) zKoU$GAb+gYKc^hH=+RrtU<6#4&U4 zWU>!X4UNW)YS?64Cut^|>7YgK_V&TBH8Ig@?e3qZHum`31rC$q+!nt8`;SP zk}`$2K)edbg+ST^FbbSNVuGg%B%{oM0!jEZ7f8+DoInOilm~MT5t_CbkjT$|Ww@un zbmB}K8vsGYnYK1_4R&Ukd1lrHj?2gxRr1hu1Tk7MCi$)+Zban8b~Jm@Qra>_Vt-9- zQ=_7^j0^(K@`WOBB6dY97uuR&k#z+UiTktt$4cX4POEv*?Lkmsmp&Vz9#^1-)s$_h zLTM>|FD=#X7p-?MgS}jt%-A|TgGmyuC0d1X$6~~nJF(n4%r23Df{hM*%>ZF!d3&wx zs15Mh@f|${H|Xb5o4(iFzk%N0T^)-DUe zR&fdSl%DfgZmfc8S;vJ8%*IfyZ`d8w*i9EY;>)9kqSTLT*FX~emr&%j|94a5wNoiF z!x%29MRwp?QjNdmN6Q19=RgiQ0FeKEAobss-|Wj)qM`nM5CY$Je0&S>z-)i04M5(> z;d95jrF<+p8mJvz1libqv_}sK?6IXOI-2!LxBuUVFFrw@(fAP`hT4+s+kNfs*qW4w z-qQDx3%~v5n+7H{WkTqXdjpHe5FD(0Y9cAI5hmE3_!Bv9&kpP!`oIr=vi>{sKgjm1 zA6@<3TmJaDr_TRCT0T2uplJ2xDvS>Dgd01d2dp|NpgCTWD6yaOk*@3q9oaMW?0l zU_Jf~n@l`IBK2B0U0K?S@_BcEaxYRJMRTag!nCGNG!$Q9-*;fQZOs~*nW~%P6u+TA z3O(wO=LT0Vnp>~)Q8#EnEl?d@fnAJ$O`u5Qx0i3-zUCG+_aq= z@o97|#Ea9iy+$K{Oyh8XJ>#oB9AAi@HgLh@rNia0CyJMNQHS9ezsrxvE3g%QOu^0V z7LINmnuupqw?etbtFD{6E;1wOmXmXH$$lCZn|*WN6xszEUIDutBjLx+6QEtvvaJ=_ zR+f!`OgCs!G9c^92qcG%Zx*XC&%@Y9J(P7V=hn4vT89_CAt!L0h8D5RXmB1qvcc9m z7P~#0ZeXOP*;#O*1?5ykN|Ld`ViA|T3Pt82h=5USCTOZP#t~#vskuZalu`q8qiDhx zF#vEzQ4rvH*6AwG()$#=VV*KnzzIsov*gKal-i}uDNI7`I?ijr zA^$=PFc$TSzNkd26U~+c`C_(M2T$D|0z5vAjCRl^VABF)lD0S?HLeExtK7Sg6ZcW_5@bD z+*s5#++Y=y?dO-K9jdE;NyjOkmsYfc#cnIwua;IsL*Jq+2*Gip8h4l?mN9&H4@wy! zL?(@wnjvAx=3&t2OsWknHXjGuU}%LX7x!{**tds9zX)oxBw<)o;X4cAI~qM8QeEKr zX(c{U?oJg***tL;*G2Qu3L9q{J(=I=%_rlR(*15~`3pe5GrL0L>amJw=U8*euyt$& z&))U~@dDfKB%EdPgmX3N9&L21zF*W&v~j8eLTwe*37YB5PC`He9RaGShVSsKwD43I zDJczSk8Edyk8FT2{X#8F|0xj70zs)ft>@*K5O= zTCxI?a>E_ht3W%S2sC0kfLob%!gN;MhqJRUVS%U&cjOYOuAU@ucFx-2?BO}X674Eh zF{^1uSfD;uv+0UGRu^}5COvp!q)Q~^_94pBw2)f{@e@Pu%&=*(@5O3_EojJ05RWOV zM}+TyxYkQnz##tq{ck~Wl5WR`4eTE0aXowdm_|=BO|okLkf4K7mSd>n*DY6&o#`g$ zkyuIhlenMEdv^?XH%fi^Uw2_-(Bq*`4Af? z>$8eG4olW&7B}omtj{QJ1fj3Eu}WZ5%WC>u(hsMrS#1=8JqJ=01I=U_JxMmif9|u6 z&Qmm)nH>*a!4+xSOfO^+f<|4s;;O`7m@FOODow-qKs}a9kkE^1N4!(fwC{o)zX9us zH70%A(mpty`Nk}XBAcRP)<-SpiDSsgzH$uBk&kJt_eU_aFPRqq8I7s17>{VwkO>_b zKvt}US1h)*LF6Cycd+8klfG9NP6%rYBW$E-37UU<4BK9`a2$UT14DrhhK7D@R0XKM zRtxBbI?)LzAx||0A)#5~Wl2H`Kel0g)S#m%k5DPJ4tLQ z5?hr>r;(>?-L9py%dR#1c6ss6v?uA=HR$n2HuNFjz&yFb0E@QXbr{G>Obn*>3C(1N-%p@*e(uIt#&bY?OWNNh!9Dc|}) zCrjzE>Tj&HhT+@vyxeWghaU3P%KS!sim`$E^wL#I-FT4I%EWF z-Kn?RCm*Iq7a(URvxQ}bs6AuZ>dCAe@X=7q zL?!K;S}4z*4t*R@qW4tH3#Ca4q(l6hGC=IamcJv3WzT$T5kNA9bx?GE^&CsVDVaJEL!ugW8VHb~P`=UV_{SqnzX- z0XtLd(g8|Lq~vbM4U7q^*fmjBqNS?nVYy&F z9dGq2uUN~qF6X}0z;T$Yk#{mLUT9XKoRL-VX&;lq7Meoer7Nc0siPb{DD+5L;#7)f z>RV+}X+W?rgX){(9VJ*}l-BT7BU8Pasw(Ze(JP*qi9A>_+7+P(IHg<(2$!4ePq(_p z%mgadkeN=?%YHp0xqi6dZcPK?ej zPACHl=fuDAlYr=5Lk2&OQ|ruui{V*KiEw;z%JiQ=j-miHW)}?feta9Z%5Zy$730aD zJ7*?7E{7RyAV`#WgWtXAhVyqN{@@#a_S5sBMlkpfb8a|q*UrIj9Dl>W&a+S!?EBzL zp1#luJ+fxnfE8MC(E}><`q$jy3K6Rrd`az{=LypOcfN&ZK)S5@Wfl&HyiHl<0&}!4^5Zc$#*0gDD+~XEe|#-H}bOeC8a! zrcW(B$O+y78`9&sT5oe@1yIH9IvoMSU1BYbnJ9joxm7qT(`W+c+tx+pHc`yA(}(w_ zQkief_Tk9cZIIt0{(9;P_cmC;nJnCkCuX;6 z)p7_O`L**Jbca8Krm@yj!RPM1KL254x=VO9eqs5aDL}mA5;+A?GYiPJo*h1s2@JH z7x%`$T_90)NYM5O+rZT`6G>)2oXcx;S=CD1$KPVWES%^V*$J338;YLSn65q> zl-OZ9&6J`sJx6#thE)701{%C3qsOkDbZsg@dQ9Aglj&k8i(l5lv(kS?5N) zID`pR4v2FmdV7;VkALRJ8aNA$yW(baC3P2qV=;G({2%yONqs zk$M=TDkxJf7W6uamN0{~SQ(&*@kvhji!v{~Q*J90!={e*g0B^?0(5uNS8NkEQzOxzUGge=0( zv4ZUg(wC}f$VEpC{{zQD#nJ7)PK$ajK!X_QWkS?H#Z#m(x@yNG`9_S!! zdO`?W(Kr`A4Dm)7ZTJ83=hqE$@|+XATLeu@uta;rg@DbN1~Skb*mgq!86s+{i6+_J zNOTdyUi=OkLZ-ByVU1`NOy^m%KaRAT0$l@W5udIzcIqw{DWvwN*u*G&*?EqK-iBFHF8LfzjYZnq)e@WCw^Co`u%Cof)){6A{tM;Lo0AM|PcAjKSGW!=0 zz|sD^RzTj9p~@apvyx)l*|wkgKk<1al`qDp7fTw|jE(qybh6s!9*(Ag8{LCU2MHPT zXu=r^8liOm;JLC=rh=>wJ1EY`4UD(Zh{3(3T~2>r5eY2#+refuk z-=DwSlQWU57f^0|R)%6cKfHTqA<|RJ{i*L|Urn=nyW=R^mu4UC&JaqRy3Y9T{rK57osT^DF zG-b*G(`Ba+iCcVB30+>wVjhGjXVpt=j|w|)Wbd>Ahp*fGe8t*w+=J&l#mn;ox8w!V zj`(Eqp0Q`7Ge-ImTmV3G)-U3gsiNHyXkOY+EFG_msJa~Z4wF`etQpHk8mv|}%3%{+ znN8eA{LDtZP*!6VndPKDvM9JhkF%#}F3k3^l$DugUsRy=C|PExs}9U92Z3SL;)TG# zvk)EejHa2Jf`8d(Lwahf;W&68$?JW1T#0)jBOX7#bhqhTtD@@I;vVSC53X(%r=(j| zlAe|e*EV<8@*OWfWI3sy z`>5RA#z9u2K5KuD?%0K#?r4R2f^Q?wg+4G2u-2dVj+KA)Eq5>tl-^k=he6G1eWt`= zg~H`@PL3xWBpgf-tPAlhjZY>+Ui>{jrA-F%r!g@MtMQHY>~>#{(R2m_95|krG+9l` zOwZrx6L(@KPBRHw`$SgA146bLlaL)Ttk0H3bVXXZ@3mc8Znr|(X)@R07)HEBE74V( zPvOJsddFUZ62U}LpGYFj!ghbM&95ecFV`n`l+~>DlQCGVpmnf?g4#E<&yU_>`zvyH zr#0Li@`ac{GA0GqhX9;IxB^*VL&_tvF_z*>?&X-5m)H&+#yj2Nt`+z&mfG70=85he zJGt$GTftpCD>_bidT-=?LUQv+-l6RmiNDhv;l#Tw@!!}V4T=H&6?rL=ckCVd`gi{2 zeRr04wa+IhZIx7QYW^LGf188eAKg=DJ-}vz2`8@@OTuj4Ft3$3iNx$8A$hYLzUe1> zQQS0C+A=s+`C)VL{Xf2QFYy8dZJtvhX#nv86-C0V28b}fG(BS{FKSs;iE6AF74ZR) zkW^RV=IG|Us8kIV?UM4E5a7+X8mS@pF4J_~0d<+gs=96d+$F*T(}@LBAA*v$Ma6`~ z_KO&D0A)0=?yPSPe)Fl{eaLmkGF>-X-2qFf5k#O%LZ^=3uHXe+j;F={RXVuPO<|8$d_wA=Cq zKYP;KhN!C7$Ef`N-u|JTcM@sW(~iMK*WbibJ^0=m*Xx0R+Ow&G!HS2bvJh z;%b;_=4nMYjmvbC%?7aDF`e5ERmj;uP(f^9`b@-<)|hcCP0IS!Ie6iF{`p*c65nL~ zn#!WwQaBdnIA~+K-cD;I_h?I#MI)^65!nr&Njh_US!7of%$Ra8dXU|av)!<^)D{R$ zk;+M?7I7I`o?*#Y0RZjMXG%`W1{p@3Lkd_s9TnY37$~qpg|Za-kEnuLoBq?YLe`xc zPs)-N)vb2pG6}JAO#7Wl0H=B(>$!I4x_%%k@|sX`fx~7^RdQ=%s;(mI&fw!xw*+9e zNwQ)^M&WwvR%%(`R(H4|%)CI&34qdt0JzNfe;V(nqMQ%iZBl5Bdyd<)P2AcV7xQ)d zA8CB>P2c~?d4^UleTT=ujiQ{UW^(974F;R|XkO8y$*)}*wCu_lrfU@0aD)Y$F(-Ph z7jPW+c;X%=xFqBA8UM4#k009k-Vdz#-ba3CV5sMbmu|W8r%Reccbzf)@85hzsW~+M zJ2QVYd*1I2&*fE_$J6@DqSp)TlTRz2;^2~YfcHJ^4SiU`5z1n zmE-v*o%X)(JUleBqhsd{JLfcqW}oq)#s|}92Zr8o%pcAC<8S$bth#`vrosMn5Y$`2 zvyhPxW0U-@73bSR@?Z*+Xa@I>u4&Dj^ zrnnV@IQ;5HSkN*j@QTh!l*@8$SS->lkgz5#NJOrd_N$Bqtnav$!A(A(1nSnEd(YM; zWil-03;>pe-6JbzF>P0j0fvb4;M2XoeE}8Z;iU}sYv?5AUqhcg>s`I~ulyI> z2d!J}9eT?b?|bC8Z@8p+Rln?Mi3$}rcCcHS-mSjo@M)x1BNPaZ)k?ex)pE*ta7is$RFY978&xoAOazFV^SE(8T$7{4)9q$&P= zps+5Z_iW&po@8MX?L{V%&0%m#2JsbYAAaf!uQ`4@o~#!g7+~Vu&Hj9!r;^VTVETLz zgg8%$_h}q}V>yp#VUKMAEo#FdSG~~@FbWMk{^XzDY44EZeKF(XztFog>@9Ku!5Oot$!zzvs!z+p$a01=}Xb(@%W&;s2)hldw63yLUbHg`e=QK>3?@+`sD=B-NS0 zm)-Z^z%Cd|Bs9{6#3iYJba(%;s|?2K9-gSz0p%2-nA3< zmIiNAw%_1y}k#Lg~oc95oO%JT@?2QXT0i+y}JhAe&0#w@7#4q>e)gv zUAW`Uq?nf;6CV#d@ps(`g*3z9!_Fq$04myQ97ujFt?o*yyW*Wb zo}Y;>Ve+IZr9v$)w6!vV)2NqqCGW=ZEz-yy!EP6U3=Ow(a`-yZ4t*Nc1i z&Nt$kdU2Zhfa0R!*w(0pJ@Gl+yZ-?HyD9{UkRW?dB0B) zRivzPI-JIu)mj%q#vW&?{al+UuU>es6S+DggFE@RpHr z=Qyc=yi?1MIdNnUbcZ!C9L(B9A5?rluxh*{pY8W0&OXQQ;nnx5z2s{*!q#idC4*J|e!HfQ+KwbiHJ(ycp}Fca4so<6SILAQ5cK6w~N%q9c77FHdzF z-6fk8Ss?Xd*NjB2PZwn_1aO>rgrV)%FcKHeVwpe_7&jgA;$s-S;)^iH$3U1Iv|3w5H+S-pn0Z!aIUk4>cO=5-sVsL6#MdoWH!US(XuEnD%Q)APG{b6KY3q=G(jU z3DfGeoe1~19YaZdpA8>L@Zlu1H$O|Yr}Yui)4Qi*R;usO>qRL56g?u~PE^0RhjBXg zMjT}_hedN8){uNyEf2}j85EyS&winO@-)ExtT7w-!r_N@MI}yE@P#nx*pTTpuIy2Q=!g}+GKz)*o$5j? zkhC%eP(L4lIIKtF1AXgOyS^O-u%YGn8q2kd6j z1Assq2UL`Kxu^hjgNr&pCo$P$Z&r|U<8g0}dHyaD2-0W1<4pTft z1$S(Pl2VJhe2VCN4~C4n#1e*Z^pmL!23vbawMOc4w1fj=K;dQn>4dA=TX${qdVE@U zZF4Xed^Lf=UDZo~ZS{{0&i)48<7Qn-YOTA|qj&f%s+T6I9>2m&l(Ba zgmOU??Jl)`aE1W&5s-4KE=Y&ONz&WG$svO|%k;iM$4RtKQ+II()@kGno8U-{SMP$$ z3C_HWeg~NkDlZq27a1u;d9g@TCkUdY8tW09WERiv2<=3kA`0jdAKKK;#T~& z4^ZebP|fmUEw{^+h`;G`c$8;)TU_+7FU$t~N7apv!XXq+hyQYIa&k7QQbr1F2_v1T z&(xoi=;{L9osKfd7UqUQwVY)E>L3>m(0sf=JoLi8Ezbe z3VB}0myFg~ayAe{O2DQJch7J)I(ghefdqx=&)FHl?$A+3F|97t z!|$=A^e#_wqOySK;OCZpq0D<>6O^L5nSPRZd}2^@1Y!iyZr^!tP=|Sny?KB})G>A; zs67PHuJMC@YB%(pA3m9UYrNZcKO5sHjvR9a6C4;p zl;=$w?rgUDOTJit zB4mVjh8pc&$n=5*B{!GDr3HsF4pR>{RL6-CM|OD~43_TLgiN46>pMo-hL|Awn}FX! zqh9X0(@*HAC1fpcxm)s+aEO5e_PzYdIM-QEadp zYtaf?+FQcvY1u{u{SXXL0SNm_Y3&kDM`Drjs~C7Q9JPLJLgU>p9zKI8RcBnR@Uk`5 z0mw{htSqB9tX0d1t4$*&$24`GhB1}{Ba`i$17tF$MG@YyXf7z#4nMC+^4Ik<=Vaca zeZ>$(EkQ1uA`$-10=Tcxl=?8DU`S}wm!uTk=o1REUwH8B$2a2F$ltJ0USg%m9a3~b z=QwkN&D#@i>pj^*=3}&AziaV6|FzhNvP8^qFW;lU)nv^e>%1lnv(0PS$dgwLvW`oH z0rm82ZRlfk3gBLmkI8w9_vlqrMN8o=DLs2tUi$PymFD!~v&U!_%FBu!dZj6q{xV{RT*hvz4aUB@Pzix!?vc8x?^M4cECRkHgA^6tN~v#^s!9Qp<%& zEz+QNbhqGekZcfD(3^j%VH0`XI?!@m-5p?+=@iK-(1xZrKvs{EvU_!ojmcnSE9k!; zpz-Q;KvzuL_j?5Sf@XG?y{bui%=~Q!yZ;`>@Z`TnwBKH6MAwP)Fu8+GNEUaM&DkTm z^Rhq=c{ct)c$p}ljW;RxNO+-pQ)yd0g&5GZ&DlPyS3qfj&3Ffb>hqIK#4!il=GtV-O z%!|k}bL`a0irXTM6*cLW?G7NS$^($Vs@r@3CsKl{8xsPv?fWXbAR$4;hUh`q3~z*u zY|<&<$YV7?5;&pT-ndG^<}>A+`tfprcMU75AYo8k9_>I!b@Aw@3}oWdI5Cp>CefGMmMf9V^(X55LTw@P|)QL^{)TOal8k-p5$ zZuM&j*ll7h2Nv8vT?L=%%a|Dm@L>zSqgMrQ*DkXo7yP$rD)``x%uMwWAnxx`!N;}V z>&Osqw}MZ-$cphh4O(b9WM_WY3O<<^EKL@iTtRT;`2`z+>e*vOK@@yPEFmzcryoGe zf49PqvUR_PD(N&$Uf^EPWt0pR&?7<=|zvvi;uS zi+{|Df4>F%fa1p;fXg#~#9&f4Oy}Pt>07bjBz&l0?;3N9 zbvOi?I3~5dkxsW5c1cHcUoD7s)bhg?-RNbve8{z|BXlLac-+0==ZXi9`P#t5`-X1o zz3KJu`O)9-9w5*D8qK?t%ETudvyxJtv+qd7W+#Wt-Tt zQbYUL?YEftWf#3u$zHI=V*||F&a^K=A~xBg#!j6$QH%9NC;U650JK;~)Y@8XwNcZ6 zn3Y%^J)kPvgVwSEP@o89y!JouM!fN|f~g0Ecb6prr`RrXAaUk@yWwBw2TH zfc~p#n%LR7DCvq%;k6@X%l<>Q;t%52c)j<3{rPOg;fnn~ThXpt|Ib#ua9eTK|8ZM! z)`6=2AGH;i9|+)u+lqtQ{@-FNo`~ZX?x0yhNl*$E&xnE z)guMF53ZOGE$#?$jqOZ{y@r9WSSfxj{v;w$e9hHY67y4Aj2QaDH_)M3zUJv`zP=X3 zJ8UrNkO_BPwK%>uPRg4TL0m?~oq~Nw7vv%bZ&DUUPer7#qhlWCSGP6j*!2`XCv`f9xn?l_V z!M!Ov+xf2@vFvPjyIfT!jc-k-aS}Oi7%!27w{lX`p1l@8h^M$koQ`cdP-UI>-;Lu6 z-V+l^x7#rtfXS#IU(wusuClas1Oa5&)z z4vFpXz^!|SUfj6;mO1M#Z@%9c00rCg%#)-Nm~!maVH)Z#G$fo1u?jZ$uKP~PjN)vU z*2?qD29pT4)x_-^4G{}r*9+Qmd3tSOC&!K>H9Tf3kg!7v&_jelJWuOG*LIzB{hR;j z)}|eC=v1aV;*ew2+IV*WRys~ncGCS9!?jCyUWpihin#-ED?O5gGw<_jnOmX7jK*|Cc zNF@r8tIt~dFB|Wl4#*-UkUKyAz+XK0+`PY&9G*}-=Rj~dc5dHz3*5ldZ;rq9otpqxqy%p6cOSp$#O(V6 zljt1)j^!AKooz}Uy81nj{_3y(^Un0 zNVTcjd7GAGz{m?nW}kJ=yfYpL<@x-Ct(v0Ed960H+q>0bOYT*2iY%A2`Rsc}0?Uk# zBv_hF3|KCXtRcqFDXxG*FdVQ#5r3Oosa84uDU&lxOPQ1>ed43nG=zLAe5#xS;@Pu$ zw^y{$E%@lIqFQ2Wnq(cQ%^`~s@$X1m=~Ipdbct_PYoVf=JG*;lKJ>xZct@-=qsPwbMa;U&!8=ZA?CG66GOLeedE%OKdCl% z-#zp<4}IVN`)~fwf!!3bv1IRrojh}%Ouo4gj$xJ=0d44re%E))CB&v6q7Z}wQaf#o z9x{ zEeV@=1XB5Yv*U(=L%&5#fN3cegqaquVZbE3MaU{tg*gL*3@@kSXfMldqJ~Q_ftdp`tFnk=wiu zx7OO*K`?*V7IUI2oFc_8Ojp!`*WNAgyiYmra^L#q-SM%lv=bz~w1V=4Ax6`_=P%sl z3J0U@=?s@Ucef&HCmTO-g zPLaWtnCbK_=IoDAnWTgrCx$9XE@M}HKKzopwl2(a4Dg&&|Uhixyi{nl?W@FqX zyJ+o{ZLJ~OT3Ou0_msX#kKmie&mgW4o4HFt+@>h^!`lC0t+R8W7s}Fws17E*X<3SfL+75@9 zdV1Lv|H!A!l)yO%xaV5fQp$DH?3AJa$2g#Psf8^mFts`F+1QE~_n|rAT1f}yHDPDpDkaS8Cox|5U z@}!X+7eXWZA)6D5+9obg8axaa&6e75lH%Ay(>XciQ~G^bV|H^&zl|EN_*mScM%l(Y zCYOwQIF*xKVuq<5D)}if20){ma_!g{LtqOrmzhL+hu-?g>NDTA<4XffHTMo(fBUWP z`_k+u1}1h7o%^$Qf8>I1c}+Jk9*R$geJFRKcsTUl1y|hTo$^VJ#%YzZ)c`Mm&H%sb8J!+%H-E^d>q5(nkt{UA|7|Wcd z$y-j*gLCVtPBF4r(w^0$=~A4R=xy*ur+ph zRZMX@20ic53wCLQ>Uf71h&6*qIwVL~18qCbUt5PSsTF25>VjgI`XMhE)vGMoMj}VX z_UMsFEBYZAiUP}_1V8h z^`B2xPkxQ+znH8(|JSJgOFz2T_ZTYJl~B`3Q!n@Y_U)K}jhu+-$xp;=VP~VAh&iCZ zFL4~RU(V90m}^x8PI7)`i=B$ei^iUVb>p@9$(U+huxP9(aP!sFz4>aLj_IL@jnvux z>hOmzIQ`~_CwR#gNM_~i_FDEkgiJ9K_4pq+EO6W0*?JP)Ro=X2{xam~u*8^=c1T=S--D_{QV z<)@yy^6C|%%g07ntQp(5bmij37`ems^YI&e_e9in>QuvH;pChuS%|5zv-G)8sDVgmK>Liykh;DYsOZOzM2xrdFLeyy=&I5TD^ST`c<3OuHN{PD>set zaK)xKtzC1t^ov)n^fHI`YHxJ4LA!Qz_1N;2D>jauyNUa2$LQwy^R3y{t4>s_gDX~E zz53EM*Q}mwcgg7L)n4BJ)vJynL|uDy2s=ornboHw}snvG-0=<^fR*zV;& z?=-OV#=ra98#dg~{EPEfjdXwPbLL zzxGUaUw?DjUEdt~)|bCM;GjG^@cP$%XUXd>`PsnoKR@w3l|T6Yz<1wx&*T5v`0Ii1 z{#oxI4F3BE2QGivmzMnN?_J4npx@gSz4+dbR9^nj(9Z_`>9Oj)XWjL^fswcV^x|JU z)zMtLY3%iD-jG0}g)d&{En6|NdfAGNBVP7QS5*7L@7?oTKRK(};;_VgqR`z3H0vyNy>vHLsQDZsGi7 zmTuOrzvfMoId*r|+Vv~OyjANrUAcC(w{dLMIp;VHap`tRKeq*4?Vs-oT-RQD)2WN5 z+`ja*{C4%ajVnjjTsyYybuz=wO<*5C6F3*rDI z4+~55dI3<3kcTe^Q4jyHg~}02i3=|P#*vHB!dYne;i>X`1$chyqUVEf1odg z;>aaT7!ThIC#C13r83GFbz+oHT{y|%Q%`M^rKPJ1<9+Rs%UxBJyX(m1))nO*IdZwR zMY(5=TyD517e`0z$C{$tSw}86R+PKq$mKQ`iFjI zew8VFdnqP?XBxm4@JxTtKY$OdIcNkQf>cw7aOJ33gMG^jhrRJ&*pTf2*yOZeKlOrP zUw07b>kfoWh5(yG(`Dp%0ra@~Aht6QGxzP-xk$rVI za`DZhFHoWzc$83YIoqM?-TLG92fM91sx?e((H9UXNS zH}<^4%l_@?$On`CM@KWj&l8d~;Ba?He)t$e@=uP5PB=7@-YF+1NwV;D3%7Xlui3PA z?TIWj9~0eHS$OJcFFpN?MQ5IMc6Pzw(BdWSw5@(lu`Lg1WP`{e~K1-fpyn5`^Q`fG46MpUL(NWscUmwp^ zUL7pTC*%nZTXA7>kx~yn4;LsW1WS%>@zR*T<*8Zw6_#H=k$f#}rVfF+aEpKYfKf@)c{_ zA#SE#jXJO3b7h|XZPF|A^wp%V&eLP0SLEq&(i`*iZKTKY^j)Miff3C2^BK<550GA) zr$0@4U7miF^i_HKtE5-u>3yX2C)%m6Gx-#KCCi(|XEvXs_~`FkbVB9k@M$mC&;49J zNAnSVE~4DQ{9wwndEFrx;&~59*E~;t>%(4l^KsFf!iRmX<;zwc|Es}&{^h0Td)fWR zMdfDr;CJwwA2=@ZIWhQYF30eu+vB3{V}X(Ix%YCucvHgndE|T9&#BX?cMARgHQE=> literal 0 HcmV?d00001