jpeg-decoder-0.1.22/.cargo_vcs_info.json0000644000000001121400477074300135040ustar { "git": { "sha1": "3bf223f7dcaa03cf0422e9e79eaf936ff406686f" } } jpeg-decoder-0.1.22/.github/workflows/rust.yml010064400017500001750000000020011370007336000174120ustar 00000000000000name: Rust CI on: push: branches: [ master, github-actions ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: rust: ["1.34.2", stable, beta, nightly] features: ["", "rayon"] command: [test, benchmark] steps: - uses: actions/checkout@v2 - run: rustup default ${{ matrix.rust }} - name: build run: > cargo build --verbose --no-default-features --features "$FEATURES" - name: test run: > cargo test --tests --benches --no-default-features --features "$FEATURES" if: ${{ matrix.command == 'test' && matrix.rust != '1.34.2' }} env: FEATURES: ${{ matrix.features }} - name: benchmark run: cargo bench --bench decoding_benchmark --no-default-features --features "$FEATURES" -- --warm-up-time 1 --measurement-time 1 --sample-size 25 if: ${{ matrix.command == 'benchmark' && matrix.rust != '1.34.2' }} env: FEATURES: ${{ matrix.features }} jpeg-decoder-0.1.22/.gitignore010064400017500001750000000000231357303225700142770ustar 00000000000000Cargo.lock target/ jpeg-decoder-0.1.22/CHANGELOG.md010064400017500001750000000064601400431673700141310ustar 00000000000000# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ## v0.1.22 (2021-01-27) - Fix panic on jpeg without frames. ## v0.1.21 (2021-01-23) - Fix incorrect order of MCUs in non-interleaved streams - DCT Progressive images with incomplete coefficient blocks are now rendered - Fix a panic on invalid dimensions - Reduce allocations and runtime of decoding - Rework multi-threading to run a thread per component ## v0.1.20 (2020-07-04) - Fix decoding of some progressive images failing - Several more performance improvements - Add `PixelFormat::pixel_bytes` to determine the size of pixels - Cleanup and clarification of the 8x8 idct implementation - Updated fuzzing harnesses and helpers ## v0.1.19 (2020-04-27) - Fix decoding returning too much image data - Fix recognizing padding in marker segments - Several decode performance improvements - Remove use of deprecated `Error::description` ## v0.1.18 (2019-12-10) - Fix two bugs causing panics introduced in 0.1.17. ## v0.1.17 (2019-12-08) - Minimum supported rust version changed to 1.34 - Fix clippy::into_iter_on_array warning - Ignore extraneous bytes after SOS - Support IDCT Scaling ## v0.1.16 (2019-08-25) - Minimum supported rust version changed to 1.28 - Allow zero length DHT segments ## v0.1.15 (2018-06-10) - Added support for WebAssembly and asm.js (thanks @CryZe!) - Bugfix for images with APP14 segments longer than 12 bytes. ## v0.1.14 (2018-02-15) - Updated `rayon` to 1.0. ## v0.1.13 (2017-06-14) - Updated `rayon` to 0.8. ## v0.1.12 (2017-04-07) - Fixed an integer overflow in `derive_huffman_codes`. - Updated `rayon` to 0.7. ## v0.1.11 (2017-01-09) - Fixed an integer overflow. - Updated `byteorder` to 1.0. ## v0.1.10 (2016-12-23) - Updated `rayon` to 0.6 ## v0.1.9 (2016-12-12) - Added a generic integer upsampler, which brings support for some unusual subsampling schemes, e.g. 4:1:1 (thanks @iamrohit7!) - Made rayon optional through the `rayon` cargo feature (thanks @jackpot51!) ## v0.1.8 (2016-11-05) * Updated rayon to version 0.5. ## v0.1.7 (2016-10-04) - Added `UnsupportedFeature::NonIntegerSubsamplingRatio` error - Fixed a bug which could cause certain images to fail decoding - Fixed decoding of JPEGs which has a final RST marker in their entropy-coded data - Avoid allocating coefficients when calling `read_info()` on progressive JPEGs ## v0.1.6 (2016-07-12) - Added support for 16-bit quantization tables (even though the JPEG spec explicitly states "An 8-bit DCT-based process shall not use a 16-bit precision quantization table", but since libjpeg allows it there is little choice...) - Added support for decoding files with extraneous data (this violates the JPEG spec, but libjpeg allows it) - Fixed panic when decoding files without SOF - Fixed bug which caused files with certain APP marker segments to fail decoding ## v0.1.5 (2016-06-22) - Removed `euclid` and `num-rational` dependencies - Updated `rayon` to 0.4 ## v0.1.4 (2016-04-20) - Replaced `num` with `num-rational` ## v0.1.3 (2016-04-06) - Updated `byteorder` to 0.5 ## v0.1.2 (2016-03-08) - Fixed a bug which was causing some progressive JPEGs to fail decoding - Performance improvements ## v0.1.1 (2016-02-29) - Performance improvements ## v0.1.0 (2016-02-13) - Initial release jpeg-decoder-0.1.22/Cargo.lock0000644000000374531400477074300115010ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "adler32" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bstr" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" dependencies = [ "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "byteorder" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" [[package]] name = "cast" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" dependencies = [ "rustc_version", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "clap" version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" dependencies = [ "bitflags", "textwrap", "unicode-width", ] [[package]] name = "crc32fast" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63f696897c88b57f4ffe3c69d8e1a0613c7d0e6c4833363c8560fbde9c47b966" dependencies = [ "atty", "cast", "clap", "criterion-plot", "csv", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddeaf7989f00f2e1d871a26a110f3ed713632feac17f65f03ca938c542618b60" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" dependencies = [ "autocfg 0.1.7", "cfg-if", "crossbeam-utils", "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-queue" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" dependencies = [ "autocfg 0.1.7", "cfg-if", "lazy_static", ] [[package]] name = "csv" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" dependencies = [ "bstr", "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ "memchr", ] [[package]] name = "deflate" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" dependencies = [ "adler32", "byteorder", ] [[package]] name = "either" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" [[package]] name = "hermit-abi" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" dependencies = [ "libc", ] [[package]] name = "itertools" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "jpeg-decoder" version = "0.1.22" dependencies = [ "criterion", "png", "rayon", "walkdir", ] [[package]] name = "js-sys" version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" 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.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" [[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "memoffset" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" dependencies = [ "rustc_version", ] [[package]] name = "miniz_oxide" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" dependencies = [ "adler32", ] [[package]] name = "num-traits" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ "autocfg 1.0.0", ] [[package]] name = "num_cpus" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "oorandom" version = "11.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" [[package]] name = "plotters" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb" dependencies = [ "js-sys", "num-traits", "wasm-bindgen", "web-sys", ] [[package]] name = "png" version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ccdd66f6fe4b2433b07e4728e9a013e43233120427046e93ceb709c3a439bf" dependencies = [ "bitflags", "crc32fast", "deflate", "miniz_oxide", ] [[package]] name = "proc-macro2" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" dependencies = [ "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" dependencies = [ "crossbeam-deque", "crossbeam-queue", "crossbeam-utils", "lazy_static", "num_cpus", ] [[package]] name = "regex" version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ "byteorder", ] [[package]] name = "regex-syntax" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ "semver", ] [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "same-file" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ "semver-parser", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243" [[package]] name = "serde_derive" version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "tinytemplate" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" dependencies = [ "serde", "serde_json", ] [[package]] name = "unicode-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "walkdir" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ "same-file", "winapi", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" [[package]] name = "web-sys" version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 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-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" jpeg-decoder-0.1.22/Cargo.toml0000644000000021671400477074300115160ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "jpeg-decoder" version = "0.1.22" authors = ["Ulf Nilsson "] exclude = ["tests/*"] description = "JPEG decoder" documentation = "https://docs.rs/jpeg-decoder" readme = "README.md" keywords = ["jpeg", "jpg", "decoder", "image"] license = "MIT / Apache-2.0" repository = "https://github.com/image-rs/jpeg-decoder" [[bench]] name = "decoding_benchmark" harness = false [dependencies.rayon] version = "1.0" optional = true [dev-dependencies.criterion] version = "0.3" [dev-dependencies.png] version = "0.16" [dev-dependencies.walkdir] version = "2.0" [features] default = ["rayon"] jpeg-decoder-0.1.22/Cargo.toml.orig010064400017500001750000000010631400431664700152010ustar 00000000000000[package] name = "jpeg-decoder" version = "0.1.22" authors = ["Ulf Nilsson "] description = "JPEG decoder" documentation = "https://docs.rs/jpeg-decoder" repository = "https://github.com/image-rs/jpeg-decoder" readme = "README.md" keywords = ["jpeg", "jpg", "decoder", "image"] license = "MIT / Apache-2.0" exclude = ["tests/*"] [dependencies] rayon = { version = "1.0", optional = true } [dev-dependencies] png = "0.16" walkdir = "2.0" criterion = "0.3" [[bench]] name = "decoding_benchmark" harness = false [features] default = ["rayon"] jpeg-decoder-0.1.22/LICENSE-APACHE010064400017500001750000000251371357303225700142500ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. jpeg-decoder-0.1.22/LICENSE-MIT010064400017500001750000000020741357303225700137530ustar 00000000000000MIT License Copyright (c) 2016 The jpeg-decoder Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jpeg-decoder-0.1.22/README.md010064400017500001750000000017741367174505500136120ustar 00000000000000# jpeg-decoder [![Rust CI](https://github.com/image-rs/jpeg-decoder/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/jpeg-decoder/actions) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/k65rrkd0f8yb4o9w/branch/master?svg=true)](https://ci.appveyor.com/project/kaksmet/jpeg-decoder/branch/master) [![Crates.io](https://img.shields.io/crates/v/jpeg-decoder.svg)](https://crates.io/crates/jpeg-decoder) A Rust library for decoding JPEGs. [Documentation](https://docs.rs/jpeg-decoder) ## Example Cargo.toml: ```toml [dependencies] jpeg-decoder = "0.1" ``` main.rs: ```rust extern crate jpeg_decoder as jpeg; use std::fs::File; use std::io::BufReader; fn main() { let file = File::open("hello_world.jpg").expect("failed to open file"); let mut decoder = jpeg::Decoder::new(BufReader::new(file)); let pixels = decoder.decode().expect("failed to decode image"); let metadata = decoder.info().unwrap(); } ``` ## Requirements This crate compiles only with rust >= 1.34. jpeg-decoder-0.1.22/appveyor.yml010064400017500001750000000012311367172151600147030ustar 00000000000000environment: matrix: - TARGET: x86_64-pc-windows-msvc - TARGET: i686-pc-windows-msvc - TARGET: i686-pc-windows-gnu install: - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin - SET PATH=%PATH%;C:\MinGW\bin - rustc -V - cargo -V build: false test_script: - cargo build --verbose - cargo test --tests --benches - cargo bench --bench decoding_benchmark --no-default-features --features "$FEATURES" -- --warm-up-time 1 --measurement-time 1 --sample-size 25 jpeg-decoder-0.1.22/benches/decoding_benchmark.rs010064400017500001750000000020351367714443400200650ustar 00000000000000extern crate criterion; extern crate jpeg_decoder; use criterion::{black_box, Criterion}; use jpeg_decoder as jpeg; use jpeg_decoder::ImageInfo; fn read_image(image: &[u8]) -> Vec { jpeg::Decoder::new(black_box(image)).decode().unwrap() } fn read_metadata(image: &[u8]) -> ImageInfo { let mut decoder = jpeg::Decoder::new(black_box(image)); decoder.read_info().unwrap(); decoder.info().unwrap() } fn main() { let mut c = Criterion::default().configure_from_args(); c.bench_function("decode a 512x512 JPEG", |b| b.iter(|| { read_image(include_bytes!("tower.jpg")) })); c.bench_function("decode a 512x512 progressive JPEG", |b| b.iter(|| { read_image(include_bytes!("tower_progressive.jpg")) })); c.bench_function("decode a 512x512 grayscale JPEG", |b| b.iter(|| { read_image(include_bytes!("tower_grayscale.jpg")) })); c.bench_function("extract metadata from an image", |b| b.iter(|| { read_metadata(include_bytes!("tower.jpg")) })); c.final_summary(); }jpeg-decoder-0.1.22/benches/tower.jpg010064400017500001750000002120011367172151600155630ustar 00000000000000JFIFHHExifMM* (1 2ï%OnePlusONEPLUS A5010HHGIMP 2.8.222019:11:26 22:37:44.6"'d0220>R fn v  ~|0100 d2019:11:09 10:59:362019:11:09 10:59:36)cddMM*3AD602695602695602695R980100N:ERjr 03Y'('+ ;$2019:11:09(HHJFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?.)إ{3i3bq?bQm"=#@Hv\umXXO-٢e=iLFړmh 6Ѷ6F(=bhF*LQ@G)h+Fړh(Rm@K~)v)qOh&(@ I5/٤lm ^Gwe+۴p[9B/ aGҸ1U ;#:rlv)vK8mmI1@ &*LQbF((I1@ъm2-T K\S3P!hRJ1&6{iqOK(KDeV :F=D\XI_˙C6qRL)cp j6?Mc?+|cn<\S ZBv$.)^ F)&( #F*LQb#F*LQG6Ԙ(1E 6ԘL.G]if1RbQP{iqODh8'5g\л=tl k*t[Zp[ 1`=3:{W=ռxK,w`/"ַ21 zד~kRa4kGFړm.`G&6x&6[ivԛh4 KS1h`@\`Z].]\vvѶ"KK㸠:r*McHd[hS=@"3*3nA=)o% 22R>\gi_[M*;Gd.FRϲ8Į! N8y5nh|[)6Ԉ3%(끚]SwgQZmmmKmY;hSmmC66;hSmm !KFm6(i@6c6ԛihXf;QFEhSNo*]GU}>|<_,)lszϖ.LAJ(/-K&>ߔ0}^usw}  :S ؂E<Ѱ!fϕӶjZD W;u$@Szsx^޹+ruV^qߓSwN4-ԌeB=?ukTSO"Fʛmk{"(Slm leOe mM!KK;hQE{iBXˬyH҅|F9m;m?e..&KWvUMRk{=2yxNZ[Eekm~ltc^l=;GuqZV G8f'TV~l2@w@ݞQ"9c_1]`T).+KS4lttu2Qa5 wο-E 9.]r2p^k\{Ick+Ag`I3^pr\NLl}.;iv(])R"1nʟimAeOs)6=G0rmE4y)vW%ڝ=mg>+b5V/*Mt&KR-*Ph!6T)vQkץ"Ŷ.mS Ej:e)c7H. 9=*丼I#x7 7?{?1rb*s{Rq\|1 \KFUH1¶5<:116nm;ȑH,r?^NDCqV* &u(SrG=3Kp4N@8>j}=Մ̃qEJII HHNkZŔv맫$/1l`c 57O'uRyxx tfYeX$nOeMs!\{TiV4gv 2I4/c>&RK&J=ԼUypBA/Ljc! 7gD0x(#:R3y0e8<EgO$LTcs޴,:?j֬aUqZƲdJ`TKadgc>]gm#MXWQ@a%j3byҲ&[ٮp_\fU8zvMT F*0<cuܞJy3h`Mȱ\b?v@>8ʯSD;*mnmmyύo&(j(~%3LۀTf2 ͖Gt8n6H>dW9bH&||z f_MIi׆Y #[BI%FIHC.X㓟nZvL _VW~Oojs"~=dvIok Ibd'J*Z4`퐝q,@$[s>Y3U%޺OX.mg̑H'G\篵9=[١O.%F%sj4hix5ͩދ:`H9jܷ"hfM-Թd-J64cl;FvY7H̡dqm,5Z d &GqVӳI.CYJdEW%GYsPI@/c(KF[,0@r3s(e""D#8#oCp.Y84QI7aO,JI+S~gNk3,I`mçҊ*nݒ"*?JG " ۜާtQN[SXm%W&@8-u{Tzn,vraߞO\GJ([&~e++滐I!a$[NaD1G'󢊧3M܆mA#eRPH-@'j-r%Q&)1D@ H:zEKtCD{,q^ H{¬J##~g6Oz E J-Gm "XBGF#~UG+Ehݳ8t{E8#&T7Fcs`dMQMpL http://ns.adobe.com/xap/1.0/ OnePlus ONEPLUS A5010 72 72 pouces OnePlus5T-user 9 PKQ1.180716.001 1910081948 release-keys 2019:11:09 10:59:36 Centered Compression JPEG 72 72 pouces 1/1547 sec. f/1,7 Non défini 100 Version d'exif 2.2 2019:11:09 10:59:36 2019:11:09 10:59:36 Y Cb Cr - 10,60 EV (1/1546 sec.) 1,53 EV (f/1,7) 7,40 EV (578,68 cd/m²) Inconnu 4,1 mm 23 octets de données indéfinies 602695 602695 602695 FlashPix version 1.0 sRGB 3456 4608 Sonde de couleur à un capteur Photographié directement Exposition automatique Balance des blancs automatique 24 Standard N 48, 51, 16,3929 E 2, 18, 1,0441 Niveau de la mer 76,711 09:59:36,00 2019:11:09 R98 0100 C     C    G!1"AQaq2#B$Rb3C %r'4Sd9!1A"Qa2q#3B$R4Cbr ?y(LZ>I0*lG t0 =ꓱ'=4< 5ha>F>MUzz%i W8#ڴF0y~(Jў(%_Z m* փDg?څ 9Ё??H=> %f1ՠ =9C"G1ld 3B # ^AE< h lvhPQB+?M'`gj#(AlP!lL}* p2 l3fNw#@#B#$zжFGYD9]P/zhn8W(bB1a@ʍ# ٬\;5`hDKh3C@@2EK"`Ilap@H09#慢ArN{vQ8..gބnM> N00xp>#|i^oj h>B6$ߑDQ{fAChQPw2"kX"]-epVɍn7`}{Wq۳hwTxFAHtS*3@H'<(4%QBe3gڂl,K"R|ONq4 <@,q@{qh{f B`zgq\ cw҅Cރ T%>n T2(d6HZ)+ T6F~~!%S95B6Ɔ_ Pڡ22|U v>(A=,{C@$ޯ#ێ ` 3`U)#J o7 {j\?h`Ac xZ23{hX}CdT(63*Q*xGTjAoӞ#ȲtES?WӎzUI>&<߁d\8%\Hɡlg=%.߷҅=@ aP{ ?AB0oGZ2 /~3PNɡ,{GPX?ABI#Pn;sM>4َ?zsBa@5 "3{r{44öj0ȁn*{x(Qm%÷4Jj T p0 {aA8 (F0 P_1ٞqBPQnj:*e{+ դFqE=h,x2~Gzcƫܞh)@eJ'@{ ( ';Qe8B?١AF21Chp~#+CF3ޥ9jqڠQP>C`گ$=.9Rʌqkm9?歐6;%(⡫9کl@prHe٠RFR0a'z6׽*ީ0  Thy5vU J(ʎ09qZ!DC }([r;h͆IW#ȡ,?Y0FhEpqUjl{Ѓ `fZapFAxg"{sZ(?j6sB18oqT2A} FzeNhAȈ(e RhQ{8,[8知J(` |Te76s{ѝ?[+V\y.A1kW#Z)xI|\1g޾<ϔD65 g$RX>R7c sU"C 6yv0P B0*.I5l$1)f{ц sڠA< @chm sCH68d!F11 %؂~/(r{`Pq@rF e2ǽ(V')E6g4\84ؙAZ`אH;wK4- @?5h 9<[qKTq4_g$qZA!sD{q=[x8Cy?5hC 298dnRH/= B"X-CI#(`}zX}(lZҩa8]c뀏ާ{Ǹ3=^{uY|ؖ0@o( "Ҩ"0Py1czj#>pp>~sY6O!CL{sJsBPd{҉TGn0{)@sD U ڲh{~րN(e!=j0a,0qX -EP9aRXf }@"?ZSӞ+|FoٌsPw{c8mGs@2f =X\vdc( !$}k@g;у A9[ 栿p*X5Kv ƫ"i?DZ B~/wq 9>;աm)@'zY=*`8J yn ,QA8UR~I5rtsㄲ=VOXaaF],*dv3۽~wꚙ=mG^"<ʹÃݒicxW-?$WdBXPXh@r0 >~`6`_19#3*Y\ ֩Kjɾ1sڴq/q←(m1aER;RX9E @2~jPQQS_pJ+ N94OjV629|Pжc~~jm &e AA8' OH*ّ$ƀ[Bojx8<|PBX''Gx,0(bۚN8@O#Oj phKX x Жq{p{ƥ+xhA5!԰?Z`8O?5 &H` Q#=6MiWhH[27, czֻ*— Ơ˴m{[ɩ姍  %N8r;|4JϲQ[i)qk=[x ڂFF0Fcnva#<^ a*^‘_\"5sr˂m&~YZ&Oq]`µD$2x~( 0TKO?) 0yШeO fGڄ#@[r3 X!m#Peǰ橁N"ms Bك(QO{R@&^-8=_1=(6y*2jsF[Id~+ *$a,ކFG2[w Ar2ddqTP(Pcoj} 1PC1V c@`}σFڠvl$qT2IW88Ⅰ19?j<`s@ {TT$P1m=ћ%J/dRHFY7M KN+tHnD +6%ԒA?P`A< _zyNQXtrjzۤۨzQz4)DOA(0Fh TfjQ=)Eã34ҺK}G[.2ǔ`Zjqo!?r;Hc-<)*m r9ٴǪ?A<Իv (l@3rڌhRǏf8 TeCK-^~ ӻ?jՃ5L%N㟨ȱBرa0 о@BqBXH `U*d'۾(l~ŗ3ES .mST_ϥ@?0, PJ;U#?<:]oG #}o!@0a;koGNQO[t~mkwgk&nM!GpN9Kyp-Y\]3K:voБ_ZVPYv2+oEl֣ߡQ<T czm8sڔJ|T qP>8a|PsօP{g# ꒃf;PA?j,J?J b[g&!#4ڣJ79le =CBE8- ܚ xQݳǵC&9o<|԰2ʨت PAc#qPrsB\sWzc\$sY'NU?[G[57ItI،>HtXTߏ}YK>~A<g:bO}- af7%qľndv|QwmntO^_k_LCc\"!IX I!H03[g˗4bٌG?Egwu=s˵FҸ01Af(S':/!yD~(`P .{bVyz16B B4%W4(g#bބ"Eq?z jP{Tj1QDFIRp3QA, W)&^jȏ(chQj>Ʋq^X/]#bCkiq1H@Qٔn"?2R&?oU/(Cs#g5AM1YA-F (f^?\pkUG/;χ6]iAghM) q) A=Y_g1)뢟=|rE PFWiR6l`fdʏP\gaR$F=$c ^= @yh<աAPx϶ bCHEs(FU#6=kB.4%P`4#ޅB'1@%;4)[^uc2v6ɍdYeVa`āw1|MIȼ*.. :'Gz)VF%" xsmakȖO(\O_{)vˠ㿱v[^K-cUV}/KSӗئ\b~~b =ipHJ5b ҆FO @OFh0~jy(,{r~=TSC5Ib#WN[~Qh螠kΚִ7U׺v+TMVngK`$鼂!6լ;(=fފq]*Eqs`xiq q @iihP 8gJ-LTac xϽg.G#4گm>P`Sy_^>(m@w"6`(PРOb0;Ѓ#'CHr~ ڀ0;Q3v Pڠ09Y9szQ{>1B9(AZ Ȩi۸(VkABFMzG '4PqP 4W# q@жK ~{Z>9⡢L7n(F[>jXb ('<ЦV:6A!#:d– *2Gl}ں}狗 Vfk^ae e̷MfEL[ܻ~SY&?=jzt]qڜ]{q[$vȗ񇍕~:BoN owczYo_zCezOAPY r~(Tvj?"D`chP) n"e|2}ȢڂÁzKj# …hip=(p( njՠ ;SgJ{~j,`qQcЄ4S4#'U^3ިd\# +Ͽ G`քW!&.3 |P1քC*>hRAw?ކb|0ПNR[Ͷ[S䬱EbۚǏX$oͽjmkӽ Rh'W@ΑǫZډݧK= uL#T>[\ӌzO[# 0OtPhZFye$:uIl"7qrCe@7gq pקrcIG9 lF?_cwP L>~I6Є{PC[^q@@hj'QqDNT_{д-hAhϾ(@ <}qLA< mn>GжdBz2 3P;l[p1ڥ+VaSڨLj MSA TB4(P ~կHdwLxYeJ##Vx$3Fj[qљ(1ْEZ20yi 5}dt2~ c}*R[=\ǵVk^!hsJk:V7\$ Ͳ,0YJDm,eѳ/ ~c굓_K_48*xYu7E>zPck}5v&B7Y*M#-YslݎّEj:Pηmk6eЊ1j P¹2 =}.hccǏG)}`~{WT sY6"(Q**6vT3BXڂ( C>F[q@Gڀݱ\l߽,a@'>JDC=P橱?5g?)ܚP 9(`"Adq{qX!޹F%2r~ Ы[F=A GzJ# )BJ/A8J"Y,sߵR8:(Qc#Z5HI@?Z5e5(X*ڴQ`f~LޥH sPжfd~HL xwjŚ/JO4.m GktUFZ@r.˫xNۋIs;n˦M2]b B9VNM)ŎX 9T&~vzs]/Y-Uޭx{Liwx@?2 hP{jP@%${P31dq ֆ3ڄc =_44<~Ƅ U Z10Vf0 (J%д=sЕDO 4 h^001j F9{(r8[ D jހx\nFGj1 ir.ֵmzwMIN#q%7JsMke,?Eb_(r!EVo&_Qv$<0p,ƺ?f)GVw=Ժ.7!Q7^[$ sϕ\La/5’QN1i ݪmb=S! څDj\sR=gӀ`l!O{O(j>*ݫ%#&B'8#562fcg| Þjٖ*qP=(x5 LUbs4Ac'5YA?jFҋcA`W8ڴmP`B9e59 I#G&ޅ.}sHvd]B\L]@Lg'%fCDQ(K?roުz].t\2u)aRb2v_PsS;Gk7c̮2kZQ4kɧ,6zUEI~0P{㗸?,hI.RoWwfNq,`` *U"qBxd\vdc#BY&_ b X2kE(0\ (Gh+@`P!2jBO4 $3jQ`1B:Ti\q4Gn?MAxi4%㊞EW ~h08hAIcPAI=$sJT8 r{PCsV 95QG)HiDK(?5Lv"@'|԰=sO%aw(; T =0zfϚ18un)Ι^h/1zènֵZK{02 F3:$*GDc>~)M,IzOCKꮶ4/R8m) Y\l}RpgeaR឵q>"b귗GgqAޱ(QϲՍ9[㳪Nm/ W8]#}Xʂ{b"5h5(p*" [BzAVAs-_?jGEhxӃ%I Y֏'ǯJ_ `X2ßڿ$g=:HS|trCn>9+ &xfkcY0>t>}븼Xn+M.'_*dK>dg%Q4,`!u~*6( m(E >o X`dСŰ4K~hnT PǷfC 1ޅbֆCo?4(/b+'Ѐ@@3S6ڔTH ooAaU'&N+%<Oj c{wNs@R*Q "aU;?(TcXBN9 RIIBѹCgT 1t Pl6^hjb BtHlqCAǴ}D{P q@Y|7+=cV4>ˣe;Yp?zӭ/ߢ~p zXXj7ztdt*wzn9zQiY|{ѩxvİOZ[Pɒノ%trtsM%B}q!*J62(8&aq@@vaA>jF ̗>rrEcuFNuJk3t5&Gqw."k+of` V9*xx2KfE]zפz+c7nQMvkۥwF (gKl@þ;[ص{c)DTgW1fXcV<emدjY1n0Ȕ;GҝSykkO{b0$#'@y2:rHS[Gz5)몵##'^ Ͻ Cfym?zAjӜڅ$Tc"#޴N#5;(4a^3P fFhd| nqB"[q(hAs(/j+}TkyQTXi@m hV0OږJTFmm@8U91C( qjEF*Ŏxe[>j6kmnLJlb`H#\8zeGn.adL&CpGrgAqP2?l@ǵS6K4jtQ 4D9jm cB}3ӷix Oꗖ|Sܶ1\͸#gzr͆+ſo~i'>]6Ⱥ$W%Xf<ବ<ӄ ;A(FQXT\jI}˨uoOY[Y\J0EwɔQ$:Kmޡܱ\G4aڊ@ZZrLq\]h04r&Yw#N `_Uha'31ϲ*u-W-cmUDI@d*Es@!3@hJ '@h%RL@?Y7!.rEgɫ[ \w? >P7Ibޔ,Ԩl;P djIQf6v+UҀӢ(ڥ~¨@>QJ/{ӲFrԗk\sOkuk"1W]8*k;j2%}PAJ$B8`1K(mBlm憇 tGD=YdRU.E##>G=s_m?Esj_بL\}%cgjC*u51,rK.8ۂ=DO#Ws'ׅ<ƇVx1<P.z.m5tZ4OXu >$߶3^Nj\wW6J sԝl:WK"=^鄱efp0$ϔ7 C,dNjޗ1~>_Sj7%uQeţӱas9(+XQFwx>'ȆP!6OvKf> "'c,04(T{3,"0G_ KT*pCfS$9Oٜ{F{bgAA'(F2"*˜j"TO%([a߱2 eRr9l?Nkt.~9C# XmZD KsPRp) 1کPB ꑏo Q ayD4)Qзyr3 b!2( P.  # 9KhR.;T?zۜH^7ӺWN° KQQ.Q.dAB~Ҫ[w0s}^O+go'<ꮡӴnu .kN9\;HĸPw|dsn Yi2i)G]uTtׇWXiw}Cc'9H5T\"!f'i1F^cʳٓN k5K7DZAhb7ɿ{JWdݬH!\ ZDp)2|Lcmzç4H`ilY=33.rr8<c},ORɣ/z|ܚ1@' f{PER}Ch{ 8)-DT G+FE5m5=Т+@sڄހ ۚ9 f B *QD.~fPmF08㏭@=kQ6 BR[y€{Uۆ`D1^m,{r84=IB)ّZm QYjѥ-ѕ<n!PHqJqᜒ 89 {P4ހ z;>ZM(H+e--̬ <{>kG_~%'^"e6kJP#~BS;W8 <^zL]W V)>Mύ]-xu=̲ӖC-Q@qhKj@ʁ5`J];u5? 4 mV n#?)+ɏrdaI4NQJW|ޣ# e|KҺoJzB +UۂVZ{,Icz'êzǦ*l)ھz  ?/`sڂ-v49TW#1BP2{T(q@y@(n;܊1@sVaC@@ |P 23C#@~P"3ڄZ01K4 PBO96=" (eABB91)DN( d> b+Ky^P5+EqvyDAԣӦsJ KtLp3XhڏMjZޒQi$CAf8@* |R ˊKO&(2k׺,UOQү Dӵ21V@dF*PړSR\tG^=)Dꖒ}f "pAhE}>Sxt;3[kj9%DvvG@Lݯ_߁T}?gBHֲbb> eƣ'Q%h;)ᚥ%&6kax/9h6m<Џ[ J)R@@Ef5$5nN$דqۥv֭Q$ #g# ʪ2AԤvLrԹ$o__?R厒*R_u~o_3GNJYL:\ZQv %TX' A9C KI#|S_?Bѿ9藝3mt7LhZI=I[Tp6-F1pUJ>=hno.[Ťs,~w=zlI\/|ޣ]O?;/k /[SA4\$+IQ$gUށzTxM3JM~f. } Ƿ4>e8 ' (P'(AP ~[  AZ{84- fhABm, 4(y}ZcB ˠq(A'byFT2޲N څF"9g"K OqV@RȠ\3C(F=یPtq>d{p([ C$\i|ǂ8S y5@ڄMC^ :^Z2Dƺک{x'/gkI G9?mtIs"Gqh $E&,o7v }ܱ t wZXi_W+<ҭ;cs"*SHH#=FsO_&-{N?׺~q.'wrІ^6/߄+pROsk&rYiiyӗF+-@!$E#ҠUi5xv^M6싗ΡmiZKصͲMi(E<{ +'nH,,~O^o2gcT(FoP4%/,9J)Da"sڀa8?ZTҨLs@=Ad|ЁdД0ǽ,P0*]K+ARP $TdJ# Y m|ۏކ-Va#5h`/>^{ճ< PAd>#[yfEڂa8 X*YW$b:Y{ c@+TBAx慲PC e8"Օ:=l_(\o:15H=QT!Hm/cPKZ )#էEk!kq0^XY@O3n'2-O h媂PuJmG&W*O==Qx55bCS!9G#nv q]][7 j_*;]c,2<DŽmй=#Y1~zު\ɌrAE׷k.-y\y{K ~e? KjvȻ.7S1_62TX*HݒH0+GG-F(G&_&xMo$xNzt.溊J/J A)Pp iSRWoSrk.2k~\0H=yu @bn8(m@qSY Q!2ie+ڡ lh -if@/jy*{S(E@6r3@0__2Hh{1@i'@S fN> Xc\3 Y6H8le(J$ֆh{AX[20@Aj M Bڡ=QP >?@P~(h6sC! f+P ZYSH@^"~JѶ1mԖ]aPy}I<{wG+ g^h^{JS.5ۈM?KI|0.3&[vcCSO>ZMxJ*DA!B1P\ʜgҖGfqC=yv縭yd-,1P)١( (@UPP=+dTgTY%% NscnmmN+O|3X۽AOwd #㌓_A(}lonMaM6HYU2wM)G˰svO"#s5 =V=tn.5mrhv\e ig;ofaQD͜veߎV*ÒZ_]Uyvnc5[7^B~td.ЬIv!\_K%AtP~"kմǦNum}<BFP(Q *AOOÞz_3Ϟ:mTq3It^kѴxu3d$yal}^᝝&jrFXcS[mJLb?_{?Gz~ާ$>M&(Oڻt({;`1T1bϥ,cٚYh< CٌqPGC>*Y{qDDLD kvDBR5Rȸ5 ۓq@0Z^]PRQ,;vE"|VEABo #\(i $q$@0( {#B ^Pe1Bh[ O[8{8O[䚖  lg@2~-hjų歔TVlŋgJx?64vkQMvtD]R^JDBAeTQ_7fPtL/oBm P"HM $ sٲ{yO  .:sBdzf(fH$B¡v(a g85sK$[WeVˤzLWD鮙a"T}~U(z(N7۞I*[dЌ"ٶt/^zj+/[4~`C f52aT =% VM |suJuHȻ+43#RLqީ:P|VllilЂdUOj6P*(0R .9{j0T&5&;TeC * (0ղ8hH;֮6{VÞԳ4qK5CK2 e2j&F' `*f(e)bX$hO C؊Ĺ9 Zv xc(0$\wv#V.=O krmNI6UP>e|2>t/*r6âϬjCh\ieٜP gq OrGyeS=&>TM.%=jEb1r?vtRNӃ*SPi$]1}~k #R(&7 Ncla>[b.x8MbjR=Gδ:7RE4zV%սWk+ p޸IMgke3\<:k}+#]2doB[dQeY/!wmT3mK.LR> _鞊ϡ۴JN'o s_iR'o>״U+H>>Cٶ|PvH/H3@OڈPlZATPЌxhin~2([8Qm5lK*UC & Q4{3K sXL P} yhb>;Vl$k% . @a?jYPS{3f$ԳCTH%ޡX P4KTR c*(F<{U4/+l,>y@=2Pm6jY|T%^p=j-~&&n讛=)PT`2p' 7w眧jog,R v9;>aJ["01U7 Hoیּb+dzMW҅x~[tlvK=W}{eM|F; d;{`8  cxig^qwgR F53|? Ąq/v-=;ۓ_,ٕ~9t%Mu4) (3++R &"6%m^j'Za89}X'3dվパUȗ4oO}_ 7~`]d@@ϊi"4N > xǡ_z|6mswK6vFrq"gX_V8f^TtayZ 3509 wǽ[-N4DGҭ{{qCIX lCfnidj( cZ~,hJhP ߊ"?Q-f{U#*YPlA ڥXj a^h m CUA*X!RDQbC K Pۚm'AC ,ԲPxL4/+61-5,1VT;D6VKT UI}7ҺL3\^N[)cZ@CI58r?S[PUScu9iz5,סRpLcqt8RJ%z)ܥ%9mhTQDZGoj3C,ZJmIQ';UTs#bM(ݻ#to3[HzwWtwza,%m2bLlX6OltqO=mHZæzFTA5iSb ")']#9WIj!rX:{0kMQߡ2O+$pv^=x%G^x2G"}r,pGC&Sj qO=ۥ_[92$%5tQ;q/?K~w;Jy=RmAEK- oj%"I.{qR .GдYz(K0,Zн, Gf h6BPlT {U{>h]#₃ɩfjdzST^M{qPہR ln=j(y'X`1Tg5ET\c⥔9(?z\RDTTH3C [H-K) .V. UifFSYh[/MQ)dhzݜUT@ZYQۃJڇv=!s,a _7_Ӹ.sU7i~ޖCiFvE6 $WL;&uR>arw&[|R}u5BNGox)4dJ&' bgN\hb4 O:X-6xwfoDZ@#1`F=e;<%R|Itu%jz2/?=[ᶣj9=Pu DWi%$w.#1e,mKU "{/> u?Dxm1u9Rŝٕt;k,ژ$yJs$5=-Tuumemsmqn-,Rn2, F<07-Q<#Fk*!6 wo8DG]cp_Vl yq_M2,_T$k?\[O[8Pq% @+^ VM$qH*Y=Y[FF`=;AZR.-)jՒU#"cRN*6}id 6Rʶ({>lnhDm C V1P"<հ=AF/9٪5lPjaBP+j JY j͖J%@-$YQzAԲVl&JQ Y6Koҥ4GK#5whljɴF?FHxF[1Ω_1mPRCfA؜ W&Hr62>:M"YRp !j]l=iz:jE΋xsEt俞ma$s2dXݔ.F/%~8ܳs~U4t ZźZ9Yl,(̾epqR[u:NZzvisRT! R7;$M:Cg~]V fD@6հX4,{jQaK(sYg9K-1|6{+WTs)+r6@ナEj6q[a0Vԑǵ%*ٖ//X-TJNhآA~S(})dA1SqRMcmvRxtl0,m-Y9S5S%ЄB۞Ux $h({V|^g @Y5Z$:)-fVl?/Y."),mXibFXA$bs/c%ї]cZ~Lм!%4ݽd~oQ,~ (#K6L~-SM%F! y*udܗ)Ot{23l2La~o'V([tn,5;&)c9&!Pad+(ol=/I)$G8b>~Ω˥ʶ(6VXsRf@ٚ([1K%K=JYEK=ޡR TC l=PYe (h{4%歒c,PRT?/i)ږJX ޖj`Y*P*ٓږ(6~L= H)e$ ٩e"Č>+[ڄmod؄֤vdxDMȭ3bm]>n1vJ`@&f7,m$#jn&i0,P{rk-Hc [-/ J1!*?_.\3&dc OR-l̸b"m9W9dׅז}qRQ~Sm֧p3@t-Ky<8:nx +R]]-+P0,=Y8Al#rAnF015z0 +S;N⾶ύ֓D㮗WI& |Qr^`ρ>RGɫ7}cgեh5̱˓ŶC2 K;wo6'՟w5"nԮmL]f ]krvk#yF҂25ן֯V;- 3j=?qGrG?fyڒg4f.2q~TjdyUSCV2,P5 9SqCnpjYF"V?(6N~j&\pjn+{܅01jn^*ءWq+MŠ7 vbR!T6i!|M ihHRAd,c3Sp}#eF"=ٝY(\Բ [% G͖, %]H6ԲRidYhj".Nu]]tU- £*<=@(FI87ɓq!g3Nk[ia[/K- s"3ߖ C Gr{,ҭ^ .dfU%^h/#ڭpnDp+=@.syZsytC)4ϜyN1~+ɒUQ 7ovz3wޘ%0ߐ&^pD@c@9 5\M>yZZռ, IC)A_ey,/lβu~fNOjhb_Ïyb";qyrG ?{:_6O.<Z=;[9e#C(uk6pQw{w>56 TΓԽt2IG3H'nUzx8w6zr/N賤]Զ@'4k 76ps\Nx1gp·sT,E՟t߳gʪK\o39>O9-8Ur,%.7)bK8:0=Grr+c-w#VYu'T>gKaٵ:r6֣`< bNz1t)Aʺeu\)UY"8gS;AF=vWfx/NʺdHQ}:/-bE(k&G#2;_%o#<\ϦzgB=7zNNai o4ODŽ6l)chlTî^KǪn-mwGc/}|oSGz$pެ ֛Ju b{f^'+T (QnX/sA<:8 M.RB-{Dm>PX7 Wi<7(oIMV[^haio5d՗BI!v#XVfX~Ll$;v|{ W'lgi6z j8?s]EZcڴ..@,⯸OeAS/ɮ޲kI{jؠ*[*@R0,Uo4{im Jne-J('(=yʱȫl"P,ˍe`ZMy%2xӶ1y~.d+2aCq i]huFRK5+{K{JwA\N䑎 pԱÚ9cO?zP([/^6X7ѕO$ɀ0/#XS' }}DO7v^|E~h6ߥTD,P*hepjY(_,PFZ1dhxRٚ̊Y~_ҭhyu,PJXK5\ڶAR,m!10$[I1 2~ &F7$ߎu ek8=-bC%ZOih1n~: m-a` -ʕĞI`k޼,'=L$t؎,w2N)I[^FVdY v0;C9r+(%cc]΃mi_K4c,'t\q"tۧnֺ hZ=emIm.#!ϥa'8;F yvvﳃ>ܸ:}H:=?Ve9~ω/њk^ SFe2P2c8q}Wc-$RޫgGHŽ/owu1!܂rrn109Dն]SAsouiP]Ѫ$]i#QwfPXdsyS[N/yu}f'":<FA_AZE9 ƨtW[^"iRj}=GX$qvoc+:4njqldܟjÙȱzlQj"fdNZVN73,=QRlhztp؄y"1AV05l|Vw)l U6Mk 6 4( TE6 vTJ ^NTvw1ܻ"\FwW8I,urfljn9tJ-IݒUʗcp`98P EyYNkk=~9^qyDok?;suy%%PűmTdAbޢC ďy7s#R[JYGi4qK$knK,ѬMʍTq'=GAk$gߎm^+ງ*,op8dzG Y)k86w}v DE+>$nH,Pd W}pxs)G|ǡtޑm-aë}*ʩa #x i)i~\˾S^X٤bynTHF G>?RRM0iV}{|R$&Frlm3]0M_w"jv-5hն`z5%h :3[,H-, ',m%mLqhLZ`Җdߥ, >K4jdRi *d8sޮ6T{)!NIT$qeɲ ="KW6S^M%Ź0 ۖp_#-U-ڽ2|Og/PiډnTƆgbA#8 2 xZ4w3 Q ]QO%M%ܬ" @ajq:҄jOY[H尰h?2@uZn4Zq'`㝹n"2RܣT\\\vhVطfg([~i,Df)YDf"VJءm,mveH8&Z_Vm,xbaVH[)e8UuVM="E! m9P?b)KO#I.exgkw*1dX FuNN<)9c{Fqq㚟Q:Gىӭl2q  rIGQɖW||{Am>qVʷws3Sd 1pI'sm磰/A}7M[ڲ"!KOJaXsUM6ڶyKԚ pI&֑1F`Fz-(7+YnRhhvn[%]Z΋$~~]qF1޾S~ţol|+2EiwY/ӹ1Ms|ۋ1 'y2ً&MӢP!4HLq_jGP]*-G1dZ{VxKKۊXVhJ%RBRPcڡhx⥖,P;2L8*&(BAسACmKkzTM Fhs ]W<*@X~>ٮZ}r> 垘"!s#1S2)%=G#ff^{'Vw)# 5ď(l**>{e"]IwykK5F.24c|v9^ŵ$gm^ .kBUnn4ۣ0fb,;r<>?Vp.,"[@+DVʩ;>:4jgUyp(#Mǻ`!p9${C6Z9>S:>?JkWV4\(1~a6P9K`'8=z9 h.Z/h*Cb;nMYNeئ[b?jn&< o bf@9AjL[Bh2qKO_˃Vwܟzo٬ 45($c+'g{Gy|Hr y4{c3]Awe,A.XySʏg׹f~Zơi{.1ZwJ˲ʪ`>NPS8&mtjUN-p 9R7&$F'tgUxz5t= =ͳYZݬr2ЈF@@Xa^'ߒ3uqXjuޫӶ\kZum, Ψ%$+c 1EFVG&_]OWx/QTgȢYy`drȯƱ8E;n,tn7}'U$+"-a&ʕdzp$m$hU<,C4!ѕ*_fɵ[;߆cm]'i۠oLȲ)nVOISlA}38 |=N;r-P*I.qR\9dc5lPcxbm,=ZM,PlҖ6Աa9ZL9cmʥhRѭAClj WV@ސpő6'f1uH#$iK?NN30#$kr_#.Ʊya! 5 8C]I;?v,7urn;g6l8:qsvf"eHèR 9ISklS$**Wݛ[+ F6kAeHL@DEYpgn7n7.~fZmoKy" Y#"bJ)/*ASʱL㲻GӉFtz\%ƒYZ,CzF8`y6}:te>v[WӯU\Oʁ#i(,woE]--苩,uX1*F+wmgΎ RFinP5BA&2 n#ܛpE%6bů6_^lG'I'7pB>ҥsΨ Y$8Y}ryrsm*y6Ox M,nfUOo8U;$lWiVHKO;Gн1'KuDmtU ' ӣziÒڻuH5N|U\+VBA7i/-G,QO#7EMA>,,C֝aoGֶ.ZO]# |% P1pR@7fyZt}npce'Ole:]>cb!KTXgg/tk@:|;keȒ]]?RiP\y9؈ a AFzi`V`r4{YZʾ\b[IoOmrJ m]Yȴ..5;(djK"n# YVDG$]g%jH0n?(gI1,kIvUϳLcQ>u!0h߮i$3Vpx;bY&+WnM]2-J8{iqobCǘ2 4R4e " _K)ku/VV雮.Nڴ6w8LDC;CB5M@r'5%giŵWG=SjGweyވx.[k)rq+ܮT(5ݣU]OOCYܛn]'dMMMc`10m̛ٻ.NIY£NΡ=UJNC={G3/X3d X#S^STD-WP{ - dFaNLꋅ@Tm ]Y2)'\ˉwD`NAdS|L'vb,X$,hDOzf@HRʀ5,@K'c)``hcBY-,VB}b/Tae΢di0$3ܐ*M >-Mֿ-zwS3.8ի<߬mcEmseworf !CV}(Diaj;9!+EO{9.7$[ l]wPǹ<Ϸ$u'ZR[Ky웢nw m9`0lAN*Igs|Y\ &8d.VaD9]z4WQUTk512K<$k"Bv(@\W #ղҮQ;@ta%H8>};яv~IcVhPH38$R<(?;.}ۛH}w\̭q$42O$q+FqLj*n#?5k6z<әv #~AlߵbQMno4Jׂy66 }1cvϧou>W*Qm&>fqjVn Dqʍ b1*TqJi3 ;AkXW1+`S O33q{\39%]ɐ յNqVA9b̾pdr :ijP 8nsUbJ<4W:~y|XlV[#W˲힞\#?a$Rk+"3qj W*z ;׏O\KܞiǣR˯mn4&-CVQG𯰪G"A1M#4r˯$=k/jO_Kko#^iP[bI@2ayn[S8qs`:gMuGjھ]x}#~m 5K 7G+"eA/+PAOrXQ=[v=&-+MqwpƉe5,@oLMɪTrO6gSϠt:<it٧=UP̊NFBij9٩fǁԙ5ŧN\AiiƧ 2mI Dt/PS>3ۏeH?PƺVkRI{xGf^bTI1$F$rK~dq?RKT3]w/{P- ;Y=.`m=/JH%IcŶڮld3/p6 HνCGCz+m4??/$r?R!7D`"؄ ǂy8#-.l\'n:8Lk5tOSnvD]q Dyi¨6kFgMtߓtRc:_V\jwլ,׬)ǖd|4"f57P19MQ!wb065?(fN/%09G$\~ǂKWooձ+`k,#l6k min2{c8Mqg.+QŶ2YLfC0Lco5(>U*T}VƗ5FŽag7f³ F/bqz(bRFfM$$' pqt:N΍c[[egumDaF—1I$919]%VrR4Wۀ\O#]4_'GhI ܱp-?`$~?3ڽTέzX$ ۈTt4KmOl󉕕d $2B~k)`<{#-4o`63`3. arKR7aXlFRq5{OOe.4ks@P+(Pʌ^T~w#JL:8C@HZ?Z*8@Y {X59G!u y::I&ݽЏ`/l`Cpv96},XB]>Eӿ7YYP!l/A2MqJNrG;;_Һfk6Hs}uS ';ڿ:NѽtF1zj[}$\H&d-R0To% s 9gYmtzj+OZ' ߨNvXⱔi*b3XWs&~#U|'}e:im,2OKx|AoX(Wq+b|M^LoFâ|Fӝ-kvz.bNlSD2yr$Ǽ+HJRvt?RXuM=Kְj6b;m%F -<jƛ:W3}}[})I7ʤ% 9%9co. Ȓn' *o%O Kq#ګi"onp"vi4p69S;K V~*& R5prMQS|xYo>}bBW(tlD^Esp8=);䇓 {1PTxIw3Jy5huwe򠓴ɖWgj꛽f+J;SfbUYvĒ &0rvm+eE3:k@l ^B7w?\T,qcfid(2iv+n݌ QQ$fӧgӵּg"Z `i,)%8/zgK,w-}c׏j \H.с# AzڌMNcqX_\v`"hR**28 pN95+vǪ,:nj̚Σ+βyy!qpamh$6ѻoMN{ڊJ#E 8}9;G`;^ m2Vie'q͆Fp<8. 0:.Ri34R/X۝.Uye"FX#m_3yV*JX5)K7FU^ B+*wx7~X&w)ʀdaGw2ٽ-ƫ 7L"o)[tg#xq.hS\"Ic1E< yPq䓟_D:$/ɶD"b  FG+ZJ&5wj$+wyqR8?\qf5ч1R]]\18/u~L Rc.Ө{2ʅA9 8xexLFIM6Z5KXoo.đ ,|d?`룋$ŝ$e>LTy5{E*Tp6onzӤnVPHB5zcIюK3Ie $[ (Ӿ+sެbl4tXP2y9+G,9P,~ѭkMR(D-<@_,*%4Vg'ޗЮ5ޮH {Kp]JMΧq!n Lĭ:O=twAׂ-t%!w򘬬/zc+M&LnG[85:G6ɆIFq5o5͝'$R r*X Y(]>5l>8!(kӽQr˯hV2r\F6%68<N|x>F뎇9eyz%U(d{_ *mWan޹.~kovj[wk oHX +α/%޻T+bmKc ]A;X52XGs.%~r~4) n"ӯCpIC̃AYRs=[ni֝:OQ0^XErIoy:g +rp!1xC:wQ6z K0pࣔު8W>. .K1jia4WER& CU,3ܖ=ǭS\&t36zeyVQGou8>Y' 0+zq ;yJ͕&h8Bҍa$`ɇu،d 8|taON}YDHqfY6o~[Fdn).t$dnrN2WۃWrL?<xiyWX=K8y.Gg,A'9 uCǧaKoo푽qX)I7^PGݟmۧ4F {QЭe3*7 'O' ۩ޮ2MfzvdHQ,?'IIsZB.oz漄1H $|@qz8uO T~R:% VihvK{9 r4-6+,enR<'jejA8T(ѧ{.&*Y`L3,lXΉmXYØ"WIvyLYS[q!mN>@nxQu!=c\] )CX*$3ɺR;>I( օ῅1xI}MNKu=ODD\ <)\80ۘFwdR3˹}j>!@~'ݖ}3Rag7²m#VYcO =x'&нLd%m1d6K)ۘz+\g5kZޛvgWZi7}]5[%Ics9$sG7Q |6vzfk-P I8)aHbwz v,G3#Q?zBk"uxom0`*z*K :M$q*mLǑ97O"'G찗!}}6t9uQ@;Af2>~Ǎy)6:u^cL>n:Y 6 0r8q?R'OKMnj-w,}͝yDj#co0(TG`8}4u o/5漶#kYG~U[ 0< U+=ߘyGEL\B۔?

?#wr3v]AC<2.n'P$}yn`^ӂN?OO+ywk$́bevNs TYo@)y`VQw%enӺlhS(Va/=ޤt$zq95m {jr Us6F18bqgDmlH Mg8r?g\z'9*9F8#=\2͕nLhB8e?zϔ<:so:r.X<gm,GrTlQQ+F@dgO' 3md J0Hޱ#qc*N䆐H켮| {~_,Kq[PO6OuӤiuk_2=ϫyϿ߱ItqiWFeG.ItE/h6YdvI}Og*vcN\8=~kKe#DKx,TH]c+ ޒ;~xj2)_nK.e/' J|m$Ѫ(EFp{~k90,&0[%$zʖQ eϺk?=dol<0G|x`qMOA<GVbeIa@2}ן9M <}Lr:/~=j]@IR;{2kr2PKMrΏ>Uiu=6vtpAө !pUbv 1֔b~N<3MZtnԮgA(m ZsKPXjdIi7.ϱ? 7^j_ǫjw[H1@ļp?yG`=.JgG3~Q2h9;{~%2̻&< n^RՃrD?[ImsFo6jN~ sFok;R=:x5}ij.p,U'8`xar\^|)NivZ7k9Bې3;rci;,_}o,K9iYΜzanJwWVz>ǥZ: FImfP3k$)"?Owf7,J 9:=ƣX)[.|ܽ.Ѵ(q+껾O?-+LJTb "}X9'4zTwkF2F%lDzIVp?> Com:zX.tY! 4ʅ '!JǕgGAuxO6k!t,JȊ|нE[cmF.ܠ ɮ)s]O,8Tdr$Vڼ \yȢ#*e;nʒAGz ^#l=D W8 r;y8"8(#MqGՄѨGbhm'*O`r;FB֛UXnHW,싴39RX`sqώ_R6:FO3̆8<ƍ9aO*89v}Kx+ i&$3:=u 僮L} =FIĆ1 MWpN\B. 03H<,ɫL; y|e;A 9PInHuS6IC hiVEc0B1#nHOWe#ÊSnG2H}G[Y v6Hb }=LHrwr8f$,sBa/ucp;>_ⱹ9$elՈ5HF#aN;N>09i*2ѤcT q\q3;ck-PLEg2QOF[#=x|\/$Z,DۘO9߷lĥj-2,2,2x` @9+ZI#4RH_ьrۀGǷΎB3\sej37zno՘5. Y_4D)}>M35.7rGzm22R6y"9>ă_)MbU=ٚ)5?r$\qB{bWfxിkXٔ#Y6؝0{䓀ùɮ$qTkdKʱA4dc.ܜ),%S)R8ϊAd 1{9rs_MuNgxv>EđLH|I'fcFrº}x5_3 k:èPH}92Ž<Yd'zSޓa}X3ƉH}! tK$?e"% aY􅦯M[A( C762wR_{`^ҵ++Jשd[ !pM5mu}O׺&]uk IJy*EXȀ$T1' oŨ4~=P%i-eBYusSgѯRRXǽL 6`s93^[;υ~Ůt4SHT TeR8Ϙگ1j'&Hͥuc܎#5k*v4- t{Z6+O$*?Nq9%sݭOvwMYM]a<&r PG0?œOly.ϑotͯQ%k^6Q4I$^L}xf|Ccb<p@mLj$ޏ#Plr) y km0 Rx;HW7aiZE,.ȱd{xK c_8!?SA} wuy$Y'RRgvX X3féJ.cSI%@#յ'(Pr$5)B{u'>1E'?)8ҹdʺ6F)䱳3+[G6͜laH鑻6?fY ۈBdm=FO{*8=zKcv$ݲ = vGzhn"[98$1\ 0صW6c֤=3 <&VK;8H⻚+ym&ҬP>X'yxAm1@Hom6-',gprN 5G$]䴇[ɁpK"F [s""0 [>{W5ga; !mKX!?VFR@^ Jva.MWGH-kk⛉A HC!$`6cۮ>Ȓ-#G:< "Mce{n-bV݄(Q)Kq]|ڜkoE[@nMӸY&LLHfe,ƹ5J<4w]\i %&YRr|ຂa0l{įn_t/cNy^C,^YfHS r͎8鬍cGVYxjv%ޫҳAu%:m,:ڻه;-k%Q WκFx}Һӄv۶?"˷#؏`1߆Ԛ>w=ja;-"ktE$aU>_ƶA<@;}Jץu KHxQ;( p 3\َ|o ^u.>q<]Ć5 %sI,M4HWJ[d2W(C 0D&z= xp)E}?BI)BonNIds߷ji>{qwH#T};;c)B٬1\~ 6 2y]* g#81nΞv__W v 2̧ˇyBnT dWB#֞ɔ[Xuͷ!@ݸ$CY6pO%Ѫo7ҦYclYdq9?Ojᳪ:.ѵY fW |=Bw$eE)+嘵Hʕd\co{+VYFMy+Z2I8@$8<| V5Y,R_5 u }-ĆC79<0ǫ#>d4"C\z 1=;@/"K"ct CH#s=\IE7[ s +)F,NT{rC9'Gk|Z4|@/=w󁃟(sY8ԾGZ5 s;>5Ls&btjFH(HGc׭Ҋ7S)?P e\XHF JKuj[4ȒoYcU y#cTG9'ѼMs=.s|m"Ul ;drޞiNh}%֩uMq kpec*}f,ؓGl_t1N\9yg#kP (۸pbA*@3cty'Ǟ_2EGl?I $,f;{=2UΫgۢFy[3_?YKߵ Ծb. Pyo%x7/]KWwy-ސu[͡Ck ;"!NWq9h^?+BuáZݽڅPfb(gvnEsF,IO<Oo%EK1,D1M2Hs$8kk}Dt΋X Y,ǛR8ԦP` 3)8>7_f:f6 ylcih"{iw#D/`2W9!JBtΕF>ZmZ# +|ͯ#'g>KueSLGMKtXE8Dؒ6E5+:`l$nǟq5Շܥ7.>84տ:}'y<<6%e׮ܰ ǹHrQd60*ЃwX`3] j携(&ZE ]AkSP`67`qF66IYMq@ ɜFq _O@WI䢹+ڷ^YhKąrh2PW8-]ig<>MwB\k]OhYAsv,Ѽ260•9,J|W>M yls &34\F5'nK0}9ͬn#ӄgky,P]34<*NҾWp\uٯԺLK{r Ѫ'И *ĜpF/kڗq,ʸ*Wv}IdkKTb*YXI;wUGM%_CֿcҚN?m#6!rp@ $GiM%(C="-oTFq *UsZZ<]b9]ZPZK g{Zga<'> h D fW`~'89ȓG4%;zOk#IXʖF(w[2{ kg#Y9t-$'舳@veaH$['FwΣ~{s9X6TXeWhP}$ *E٣ѵ U:țex3!_!#w%(F-¥-m|LKoE!I-0): u,}EV\z糋\PUى3*̺Fj7zr_hikGǵخ\+\sKp2];֚qBK;K淸fpX&/H# 6]#%7JM֭ooahMk.gjE 1C2gsm%I-Mm߻Y$fX RC pH*H]j`Y'Omg?m뎾;}ZuΠnŶK(k9oqԃTLRvYO\Ե:hW-2G4,1=53.Lnt_i-o:o:W/)-KKh*nZ5` CFryN{9\zR$)s*U@0Mq}*JGϹ3_u]Cuݤy$}'<)n _TQ\D*^5 6=YU \xqId͚Q_뎄4Qoo%/^xotU[lpOxm2Dl@gӜMQ/ Tz}Z̈́]jVU화 s`G$72eSB-쭵Mo !lHXjαm@8s\rDx_[%[hK4kv`rHrHʉzᵳU_}:ܒIuus<ʢiZjepvInQRR-[uGE%3ǿvF8gN^X v j\XvƏs>S;I/ v98],+i#:gi',32`5Ġ5f-ӼcsH8y8\3KlFfJ-F4O\3l Le r2?X9J;l'(FGU\kחN5nGWT8 vvj.*)7'# ]M*ϱ"nfFtJZ;If8>Ͼ)|uhT9Q$Ҹ2RTrESȚA*W}\X3Xу躆cij#*ͼ$Jy5#\#"Ǥ749]Iqr2A*p=0Ez|ot^ 4v~z5pd&/Y9Q/$㖲> }[c'4l 'Nq!Hb ['rix6s-ɪiYj6h0)qc`y  HHVj٦K>8l5!bGѣ$VnUibɒA> rNeSV#%,r_t8'W:U?<wC,@ϫ#QpaОm#6K-/ rmr"rv(s=CRpQ6NCTV{[n$&,H`G{ggZ:$!O F.즹{iXۤO es+I wۅA_eKT醘ؽ#܂%m~81ʵXGdWm'.EՙeYZ B˹YrO'aIrK^ڊ*E^Z6g ;3` 䓂p>k9':tqfPWgZ{rV1X+I]T#WS9&m@2q%Y"3lP"we?G䃓4m\f^֞hu|3)># +YY&i$K}CQK`[42r r1klNކO&Jli?NtuFu+I4E!]Eknv8?o{N)tV4(m-1$,fI^4?8gn a棫:[K66нMoXhO"rc, 28{TWŞjIL.4jbة89\>qF2ks+G/`UzF[;y#.%#~A]NN.l:RE<#R0rѐq??ɍv'讥ִmJK VU(Q*A܌N1ƓTmOiүwտt@ZKR n3,!J78v'+Qs~٬TE]|<髍R]&1Y0yhO$ mE5~#SyqXy|v; BQd'5Rwq M&t_MoYOPFpY\ȍKw 68e϶E<炕Ҟ/jQj5yqI`}.1,v26<Uw򤒮6;GQu.?NغoNL8H"p Hq!c6輰 vqjQJec\g<;D/%u[+Mctf2a*cnd͏"GT2x~ zDѺ-6WG3A,PA~Q!?}Er9l7"Gҝ=ڛ&[WYHy@$0G] <9ju=[kH0ŗhS'<|G__DoWngR4\o 9џr $PgkVRHIs[tM˫%51*n0Uӑ8'CIq3Hj]=ӌ\T$0ŏ5לr?xe^=P.aٙ F~>MkM#SE'ミKOKUk%p"y 9Ni>#ZKiD7=_5jN[HC@2C$Ɠ~,m'I {55W򗱏_V]aDpi9賟^Ne+/~; ^-eno o\0ɴ(Q,%U|LXjpeg-decoder-0.1.22/benches/tower_grayscale.jpg010064400017500001750000001517561367174505500176440ustar 00000000000000JFIFHHExifMM* (1 2ï%OnePlusONEPLUS A5010HHGIMP 2.8.222019:11:26 22:37:44.6"'d0220>R fn v  ~|0100 d2019:11:09 10:59:362019:11:09 10:59:36)cddMM*3AD602695602695602695R980100N:ERjr 03Y'('+ ;$2019:11:09(HHJFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?.)إ{3i3bq?bQm"=#@Hv\umXXO-٢e=iLFړmh 6Ѷ6F(=bhF*LQ@G)h+Fړh(Rm@K~)v)qOh&(@ I5/٤lm ^Gwe+۴p[9B/ aGҸ1U ;#:rlv)vK8mmI1@ &*LQbF((I1@ъm2-T K\S3P!hRJ1&6{iqOK(KDeV :F=D\XI_˙C6qRL)cp j6?Mc?+|cn<\S ZBv$.)^ F)&( #F*LQb#F*LQG6Ԙ(1E 6ԘL.G]if1RbQP{iqODh8'5g\л=tl k*t[Zp[ 1`=3:{W=ռxK,w`/"ַ21 zד~kRa4kGFړm.`G&6x&6[ivԛh4 KS1h`@\`Z].]\vvѶ"KK㸠:r*McHd[hS=@"3*3nA=)o% 22R>\gi_[M*;Gd.FRϲ8Į! N8y5nh|[)6Ԉ3%(끚]SwgQZmmmKmY;hSmmC66;hSmm !KFm6(i@6c6ԛihXf;QFEhSNo*]GU}>|<_,)lszϖ.LAJ(/-K&>ߔ0}^usw}  :S ؂E<Ѱ!fϕӶjZD W;u$@Szsx^޹+ruV^qߓSwN4-ԌeB=?ukTSO"Fʛmk{"(Slm leOe mM!KK;hQE{iBXˬyH҅|F9m;m?e..&KWvUMRk{=2yxNZ[Eekm~ltc^l=;GuqZV G8f'TV~l2@w@ݞQ"9c_1]`T).+KS4lttu2Qa5 wο-E 9.]r2p^k\{Ick+Ag`I3^pr\NLl}.;iv(])R"1nʟimAeOs)6=G0rmE4y)vW%ڝ=mg>+b5V/*Mt&KR-*Ph!6T)vQkץ"Ŷ.mS Ej:e)c7H. 9=*丼I#x7 7?{?1rb*s{Rq\|1 \KFUH1¶5<:116nm;ȑH,r?^NDCqV* &u(SrG=3Kp4N@8>j}=Մ̃qEJII HHNkZŔv맫$/1l`c 57O'uRyxx tfYeX$nOeMs!\{TiV4gv 2I4/c>&RK&J=ԼUypBA/Ljc! 7gD0x(#:R3y0e8<EgO$LTcs޴,:?j֬aUqZƲdJ`TKadgc>]gm#MXWQ@a%j3byҲ&[ٮp_\fU8zvMT F*0<cuܞJy3h`Mȱ\b?v@>8ʯSD;*mnmmyύo&(j(~%3LۀTf2 ͖Gt8n6H>dW9bH&||z f_MIi׆Y #[BI%FIHC.X㓟nZvL _VW~Oojs"~=dvIok Ibd'J*Z4`퐝q,@$[s>Y3U%޺OX.mg̑H'G\篵9=[١O.%F%sj4hix5ͩދ:`H9jܷ"hfM-Թd-J64cl;FvY7H̡dqm,5Z d &GqVӳI.CYJdEW%GYsPI@/c(KF[,0@r3s(e""D#8#oCp.Y84QI7aO,JI+S~gNk3,I`mçҊ*nݒ"*?JG " ۜާtQN[SXm%W&@8-u{Tzn,vraߞO\GJ([&~e++滐I!a$[NaD1G'󢊧3M܆mA#eRPH-@'j-r%Q&)1D@ H:zEKtCD{,q^ H{¬J##~g6Oz E J-Gm "XBGF#~UG+Ehݳ8t{E8#&T7Fcs`dMQMpL http://ns.adobe.com/xap/1.0/ OnePlus ONEPLUS A5010 72 72 pouces OnePlus5T-user 9 PKQ1.180716.001 1910081948 release-keys 2019:11:09 10:59:36 Centered Compression JPEG 72 72 pouces 1/1547 sec. f/1,7 Non défini 100 Version d'exif 2.2 2019:11:09 10:59:36 2019:11:09 10:59:36 Y Cb Cr - 10,60 EV (1/1546 sec.) 1,53 EV (f/1,7) 7,40 EV (578,68 cd/m²) Inconnu 4,1 mm 23 octets de données indéfinies 602695 602695 602695 FlashPix version 1.0 sRGB 3456 4608 Sonde de couleur à un capteur Photographié directement Exposition automatique Balance des blancs automatique 24 Standard N 48, 51, 16,3929 E 2, 18, 1,0441 Niveau de la mer 76,711 09:59:36,00 2019:11:09 R98 0100 C       G!1"AQaq2#BR$b 3Cr%'4Sd?9g#S1TF;L'MS>(>j3~1H#ښy~*FN*~m|QZAz3MWi?ZqJJ8&?z\XA@N1`` hEE0^A< AN2)xS Jj2>~84^wGJAdS g4Rf>0(ir)ލ骜cOnɤS Ahێ>jq/F~*>ԀF29V4r sOiw4mqcO֨(QTqBL'-ݩ~U⤌F}' > 8i^ojcvcc.;F{fgBd7g\-Rܲ7. 1m9lj2 qH&OmȠaۏ^1H.} ɡTgU2F8Ƕ~j1;$l{MN8Q$cj9٩)?O3߁I8N>{7)23@yb{wMq^Pp>k(ޚW+i8zs5]aOqK˱xRqR3IA ?r`Rwٸ=Pia,s@MvFsޅPޑLgs &R' PmſNLo"wNިd_N92A r1)9jp 5]o,9 =%_VE^{%SqOa'(oGޚ4"p3M=)~6TGqMW;ɦQ4cހ>Ԋ|cւsv@36aۚ=zހ=MTAAPp*OfyN6?= `V>F̓;=R-je0{ n qj Q )U*yTq%GNW"\c89 hA} 2NSیR1 fvc;S!T>hrJQ 9ǵ%( ٦H  2?Fܞ9^􂂹ޚ?3q|T$3ނzEG^خ HATOx'*r#cxUړ.3h ګ` ֘\s(S )0>~m'<ޘC[1rP_nƀ#=p4D}I99MScⲖ}w}њohmZisα6Hk. *rp)HS!\{x' i'BxW$ cU{9@L{Tq*BccApc@^@MW'7j 0L#$dgMW= &Qtx䑌vPSO$0&y**>#pI^8ޅB ۚkێU +ϱFG'1P^{0~=궖$e ޫo#KcgޒTEpjvKy#b7q7 9HP(@yޚ.Ͻ6Uc1n(Ojj}'4*zr1Mc#ޤ.0{x8Җ0{SS%B{w;hqex]߿-ލɧ;ޤ.~1A\;|RٌsWOi϶1ڍqKpހ2y|r* '@RҘQ‼S X4B},)8F=i֘wQ'>l@^~۽^q؊{sis8Yyp*u:.%:b썝܂άG`#=}CÃݒhۂ}Ƞ|vOnx<^ &l jXv4c(?Z2$`gIW)mi<mS-; \;88#8WHۂWbFp=k'Mu$C~:V\܈WtmJ"$~xkM{q=c{њS`CQXJc4dGW} tQ.ci=O(](e(i%8CWMƙc3S-RaNWԀ}5 4l3_zqѷ‼ wvAi*p7E5SڤQ"Ȧ)jdgۊ[qdMQ\1AL?,c*qX$9I@8P뢨Gڤ.Q#P3WcS gIcB|ph?<MTS1 >*)r{})` 2ݑڙQ#2?GjM8dҶmOƿ;tMSC_>H¤h+D7A(WT`~97/XAmHrI?^. uw|ɷr8\d1prH0'ckiN-:<]\#k08.ÂFEesZj<\wT<⍹D`{w5R0>@>撏|QsւSQlc8~(vM G9&xPdR x S*&`j[wvQ^?M{;bb8^yAL"\}f0J008ic<d sL&N1Kg=4(wUIW#⍸8jsL/n9]dl~9!ASva蚎KssmOʤlFLW:ج*Ioo@< g:ͺbO}- af7%qľndvs?'ÿ /!S}B. L ,s$dy+./w^}̞q#. J+{UީWj`c98yz03)HG=f"wg@zaHc@{Oo cH08qA?1B3s gڧWcA9IHxF8[=hhhۑ IPO)O֨.{Ԫ?Z;=#4L 聒p; **sL( s`s{Q1WF{F01KfA8 8ڞ̏=VÜP;mi0=P>0oaj{[u f].uT91ܬ"L"_,8CҚF'DDdQat/arPM0; R՗AǨXgaxn-TN%-cUVmq?\ VIW5 ?Z@9 P`s@L{s[=~{S mސ_SU)Q]00~֍cLwSOh$P~i?>fw gSO{?z]Z6=" P\*Nj q SXoluo-=FeH)pp/Uк]%5RtObk}(Q~_A^t麭ӱZjs:\ &7M''; F8W2wm( }M cj g8 .0Gj[p=g>4xIQ?R r}9T=L ?6J0;ьwFـs`Pt99szk>ءjj09EP]ǑAdz@b.d 5@02s@\AF0) 4{)mZ6Aa1{}i <犦|TqOo)s_RjHw;%HA%v1اЃ=gdF9zM=RTzRUmg`֑^0oG 80*>jw?"=N1QiZOO}O{-i-VXmlcǬEBWGi>֞t~=^զN:Y(MîbawWH:w4t.^Ilt̒Dn2HĀn@ᙳ(wU"m#MیRǿ5A= 0h?zey@x4OZ6AFq%_K${s{r>mIPsLsqۏ?Z#4ϵcKn3WD1Fwi9`hю eNAW"~@zjCߥz[Zҵܩ%(^mfR w;mc.֍yR½|>4yڅ[໷Y2H2Tmm:.Q g[ԵM6EyQcTƇa\^B0 ڀWsRT}WJ5XnoޑRG֙9pOic۵0qFh -hڧon a0{~(20F>!}Cx'3[1ۓL`}(+i 63On2}0'r~[F=pHAoR 0GUs( OF>TUoޖh)NTٚ~{O( ۿְ(_kcJR˭ىfgE\n e aAn+/4ޣպOS.b_4˻MuK{Hl5Y8!63;ucUԩ՝Z?I%xnY]A9FUPmǓɺ#`c@ -0㟚jj^m8PW悹{3FnjBj0~v~Z3h+q-&4 ڑ^h =y=13ښ~*zi~i4J>xriIWn~*4sz`ccXDZڍiʎ3F~~*Sۑڌg XkZPV9gufn'۸wYql9uOMu/_u}&k:]5}>k/Q iACW rktZH!Ե2H h]|F c yyW+*sP~ig}'()fЫMSXLԄ caq4(,O,ލޘL})c{Oi jP jxc{mjI[K+[@[=*7/HʄFyϨ^6Q0ޥXK#,2h<J"y_;v, 尿~զf$Pӄ2gwp!#d8b26oI ENMy}-?Z{Fq@N~"@mُj{* ӏ~P@MF2i5[3DŽ!3MPA4\ ֘i3V| E~1Vrږy< w5* =F9ZarEۜȦ`lkY/Cu\IiZ̖KtK 2K+W LoTXLݗ$Q5xa׈=+a94Tӷ /¡8BU3 [z?3@ \r)P[{ F28L9#h8zxd0@*N2 H/z{~F#G4m{PxP\ Ԅ qѷj@A=X~8MS'ީ@A {)($sT0y=A9.2q_lR SL xS*j_&GTOT \uSޮ3E\&>XM[ՠd s l#3RB(qZKгhM>Ԏ6~"cB`W,[&O AU;;2 ~IB }V/-B{/AAnؠ .j6 U`'`AP~ Fџ0>jvXڐLU\jgA p6BϽ>{sFߑOg`1׷jEy繠zN@ @{S'XPZ⒦>L`oKsB@ 8oj7T*\;Ѱ/pҞ~yyT B=h+?14qqsTH2)#沽!hozCT.ehʢ_Ui;mrotV35A UVr_K9SįN[󠤊昣>响ۀpYO]_uQ7V4TΥ- "0m N n:oa GmS,aGRyrT98r "4lPF1J>2AjpsU>(P<_a]?ozAqMb9"y*_mPIA {0=?dz?p 1AA]AB\r;Lpjpڒ~B0;1b8RŁp>)#9㊕_%qSp c-/N/^?^OjhI,Xqßڱq%T+]BѱCK>–zdqsL@p*]@*B~S8 G<{SU8?zXշ3Oƥbgb)m3~!k[; mFTl%]NYOPm9[>Oi/SW7cKy=j"2mo8FC&HsGh#;B8}q!Jc4mFaxWT=zaF%BheQ"(CvAOn9rXCjE}Wލ0)~>0R`+K!eQu%%Yy*o,V[{SU8ޫn[Eqڂ4ڞ{U,c=FB]CM]6{v Ky"T-Lgj/zz[Q+ _դ4&y#k>)A[9#=&gKאy=1{ֽCaͨkjꖏ<-˕wuUKo~^\-|D5T$!9rwsPNQrxfv@޺c^{fW'4 ~~m۵X\F3IaXEqQ5HAsH/|(TiނM-WAQ⒨Ԙ+8ZjQLqSq]?4f?^|(U>ژ^hu?z2;) 5 |Uh"j>icQwSPA⬏RzhzOŪ(1Gh1-#+J:âO:uKWz"wkgBGy[0BlHWOo5ǹݾcϷK,ewA}O֬obխ}U8\a[ p!b+]KW{x$:+ kj[vAOG~hU0; l)q5\n 8mOօ\Q1TLj `RW=;j\_ږ~^Lboޭ@PGKT=PSS#B'L2`PF@5Q$-'׸^p{GZ6=QiȦm% Vh<8⺽AiuyZHJ$q9s3o'ow4n4+BLmJ ۉ$[d$i!#Ċd ɻ۠kz;LMEK,aFFV'xOg_ֺVK1_K$":ɪ@"Gݱֽ[ ˤ!No򦀟ڣo>cF?:}:]SDʒi3FpVVviXUݲ_wOjtY[Y\J0EwɔQ$:Kmޡ˩x{io9??%WqZ(ZfYۋ7|6 ڤ k^_6z~Mꨀ #k4 =ێ@f*|Sx.=+)McW9)n1x0RF4jim4Zq 4qL(sW$ R; 9>J'=U22;ksHG>)*wګ40jq?Z6񊠴m5A}TmnkQ9u="#a@aTH2Q? 8FU*?za{1_\7}; 5-BkJ\9DQ }JmGgP]T:btsrO5Y'b\`(; >=Q>^kia ^[ q#յݴ:bG H4- F7i[ ,۵+^m6aӚej[4d,fo99SɫQjr{QKh_z[2q*ڤɷ0 -4=ǽ}s@A꧍QW ?Npj\\)6_jER8Kn>/'tږiUQA\7B9 j^UNwP 损J\\9/{jhcB_>6f1@\{ނb)c4k}oj:ODV6D^#)lnee` s1SZ?/e,ٵ[X2TDV9DG%i{vp$fB|%,rm=vj>Aj5&,mcK\ie$6ڍc@"r)ST>dgޞ|qHЫjei93MS)5C>‧ oiN18i>#p Z]5-O>_(oM}#ܶ(MO\#jcFkltfiG*`FO@eYhڏMjZ撒QiCAf8@* v^`y&wS&F6GViȍm~+vHsLxMSv#Um@^,{}*c~Yޏ+'qOi'4 {yg\;Z$N=5%hHvW[iWb93Go`3Zӏu8;kV(3eU R;gxi{/YL:dZӑv TX' A9C4_kz%L;M.a!Oap>sVcU ~+3wu]DIW\g~Aצx}j~h.c-(.!2.Ih0 OmP_H\{qAOSTz{)*qK` )ހ4dbݣZ+S40r+gW4z$zJWڐ\caIPeۃںJ(X#= Fh cڨ怽N_aS*qԪe?j\{SGY=Ǹ,+%}?Ҹ x#GL=] A{yml>|sc k2kJK_Y`vxNOpMwz>ҿ/9Vy[KwvDU>G%>wI&K5ua;s0L~ ](::Z^tъ@!$E#ҠUtW\ZjL/kyslZyQOH yےlL'85;}"昏yo (TA6gڒ9R5^i]hw6Zqq$6 (mpA#n+M`WM'K=F7\XK1BH2nM.SօޑwҙBUEuG!]sUҼ]+*L +d3h8#\!@{S&J1BǶV,y|QٚcE捾ԊUs@Ny&ڍPL%8Wޘ\L PUhXOh4cjUW&ITS0QTWfRnI RE4N֛G*,Qѓmel`NAUt7UˡiwǦ+%'#$lb䜞+9Csɂi~y8 qU?O~1fQR1P>*{T @mL4mA)짴A|waB'[23( NσMTL/4mⱡ*y^c{be>fϡQTqT#!iH*z{j})X{PTg#ڬe[ 11F6浿tG>ͻ4qQH9 850j&$kob&Gtv9 I @ H|b螡C&s>ˌ,m0<șCWއoi=BmVv׭# " +|He?BݏQk մǦNum}<BFP(Q *AV-q6Q\ `2G`?j6 cڍUq6`v48> 1/$"@%RǟjE>ir~>z적xvGqLcAOo&kQ=I=OgEWU Zj(TFa>qF3BI'AG=ѷ0jB|%T62ږ>x?Җ,vkQMvtD]R^JDBA:u$L/oBm P"HM $ sOZNZL}Pu | đhUXT. !lZˤzLWC鞘a"T}~U(z(N7۞kV7Y~R٦a,ѢƲVl*¾G_<<]^O1pd><1z_k"GLK_A 崃w9bIQ4Va3=R M2; dU`MF~Kh?j[HA5%;R)yx8gҞ(Glϵ0mSMLJhT74dVڂbN)4cژ\}b;VA֠WK捇=ۏz{)\S) f`f !{\:Y\]/ Q&d~zWM_ڝ0!l2:ᔡ`篦K8tIBHmK:1̾B 3l!Scj)h!=DtOkipy/e1Ub+Np+Vs?DK5qbEF)m7lEM{GKe[mh;_ȬK{/ ׍VA[pu_WCWLFd?Ȣ2ʲ^0Cg?m:+K['[%F'z9&~UZJ6f;UmUq?ͫ%{J["01U7 Hoی֮==>Ҷ..ۤldcX|+SONCY?=~N@Aq@vFX{r[uM F53|? Ąq/v-=;krK h/?Kw4) (3++R &Vy<)D qũ9'.bPxo^~e`\w`I9@ xxǠͲ[\]%Mbi qȯD+Oe5^{SU 09 RTS㊭ f֣fnhj cZAyA_Fi_&LU#oj{8&8U>Ռ4L'5^j⚯zaxEXc?ښryyOjLаqKHqKn( P?(kMKΛo]Geլ/Z'[\-ʭ R$UQk^ID51LI*zCSg[ڌ%қiRTEp@U?Z^]?޻ofK=FX/!6*+UmH%k+ N P֗N 4΄w  VӾ7ޯMi;%UExn ǽoJuOM^uEOr.oyot? }U֭I$VZ0 ,j8k?qʰ Y?7N]>ao./F AbG޽c=QOo)U"3Ѷ}R~iكF( y|فڏ/& 6 oڱH$Ы{S}(^1\84@@[i P#ZR20q@L{2R <NO&5i>ޥ&ڇv=!sJ;V֮7L=8VZ5˲-l$ra66<&ԥn]Mm к>S,Mdɽp4 OzX-6xwfoDZ@#1`F=e9?=]ᾣj9qc=Ai??m6IgI%] n@YK1A|6T~&ZN=JwfVG]G[.]sQ vˣɬ' V2[X$7?!Fk*!6 wo8DG]cp_Vl ՂĒKGUc0>(W8*UD+bޟ{@5&.{TsS>*Uc( ACF>}*1AMEO1BTub?lsT23 F*ځVG;SUm8HB($]w:wh,bPI H];Fmb+tkkq-K39ES|BjTΏԟݪir7Xq!n# brT 6psX> @N?W8m8xN;v^YkxI;N.q2 {]G᧍k:OivM _6MWyTDi #֭ZEZst2^Em( 98a+ 6BJo[O4Mу;O3 lhLr08k+5/ zx1xV,0<Fm)iނ()@OF|AJ6ڞ rT#C@qkQF>3RP^MWi4TNњaqQMШ?X6J/ʰ9`aiIŎK8FA^Q>,g*wcǶh4*m'5DU;J=8yxg nL{K81`v{@{_?]_iQGg͝.")q?nA>qu iR].C|ѭ 1"'p`B1J f{^WcӒ&q$x)S< C+ RJ7~E 7KZ~#9dbI^aQnb//lGIc'1, 6n ލ{eiy1K4AF !YAw:T.4-eͤ=$8V;8}zgUH!?+4l'⍄P#??-=ޗ13Of2yaX{,_ށj;gޅQ jI8RzsƺRzHw3zyLW!d\>J#>n+WnXFd.s!_G_JmL7K%Otz$Vj[ܐ{ۑ l{wǜև㾕WnFL2.9jmXuit,Ms,rm̽87^Ԯ˨ۦՐ+ CNN 1;kWubtQFrI8W9r=En);UFv~y_CLi4|qLii~ph ޟ~h9qAGV0Gwּ?nӵ?R??Y6gqG8WRk)4&y [eV*7)B q3cz1xYKcs&s^>a5bЖeI$WlSqMSHڞS<1{3OKˣ\7Sm4Z``bk1NZ 0mCQ0G޵[^ҭdyK4jKA܅8l ORo))fE8$Sh{STjaN1A\6s{9+:͖ w0I/6w|`zWk[e}P|y=!f:(ۏZG$kL^ Fe/ӯ^YLʪC:A7} x/N?ʹdHQ}:/-bE(k&G#2;A龚tw Hm c@dFϥx=i^9uezŭ,b~U 꾐i'Q`+h}Bv9$,:"״N 5cy!0vK0Hl/c{o:W=C Ony$I (¹͞8YqUqS{SeqGp*zEkHQ>-J6Ohw{iR#r`v;H`ACʷRK*H87X]K/HԮ-*WpXV\1/r]ZFs`WK#%oS^4tN zVMFtۧe@H{ gI*oI 03=Y kfӺ:d~9VUJ`?Y${VKM#pK5LЇ{r L֬CzQR&z[@ifEy@RQkxt v.ȗ6ĕ8R}ZGSm L2JRa@' $;HmvV;WYbU ,[&FL~V-$0Hn;MNYY#[uYfemUU.Tmb98Xe.J6s$sxkrNuH`t&)^0!#t*@bݻ bFz>4gRZU*@,Xmƕk^, ql1xA*$ܣx#;'ZUp|R$&Frlm3[Z0sVNRcف`TSX AsU@zBҫUl_Ur2(TL5>b\NIX_&j߆ѩ>P鱻\$;f#\[r+ kPiiډnTƆgbA#8 2 xZj[DI[DAA8cV[ !d QUF ~H]Mΰwt۸]<]" m`Gy#I9^╎MqPI O9+r2H\g^CIss= $"G;&^#??>s{ahDα2 ~O {:"KI !}5[64~@|c=EDCu=J-tY#'1?FGy6zJXj}vѕ"ڬB܌׺/Xf,=6Nʶm@!2n?:kߘ>E&μtA #XċPl9wM>i᥅:ơPj|0лfUS3SFŬ$4;]ėvš̃hKx)*=QsRu:im%mRXac N7qWs{bR^ocp,1E hm$ݸwS[-2zӭu;KHZY[M99Tf*8+=8ρZվi+kiฝ^Kp%ڮrѓع$k=Q=WuƛiZNf{wwTbmy.䳶-kIw^jڭ sql4Pv `A{sR5Knb\(]!=#S ' ȼ 1ǥUZ[$rMsjn6lS V8'i;]\jȋmh.2#.-I.ΙG8aZR]ͦѥ3,O`Cr yټ/^UIwMƪAI#`ݒ+e$c,3amF쫁<95ދak\ <Ն6;w7rAϪ7KX8dRQIJ&ݰ vIǣ4sooA%ƒ`|s3" =k##)-OAr(ǵ%T> >+}7LL!IX!F>#\W GUkkɣ__GI )f /p}JrTcHe|Cqi!|$i]}_Սuu63Yi7G Z row_+Mczӡ8m9meecq:'缹Yn3 0T''$ԫbC+SMIDKA$s-]dF!R~.ϩȯ%ӵ LS鎀 C7b.%̾G ;񷏚~ԟ^/LVu#Bd2nd Ap!hԑk[_3Ij-Fc#Es̉uAttX:ed9;}W㧺6>6wpőO5pPd?NӤt׋eyWHlGlT6n 瓶ho9T*ecå H]SY%WGVM}[+#Ȃȹ:O,R}¾뮓mFdY7+'6 z~)qL4c`|F6ywCIc$ %Rr}[x{.iHtƚyUXaQ>^Q؍dp2BůiK͂(BT$Aݵ=hM{, Pn9bΪB #'j݀y=C-$=Ĉٚo nS!%s;9:_[߲0$XX9 rq=k}a2]B1g.`cpqVu[3/Bn ?$vWGCw73Y*7*PU{}\+^񛥺߭@n$;nJhݸ|PAT{R ~@xA>gmgrn%v7I4e6 32ofuIZFv6r%9I| R0Z`I#D&uE *ZN6ye9S4*2G"4`B tMH6>[pMOaz $ -u#H0%8W^"iYtպi庐8ݝ#HU?NS۲ȂEjyARS=.VҚ.9 d [s+F |k(.#Qx $JU]ɗHB0j:捦[5HW m,e^"6Džޤ6J Xm'+q.mB[abĹ\⓬cEmseworf !CV}(DhZuIs&`_%b=Fw"lu8mżɺ&qHӖ 8v[Q)ijd].'ĭ2sz#os޵=VMDB 2GIȐ>2>g7aLxE_:CpPyWMѴ3e%vV֭J g]@ @'0ϺM[OI^i%}I dH<Vd|~q4mCaߐ[#^nJׂy66 }1cvϧoj.gqjVn Dqʍ :o⋦N7[^8įL3!>N~-W.RcJcڧ8ǫ nުNsKv;&tTx֍ׇA7 ST{ry-XһM&-'MqwpƉe5,@oLM[Hy4nl{AުXfEv '#!|PYŽc̀q$(o%оӹBLxBQjI&f{Oml5Η),Pivq JV?p֞F.h/y>F>Z,VْݔX̆`2ǜ.s2ky ѥA*m0>WlSk0Tn v w|wX޴"{g !CrO'=n@͸zCMjtV8D8YV@H$)/ezVHM l #9>q.%Hcs!`K#9K iwW 3Gm ?cF?W}G7dov"nXcH)נ}㵗_k6|w,"v~ޠSF']C'^֎I7ot# cr?.+:]>Eӿ7YYP!l/d+fn4?'8yZ(ڗQ9.0sn=zF>5w-ξ.$2)79e$xx|e+L>B#8 |]e/UjIٴiX|" 0[ʦ~==CwZ6qY߶va+:D6Ъ {+g[EI|1p0`8Q/K=u/.u;mV~b(ܫI< S!l _` 5{\bM0\bUkM7I.pRF4F}ϰ<םOh#k]:y$gubv*d2x2T;d1o=ɮ :iWO6٦W blTvkxu; YљVcg%sH,+5Bl ^B7w?\T~id(2iv+n݌Nϧky< DXRK3q=z޽=ƢG9v )'䂬5 ^?7Tʥ< 'xM5]UZdֵ^u̷ˌ+ l;A!܍C?Si:tsu>Cm! q(F!>;H}!jMĆQ<({} 2ߦD.jUh.q1&BS +p6pc S6nG`N7Nxc)i5yonY ~QWldgÂ鐜#cIiaWntV9唉bD|X* bwj/Ρ{;K[<@,;@V`Ųk;B5m`\oldERFZv3 S+ෘYU9wI957]"BGrH##]һEI2J\}Ԏ>qՎ]]\18/u<ӒGkyl(dT\v#~8nr43,w6Onʡ5\ ~0k ül#9nP98<Xi2̩?Mt!Y@[siY`Zv`q6<:=ݚuxFYP'8'j5KHoonđ ,|d?`O|<Ϛ`IA=v.Nm !]X>[w)wm{ՖMnO'%{¿Otb^㾰VCIV*W#] !)S w'N;Q חPȰH!pN}5ܽ=u`Kaåje8dHfkVz5mpɪ^ZE%xh Mf<4ޚ֭RI?7Kgmv@9UbAC8 }?X(/(%\ڋ/SCHO?_uNtqp2E[ݬRXCrU nu;A w0VCWOxwqz BJ[B{k/)*WZLV?=zFi9|rCOӽQr˯hV2r\F6%68/\t6P? wk-Z@#^TcnWһ{/_5@z?5cn,^Pg ÀB/%޻xo8.m0RH#(*Wo N(.LM ~kyV+:<d90\zAֽ:OQ0^XErIoy:g +r}޳Ӻ Q fr;Xg 5K 8+ZX\όQ<=?_>k<﨧AqR-{kƺu[kǼO:!,A*G;VYIqu}oxK[`UP. w0eRÌiӢ4ԤhLҘv)0=j:qMlmf4*J-U/e =9%L$k& p ro'U_Ey%eLgI&6hTTP3ǫ2G$vf[fӴpnR$,pF{-J,l  &<[#,S(xYQ9'+=Ak^B.a!]ޭ,NvHfGuC=]JĘ-\1TcrX [N֬QE|Ipqn7 ]xX|[[[(ã KU75jnn=3aHxH,$WwOO7_F9cf,F!PHp9a,-Rqg\4{8Md8m0]6v7 ֙-Ko DR{a#BĄ@Nns&782GȧfTڭ'9fl).t$dnrN2WۃWB<x<㽨%wdx*vcc? ްv;/,v̰lqk#*mѧ}dxc<{d`Ҥ3V!Rf]/n2 pqUʃkbX6F2==~Ʊ#8e |.ӯeرn(e\r UP׮5i=;CQx4i`$](]F8s_#:_K_,[- /|`:>6LT9\nx?Fyhz~qr˕lZ{xΤ/h$Ml‚}';cՌq1è\Cq~w N-b`HiHl2םV~^mj.i"vIvǕ<Z}f>ӭ<%;J[- *XRy,R:Mg0mOI0L̑񵌠 ȯoG.O zzGb ^@vS1rx@=vu=Fuym&[Cӷ-#$RG"gvIWW+Q[U3C<א2[yiUď`;'1A5r< n`1~=#nѺI oo_&#b~p8H$a䅷KX*`xl{w&uwagc^Y%3eLmĆ`~>oQu!=c] )CX*$o #mz$yRqa1$ɯ? weLԣY s`pHg^7 t/S.m'lDbvLY>0fє[kZޝV_WY7}a5%[%Ics9$sGt mg($ t$1nd;C;p_>skUu:r[0\nv;kKSni i#YSjdVEִ/è]'%?;s(ǧv f--bNe|ylݢc bcܱY2ka4ͪ,6Q[[F*ʭĜ.03ɧ=:R ؎DU}rޠpBbooNNH̳JY$36*mhZrk1\J& [`H$R1֡TdYCMwG~yWX#)QnXv2rAxCȒ1ϧqpxnRgˎ'diU@0 q_4A;FsW#F\ŋ#wnhB8e߽vu-Vߧt]y$ۆXyZ2&d.kC籕so94qL}7cB^4фl3s\·EpD?1f bB۴ QO4FNft*X(ap?-YIU!P ċc  Jc[;/pRAA'g|Գ\O$q&FV w+ yiY9Y%)0W^~k|iVHme #a.A:?zZ]OM(EtCH\6UXmFv3կWFkFy H/e 3 Ncy  _c~o)z]]uW.bxPJbO;9Lj,{1e+jv3{g~&< ܽ=zW/zxg'UXN4iZǵ!mc9`)|I/Ҭ涍f_:pV]q*@Pq^c[[a <ΌHٚ_-!?GzZGQԬjV;-ϖE6;./"D4J/! (j,㑙:'H"lddJse0+~ g}6)zX.tY! 4ʅ '!J.o zYnMcO&:}@öp}~C;zKWgYG-"y7"!dkQT\{[{8mhvս[2y4Y R5 Y['jbAk", }c*I'ֱ}l#{&p@9 vpEsZ]XIqFAI!Tz{܌VO@UX.HW,싴39RX`sq/Śbyd14mryU$FY-[ $`ː@Td׃+_0A!R# HQ;Fs.=\D6[C)e*hʂNrFXQyFg&(.-XFY=Y*9oIے5͵Ӭ.w$QLuS6IC xZB1#nHOWe#,x.bed%!!*1 mCΫm /`Վ(1|+#|#yv[Ҫd 2HI9>{jmY9s9]btnYV3/r#Op-E$jF8$[p98`ݨ.M E 6yMU0[gY .XJony"ڻcTH4kkqd˵[` 98!SӴ'P鑬#-{60 TIOoցt:~oj.^$\*/0A  Qަꗾd:h[]S#v #, m\~go'ԦEt؀Py^g2q\_YuSKc,P&L; n _iIu xkH$ Fa۷ A֢(Yfu*Z7|q26*3%O|j$?sXn3*ֳ?to8%## YF1 d`q߂q+[W,*w+^+)x62zjYpDx>`ffuX<0O r{J٢iwZ1Ap8͋sh<G}OW{W( $( {cO^[CKH}hldGb\ CX7+z8RI>9A(q<׋uPy,re'_EOki2C&Rgys6 C2=55+}iSi֖c:E  9lč ޡ=Kj6WڕRmi%C7#VcB{Sw]ihc.JV= ՒCqXO 0ivUԚ6iJ[[Gf%G>x6=-. Nu pUK\LHrwZT$X]Kz&"e0Ic c -՝My wVw:x Goɾu;+ YՌ~rBv)$zZj:f=v:DQߟ] m`G\ʺH̭m6q{pJ;#$VCK!A[q]UQH|ocv$ݲ = qZm栻Hn$hFTr7@H#1hlǭH!@9( {g<yGggIWS]Eo-U9h<11BHom6-',gprN iOޝQpK"F [s""0 [:n#QӍIk'UP9璙. wM۠V!prz>qE Zĭ ;f`P('uEaw-[GkoE@nMӸY&LLHfe,Ƹ:MK(MN+y.%Kur8`=]MnIl^{ן:3K-֜#ݱ]1{T}'֚ݶ[Mq}vݕ]9l*+[A~A{;}Nץu KHxQ;( p 3]<o bu.>q<]Ć5 %sIOtHRKu P[yHa`֗] guBI)BonNIds߷j{V BB"I tUPwDKIxڱR"hUȏW` =Gq9O:Εn`[,|w.FO`Iin}fONCO6܅:v{R,n?̲+>س2# 6r~Ӌlnji3+Hmsw kz吵Xʕd^\co{ՕE9E$HoZQEo."oۈf'=X`dcz)-7@/"K"ct CH#s9=pVS. X9¶! 1yL$ RG8K~]J2 G9#9?[bY" #nr;.Gge,$cw#׾jWƘ:$qXpHhyoz\nKhĈd1N{HhEj}K_62цl8O| ޴A[O4څ×}B9 $b<]?x`2MGل?H $,f;{+tG]{u[mO.yjK"<07m*H:С5ЅPzェ+^t%Zhz\:i}?Y Mvf+b>V}o U:1u4zzZ%h)Qxo(GLEly#Je 2n8'Y#`16'r8 H J/m?Dֵ}ڵ,F@W_GV~N "׋,豴!"q*1$%&uH=>kQ\GlН;Fy}&C.xfX7gv='P Frz ˼B 4MW[G4<>fa($ ln8ullI7Q4AY302T c u^ё#B?[} eUpZ>OGeosv,Ѽ260•9,J|Ws4f+HFfﻞ vԬo% w3U9CW ~Ԛc\onZZ5VS؅յn캒;֖ rT%zv,8KsEֿcҚNEq'S(\2{d21zE[yT` %F օI h D fW`~'89tWP]iIpwi2KRn8sOaߴg舳@veaH$WmgRduDEGg*y,2(>Pu U:țex3!_!#w%(F-Go1{Hk L NKQbW95.nh俿Г֏qk] 09!rVNuGP9$GV1I%`umgKYֲvP<*_qw6'%O;Y$fX RC pH*H]jĶ֟ |K@4nYd2A5qHUSru_j!%Pjos_$1''Y.Οt^i쮬ro&e<2*~"=fÙtDְ[MĠmņTqnO8zE.'noYts"~ b@WkOl3Oj[uOMZCLy/_Ŋ4 uX",>R;u>=8^/L8۝ɐH)PPxs]]7TMtT˷r+%ďWWП?{ޠЭt vKo^h6hXbp9\c +/oYi!UUO)hij8#Gjht=SӱnKzȎنa>I;GּXk.ne&YZG.c'9XFL:Smc\e`¢oNq݅z^ZVm-S"^C+$Eʂ`@$ܨr-/QkBY[G91Qxyӽ#~Z0?&,6\)4ҴԸ>Ցϩ:[T ,%Ao0'vMBprʯ5u|D]:I;{y se6+z⼻K)VMeC`*<3WKCe=F3cZ\ȧv8ؐx) ]jE.# pJ)MĒ`iU3O{7QZiwAO+njr9Rx淒Lc,X4nn$ OZwpF-(_)DxX1{8&=uNzǧ$o(̑<)db nzYk{]mڿO$R[j rXʕ7-`#o>bu;W1=%fH VUG+V%ikܢKx΂VS H 0.bGCS5kNJ4xKXX]2EhS߈=/ӟ-:NYmIx 6I]6V`=e[5.oo+yb-I $Ds$Nk~^6dc]o"ZT`B4+Qx!^/G dԭ 3)AI:,^'m u!ː7$m^X)|;%#lt]XgMt'PMٯc,ha HURF$x,OivzjڴeB7#,bb!XkntMX-Jش$F+i"noVm^Utֵ[ͭj7zuc_' ţ66yvsZn׺jQ֭-:+HPD!a ljWk0ǺN^.Yj=@~Vribut#)V*^wpI޹iKmӰx~($inM[nbE8\@Ӭ㩴,w yGIeWgF[#k-j g:xc@=cX#Fq dBWzB+z+uB?R=>#fB2*GG0@1}oa^I$WW3̪.nvPKk)Do]K:-,ѝ͗F=FkOBQ[.}#޳:-vv_3v@sq޻HM,32`5Ӷ#g143瓌Y.wRQ6%SB܌1Gknl'̎{ëD;r;}k&^nYYgWIEc Zƥc-À$3cnyZwëBH? >ؚ kWK2^6S;?ںij#*ͼ$Jt&+).#"FH%@cʡbJS;.H#.-6M:K-gۍW d9_PeMRkn.giN[< İ|stFxcYyB;cI9s_jz[]EomIlLYD0Aa ~K5oˤO es+I wۅA<=E&yeq$7$G/Tx ;{puOf{9!gYf,n 1am5fnXA /;rOb?Q(Vi)q#y~YpNkA!\2O2  d9\?MVT$sk=^\mNAHryO.sѥDf <>S}EVW0FV ﮵/5 J-vilӦʾ'3y9O?6#GR-ԇRhOTR܄Y6Ij>ӌ'NZDriR`3$oyc3s[.нEMoXhO"rc, 28֙]C?Ph:ŝ_S{=1 `pr}/ =C[Dy&@˽ gH$Wvp)*w>0s9+.:*uRUe&2qʐw#2z=Z˝Yw0Ax)@2C36Nz#ōgéjA5(d.[A3p sHP8e%{l_>_-ؕH7K!a'~tOkIUR0./"r^{ +arR4*P e$YOPĚc +Kǯ+."V85~1]Qӝ'akeMҭ[:MU F6e 9 jpeg-decoder-0.1.22/benches/tower_progressive.jpg010064400017500001750000002050651367172151600202270ustar 00000000000000JFIFHHExifMM* (1 2ï%OnePlusONEPLUS A5010HHGIMP 2.8.222019:11:26 22:38:11.6"'d0220>R fn v  ~|0100 d2019:11:09 10:59:362019:11:09 10:59:36)cddMM*3AD602695602695602695R980100N:ERjr 03Y'('+ ;$2019:11:09(HHJFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?.)إ{3i3bq?bQm"=#@Hv\umXXO-٢e=iLFړmh 6Ѷ6F(=bhF*LQ@G)h+Fړh(Rm@K~)v)qOh&(@ I5/٤lm ^Gwe+۴p[9B/ aGҸ1U ;#:rlv)vK8mmI1@ &*LQbF((I1@ъm2-T K\S3P!hRJ1&6{iqOK(KDeV :F=D\XI_˙C6qRL)cp j6?Mc?+|cn<\S ZBv$.)^ F)&( #F*LQb#F*LQG6Ԙ(1E 6ԘL.G]if1RbQP{iqODh8'5g\л=tl k*t[Zp[ 1`=3:{W=ռxK,w`/"ַ21 zד~kRa4kGFړm.`G&6x&6[ivԛh4 KS1h`@\`Z].]\vvѶ"KK㸠:r*McHd[hS=@"3*3nA=)o% 22R>\gi_[M*;Gd.FRϲ8Į! N8y5nh|[)6Ԉ3%(끚]SwgQZmmmKmY;hSmmC66;hSmm !KFm6(i@6c6ԛihXf;QFEhSNo*]GU}>|<_,)lszϖ.LAJ(/-K&>ߔ0}^usw}  :S ؂E<Ѱ!fϕӶjZD W;u$@Szsx^޹+ruV^qߓSwN4-ԌeB=?ukTSO"Fʛmk{"(Slm leOe mM!KK;hQE{iBXˬyH҅|F9m;m?e..&KWvUMRk{=2yxNZ[Eekm~ltc^l=;GuqZV G8f'TV~l2@w@ݞQ"9c_1]`T).+KS4lttu2Qa5 wο-E 9.]r2p^k\{Ick+Ag`I3^pr\NLl}.;iv(])R"1nʟimAeOs)6=G0rmE4y)vW%ڝ=mg>+b5V/*Mt&KR-*Ph!6T)vQkץ"Ŷ.mS Ej:e)c7H. 9=*丼I#x7 7?{?1rb*s{Rq\|1 \KFUH1¶5<:116nm;ȑH,r?^NDCqV* &u(SrG=3Kp4N@8>j}=Մ̃qEJII HHNkZŔv맫$/1l`c 57O'uRyxx tfYeX$nOeMs!\{TiV4gv 2I4/c>&RK&J=ԼUypBA/Ljc! 7gD0x(#:R3y0e8<EgO$LTcs޴,:?j֬aUqZƲdJ`TKadgc>]gm#MXWQ@a%j3byҲ&[ٮp_\fU8zvMT F*0<cuܞJy3h`Mȱ\b?v@>8ʯSD;*mnmmyύo&(j(~%3LۀTf2 ͖Gt8n6H>dW9bH&||z f_MIi׆Y #[BI%FIHC.X㓟nZvL _VW~Oojs"~=dvIok Ibd'J*Z4`퐝q,@$[s>Y3U%޺OX.mg̑H'G\篵9=[١O.%F%sj4hix5ͩދ:`H9jܷ"hfM-Թd-J64cl;FvY7H̡dqm,5Z d &GqVӳI.CYJdEW%GYsPI@/c(KF[,0@r3s(e""D#8#oCp.Y84QI7aO,JI+S~gNk3,I`mçҊ*nݒ"*?JG " ۜާtQN[SXm%W&@8-u{Tzn,vraߞO\GJ([&~e++滐I!a$[NaD1G'󢊧3M܆mA#eRPH-@'j-r%Q&)1D@ H:zEKtCD{,q^ H{¬J##~g6Oz E J-Gm "XBGF#~UG+Ehݳ8t{E8#&T7Fcs`dMQMpL http://ns.adobe.com/xap/1.0/ OnePlus ONEPLUS A5010 72 72 pouces OnePlus5T-user 9 PKQ1.180716.001 1910081948 release-keys 2019:11:09 10:59:36 Centered Compression JPEG 72 72 pouces 1/1547 sec. f/1,7 Non défini 100 Version d'exif 2.2 2019:11:09 10:59:36 2019:11:09 10:59:36 Y Cb Cr - 10,60 EV (1/1546 sec.) 1,53 EV (f/1,7) 7,40 EV (578,68 cd/m²) Inconnu 4,1 mm 23 octets de données indéfinies 602695 602695 602695 FlashPix version 1.0 sRGB 3456 4608 Sonde de couleur à un capteur Photographié directement Exposition automatique Balance des blancs automatique 24 Standard N 48, 51, 16,3929 E 2, 18, 1,0441 Niveau de la mer 76,711 09:59:36,00 2019:11:09 R98 0100 C     C    }WʩV% B-IUDTRA*R*@@@\i-ʲUj*hU  ;9;8ur P JB)E;"YTK- T¢ԉYz7ޏ DJ,LH ,Hcʪ U *TIJ?zU ((Yj@B&;1PPKRU!HYs<_eRZ-T@-JP(PQ`-BU) OپύdE"UX D-XǦf!QAaeP(BBIJg/ ٬J%\J!HR- UDՁ:fb*T@R/g~[fQJAeP@XB)(T@ Vˑ__gOo=m Z -!RVQ@ FUDV-`Ɇ }_wp*( %8&RNHU(@R(BHRNL_Aϻ7,|󧖔JURHŗQIEXbH:fH@KEB*-{Qzxc&HUA*E[b tLBQTY B% {_?bQ-Pa!TD)HP@Y3%T*TaT@D9i|ci\h @TPJ1%XPR AILߪAD -@BV@$:P (rZBJcve_  "[HXB@X K)e%b!`B;;93䅒)(B-(R\XU%HUT*%7}_usmJ) %-IiA(RE) @Y?atnnב*JejE@RX( P@E(JȂ,J* ,2/ɿ F8V (V )P@ZtJKVĶrPV%lA (,/3]QW,ER%RЕb!HV! "U"*yDPG=[:S46lϏ,EX TBH "U*(Vĥ.5bPWf?F|N QaQI@`B!@XȘ9䨴duԅ V (~>Ϡ|¾}_!UH!i@BDAeX9(,`"PUTl{w-Myyk0:HR@)* @A!bBԕD T +.rq- TO|vuu/_@(R@`@bURPcWjBgўPP BUfy~=^Wwp(!H U )yK -b)Am@BaFxER//>ǒӻy=/'-J"`yy2#,B %R gӞ=?z[/;Q}/,@RRBieRBT1.[T(%ߎ}Ya*Gj8<'^==O @BY@i (*dǑB‰@ZB!JӖZE">z?w!VP,JPJ "--j!BZ){qˣ=tPi9sy:0}?7s>T(PRP HSlY@j>/_>~׃{Ox@XHU*@VD/)yՖ)(T3re~o=(B HU(BL lJ՞UT>whݏyޢ2@(P b-^iV1ZJ*YV9 oW~^{~=K h!@JYbRPZ*Q Ai)9]ybTSKxs|ѕ/n @bXP RYIʕjXA*PH {zo7/k=JɒA!T!@*İP iJU劯 BV羏QrtwoRU,Y d% YqPr! !@x :qvvj의ׅ<{B@,*"YV JXeJP-[!,!HH_Mx*rq|%` i$**dJ cL Dr*8 R<_{x~$[ yqM{`%[   F+ QEU [D\J8ͼco~vىg_abQȳ.RY*Y(P YBU`YmYV %R4;~3q^S~Kfv} >YĢNxgeew& ܜ^ T YXpXd*IZ}XeVϰuqR)J !Bʱ;iϮ9Խ/+^9<'wɸ~ߣceن|7ˍYeV̰ultx/[8saf95ztwZIK-QN2G٫ke8o+8fWC.N;дIPD M[<g;z6jXܞ^}ߩ{7(JPРH^sZC];÷Ư&q//m@BR!@PTcc~IzK~w5Ėt]:[=&kYwk}ޏnNS!̼_,rVrfcڞ yN5}7oj J_=~YizonǷsuyyXm>Z=?ѥկ׻g7YV*XCy>Nn՛[,6rNycullú3a)JDK9[ Mtۏ^w^Ƨ_GZyXxz;vcwJ `.<}uמfE|c匳ag׷"cvvJOȅtuhϻ6FNf=gF9|O0yt݆u]Xwa>:vL|z??v~~]/Qt?KӫtzGFS?cOgѢݯ^G yɬ_ 2ѿދF;z4}{X^5|>rrmseuhe[1|:ɖFXhN۲g<\qvq9.qsfF?Fm~ݯZ rftm^}z=cs^?n6L zvsz',r#,'6bn8̰kf1rnLvˬه|yb#.;1{7?V}+/jݵ)#<7zN[غy{֯\2a~z|5j7ż:z9\c>ӫn9귺1ۧ-fdjû^ztoǖ7,iy/ 0sÔg}-/y|2ea_Y˻15|n{9W~z+V\YLFzv2.<4&F'[7NA^s&z[{qtriz/9ק߆&])o7ѷ <|7űt:8l|wjѱF^_&ݗy[Yt,ّ56Xqtmճ?FF;:iz2ێ<+.َ~t=:|{q}O#7EKՎ܉ /)1,:KMCcnY2V1yo1R5J+MMt?n?0 )~/w[ۏlbْDzҟmxpEL| ibR]54z~-?]*cΌR2%"{OجyS|.Xؓlv tSC|V:bi3\1+lɀTgc+tWО^呣_D( hic8ƈ@EߺL l͟G!QnW;̏\R9:6:X2^[ [c ‰jUnR7`HIP1B.V=u;<z|~+U3X鎣n̻ ȴq.i+uCҷ f+}[X v bIT5ԡ;\{Ǔ1E*g+d&8Bk&N 2޸Xy1{K`[) "Sk2ݤ+L{Xyq-@`#\fGI˞|V+ǀyX˸=F#ѹ#_ oqe;<8cX{ѵ5S >&IW#jAYdh{[dMTk--|8Vdm:z}m6kWy%D3,KcfiwJh+6ycgÐ^ϑN;GFG8cIkIz¸ȴ]SS:X[jCc)GF\e){]Q2_z-/$}M7 ޥva{isRsGHWjM3[Bj#8W~U 69yIUGZ];LsOc$<\M8/Db?M +~ljw2Dkvvb|w3g vFQc"Iܗi=j˶WiD:ǻ6bTDg ^? kU UҎ5Vq1"^]wvzlb"'2e^xn"1ϖ7 -#gøG< 'pM,x )Y"1DK^'iH ͫm HDXȼ{Ok`kKfx_ oGmB>b-Vsd(ڞxfP aZ,ɊJ]<IrƼFts$$ K%IQoqYcG=xH%CmpUoZ!$5ۊA>`Mu<Cw!Gҥ*Ht*ԅp&pn>*]걦iMD<_!#$ˋba%ۙn÷(Kt&Llq.G0Ux# ml%2AðJݦloFVG{(ƿUhE)aԙ~?P]+Z(HZdGC  rXhYo# R]S{3^!^J ,lEhZ,Շ;d,ZoӍ4`Hn)$AIt'^\ds,I̋w)sm A ![6];CbXf51m+J.1V2!1 "0APQ2a3@#BRq`b?+ν~8EvrqߢG O2~k.deFщ&%FeSwjGiT8Իg^끢?]~^*NM{H޶8_?}5x-{hG% Tq5I{ W̱Ro{Q7FO]t~¼F24~Kd)nvjQtSfJ1Vݚ[O~ëūR*V/|yw8ݗd"8ޗuyl='gVI]KKc|ǣ4=ޘw7dق09NSy-L{K} g,\.W/_/|>b4-Ж>O!nVU#iy54.#Kr嗩~&IRG_F'FE(Kb\zOG)r{r^##ބ*&(Ҕ"GǍ ކCKEOcBLF_iej2'+.tvZ_ȦI9rdefKj$贷:&rRZ#-.7!BCvո%{ۚj3K8IIJ.Jh6B%mGԞbgܷf#m9KclTjmt.OdxD؋Chn,1ӒrOMzо}9_vq{ 7,#>M+䨊Z]Ŝ6jNC槹c*ɓURI8u;GEHyMHսtv0=R"2KݎW+kϭ:Pv)Gt2'U!={fťԥfF"2Q1!1 "AP02Qa#3@qBR`b?˛ҡ;^|8K`/fGd}cE{Wra%bnPM7,'ޥf&{2I2Eo |fou%Ks)`c^tOν%扶7s}Jө/֌e7+8vx~f1k,KlL䒾ٚ':mю6gSiymuF^v̛Eܶ4d>UGHi߸%8ij/>̼?j> 1a&H3tcT 1׷A^4X=jf'eOnbǡ\ }㷜ɤ؍%-8گv[d\Z*oԅ,cc8K=*5} rK}$G&E ܖ '≮)6C mkܘ dKٳuF)ip>o"=%]JԆ'50mĪL0QqL18f,# F9q-c-FJ4}EQ{8~ǿҡd`\5&IʨO)M޿ʍZ~a䌥qbnLp׉qw0Xw'-].],xZ87xW1GJs1bvF?VpSJ./xJmQf_6)#rp{uvK{2A麳C8Y_ۥdr\ܪ{֬PCC8>Rd6p2Qe_x{X \:a)8IhYeYe/r;7]%R5T7,e/eI)}6cZa9Z%'=z/Y|lYe_+z b[%Jٟɝ2qF^8iFQ_ıJe%_wBq{!ղ#=9c.TW.ƣQ&_/\D VάFأ*]>ƹ}GUEj"cMdmr,eYee8vOK%;Yffͻ6~a)S/Hy} jDԨ.lkǙi9w%JCșƾ㚑wuYe>2..̙%&]K(DWhv9-[YgQMΣ:^YkW}uN:cJH]DHATtr;*1̍@#GDs[Ye_2X(yu Q}4t= N#B )G Nđ-JTJ;[Q]Ɣٛtf}Qћt^:bG[F̆h|zIcDCrѩ٭{csމ:uEmnFu+_<6ܭa;{V捬dJՑ"brم4ї[ џoTǹ*+b cTYe'ap?+  !{d.3UEۈJΞ`^VYe>6(} fÚ]$[cB--K&DW)ŶcUə;Ν!Oo uCHrsd1Jd0;Y|&'2+CXV/R?5\TR-x(Hf6O#+DbH=da茉P8̊ *JǍF:t ӽJ&DEodZ+{ѱ%lOuFEJȺ5uC̎Tm:U&2\}JZqYD;;GӘPaיMʴK#϶Gw J10=5 uP Jʶy]ó2)ACEq;0/ 7.uԥNJAR?V6^lh}e.6R"|Z=-D %\M L}2*>'߯4P ?[MiS/4мB=yXByʞ8&Nz/eV Ҥ`P{ˤ48f M:0X+*>΃)uonگ7ۣnTϴ/ /I}ִ fTO>{kPkhZKSIzF^'cfVY^̧)eNOP˟vJW]d4Z Ck2P.]|=yoH-rb[%ͺ\ZM) Lk%ǒڡc-xO`kyL%6L_}3 C^ڒ8 Taٴ^ o! ~N)esZYYW -miG:{#i(JܷG֢08mOΧjVqJ5󯿱vE\ӽ,M9g.!xJ{tJu+FS˻m_yyʘZڭ`!B7㧿XO7>4F\♟XW0TbR=XO9|:x[]UW}~9B\SF+G  1@Kz[!ZĔ\Q<n%r>]q >; ̓|a!$PS'^Du*>탯Zm幐PQVwSXt ߹i. Tn!+uw!ÐђLaM1;cI0|3+2?>XqG E[AN4JK~4BC}/#ւidD+xSKD/GR9k9v-KY'^*NiKJh:!^ˊǼ~2S9ư<ذ־lFcչyxd%cZ(V/yH0pJrFFOt^2җJ0PtUZ*$:Tu#f)o%izPu^ܾRBB毲[JGP4_|~K4\8Tқ^|g_$|>Bҽ{i=PY4ׇu*¤->4* i0y1LlO3H9idZ@N/9;uKmCay}c>] 6d:VL;qƖ9Y *}lۤqصkݺEb1K1Ju6Fw̤Cd("ȺMefm`-'W'^vn#,2^q$'֩Q`*UTZG2MLEǟ7饂M*Jò)3r`Ǖ`ז(}=aa§xs/UFG{We\SwJ?馀/xP_#R'짥ZJ[?gǴ?3Kqګw!l]FwAϺk`p;&P+,. #RۄV鞏#OfE7O)Ï{K )8稃m4 eJġmα[ 3!!/F|&WjԿ}YMzBoC)J1;FW<}*Sm^Sp,증Ôb1 7bws̙?2g;5()CF J7'CJ/,-rM\>eˈM:`xsdW;{oe) gm=#ۗ#V5>R*4U%;P):e'nO_Μ!B6#頫`TۖLO9mJQ".qcbn=}G5\<_f<m#olQzXptVCfƓ6onT5=yI*JBD8=)/GY*i)r[%`pn(l)[ &*RIs>Hb:Hm qd-ҳ%7uuVtZQJ{8|#sHWגG:ٛJb kdQ+!zM5+ShlqH58\ivffO~79:RTF]kf gh?JAkpp-;NiJz\¥EĬȹ-_⤲d[ !sX-ЖKG{{?A$1W@\U?P}xGF%+iYŶ_-`9V4"]QѤ1MҒ@P#YX"9suҥ odSR2GDH "m=ymq-c.#*i6H⊺.L jbfG$ӸT:}SO6+VYkR&*Y*T v}!ʝSpZ 3F?LyeXtwRAErMT-ie/=d#~@(4ǩSpHs<&(xVDV!+K4ҐhHd|Ml u%K<$|h4-PN?zt +hQ;(R;C=Dٞu )TYJ؅(fIk5U#1u!YB JKn8nIMaІBg W]Y e$]b^$eHPyu.ey!NJhqJoq;67CԓǺ8žQM1R$^HR>eQjE Tm%Us/@)+$&!Ɖ7^"[OtRcFSs yրRSW TgQK9ҧJmY'uQE 9sʄ*A*MAJQM 5$s*PAIr) .ʟخYJ{knKebKa]߆[so67~/=  t6'RLL2R[St\1 s'.4_u^7D;<7M`D!C>yѽt' 8|)ǜ,[UH;w^NWs{(H9E:l{,Ϟ┨,$e~~sjT}$LrII.ߍ ÇVbۉk똥z\H>tTkk4 h`\{cd#Awxt} QGVvgE(ZoE[_V)\,2V]0:Ҝi8İѓF3"i8p#e',t09l-*m&܊/ QۆͭYY:olej6f`pbdPC#vyw@ǝzFk>|ikV1-iRQ *z?ï'p6GL s9FSJsaB\*$yJuP|#8cg{֘dǪlɻS9Lp"RgUŬ?ԣB oE&~?ʂ|+cBR]{8?۳i"RG6 S.8ٸU\2wژʮZK8H_bkdiU'>=vvZS)'J qZ攠@s*8ү& xQRށU]y3Spk2MKә9!\b^g, &(';u<^†vV堫EÄލLmٸtp|k%GTA v)J٦#ZÒ aQaTp!$N+i%J:<8XTb~Õbhq1[73i_v<ҟeI"I=N8IMawOYwgmҐq)!LIx]drbǵb۲ǏLnV(H+hr1R*o 3@2sƀ(\ Iyh\houtW+IO))O:M*܈@/ ʸޭ#N2d?t[Eؙ늱 YlU=r9>yp&LdFnLGhR2O,<}iDQ@":r =?@)̌M*0 ]Qh4Fap7)-'#^-#N\)I\ 8S^kgM'eĶe) .U 2~l'hSdu֚)E +g iXMa!I²2Nu%0+9 <Ii<]aZcv¡oi Ķ$TPD9V)8I:e;tbOhDD\&nSbz*J@[r2)D хEn1KI (,NN/¢mPObQ UpxVy"(A%'?uA)_EvoZLMwGZ )nSS*4Ҟ2EIMgwrRJ7'4/Tnr)Fj\%:[CF KBOhAxa}v^sÝ}TuSAư"i*5Dp\$4H(H׀2M1-:d^w$ؼ40)8ΣugP\՞rB%wHZKy"gxt@9 9EJW9IOIA!,.tMsKBJsTU&]p5@uƖS%Ѹ % ="?*qZJ>"E."[??Υ5ʞaZ[=t-"IBM dζ̸P`QkmcJnDXe0iRDʸvE%jr&Odx͘p2F ʰ-N(te L mR1k ңi!7¿(!1AQaq 0?!QzNP"QA1zPhZQT("hD4QD(WzQk*(Q-q%p) /38(B="Qh53uQE4ZE,Eh&FmKEPPQh(QB4QLj"zVN-PXuj((Qj(DQE(Z(Z(\#EQMNB.(E;-Sm'KED0QB5QL""E37дQ$`~tQAŢB(QDz- (B"(]hZoQhG%p06k"A9 rwUZ("-V(qEQhZ(E1E^JeUo1lلS&ӽ$ŀ ,pyEEE(QTZ(QEqT"(EQEE-J(A q *Z`8P$P7-(`1cES=TQEM^EQhд/J(Z+LBQ|00K0hr N=G< (D+Eh-TQh/BESJ(Z)Tb}|1s"e{ B&b՜Y0C`QhдZzT EQjеZEr$D5[T CtXtW;RxbzE Q(QhEKEZ(Z-K@4Z(} (n +cs= =`Ţ@EUQM%QEA'EQEZ(.(a^ ,)17pv`wGa%TP/JEEqhDƪ-6Cs}ЫUQB"(*( W"fS"$7uV6-ht]E(QEbЍ@(Šj@ EZ(EC&O[&.mF`WG`}ĴƊ-PLдZ(Z/BEQDZ/BmQEEqR`mmI(SJ…CJb,ճ-EQEQEZEQkZ( 4QhE& l%8#  b(((ZQEhZ(EHMQhQzT1@"C:{p=TY^LSoisB7E*ТԈEZ"CZ(Z-wj-T^&{2F. A,6eces1L&V(hEQEqAB(n(q6ED3Z5\ĂB '?N4`@́ AZQhQTQEQE %E-0QpjEZ-G0I%_xA ɧOv-9'`" W&0A2{2rD*9?-Z(EQEQEQAb (EUP"-7E-Qa!" oA@2 Z&!rY "PEQE((( (Qht TQh(9!F,D#Z/BhK0 DQ>>j6(%>S{j. otN EQE-QB+EQhQhDGQ(BEQED^߽((] rp@J z6͈jK6{%E<'-Z(-QE(QhbUb(QEQTQFXJ:B+(@@?OJ@ G\&g29>`X$>J(Qh"(U(QEEQEQE/B`8hZ* VD#.c2#{. b@B(*((((DQE(((EQ@!V㈢ДQL`@3!6IRd :1(oǾQߑ\:}r`~%aaCNB,F[uˇJhB(Т(((- (QEԴ((j؁&R@& o\ET`H}o& Rr(EvΟfqhQEQEQEQB4QEQEZ(EQhS(7QA YpNKɫh:!Y'-1_r KjU)y**@􈯖 (- A(!EQEE(0(()PLD\+twPk8O`%oUeadWS3tQ`*QE^"QEQEQEQE(EQEQEQEdP$!PUJ >6LlHʀ<U &(nE25 +b/;F17VDT 3QEUPzB-QEZB(EZ("(-QhZXe!N ;T+^n 8,fFr&dƙh1[OQh-0QEQEZ(((EQEQhZ+(W`D; V ģD.hqD 9p,Ȋs;P80͍ TXVv''@hF(( ZQE(((QE-{s"e3iBP9I<\/i|AE+fHf$`g.R9B~qɃl(((-\`hZ((|GEEhQEZd@E]KLv*c_N`+qDaC0@2=1+ MʄA((- (j- - (B H}Z-H @K? B#1a`WS+1AuG# ډ(- ((ТТ-Vнj(BТZ7ψ -C_95i G$N6 >ad' (0޲E_vcF (܃6X\QEQhZQ^L"hr&8;f-V(j(P y_QLC,V⡄<bXxEE yg@{"(aR82-n%Џh>3h DQEQEQE%*'N0LDّ+4@^Ej(-6ïM%֢G.VY,=UDXWdQ=MTd bH brPE#DF0XXѐ]%c g8j(#@9*}e"0! F"`:"Т()HQ@VqA[oR@r0]Jxw$>,]77p-h>HMyP9=V+wQEQhD""z〞3rxq |9%: CK3%.hIf+rWoFR")-|'DPJ|f0~΀ ہbdG0qAG~(QE@_ } .{dG7av ~ >Jc+ 8@=vH;!zaG l1VD;%xyDlԀ4\9$DcR(EQEZ <#( `,eSA5|YpV؋`|S,nDјnfrd C;AT 6a=6a9&C oCdšE:p0 TPȶ ,O`cТPT"Il`" VQ/e3Քl*Ss׈cT SfϺÉ09({|At8#o@Hp4na#4gGE ܇cx`C|_^"`O F@5 i{ 86Uĵ.lTt^,I0ɕp]7$' ճ)`%X&m$Ţ** 2x:P8%G994$" ̰ChLT0P`dE*(Vi\ʌ `@43DEB Dze0E*EQhQEL`xV(9x7 d% KJ7B4 "M+b@' Zk(!nv["-9lH85 (ߘv W^sNv9# {pC ;ų⿗҃X@[}ƏU! im8b `ՄvYcO={=-c)9 ÝwBC!lP#K('$)PN*F v@ֲZ.Rlzm?oEP$~" 9W#o8댔x2Ą l 4(r QO,:so\@tn8L|!Jk(ٟpN3QThkCW"J-}2`ǕnѸM(=(015BP%[2/.B`R |<)Ds)o Mgge"#%Mw興VaDCB_8F,^ $0@ƯRx>oR,$7(_QfRnC\g 2{:$``. kTɂy7mEܮ[;ȂByȨ}$K6, /Wt2.@p\ '!L;Hh@P I 6 #Vt p&4$EۛU+$H3 jadg$xhr{eX>tr  [!}J5K`Y;'$56 #HJtrvRXlع&OcP jfh9nq Hwy2`\mq &˳F,ρ 7D28nEa4=K_#!B/DXxo `0Oeufp>aŋa07 &WTFl)fwx+;4HmhDؐ 23Fy>#2a B`_Գ!HAgu htgM$XgHOF("t^Hh# Lly ha\lNRj 9&g} ?8q܁3 K*`VEhwV{T -̟1n@̬X'<3b/3wBX_)RȺ>qGx$LnA-"5 Hj\$v2Z^'P !0A YJIbO5Pr d 8(8 _A *M'h (BXC0 )yYa-Y`߼2TO7C @aZe/J.5WEqh|QM}^.j7[#Xqb;`.(3a0DtL`,EF$C>h `)+" {BD6 ~dkPEV? 9n(?`Jb"6G@_Ņ* 1@\lN8*g>f@VTloU`&p6}hH,Q~O-۲wqU܁ (2d T*e8?ȇ5̮z c}H+`~|6f{5( 7M pAjgm($PA`nb @3a'͛1E2r|òm|DtnXP!Q.<#Ow '#MM=XqE+86 :_pnEr 5~;_T@~&Q'?dDH.[g<@hH"fQAuغ#=7(lh]`8p$|uV6n@~؇p, x!8PJwL2"P阺>̱3BY$s"tH^i|(23u ҋEcp2{FBxB%ZVƛg9 C{ej`|@EWo~A2<CL% M Uc@5_\,l?˧u!o b-'C0&!0@iY[ăO357LZ 5]@׉Hw8Z*b2z%OIJ q@9p3l|@A%yŘc[Y $' H0*Q{}A<{êk+w <0D5 1jv Y`$i4Rr NJmFA9 8RvWOXSA xAll'!D~ 6JDt\ .yJU8[F` /4g x<b lc4Z'&Ʈ(m/4^8ɖ;?\TyF/Lqba2"3)n"%6(ڛ.ꉼ]`kTPJCyQp KoQe'o`(lH9HtÉ:xw*[ej0#pnQ+ie%gA߸SߐjV7Pưmd*Of {K K۩Z`k'@V >G:3D (U>T8CGbr 6UL^'wD{_DXvmGgvm qZEܒ"m#bNd" Lm*RMDJak樛/֪G,AwGj0V/8cUG,t2 88+Aаd; PDpbbW 4% dǼIM 8oof`D#/0%r"L;1DN)@&\ NoCxDh ;*iѤqV>z;6g3FN/B  ``g"JDgL5x.aZ2qc0Э D "b !PK;1D/Dx9 `|%Jk,)'4辎L=J?a 13m9%Uk!z&{TN"GOhW.6`AUp\fIFð"pbF#C4 D\Gh_Ōho^]M. e n'נ#$d!h8'cO8+7$KFIJ0vrl &p\<7D9t,i{v{Bv o/+f'̧m+#a+w3YOIJ¡u M`Id2RIɀHBWR!omKE-iB`$Ppހ7]>PA%-XQ`Tp"PpEL ՜E7]!NiD=8*pLN|vYGH,@m)=m ՛]b6͟[.ih> īlnu>P/?ե\ҎC`/q}žߠ<$Mhl1W˷!¥NOkoXi;3oo6AHO%oQ2モO}m/f'zNvokۭKowcu }2ooo?&8Gj{Kq"(C8XJϫM~{ʷMCߠKz]v0W47]eƕ`ՃKo|I./I'%vZ[Ћ6 {.a"=?y?J6T<$6%[5܂y+eh ґz/ \"[n䰓xa[dQJB%]]2$@$ \ܙ:ڳTtGcj,Lkj́%5 A86ˀQ!aw+]0GuwM*%c}K'}Qb0sh 43g bItʐ%0h3Uͫ {QfC>VFX2`dAih!`YMZe`_\{8ۗ%r jÈYL@y<1GEeI} Ȃlf4o:OiGܸ+ˍlǺFvO,\oe!vԐפ"N-Ay&|`LbOb RCxmy=9 &b 9~f1LzTV {+Iqrj6VN]A!;| CbHPNN20GIuFhTlwKj |t*~EeBW'z =/=9(!1 AQa0q?RùD2yzAosbJ\&6l. N̡⡼x ҔB~ X[X☇!{Q_:"Nb؇-_>x\1ᔾy{^JQ&74Kv=H 7DnD؇={<:5Oyד=/T$^ !SИG<.zL_Ctb#1e(%5K _Q3n2,po~_X𥧢{!&XEEwPDtqK2OCOXXHXcA,D618YW-"b(N G7y\j7J~C7Naˣ!!qs\s~S,+)a㢷 Ez3K˗b/#W\9ͶF"grQ tN9S)o/)KҟVlZ.&sOLAaɽLN)>Yb^0!߂ JM yE[_!~3b(ņ,&S|RR~&P)̽7 Er{Ax? X_'|9·ARq$Ax]hbYR:vEt}c=a,B.xty:Rbeo: q`iL} Lb)pؼQw Ox}yRøLxTXę)T{2k$/< 'HC- {)Y2)+7- F"=9/;0!x<"/+)\ .ƝMwX,,LtegмB olѯ;w_IqbNЋ@H;S.!1QxCF MHcSIGg! e7%ގ ?TXX&h1?<\AlAr 6,L=="ba*T#bo\k׿ZrM|>$l0Ɋs3)sK|RO"xܶ1T%< OWȚ48Ru3NY_< o06DH Hw!?}n_?1<.f#8! 38s T4>hZ]IdW]ro񸘂!>>eg14N&&<45h>=m~YB3x1,Z5EyD{J85x5B{D Nu%: &Q4AމTP濢6" #v-Vpl?wc$mI ԍx-?ha) cw3 _.dzV; !*bBD5%[vGwW(?ɡ5D#d&5C& W nBG'bRSM$}Bg;дa$DѲt"]:{'I BDEl] 5_(b_|ph5=և5ҧ=ec= cłthH64h̅c{CJ5G!H*8Ӧ&.k QA"|>%bBB{/Ĵ.B), 5°6,e+cC1A P1A(/U I +lЂ6obtct*& AnMAr6&h! R%mP"oEb;")_G5:987HTS'pc]4jc} vB+gL/ldBFhj#]8;>f6H'RhGCR'X7lr؋ifƶ]4BUX=h#vDq;QL/kXTZX{"DhTtHߣrQӈeGj0еT7a^-hRc4QT5po&l0D="\&b?|I)[CDѝ̸gf4&QjHT1ױ#_k5Z*rcwcQ!%Ii2i~C_{]oH.5Ss鑲 d- .gu0DDkE7o&ٳC;0i[/h]b!6" C|!r{7EG)!1A 0Qaq@?D'g؇QH?h^&8  J,Cߢ3׊QN! -v6&E2n^=wLr|oc5&X:\s'qȳ} |f)}R Q9W| )VVݯh_nl0W轘i/Xb)|>Jv|gQxEVwtN`ڟw#j.[[6\_BǂF͆$?ɥϔR)sJR払7锥J_:\JO ZT#RC}h77Ɨ7wCb)qNKJRc6*wCyÆm!To +)rR戥/)KR[F%EhA ThJr_R券OƔe/(JRo};A5H~{>þ{負kH?#e(|)D\R┥)JAJR+^ĺS6V+0c&ehQqf(i|nn)|.)JJEŸWxpr)JR9iJR)tR K,XiKJQxivKw%FJM ]JTR\QJXRl_/)p)J{PCBҿQ!JAڵ6͗)JRJRJRx(2 풔9,)|ƉQRlOkF_#oG8TT\”#o nAKQJR”)qJJRCuN_kh)Jk(O {'ܦ1R.Mv3Js )F&VR)JR UInI)gw-6CB;v4kD '3VC )[P'02S4RwQ"L{1#P8%*)JR)JVQKmASmfZئwrD\vJaDB8(JR.8U]{ | ۯj)董!ؕ'JR(ؙKdwb;")6!GLPb(|&픥.nn)D˚QheCb&*I_I"1[cECI$\/)>1pM+ !bfܲv" %RFzK"ؒk48A%H[D*'Rp9sXm'Bh;9X;7Z>E)J+Nű7Ar!b~ \)QHzX+OٲSM )tCD"2G~C*F\&5M4*&i"zBE]-A3H2^ -^-Gݔp2|YQsI;%laǡ4oHQZ; ,JJBTPvU8ˠ]#v~`shy] -^,887`QhUp6GpmG"NiُǔmMNׁc d-&Ǜ#+4\$ 5pƍ. S@Ml\{J4ŰA&% 6šCK=mbݯBd.Bm@D=Hؒe n6s}>GpKX^ 21toNJ~Ґ'; 4kX{nf[b[\ݻ(Y1)&Ɛ"ʈrRДbE{ : *lu hklDb:׹BUEGGssbc=4|I!CE=mp LIq/aiD^r-ҟ= v4lS,K> 9PN N#׽IA4AD>Ht+q.Dmn8Q!Y/nH c:cK]bV$1B7Wۑn)ظيnbD[hcu}1t6A+*c;LԒCT%] >#r/-&KP@ T-;$m1$9v4h^h3l A S|m 5X n$5k?'!1AQaq ?-{Dn W J(mr`cUe'~ԥT2ĻW*a~3 _@d`+ (W >ɭ WsBwzr=p{]},&Wq:yp12M"Z̵cF)1y ~>UW"o4l;{%~ȭD|Op9??u5P_cO> A ZB&P cuЉby&DG{*p_ol%|KOż:,;xU4pxoeU0=]kte}%We`T~G-d8no楀-W;1Wk :AG^y)WQT~lr;}_q e ,%*\FUP?ġsK. .#K:}."+JS|M -o-Opԭ1%pIN٧7(g`6*עQo0{6 qQL ly?Xiy~#FUňQp3Kd@(,ߘ*}9v(Q 5߈T+|m7aOǹR?/ۘ@HX|nBXV/7!ecZH0(MaPUPM_t*X]_0J1F7^ny5޳RJ% ۩ĥܹg//C{~!¯2LA/̭>%>t3C5x%.ꐮ{ J@Ue ~ah[+y!_x1OX7o%y:?y>A̪qL#!H'*oA[0̆6f_[:y \KLZ9ڝ%P]39i ,P  wN{R2)KĺF_0=KgxU%J2#E%4~Aaՙ(Oi@~~/hySlA*+C ,kc>W%#yځl ܢ>̔:oRbő@ԅ,;V-iر]SظU(~"^%o!Oz7&^.5)Yfsf?ke]U3^ %]~bGN~vYg {So[e '\9_¿Пp\JCcFi+W̎~4"*Q*Q-qw-"+*|o&/8=qAZ*%_@sl*Prt|=D~>-oJ;߉c%]oӑJ~`PQHb~r[9{*ۅ*IP,~y=(g2|2>lANW}dJTĝ;.H(yue_"YFrı*)ωxC QrMd-P+Qܦژ~+A-l|a >*܁p2ݕ4c~TL/_0,>o8gDaK8?1DvPR[,Jf6AGtF-]r`qZ%_B>&*W@S*1J+bQm_ܫJ{ 1)<9_0SeQ~]yKJ=.%,˘n]ŷ~_WQNĺY+oT="TJ([ ٸko+90.AY@64#ɕre.ԉ`5pM_RL_@<azH76^r`g6+>_0_\u/`2SiUWP5/>_{ B̩co{DæJUٞ',2^E|!' -ʊ\0TP&)/SiMi FP-K{ta,d Re/ JG̪"Hg'ca>*7v[q?0B%d3@T(UaY٭ JU+L V p>\a tcl2į'aYU}s`8%#l/#~U/Q'ԗM:Oij|rB`Zb\hf֚< oP'<^|HG~n2SR{q /TރnyQjфҧNyW3h@z%Mxg37ԡ>{.?}bQl=/3NdE<|A !ۋSXZMhJSbE\ D <Q<|?3T9Ԝ݋GRh *,`m cB &PE_km]8M2{7Rm" 5>LhS rw3|?])`!C)D UFxN҈!F"I:Q|J>ܤG4~HRu]!o _EM*ɢPWnYWeZ`Ս##zc`)JV =-T[g-|rcQq:P6aB>=@Du[)_pL Qbv,bUd3Oʨ <{wy =Rv~vpr[YC ]*%ZoW]e!S!7thv<-W Y%xh[qlO<5 Tj" K9°ψܖ\*Yw 9o ONOxFxk?1F'GKƳyf㬆4O(?+ {ȟ0[~OH1s]yW[F)s]1fy30, LkC%$iEkcK@BzjѸ˘@y1 t(Z´X]ReLl UӼl6k JG2A˖Ǐ_q8r%);.|U>̲9qL=yJ2 31y2J}Mml\@BwJZcRvp-,Yd&T+A` |)Jը(vBk6Z4&hM>[Pɯ.7f>b0 >?O'Y6^Tga[BMBt~/M#PG ~cy~_Q7sO5@'j,)OxkO@M =DBKԠ1y+Yw[p>qAK^|N~%Q/ڂB]ވuU*(P/-) .?3sF]3 @BեI@kits˞{}fo_g~*`Px"|~bQGVW3OS93o!+S>s*!P) T9*>b;=^1n7B8\^$ ZWU b 槣;kgn)tخCP3HІ^#C'e(!ر5ѳa?ܷ|O+3f%z39Yy QK?U rQ\ '\e|!IOu3]]VGSX;Њ:Ca^C"L+ijӭ%.+TGū\)fX';6,(+P%8'" 2 ?Ǻ& fA8_YgNCpG bq.MC_1E]_ xM{`pG*AqP UG@S1Nijn.kussۚ}ފ)ԘPTXēbRicؖp*"ܦ hWȠ'|AsqTkcz*ْaqD S̷=glL/!͋<2ljf5!rp; -ʆ 6 ¬@++|^ zp3l@!WҵY(J,%JTMEE w ]u;9 ,_nnf |Lg?yR*UgQ.0G_W8*i?mO;SŸM,CU~q>avZÐ)aGf=脢; x )(Ah^Q^?#ToP4"-m.j{&Qi`AE EMU"OQhͼ98!yxż\dp_0.e~S?^ \>ra(W*3bRyoǯbKYx%o`YjeE^iǻntK/L"z\2pT WQѡ:@ZKC;!l55W!fe_6- "b""{^B>ϟմC3g&=HIh= > GԯdL{s JmI5F9WxD2ꍫW~XRRkZJP<)dOĨC'^-pj^(zDh4qEĖ0xr(S~#TGﳸQc%B¥C9EF`+ЇbdS^9^S*OxJ 53@!)NFY$p*1\MQ`H nͨziڳg}\߷&ثtڌrbRp3WTT 1_fAeآArp}ih!Ϥ,ei I4Q1]lZ jV0!kT ,<9(wg%|?a*e|M/fɨ\Z0%C A"g'7m4zd-{ 7v兤T i6 (:5ڠ1x4P<P7Vo (4o*XIChhTFԹ S~6N|H.(e Xс2&,n-emUr8S*ve^#kug(B 6+8413d;ĿQ.1OHX~_?WKMbFSnkuwhjϋWO^-Lr5I!t3=V`웡eZܑ.VۅaOp pStlD@jXx-8W(ߑW~ܴ<"vD|3 BwU$p6ZuD4 eŻRe#{\jQ)Lg%/Pqk[A- v')B*nT@Rkje=4Gw-PAt<#Pl2:DxX0ه"Wh ,vX4]uFPb0;hUf>RTTvj?t hj ݽ [Kk1XR6`ZVz@PxGP#HJlJn$fR$J[8V U `X4-`Ui +CTض –6%7`3*O1 YM*WH.)Jxh) Vƒz !{ݠU.u`P<'*. 8E%Į^ _G AATm|WDJ=5Hj-,+K#7\VP G6VVЄ_ۮšQDcۊTјXZBQyL+ *w@x% bVdTu06(L bFU AkaTȋp km)v%TJ%Fl 3?Pcp[7N'ptm%jvHl$5SbJՐYͭ2 )G@)W.beꂓ)ݎ +/Hv?d7em-*WK:7$+E躎 Nq۴]~}V.HJѥm R(]},QTUKQ ]Ɩ 2V-d Y~ ND_2hP(hAQFWPx{gj!VQQ;VE0 uWZa XCڞfWJۘ #]$LF!o )Hgje h&EiPkFhHhjñM, Ȉȝ )2cB)Ic*q~"G¬C?B3Rx%bU ꁚBMPBtDPE`R@\-@+'QREZAYiB.Ԗ]Xl/`@WtZU3Y"<~. 4.pYo9uwlT Ai ݪA`>3lA..OvVb"תp A^Kj1K h ;eh. 0enZw`a{W)|AX+Eª+nIP[E4QZҗK6.'ejHKeZ7a[)kQކI.yZ^XgnGw APV튒R-""_dfk@BQR&peahݪEڕ LQ@˿ J- T@/x/Vkk dA\蒃d*䀮yr¥ ֯El976z8q)Kꫨ 3R]ƦU*A`ӐUjV4 TT6kWš8Ytↇ1D\|dj|("׫Ԡjg3`h-Z"phu8i(0#~h⤄M==Md*,Z7 b _J+)L`銂lbuT `%Wǭ?} ,8PEaصx ReӦ4 3uʢ{t}Wp|#z?0j Px"K^DžF=,VwΕi7V&] /#@M]mXVЃTqCRo(l]P9[RmS$`"(r[eTRSkeSӧ'-$CjZsY87 T|a!Bn 2if KE#7L*!Մ֢ #.ӽՄ i Ea )Ȕ"]~_؄cqۛq܀Zӟa|BW2κ4F|'l+gG_(*4m% paCT􃯄,Ÿ>#r (d8Jk-d) 4^+2I,]qK,Q BZhT+c1a8hë%pqB:6ӂఛW yPUp(Ev/e u +}aOE-vju[Ʊ 7^%j5=?4a*åm[iӉʅ!tiaZ/ 6߳' Vg9Z:ꕯsQ8+}e%Kzc(UzO J [zxFr%(F~c@BaiZZ,6^Yae]ז HD.M i) ZV1/6|&!8£Pi_+ǖZ4 /ϸv Hkp DR%VS  (?qAD`3y[՚)v%;6BTGV.h6M0d`j9BWy nܪV4N@ ]5-(>* ,TXY㪆B/+UQ(koA NƏBR8BqJ-.d1 pX7V=fG4*Zi4m[`/_4SVT]j⒛E50J4ABRD`RDoM]X 8)F}4tPoU1V[L]chWkQ0uCTiJ.)JL*'[JCCH-t'UUi _~s@ vZվ#]y* z"ޘ-rfTL8] u_2쏽i",U')dzr%gsBT GTU U W@ vC8ŸqLmhW)'2^7RfAr{Za6.3'^)V8y. ޗђ܁ k6 -4AUAZK1SfU4B'€J P68[;qho0\ŸMjd7P=O9R{kG*>EMRRm*[EՏ(`bFU4(E6xl3&=(2I|pEkf*X݃:WizXUXF5u۬AkTZx8 .WVX(ԫiiEIٲ~X \7?RQmAO" a @/^#>qztۧ)] -e(R2'h.}Jľ'e^5̣ vZ: (^kR+i{@5o ,%T͐]o r`߸BӇ+Mx;Db}h..8:V/'2 .)hiƕo|y#ey`#`6CC  F@Z\.xB{dMҍ`+x ѿ?MBXt VJY`R%H50*"K/v^ tx x">tsJUsVW6,c lدowV n\),8-!MX;T^9VmI5R JVg_0=ΥG$R^Pym*ku2 d`.-@xw 3 ֫EzE%IhH}C|]ۙpUYhQK@:}!s%CKSgIZYCvZ"&R-Z5lil t-mZ+ *80 U%tm<@ (XU6 Q0jD `M!0'"Cnc"#PK(%fPz&] ͣ$@e"qkQxAEdK|j}{1(q7rFT+ݬ"m 0'zmp)3L"WǶZqr[G!C< %^շb/ !`OPh*]MPCC+sD."Pbk!ZE^TimߘukP)'HD d.U@ _j!@Oe1ȨmE]!([4Z*´T%.X„Vѳ{TS@ᠳLAc^ я*@-GF޸(e$y%> rq A ֥&pQcLtD*\V ,sȨp/c[E0STB=kn ڶaP[C *l {z(Tc :IUR1uSP^ƨCv7²ڡ<"|pEcYUl4X@:=; (J+ayX`l!yj/v6{G\]-t!m)0k+Pl+d}0p;,k/A+b4zB&SŊƒ%B֣31e-]i#F<'.5eiWd\5i,FE3@M(RI0oTڅYFCB5ku<AuCO1)g|* 8VGڄ5~$Ea1zh9-#fRWSiv%")FV.d!eh^&8 WHI2!(](y@.- 'Wad^<Jն /Oa@}@#˺,fH1RP[/Z\W8!G.!t+ͺ$|P|/w_} d9텗eiϙZSȿ g  9|gA_QJt>ȐJy/,lP9JDT !vMcUj CWgtٹh͘ 5ZQx+Ulo:(JՀ#`Pף(*(9nq 0[Ovu:*8Y[ޭG[N+" 0ؔx}{"E* ]Y> zU.PYe2~JS-w]PE]I"$ ꔤ7"x' bR@-H), CATj /U+ ! { write!(io::stderr(), "usage: decode image.jpg image.png").unwrap(); process::exit(1) } fn main() { let mut args = env::args().skip(1); let input_path = args.next().unwrap_or_else(|| usage()); let output_path = args.next().unwrap_or_else(|| usage()); let input_file = File::open(input_path).expect("The specified input file could not be opened"); let mut decoder = jpeg::Decoder::new(BufReader::new(input_file)); let mut data = decoder.decode().expect("Decoding failed. If other software can successfully decode the specified JPEG image, then it's likely that there is a bug in jpeg-decoder"); let info = decoder.info().unwrap(); let output_file = File::create(output_path).unwrap(); let mut encoder = png::Encoder::new(output_file, info.width as u32, info.height as u32); encoder.set_depth(png::BitDepth::Eight); match info.pixel_format { jpeg::PixelFormat::L8 => encoder.set_color(png::ColorType::Grayscale), jpeg::PixelFormat::RGB24 => encoder.set_color(png::ColorType::RGB), jpeg::PixelFormat::CMYK32 => { data = cmyk_to_rgb(&mut data); encoder.set_color(png::ColorType::RGB) }, }; encoder.write_header() .expect("writing png header failed") .write_image_data(&data) .expect("png encoding failed"); } fn cmyk_to_rgb(input: &[u8]) -> Vec { let size = input.len() - input.len() / 4; let mut output = Vec::with_capacity(size); for pixel in input.chunks(4) { let c = pixel[0] as f32 / 255.0; let m = pixel[1] as f32 / 255.0; let y = pixel[2] as f32 / 255.0; let k = pixel[3] as f32 / 255.0; // CMYK -> CMY let c = c * (1.0 - k) + k; let m = m * (1.0 - k) + k; let y = y * (1.0 - k) + k; // CMY -> RGB let r = (1.0 - c) * 255.0; let g = (1.0 - m) * 255.0; let b = (1.0 - y) * 255.0; output.push(r as u8); output.push(g as u8); output.push(b as u8); } output } jpeg-decoder-0.1.22/src/decoder.rs010064400017500001750000001311151400431655600150550ustar 00000000000000use crate::read_u8; use error::{Error, Result, UnsupportedFeature}; use huffman::{fill_default_mjpeg_tables, HuffmanDecoder, HuffmanTable}; use marker::Marker; use parser::{AdobeColorTransform, AppData, CodingProcess, Component, Dimensions, EntropyCoding, FrameInfo, parse_app, parse_com, parse_dht, parse_dqt, parse_dri, parse_sof, parse_sos, IccChunk, ScanInfo}; use upsampler::Upsampler; use std::cmp; use std::io::Read; use std::mem; use std::ops::Range; use std::sync::Arc; use worker::{RowData, PlatformWorker, Worker}; pub const MAX_COMPONENTS: usize = 4; static UNZIGZAG: [u8; 64] = [ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, ]; /// An enumeration over combinations of color spaces and bit depths a pixel can have. #[derive(Clone, Copy, Debug, PartialEq)] pub enum PixelFormat { /// Luminance (grayscale), 8 bits L8, /// RGB, 8 bits per channel RGB24, /// CMYK, 8 bits per channel CMYK32, } impl PixelFormat { /// Determine the size in bytes of each pixel in this format pub fn pixel_bytes(&self) -> usize { match self { PixelFormat::L8 => 1, PixelFormat::RGB24 => 3, PixelFormat::CMYK32 => 4, } } } /// Represents metadata of an image. #[derive(Clone, Copy, Debug, PartialEq)] pub struct ImageInfo { /// The width of the image, in pixels. pub width: u16, /// The height of the image, in pixels. pub height: u16, /// The pixel format of the image. pub pixel_format: PixelFormat, } /// JPEG decoder pub struct Decoder { reader: R, frame: Option, dc_huffman_tables: Vec>, ac_huffman_tables: Vec>, quantization_tables: [Option>; 4], restart_interval: u16, color_transform: Option, is_jfif: bool, is_mjpeg: bool, icc_markers: Vec, // Used for progressive JPEGs. coefficients: Vec>, // Bitmask of which coefficients has been completely decoded. coefficients_finished: [u64; MAX_COMPONENTS], } impl Decoder { /// Creates a new `Decoder` using the reader `reader`. pub fn new(reader: R) -> Decoder { Decoder { reader: reader, frame: None, dc_huffman_tables: vec![None, None, None, None], ac_huffman_tables: vec![None, None, None, None], quantization_tables: [None, None, None, None], restart_interval: 0, color_transform: None, is_jfif: false, is_mjpeg: false, icc_markers: Vec::new(), coefficients: Vec::new(), coefficients_finished: [0; MAX_COMPONENTS], } } /// Returns metadata about the image. /// /// The returned value will be `None` until a call to either `read_info` or `decode` has /// returned `Ok`. pub fn info(&self) -> Option { match self.frame { Some(ref frame) => { let pixel_format = match frame.components.len() { 1 => PixelFormat::L8, 3 => PixelFormat::RGB24, 4 => PixelFormat::CMYK32, _ => panic!(), }; Some(ImageInfo { width: frame.output_size.width, height: frame.output_size.height, pixel_format: pixel_format, }) }, None => None, } } /// Returns the embeded icc profile if the image contains one. pub fn icc_profile(&self) -> Option> { let mut marker_present: [Option<&IccChunk>; 256] = [None; 256]; let num_markers = self.icc_markers.len(); if num_markers == 0 && num_markers < 256 { return None; } // check the validity of the markers for chunk in &self.icc_markers { if usize::from(chunk.num_markers) != num_markers { // all the lengths must match return None; } if chunk.seq_no == 0 { return None; } if marker_present[usize::from(chunk.seq_no)].is_some() { // duplicate seq_no return None; } else { marker_present[usize::from(chunk.seq_no)] = Some(chunk); } } // assemble them together by seq_no failing if any are missing let mut data = Vec::new(); // seq_no's start at 1 for &chunk in marker_present.get(1..=num_markers)? { data.extend_from_slice(&chunk?.data); } Some(data) } /// Tries to read metadata from the image without decoding it. /// /// If successful, the metadata can be obtained using the `info` method. pub fn read_info(&mut self) -> Result<()> { self.decode_internal(true).map(|_| ()) } /// Configure the decoder to scale the image during decoding. /// /// This efficiently scales the image by the smallest supported scale /// factor that produces an image larger than or equal to the requested /// size in at least one axis. The currently implemented scale factors /// are 1/8, 1/4, 1/2 and 1. /// /// To generate a thumbnail of an exact size, pass the desired size and /// then scale to the final size using a traditional resampling algorithm. pub fn scale(&mut self, requested_width: u16, requested_height: u16) -> Result<(u16, u16)> { self.read_info()?; let frame = self.frame.as_mut().unwrap(); let idct_size = crate::idct::choose_idct_size(frame.image_size, Dimensions{ width: requested_width, height: requested_height }); frame.update_idct_size(idct_size)?; Ok((frame.output_size.width, frame.output_size.height)) } /// Decodes the image and returns the decoded pixels if successful. pub fn decode(&mut self) -> Result> { self.decode_internal(false) } fn decode_internal(&mut self, stop_after_metadata: bool) -> Result> { if stop_after_metadata && self.frame.is_some() { // The metadata has already been read. return Ok(Vec::new()); } else if self.frame.is_none() && (read_u8(&mut self.reader)? != 0xFF || Marker::from_u8(read_u8(&mut self.reader)?) != Some(Marker::SOI)) { return Err(Error::Format("first two bytes are not an SOI marker".to_owned())); } let mut previous_marker = Marker::SOI; let mut pending_marker = None; let mut worker = None; let mut scans_processed = 0; let mut planes = vec![Vec::new(); self.frame.as_ref().map_or(0, |frame| frame.components.len())]; loop { let marker = match pending_marker.take() { Some(m) => m, None => self.read_marker()?, }; match marker { // Frame header Marker::SOF(..) => { // Section 4.10 // "An image contains only one frame in the cases of sequential and // progressive coding processes; an image contains multiple frames for the // hierarchical mode." if self.frame.is_some() { return Err(Error::Unsupported(UnsupportedFeature::Hierarchical)); } let frame = parse_sof(&mut self.reader, marker)?; let component_count = frame.components.len(); if frame.is_differential { return Err(Error::Unsupported(UnsupportedFeature::Hierarchical)); } if frame.coding_process == CodingProcess::Lossless { return Err(Error::Unsupported(UnsupportedFeature::Lossless)); } if frame.entropy_coding == EntropyCoding::Arithmetic { return Err(Error::Unsupported(UnsupportedFeature::ArithmeticEntropyCoding)); } if frame.precision != 8 { return Err(Error::Unsupported(UnsupportedFeature::SamplePrecision(frame.precision))); } if component_count != 1 && component_count != 3 && component_count != 4 { return Err(Error::Unsupported(UnsupportedFeature::ComponentCount(component_count as u8))); } // Make sure we support the subsampling ratios used. let _ = Upsampler::new(&frame.components, frame.image_size.width, frame.image_size.height)?; self.frame = Some(frame); if stop_after_metadata { return Ok(Vec::new()); } planes = vec![Vec::new(); component_count]; }, // Scan header Marker::SOS => { if self.frame.is_none() { return Err(Error::Format("scan encountered before frame".to_owned())); } if worker.is_none() { worker = Some(PlatformWorker::new()?); } let frame = self.frame.clone().unwrap(); let scan = parse_sos(&mut self.reader, &frame)?; if frame.coding_process == CodingProcess::DctProgressive && self.coefficients.is_empty() { self.coefficients = frame.components.iter().map(|c| { let block_count = c.block_size.width as usize * c.block_size.height as usize; vec![0; block_count * 64] }).collect(); } // This was previously buggy, so let's explain the log here a bit. When a // progressive frame is encoded then the coefficients (DC, AC) of each // component (=color plane) can be split amongst scans. In particular it can // happen or at least occurs in the wild that a scan contains coefficient 0 of // all components. If now one but not all components had all other coefficients // delivered in previous scans then such a scan contains all components but // completes only some of them! (This is technically NOT permitted for all // other coefficients as the standard dictates that scans with coefficients // other than the 0th must only contain ONE component so we would either // complete it or not. We may want to detect and error in case more component // are part of a scan than allowed.) What a weird edge case. // // But this means we track precisely which components get completed here. let mut finished = [false; MAX_COMPONENTS]; if scan.successive_approximation_low == 0 { for (&i, component_finished) in scan.component_indices.iter().zip(&mut finished) { if self.coefficients_finished[i] == !0 { continue; } for j in scan.spectral_selection.clone() { self.coefficients_finished[i] |= 1 << j; } if self.coefficients_finished[i] == !0 { *component_finished = true; } } } let (marker, data) = self.decode_scan(&frame, &scan, worker.as_mut().unwrap(), &finished)?; if let Some(data) = data { for (i, plane) in data.into_iter().enumerate().filter(|&(_, ref plane)| !plane.is_empty()) { if self.coefficients_finished[i] == !0 { planes[i] = plane; } } } pending_marker = marker; scans_processed += 1; }, // Table-specification and miscellaneous markers // Quantization table-specification Marker::DQT => { let tables = parse_dqt(&mut self.reader)?; for (i, &table) in tables.iter().enumerate() { if let Some(table) = table { let mut unzigzagged_table = [0u16; 64]; for j in 0 .. 64 { unzigzagged_table[UNZIGZAG[j] as usize] = table[j]; } self.quantization_tables[i] = Some(Arc::new(unzigzagged_table)); } } }, // Huffman table-specification Marker::DHT => { let is_baseline = self.frame.as_ref().map(|frame| frame.is_baseline); let (dc_tables, ac_tables) = parse_dht(&mut self.reader, is_baseline)?; let current_dc_tables = mem::replace(&mut self.dc_huffman_tables, vec![]); self.dc_huffman_tables = dc_tables.into_iter() .zip(current_dc_tables.into_iter()) .map(|(a, b)| a.or(b)) .collect(); let current_ac_tables = mem::replace(&mut self.ac_huffman_tables, vec![]); self.ac_huffman_tables = ac_tables.into_iter() .zip(current_ac_tables.into_iter()) .map(|(a, b)| a.or(b)) .collect(); }, // Arithmetic conditioning table-specification Marker::DAC => return Err(Error::Unsupported(UnsupportedFeature::ArithmeticEntropyCoding)), // Restart interval definition Marker::DRI => self.restart_interval = parse_dri(&mut self.reader)?, // Comment Marker::COM => { let _comment = parse_com(&mut self.reader)?; }, // Application data Marker::APP(..) => { if let Some(data) = parse_app(&mut self.reader, marker)? { match data { AppData::Adobe(color_transform) => self.color_transform = Some(color_transform), AppData::Jfif => { // From the JFIF spec: // "The APP0 marker is used to identify a JPEG FIF file. // The JPEG FIF APP0 marker is mandatory right after the SOI marker." // Some JPEGs in the wild does not follow this though, so we allow // JFIF headers anywhere APP0 markers are allowed. /* if previous_marker != Marker::SOI { return Err(Error::Format("the JFIF APP0 marker must come right after the SOI marker".to_owned())); } */ self.is_jfif = true; }, AppData::Avi1 => self.is_mjpeg = true, AppData::Icc(icc) => self.icc_markers.push(icc), } } }, // Restart Marker::RST(..) => { // Some encoders emit a final RST marker after entropy-coded data, which // decode_scan does not take care of. So if we encounter one, we ignore it. if previous_marker != Marker::SOS { return Err(Error::Format("RST found outside of entropy-coded data".to_owned())); } }, // Define number of lines Marker::DNL => { // Section B.2.1 // "If a DNL segment (see B.2.5) is present, it shall immediately follow the first scan." if previous_marker != Marker::SOS || scans_processed != 1 { return Err(Error::Format("DNL is only allowed immediately after the first scan".to_owned())); } return Err(Error::Unsupported(UnsupportedFeature::DNL)); }, // Hierarchical mode markers Marker::DHP | Marker::EXP => return Err(Error::Unsupported(UnsupportedFeature::Hierarchical)), // End of image Marker::EOI => break, _ => return Err(Error::Format(format!("{:?} marker found where not allowed", marker))), } previous_marker = marker; } if self.frame.is_none() { return Err(Error::Format("end of image encountered before frame".to_owned())); } let frame = self.frame.as_ref().unwrap(); // If we're decoding a progressive jpeg and a component is unfinished, render what we've got if frame.coding_process == CodingProcess::DctProgressive && self.coefficients.len() == frame.components.len() { for (i, component) in frame.components.iter().enumerate() { // Only dealing with unfinished components if self.coefficients_finished[i] == !0 { continue; } let quantization_table = match self.quantization_tables[component.quantization_table_index].clone() { Some(quantization_table) => quantization_table, None => continue, }; // Get the worker prepared if worker.is_none() { worker = Some(PlatformWorker::new()?); } let worker = worker.as_mut().unwrap(); let row_data = RowData { index: i, component: component.clone(), quantization_table, }; worker.start(row_data)?; // Send the rows over to the worker and collect the result let coefficients_per_mcu_row = usize::from(component.block_size.width) * usize::from(component.vertical_sampling_factor) * 64; for mcu_y in 0..frame.mcu_size.height { let row_coefficients = { let offset = usize::from(mcu_y) * coefficients_per_mcu_row; self.coefficients[i][offset .. offset + coefficients_per_mcu_row].to_vec() }; worker.append_row((i, row_coefficients))?; } planes[i] = worker.get_result(i)?; } } compute_image(&frame.components, planes, frame.output_size, self.is_jfif, self.color_transform) } fn read_marker(&mut self) -> Result { loop { // This should be an error as the JPEG spec doesn't allow extraneous data between marker segments. // libjpeg allows this though and there are images in the wild utilising it, so we are // forced to support this behavior. // Sony Ericsson P990i is an example of a device which produce this sort of JPEGs. while read_u8(&mut self.reader)? != 0xFF {} // Section B.1.1.2 // All markers are assigned two-byte codes: an X’FF’ byte followed by a // byte which is not equal to 0 or X’FF’ (see Table B.1). Any marker may // optionally be preceded by any number of fill bytes, which are bytes // assigned code X’FF’. let mut byte = read_u8(&mut self.reader)?; // Section B.1.1.2 // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code X’FF’." while byte == 0xFF { byte = read_u8(&mut self.reader)?; } if byte != 0x00 && byte != 0xFF { return Ok(Marker::from_u8(byte).unwrap()); } } } fn decode_scan(&mut self, frame: &FrameInfo, scan: &ScanInfo, worker: &mut PlatformWorker, finished: &[bool; MAX_COMPONENTS]) -> Result<(Option, Option>>)> { assert!(scan.component_indices.len() <= MAX_COMPONENTS); let components: Vec = scan.component_indices.iter() .map(|&i| frame.components[i].clone()) .collect(); // Verify that all required quantization tables has been set. if components.iter().any(|component| self.quantization_tables[component.quantization_table_index].is_none()) { return Err(Error::Format("use of unset quantization table".to_owned())); } if self.is_mjpeg { fill_default_mjpeg_tables(scan, &mut self.dc_huffman_tables, &mut self.ac_huffman_tables); } // Verify that all required huffman tables has been set. if scan.spectral_selection.start == 0 && scan.dc_table_indices.iter().any(|&i| self.dc_huffman_tables[i].is_none()) { return Err(Error::Format("scan makes use of unset dc huffman table".to_owned())); } if scan.spectral_selection.end > 1 && scan.ac_table_indices.iter().any(|&i| self.ac_huffman_tables[i].is_none()) { return Err(Error::Format("scan makes use of unset ac huffman table".to_owned())); } // Prepare the worker thread for the work to come. for (i, component) in components.iter().enumerate() { if finished[i] { let row_data = RowData { index: i, component: component.clone(), quantization_table: self.quantization_tables[component.quantization_table_index].clone().unwrap(), }; worker.start(row_data)?; } } let is_progressive = frame.coding_process == CodingProcess::DctProgressive; let is_interleaved = components.len() > 1; let mut dummy_block = [0i16; 64]; let mut huffman = HuffmanDecoder::new(); let mut dc_predictors = [0i16; MAX_COMPONENTS]; let mut mcus_left_until_restart = self.restart_interval; let mut expected_rst_num = 0; let mut eob_run = 0; let mut mcu_row_coefficients = Vec::with_capacity(components.len()); if !is_progressive { for (_, component) in components.iter().enumerate().filter(|&(i, _)| finished[i]) { let coefficients_per_mcu_row = component.block_size.width as usize * component.vertical_sampling_factor as usize * 64; mcu_row_coefficients.push(vec![0i16; coefficients_per_mcu_row]); } } // 4.8.2 // When reading from the stream, if the data is non-interleaved then an MCU consists of // exactly one block (effectively a 1x1 sample). let (mcu_horizontal_samples, mcu_vertical_samples) = if is_interleaved { let horizontal = components.iter().map(|component| component.horizontal_sampling_factor as u16).collect::>(); let vertical = components.iter().map(|component| component.vertical_sampling_factor as u16).collect::>(); (horizontal, vertical) } else { (vec![1], vec![1]) }; // This also affects how many MCU values we read from stream. If it's a non-interleaved stream, // the MCUs will be exactly the block count. let (max_mcu_x, max_mcu_y) = if is_interleaved { (frame.mcu_size.width, frame.mcu_size.height) } else { (components[0].block_size.width, components[0].block_size.height) }; for mcu_y in 0..max_mcu_y { if mcu_y * 8 >= frame.image_size.height { break; } for mcu_x in 0..max_mcu_x { if mcu_x * 8 >= frame.image_size.width { break; } if self.restart_interval > 0 { if mcus_left_until_restart == 0 { match huffman.take_marker(&mut self.reader)? { Some(Marker::RST(n)) => { if n != expected_rst_num { return Err(Error::Format(format!("found RST{} where RST{} was expected", n, expected_rst_num))); } huffman.reset(); // Section F.2.1.3.1 dc_predictors = [0i16; MAX_COMPONENTS]; // Section G.1.2.2 eob_run = 0; expected_rst_num = (expected_rst_num + 1) % 8; mcus_left_until_restart = self.restart_interval; }, Some(marker) => return Err(Error::Format(format!("found marker {:?} inside scan where RST{} was expected", marker, expected_rst_num))), None => return Err(Error::Format(format!("no marker found where RST{} was expected", expected_rst_num))), } } mcus_left_until_restart -= 1; } for (i, component) in components.iter().enumerate() { for v_pos in 0..mcu_vertical_samples[i] { for h_pos in 0..mcu_horizontal_samples[i] { let coefficients = if is_progressive { let block_y = (mcu_y * mcu_vertical_samples[i] + v_pos) as usize; let block_x = (mcu_x * mcu_horizontal_samples[i] + h_pos) as usize; let block_offset = (block_y * component.block_size.width as usize + block_x) * 64; &mut self.coefficients[scan.component_indices[i]][block_offset..block_offset + 64] } else if finished[i] { // Because the worker thread operates in batches as if we were always interleaved, we // need to distinguish between a single-shot buffer and one that's currently in process // (for a non-interleaved) stream let mcu_batch_current_row = if is_interleaved { 0 } else { mcu_y % component.vertical_sampling_factor as u16 }; let block_y = (mcu_batch_current_row * mcu_vertical_samples[i] + v_pos) as usize; let block_x = (mcu_x * mcu_horizontal_samples[i] + h_pos) as usize; let block_offset = (block_y * component.block_size.width as usize + block_x) * 64; &mut mcu_row_coefficients[i][block_offset..block_offset + 64] } else { &mut dummy_block[..] }; if scan.successive_approximation_high == 0 { decode_block(&mut self.reader, coefficients, &mut huffman, self.dc_huffman_tables[scan.dc_table_indices[i]].as_ref(), self.ac_huffman_tables[scan.ac_table_indices[i]].as_ref(), scan.spectral_selection.clone(), scan.successive_approximation_low, &mut eob_run, &mut dc_predictors[i])?; } else { decode_block_successive_approximation(&mut self.reader, coefficients, &mut huffman, self.ac_huffman_tables[scan.ac_table_indices[i]].as_ref(), scan.spectral_selection.clone(), scan.successive_approximation_low, &mut eob_run)?; } } } } } // Send the coefficients from this MCU row to the worker thread for dequantization and idct. for (i, component) in components.iter().enumerate() { if finished[i] { // In the event of non-interleaved streams, if we're still building the buffer out, // keep going; don't send it yet. We also need to ensure we don't skip over the last // row(s) of the image. if !is_interleaved && (mcu_y + 1) * 8 < frame.image_size.height { if (mcu_y + 1) % component.vertical_sampling_factor as u16 > 0 { continue; } } let coefficients_per_mcu_row = component.block_size.width as usize * component.vertical_sampling_factor as usize * 64; let row_coefficients = if is_progressive { // Because non-interleaved streams will have multiple MCU rows concatenated together, // the row for calculating the offset is different. let worker_mcu_y = if is_interleaved { mcu_y } else { // Explicitly doing floor-division here mcu_y / component.vertical_sampling_factor as u16 }; let offset = worker_mcu_y as usize * coefficients_per_mcu_row; self.coefficients[scan.component_indices[i]][offset .. offset + coefficients_per_mcu_row].to_vec() } else { mem::replace(&mut mcu_row_coefficients[i], vec![0i16; coefficients_per_mcu_row]) }; worker.append_row((i, row_coefficients))?; } } } let mut marker = huffman.take_marker(&mut self.reader)?; while let Some(Marker::RST(_)) = marker { marker = self.read_marker().ok(); } if finished.iter().any(|&c| c) { // Retrieve all the data from the worker thread. let mut data = vec![Vec::new(); frame.components.len()]; for (i, &component_index) in scan.component_indices.iter().enumerate() { if finished[i] { data[component_index] = worker.get_result(i)?; } } Ok((marker, Some(data))) } else { Ok((marker, None)) } } } fn decode_block(reader: &mut R, coefficients: &mut [i16], huffman: &mut HuffmanDecoder, dc_table: Option<&HuffmanTable>, ac_table: Option<&HuffmanTable>, spectral_selection: Range, successive_approximation_low: u8, eob_run: &mut u16, dc_predictor: &mut i16) -> Result<()> { debug_assert_eq!(coefficients.len(), 64); if spectral_selection.start == 0 { // Section F.2.2.1 // Figure F.12 let value = huffman.decode(reader, dc_table.unwrap())?; let diff = match value { 0 => 0, 1..=11 => huffman.receive_extend(reader, value)?, _ => { // Section F.1.2.1.1 // Table F.1 return Err(Error::Format("invalid DC difference magnitude category".to_owned())); }, }; // Malicious JPEG files can cause this add to overflow, therefore we use wrapping_add. // One example of such a file is tests/crashtest/images/dc-predictor-overflow.jpg *dc_predictor = dc_predictor.wrapping_add(diff); coefficients[0] = *dc_predictor << successive_approximation_low; } let mut index = cmp::max(spectral_selection.start, 1); if index < spectral_selection.end && *eob_run > 0 { *eob_run -= 1; return Ok(()); } // Section F.1.2.2.1 while index < spectral_selection.end { if let Some((value, run)) = huffman.decode_fast_ac(reader, ac_table.unwrap())? { index += run; if index >= spectral_selection.end { break; } coefficients[UNZIGZAG[index as usize] as usize] = value << successive_approximation_low; index += 1; } else { let byte = huffman.decode(reader, ac_table.unwrap())?; let r = byte >> 4; let s = byte & 0x0f; if s == 0 { match r { 15 => index += 16, // Run length of 16 zero coefficients. _ => { *eob_run = (1 << r) - 1; if r > 0 { *eob_run += huffman.get_bits(reader, r)?; } break; }, } } else { index += r; if index >= spectral_selection.end { break; } coefficients[UNZIGZAG[index as usize] as usize] = huffman.receive_extend(reader, s)? << successive_approximation_low; index += 1; } } } Ok(()) } fn decode_block_successive_approximation(reader: &mut R, coefficients: &mut [i16], huffman: &mut HuffmanDecoder, ac_table: Option<&HuffmanTable>, spectral_selection: Range, successive_approximation_low: u8, eob_run: &mut u16) -> Result<()> { debug_assert_eq!(coefficients.len(), 64); let bit = 1 << successive_approximation_low; if spectral_selection.start == 0 { // Section G.1.2.1 if huffman.get_bits(reader, 1)? == 1 { coefficients[0] |= bit; } } else { // Section G.1.2.3 if *eob_run > 0 { *eob_run -= 1; refine_non_zeroes(reader, coefficients, huffman, spectral_selection, 64, bit)?; return Ok(()); } let mut index = spectral_selection.start; while index < spectral_selection.end { let byte = huffman.decode(reader, ac_table.unwrap())?; let r = byte >> 4; let s = byte & 0x0f; let mut zero_run_length = r; let mut value = 0; match s { 0 => { match r { 15 => { // Run length of 16 zero coefficients. // We don't need to do anything special here, zero_run_length is 15 // and then value (which is zero) gets written, resulting in 16 // zero coefficients. }, _ => { *eob_run = (1 << r) - 1; if r > 0 { *eob_run += huffman.get_bits(reader, r)?; } // Force end of block. zero_run_length = 64; }, } }, 1 => { if huffman.get_bits(reader, 1)? == 1 { value = bit; } else { value = -bit; } }, _ => return Err(Error::Format("unexpected huffman code".to_owned())), } let range = Range { start: index, end: spectral_selection.end, }; index = refine_non_zeroes(reader, coefficients, huffman, range, zero_run_length, bit)?; if value != 0 { coefficients[UNZIGZAG[index as usize] as usize] = value; } index += 1; } } Ok(()) } fn refine_non_zeroes(reader: &mut R, coefficients: &mut [i16], huffman: &mut HuffmanDecoder, range: Range, zrl: u8, bit: i16) -> Result { debug_assert_eq!(coefficients.len(), 64); let last = range.end - 1; let mut zero_run_length = zrl; for i in range { let index = UNZIGZAG[i as usize] as usize; if coefficients[index] == 0 { if zero_run_length == 0 { return Ok(i); } zero_run_length -= 1; } else if huffman.get_bits(reader, 1)? == 1 && coefficients[index] & bit == 0 { if coefficients[index] > 0 { coefficients[index] += bit; } else { coefficients[index] -= bit; } } } Ok(last) } fn compute_image(components: &[Component], mut data: Vec>, output_size: Dimensions, is_jfif: bool, color_transform: Option) -> Result> { if data.is_empty() || data.iter().any(Vec::is_empty) { return Err(Error::Format("not all components have data".to_owned())); } if components.len() == 1 { let component = &components[0]; let mut decoded: Vec = data.remove(0); let width = component.size.width as usize; let height = component.size.height as usize; let size = width * height; let line_stride = component.block_size.width as usize * component.dct_scale; // if the image width is a multiple of the block size, // then we don't have to move bytes in the decoded data if usize::from(output_size.width) != line_stride { let mut buffer = vec![0u8; width]; // The first line already starts at index 0, so we need to move only lines 1..height for y in 1..height { let destination_idx = y * width; let source_idx = y * line_stride; // We could use copy_within, but we need to support old rust versions buffer.copy_from_slice(&decoded[source_idx..][..width]); let destination = &mut decoded[destination_idx..][..width]; destination.copy_from_slice(&buffer); } } decoded.resize(size, 0); Ok(decoded) } else { compute_image_parallel(components, data, output_size, is_jfif, color_transform) } } #[cfg(feature="rayon")] fn compute_image_parallel(components: &[Component], data: Vec>, output_size: Dimensions, is_jfif: bool, color_transform: Option) -> Result> { use rayon::prelude::*; let color_convert_func = choose_color_convert_func(components.len(), is_jfif, color_transform)?; let upsampler = Upsampler::new(components, output_size.width, output_size.height)?; let line_size = output_size.width as usize * components.len(); let mut image = vec![0u8; line_size * output_size.height as usize]; image.par_chunks_mut(line_size) .with_max_len(1) .enumerate() .for_each(|(row, line)| { upsampler.upsample_and_interleave_row(&data, row, output_size.width as usize, line); color_convert_func(line); }); Ok(image) } #[cfg(not(feature="rayon"))] fn compute_image_parallel(components: &[Component], data: Vec>, output_size: Dimensions, is_jfif: bool, color_transform: Option) -> Result> { let color_convert_func = choose_color_convert_func(components.len(), is_jfif, color_transform)?; let upsampler = Upsampler::new(components, output_size.width, output_size.height)?; let line_size = output_size.width as usize * components.len(); let mut image = vec![0u8; line_size * output_size.height as usize]; for (row, line) in image.chunks_mut(line_size) .enumerate() { upsampler.upsample_and_interleave_row(&data, row, output_size.width as usize, line); color_convert_func(line); } Ok(image) } fn choose_color_convert_func(component_count: usize, _is_jfif: bool, color_transform: Option) -> Result { match component_count { 3 => { // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe // Unknown means the data is RGB, so we don't need to perform any color conversion on it. if color_transform == Some(AdobeColorTransform::Unknown) { Ok(color_convert_line_null) } else { Ok(color_convert_line_ycbcr) } }, 4 => { // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe match color_transform { Some(AdobeColorTransform::Unknown) => Ok(color_convert_line_cmyk), Some(_) => Ok(color_convert_line_ycck), None => Err(Error::Format("4 components without Adobe APP14 metadata to indicate color space".to_owned())), } }, _ => panic!(), } } fn color_convert_line_null(_data: &mut [u8]) { } fn color_convert_line_ycbcr(data: &mut [u8]) { for chunk in data.chunks_exact_mut(3) { let (r, g, b) = ycbcr_to_rgb(chunk[0], chunk[1], chunk[2]); chunk[0] = r; chunk[1] = g; chunk[2] = b; } } fn color_convert_line_ycck(data: &mut [u8]) { for chunk in data.chunks_exact_mut(4) { let (r, g, b) = ycbcr_to_rgb(chunk[0], chunk[1], chunk[2]); let k = chunk[3]; chunk[0] = r; chunk[1] = g; chunk[2] = b; chunk[3] = 255 - k; } } fn color_convert_line_cmyk(data: &mut [u8]) { for chunk in data.chunks_exact_mut(4) { chunk[0] = 255 - chunk[0]; chunk[1] = 255 - chunk[1]; chunk[2] = 255 - chunk[2]; chunk[3] = 255 - chunk[3]; } } // ITU-R BT.601 fn ycbcr_to_rgb(y: u8, cb: u8, cr: u8) -> (u8, u8, u8) { let y = y as f32; let cb = cb as f32 - 128.0; let cr = cr as f32 - 128.0; let r = y + 1.40200 * cr; let g = y - 0.34414 * cb - 0.71414 * cr; let b = y + 1.77200 * cb; // TODO: Rust has defined float-to-int conversion as saturating, // which is exactly what we need here. However, as of this writing // it still hasn't reached the stable channel. // This can be simplified to `(r + 0.5) as u8` without any clamping // as soon as our MSRV reaches the version that has saturating casts. // The version without explicit clamping is also noticeably faster. (clamp_to_u8((r + 0.5) as i32) as u8, clamp_to_u8((g + 0.5) as i32) as u8, clamp_to_u8((b + 0.5) as i32) as u8) } fn clamp_to_u8(value: i32) -> i32 { let value = std::cmp::max(value, 0); std::cmp::min(value, 255) } jpeg-decoder-0.1.22/src/error.rs010064400017500001750000000046641367174505600146230ustar 00000000000000use std::error::Error as StdError; use std::fmt; use std::io::Error as IoError; pub type Result = ::std::result::Result; /// An enumeration over JPEG features (currently) unsupported by this library. /// /// Support for features listed here may be included in future versions of this library. #[derive(Debug)] pub enum UnsupportedFeature { /// Hierarchical JPEG. Hierarchical, /// Lossless JPEG. Lossless, /// JPEG using arithmetic entropy coding instead of Huffman coding. ArithmeticEntropyCoding, /// Sample precision in bits. 8 bit sample precision is what is currently supported. SamplePrecision(u8), /// Number of components in an image. 1, 3 and 4 components are currently supported. ComponentCount(u8), /// An image can specify a zero height in the frame header and use the DNL (Define Number of /// Lines) marker at the end of the first scan to define the number of lines in the frame. DNL, /// Subsampling ratio. SubsamplingRatio, /// A subsampling ratio not representable as an integer. NonIntegerSubsamplingRatio, } /// Errors that can occur while decoding a JPEG image. #[derive(Debug)] pub enum Error { /// The image is not formatted properly. The string contains detailed information about the /// error. Format(String), /// The image makes use of a JPEG feature not (currently) supported by this library. Unsupported(UnsupportedFeature), /// An I/O error occurred while decoding the image. Io(IoError), /// An internal error occurred while decoding the image. Internal(Box), //TODO: not used, can be removed with the next version bump } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Format(ref desc) => write!(f, "invalid JPEG format: {}", desc), Error::Unsupported(ref feat) => write!(f, "unsupported JPEG feature: {:?}", feat), Error::Io(ref err) => err.fmt(f), Error::Internal(ref err) => err.fmt(f), } } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match *self { Error::Io(ref err) => Some(err), Error::Internal(ref err) => Some(&**err), _ => None, } } } impl From for Error { fn from(err: IoError) -> Error { Error::Io(err) } } jpeg-decoder-0.1.22/src/huffman.rs010064400017500001750000000307641400303361400150720ustar 00000000000000use crate::read_u8; use error::{Error, Result}; use marker::Marker; use parser::ScanInfo; use std::io::Read; const LUT_BITS: u8 = 8; #[derive(Debug)] pub struct HuffmanDecoder { bits: u64, num_bits: u8, marker: Option, } impl HuffmanDecoder { pub fn new() -> HuffmanDecoder { HuffmanDecoder { bits: 0, num_bits: 0, marker: None, } } // Section F.2.2.3 // Figure F.16 pub fn decode(&mut self, reader: &mut R, table: &HuffmanTable) -> Result { if self.num_bits < 16 { self.read_bits(reader)?; } let (value, size) = table.lut[self.peek_bits(LUT_BITS) as usize]; if size > 0 { self.consume_bits(size); Ok(value) } else { let bits = self.peek_bits(16); for i in LUT_BITS .. 16 { let code = (bits >> (15 - i)) as i32; if code <= table.maxcode[i as usize] { self.consume_bits(i + 1); let index = (code + table.delta[i as usize]) as usize; return Ok(table.values[index]); } } Err(Error::Format("failed to decode huffman code".to_owned())) } } pub fn decode_fast_ac(&mut self, reader: &mut R, table: &HuffmanTable) -> Result> { if let Some(ref ac_lut) = table.ac_lut { if self.num_bits < LUT_BITS { self.read_bits(reader)?; } let (value, run_size) = ac_lut[self.peek_bits(LUT_BITS) as usize]; if run_size != 0 { let run = run_size >> 4; let size = run_size & 0x0f; self.consume_bits(size); return Ok(Some((value, run))); } } Ok(None) } #[inline] pub fn get_bits(&mut self, reader: &mut R, count: u8) -> Result { if self.num_bits < count { self.read_bits(reader)?; } let bits = self.peek_bits(count); self.consume_bits(count); Ok(bits) } #[inline] pub fn receive_extend(&mut self, reader: &mut R, count: u8) -> Result { let value = self.get_bits(reader, count)?; Ok(extend(value, count)) } pub fn reset(&mut self) { self.bits = 0; self.num_bits = 0; } pub fn take_marker(&mut self, reader: &mut R) -> Result> { self.read_bits(reader).map(|_| self.marker.take()) } #[inline] fn peek_bits(&mut self, count: u8) -> u16 { debug_assert!(count <= 16); debug_assert!(self.num_bits >= count); ((self.bits >> (64 - count)) & ((1 << count) - 1)) as u16 } #[inline] fn consume_bits(&mut self, count: u8) { debug_assert!(self.num_bits >= count); self.bits <<= count as usize; self.num_bits -= count; } fn read_bits(&mut self, reader: &mut R) -> Result<()> { while self.num_bits <= 56 { // Fill with zero bits if we have reached the end. let byte = match self.marker { Some(_) => 0, None => read_u8(reader)?, }; if byte == 0xFF { let mut next_byte = read_u8(reader)?; // Check for byte stuffing. if next_byte != 0x00 { // We seem to have reached the end of entropy-coded data and encountered a // marker. Since we can't put data back into the reader, we have to continue // reading to identify the marker so we can pass it on. // Section B.1.1.2 // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code X’FF’." while next_byte == 0xFF { next_byte = read_u8(reader)?; } match next_byte { 0x00 => return Err(Error::Format("FF 00 found where marker was expected".to_owned())), _ => self.marker = Some(Marker::from_u8(next_byte).unwrap()), } continue; } } self.bits |= (byte as u64) << (56 - self.num_bits); self.num_bits += 8; } Ok(()) } } // Section F.2.2.1 // Figure F.12 fn extend(value: u16, count: u8) -> i16 { let vt = 1 << (count as u16 - 1); if value < vt { value as i16 + (-1 << count as i16) + 1 } else { value as i16 } } #[derive(Clone, Copy, Debug, PartialEq)] pub enum HuffmanTableClass { DC, AC, } pub struct HuffmanTable { values: Vec, delta: [i32; 16], maxcode: [i32; 16], lut: [(u8, u8); 1 << LUT_BITS], ac_lut: Option<[(i16, u8); 1 << LUT_BITS]>, } impl HuffmanTable { pub fn new(bits: &[u8; 16], values: &[u8], class: HuffmanTableClass) -> Result { let (huffcode, huffsize) = derive_huffman_codes(bits)?; // Section F.2.2.3 // Figure F.15 // delta[i] is set to VALPTR(I) - MINCODE(I) let mut delta = [0i32; 16]; let mut maxcode = [-1i32; 16]; let mut j = 0; for i in 0 .. 16 { if bits[i] != 0 { delta[i] = j as i32 - huffcode[j] as i32; j += bits[i] as usize; maxcode[i] = huffcode[j - 1] as i32; } } // Build a lookup table for faster decoding. let mut lut = [(0u8, 0u8); 1 << LUT_BITS]; for (i, &size) in huffsize.iter().enumerate().filter(|&(_, &size)| size <= LUT_BITS) { let bits_remaining = LUT_BITS - size; let start = (huffcode[i] << bits_remaining) as usize; for j in 0 .. 1 << bits_remaining { lut[start + j] = (values[i], size); } } // Build a lookup table for small AC coefficients which both decodes the value and does the // equivalent of receive_extend. let ac_lut = match class { HuffmanTableClass::DC => None, HuffmanTableClass::AC => { let mut table = [(0i16, 0u8); 1 << LUT_BITS]; for (i, &(value, size)) in lut.iter().enumerate() { let run_length = value >> 4; let magnitude_category = value & 0x0f; if magnitude_category > 0 && size + magnitude_category <= LUT_BITS { let unextended_ac_value = (((i << size) & ((1 << LUT_BITS) - 1)) >> (LUT_BITS - magnitude_category)) as u16; let ac_value = extend(unextended_ac_value, magnitude_category); table[i] = (ac_value, (run_length << 4) | (size + magnitude_category)); } } Some(table) }, }; Ok(HuffmanTable { values: values.to_vec(), delta: delta, maxcode: maxcode, lut: lut, ac_lut: ac_lut, }) } } // Section C.2 fn derive_huffman_codes(bits: &[u8; 16]) -> Result<(Vec, Vec)> { // Figure C.1 let huffsize = bits.iter() .enumerate() .fold(Vec::new(), |mut acc, (i, &value)| { acc.extend(std::iter::repeat((i + 1) as u8).take(value as usize)); acc }); // Figure C.2 let mut huffcode = vec![0u16; huffsize.len()]; let mut code_size = huffsize[0]; let mut code = 0u32; for (i, &size) in huffsize.iter().enumerate() { while code_size < size { code <<= 1; code_size += 1; } if code >= (1u32 << size) { return Err(Error::Format("bad huffman code length".to_owned())); } huffcode[i] = code as u16; code += 1; } Ok((huffcode, huffsize)) } // https://www.loc.gov/preservation/digital/formats/fdd/fdd000063.shtml // "Avery Lee, writing in the rec.video.desktop newsgroup in 2001, commented that "MJPEG, or at // least the MJPEG in AVIs having the MJPG fourcc, is restricted JPEG with a fixed -- and // *omitted* -- Huffman table. The JPEG must be YCbCr colorspace, it must be 4:2:2, and it must // use basic Huffman encoding, not arithmetic or progressive.... You can indeed extract the // MJPEG frames and decode them with a regular JPEG decoder, but you have to prepend the DHT // segment to them, or else the decoder won't have any idea how to decompress the data. // The exact table necessary is given in the OpenDML spec."" pub fn fill_default_mjpeg_tables(scan: &ScanInfo, dc_huffman_tables: &mut[Option], ac_huffman_tables: &mut[Option]) { // Section K.3.3 if dc_huffman_tables[0].is_none() && scan.dc_table_indices.iter().any(|&i| i == 0) { // Table K.3 dc_huffman_tables[0] = Some(HuffmanTable::new( &[0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B], HuffmanTableClass::DC).unwrap()); } if dc_huffman_tables[1].is_none() && scan.dc_table_indices.iter().any(|&i| i == 1) { // Table K.4 dc_huffman_tables[1] = Some(HuffmanTable::new( &[0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00], &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B], HuffmanTableClass::DC).unwrap()); } if ac_huffman_tables[0].is_none() && scan.ac_table_indices.iter().any(|&i| i == 0) { // Table K.5 ac_huffman_tables[0] = Some(HuffmanTable::new( &[0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D], &[0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA ], HuffmanTableClass::AC).unwrap()); } if ac_huffman_tables[1].is_none() && scan.ac_table_indices.iter().any(|&i| i == 1) { // Table K.6 ac_huffman_tables[1] = Some(HuffmanTable::new( &[0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77], &[0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA ], HuffmanTableClass::AC).unwrap()); } } jpeg-decoder-0.1.22/src/idct.rs010064400017500001750000000376261400057706300144050ustar 00000000000000// Malicious JPEG files can cause operations in the idct to overflow. // One example is tests/crashtest/images/imagetestsuite/b0b8914cc5f7a6eff409f16d8cc236c5.jpg // That's why wrapping operators are needed. use crate::parser::Dimensions; use std::{convert::TryFrom, num::Wrapping}; pub(crate) fn choose_idct_size(full_size: Dimensions, requested_size: Dimensions) -> usize { fn scaled(len: u16, scale: usize) -> u16 { ((len as u32 * scale as u32 - 1) / 8 + 1) as u16 } for &scale in &[1, 2, 4] { if scaled(full_size.width, scale) >= requested_size.width || scaled(full_size.height, scale) >= requested_size.height { return scale; } } 8 } #[test] fn test_choose_idct_size() { assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 200, height: 200}), 1); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 500, height: 500}), 1); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 684, height: 456}), 1); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 999, height: 456}), 1); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 684, height: 999}), 1); assert_eq!(choose_idct_size(Dimensions{width: 500, height: 333}, Dimensions{width: 63, height: 42}), 1); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 685, height: 999}), 2); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 1000, height: 1000}), 2); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 1400, height: 1400}), 4); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 5472, height: 3648}), 8); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 16384, height: 16384}), 8); assert_eq!(choose_idct_size(Dimensions{width: 1, height: 1}, Dimensions{width: 65535, height: 65535}), 8); assert_eq!(choose_idct_size(Dimensions{width: 5472, height: 3648}, Dimensions{width: 16384, height: 16384}), 8); } pub(crate) fn dequantize_and_idct_block(scale: usize, coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8]) { match scale { 8 => dequantize_and_idct_block_8x8(coefficients, quantization_table, output_linestride, output), 4 => dequantize_and_idct_block_4x4(coefficients, quantization_table, output_linestride, output), 2 => dequantize_and_idct_block_2x2(coefficients, quantization_table, output_linestride, output), 1 => dequantize_and_idct_block_1x1(coefficients, quantization_table, output_linestride, output), _ => panic!("Unsupported IDCT scale {}/8", scale), } } pub fn dequantize_and_idct_block_8x8( coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8] ) { let output = output .chunks_mut(output_linestride); dequantize_and_idct_block_8x8_inner(coefficients, quantization_table, output) } // This is based on stb_image's 'stbi__idct_block'. fn dequantize_and_idct_block_8x8_inner<'a, I>( coefficients: &[i16], quantization_table: &[u16; 64], output: I, ) where I: IntoIterator, I::IntoIter: ExactSizeIterator, { let output = output.into_iter(); debug_assert!( output.len() >= 8, "Output iterator has the wrong length: {}", output.len() ); // optimizer hint to eliminate bounds checks within loops assert!(coefficients.len() == 64); let mut temp = [Wrapping(0); 64]; // columns for i in 0..8 { if coefficients[i + 8] == 0 && coefficients[i + 16] == 0 && coefficients[i + 24] == 0 && coefficients[i + 32] == 0 && coefficients[i + 40] == 0 && coefficients[i + 48] == 0 && coefficients[i + 56] == 0 { let dcterm = dequantize(coefficients[i], quantization_table[i]) << 2; temp[i] = dcterm; temp[i + 8] = dcterm; temp[i + 16] = dcterm; temp[i + 24] = dcterm; temp[i + 32] = dcterm; temp[i + 40] = dcterm; temp[i + 48] = dcterm; temp[i + 56] = dcterm; } else { let s0 = dequantize(coefficients[i], quantization_table[i]); let s1 = dequantize(coefficients[i + 8], quantization_table[i + 8]); let s2 = dequantize(coefficients[i + 16], quantization_table[i + 16]); let s3 = dequantize(coefficients[i + 24], quantization_table[i + 24]); let s4 = dequantize(coefficients[i + 32], quantization_table[i + 32]); let s5 = dequantize(coefficients[i + 40], quantization_table[i + 40]); let s6 = dequantize(coefficients[i + 48], quantization_table[i + 48]); let s7 = dequantize(coefficients[i + 56], quantization_table[i + 56]); let Kernel { xs: [x0, x1, x2, x3], ts: [t0, t1, t2, t3], } = kernel( [s0, s1, s2, s3, s4, s5, s6, s7], // constants scaled things up by 1<<12; let's bring them back // down, but keep 2 extra bits of precision 512, ); temp[i] = (x0 + t3) >> 10; temp[i + 56] = (x0 - t3) >> 10; temp[i + 8] = (x1 + t2) >> 10; temp[i + 48] = (x1 - t2) >> 10; temp[i + 16] = (x2 + t1) >> 10; temp[i + 40] = (x2 - t1) >> 10; temp[i + 24] = (x3 + t0) >> 10; temp[i + 32] = (x3 - t0) >> 10; } } for (chunk, output_chunk) in temp.chunks_exact(8).zip(output) { let chunk = <&[_; 8]>::try_from(chunk).unwrap(); // constants scaled things up by 1<<12, plus we had 1<<2 from first // loop, plus horizontal and vertical each scale by sqrt(8) so together // we've got an extra 1<<3, so 1<<17 total we need to remove. // so we want to round that, which means adding 0.5 * 1<<17, // aka 65536. Also, we'll end up with -128 to 127 that we want // to encode as 0..255 by adding 128, so we'll add that before the shift const X_SCALE: i32 = 65536 + (128 << 17); // TODO When the minimum rust version supports it // let [s0, rest @ ..] = chunk; let (s0, rest) = chunk.split_first().unwrap(); if *rest == [Wrapping(0); 7] { let dcterm = stbi_clamp((stbi_fsh(*s0) + Wrapping(X_SCALE)) >> 17); output_chunk[0] = dcterm; output_chunk[1] = dcterm; output_chunk[2] = dcterm; output_chunk[3] = dcterm; output_chunk[4] = dcterm; output_chunk[5] = dcterm; output_chunk[6] = dcterm; output_chunk[7] = dcterm; } else { let Kernel { xs: [x0, x1, x2, x3], ts: [t0, t1, t2, t3], } = kernel(*chunk, X_SCALE); output_chunk[0] = stbi_clamp((x0 + t3) >> 17); output_chunk[7] = stbi_clamp((x0 - t3) >> 17); output_chunk[1] = stbi_clamp((x1 + t2) >> 17); output_chunk[6] = stbi_clamp((x1 - t2) >> 17); output_chunk[2] = stbi_clamp((x2 + t1) >> 17); output_chunk[5] = stbi_clamp((x2 - t1) >> 17); output_chunk[3] = stbi_clamp((x3 + t0) >> 17); output_chunk[4] = stbi_clamp((x3 - t0) >> 17); } } } struct Kernel { xs: [Wrapping; 4], ts: [Wrapping; 4], } #[inline] fn kernel_x([s0, s2, s4, s6]: [Wrapping; 4], x_scale: i32) -> [Wrapping; 4] { // Even `chunk` indicies let (t2, t3); { let p2 = s2; let p3 = s6; let p1 = (p2 + p3) * stbi_f2f(0.5411961); t2 = p1 + p3 * stbi_f2f(-1.847759065); t3 = p1 + p2 * stbi_f2f(0.765366865); } let (t0, t1); { let p2 = s0; let p3 = s4; t0 = stbi_fsh(p2 + p3); t1 = stbi_fsh(p2 - p3); } let x0 = t0 + t3; let x3 = t0 - t3; let x1 = t1 + t2; let x2 = t1 - t2; let x_scale = Wrapping(x_scale); [x0 + x_scale, x1 + x_scale, x2 + x_scale, x3 + x_scale] } #[inline] fn kernel_t([s1, s3, s5, s7]: [Wrapping; 4]) -> [Wrapping; 4] { // Odd `chunk` indicies let mut t0 = s7; let mut t1 = s5; let mut t2 = s3; let mut t3 = s1; let p3 = t0 + t2; let p4 = t1 + t3; let p1 = t0 + t3; let p2 = t1 + t2; let p5 = (p3 + p4) * stbi_f2f(1.175875602); t0 *= stbi_f2f(0.298631336); t1 *= stbi_f2f(2.053119869); t2 *= stbi_f2f(3.072711026); t3 *= stbi_f2f(1.501321110); let p1 = p5 + p1 * stbi_f2f(-0.899976223); let p2 = p5 + p2 * stbi_f2f(-2.562915447); let p3 = p3 * stbi_f2f(-1.961570560); let p4 = p4 * stbi_f2f(-0.390180644); t3 += p1 + p4; t2 += p2 + p3; t1 += p2 + p4; t0 += p1 + p3; [t0, t1, t2, t3] } #[inline] fn kernel([s0, s1, s2, s3, s4, s5, s6, s7]: [Wrapping; 8], x_scale: i32) -> Kernel { Kernel { xs: kernel_x([s0, s2, s4, s6], x_scale), ts: kernel_t([s1, s3, s5, s7]), } } #[inline(always)] fn dequantize(c: i16, q: u16) -> Wrapping { Wrapping(i32::from(c) * i32::from(q)) } // 4x4 and 2x2 IDCT based on Rakesh Dugad and Narendra Ahuja: "A Fast Scheme for Image Size Change in the Compressed Domain" (2001). // http://sylvana.net/jpegcrop/jidctred/ fn dequantize_and_idct_block_4x4(coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8]) { debug_assert_eq!(coefficients.len(), 64); let mut temp = [Wrapping(0i32); 4 * 4]; const CONST_BITS: usize = 12; const PASS1_BITS: usize = 2; const FINAL_BITS: usize = CONST_BITS + PASS1_BITS + 3; // columns for i in 0..4 { let s0 = Wrapping(coefficients[i + 8 * 0] as i32 * quantization_table[i + 8 * 0] as i32); let s1 = Wrapping(coefficients[i + 8 * 1] as i32 * quantization_table[i + 8 * 1] as i32); let s2 = Wrapping(coefficients[i + 8 * 2] as i32 * quantization_table[i + 8 * 2] as i32); let s3 = Wrapping(coefficients[i + 8 * 3] as i32 * quantization_table[i + 8 * 3] as i32); let x0 = (s0 + s2) << PASS1_BITS; let x2 = (s0 - s2) << PASS1_BITS; let p1 = (s1 + s3) * stbi_f2f(0.541196100); let t0 = (p1 + s3 * stbi_f2f(-1.847759065) + Wrapping(512)) >> (CONST_BITS - PASS1_BITS); let t2 = (p1 + s1 * stbi_f2f(0.765366865) + Wrapping(512)) >> (CONST_BITS - PASS1_BITS); temp[i + 4 * 0] = x0 + t2; temp[i + 4 * 3] = x0 - t2; temp[i + 4 * 1] = x2 + t0; temp[i + 4 * 2] = x2 - t0; } for i in 0 .. 4 { let s0 = temp[i * 4 + 0]; let s1 = temp[i * 4 + 1]; let s2 = temp[i * 4 + 2]; let s3 = temp[i * 4 + 3]; let x0 = (s0 + s2) << CONST_BITS; let x2 = (s0 - s2) << CONST_BITS; let p1 = (s1 + s3) * stbi_f2f(0.541196100); let t0 = p1 + s3 * stbi_f2f(-1.847759065); let t2 = p1 + s1 * stbi_f2f(0.765366865); // constants scaled things up by 1<<12, plus we had 1<<2 from first // loop, plus horizontal and vertical each scale by sqrt(8) so together // we've got an extra 1<<3, so 1<<17 total we need to remove. // so we want to round that, which means adding 0.5 * 1<<17, // aka 65536. Also, we'll end up with -128 to 127 that we want // to encode as 0..255 by adding 128, so we'll add that before the shift let x0 = x0 + Wrapping(1 << (FINAL_BITS - 1)) + Wrapping(128 << FINAL_BITS); let x2 = x2 + Wrapping(1 << (FINAL_BITS - 1)) + Wrapping(128 << FINAL_BITS); output[i * output_linestride + 0] = stbi_clamp((x0 + t2) >> FINAL_BITS); output[i * output_linestride + 3] = stbi_clamp((x0 - t2) >> FINAL_BITS); output[i * output_linestride + 1] = stbi_clamp((x2 + t0) >> FINAL_BITS); output[i * output_linestride + 2] = stbi_clamp((x2 - t0) >> FINAL_BITS); } } fn dequantize_and_idct_block_2x2(coefficients: &[i16], quantization_table: &[u16; 64], output_linestride: usize, output: &mut [u8]) { debug_assert_eq!(coefficients.len(), 64); const SCALE_BITS: usize = 3; // Column 0 let s00 = Wrapping(coefficients[8 * 0] as i32 * quantization_table[8 * 0] as i32); let s10 = Wrapping(coefficients[8 * 1] as i32 * quantization_table[8 * 1] as i32); let x0 = s00 + s10; let x2 = s00 - s10; // Column 1 let s01 = Wrapping(coefficients[8 * 0 + 1] as i32 * quantization_table[8 * 0 + 1] as i32); let s11 = Wrapping(coefficients[8 * 1 + 1] as i32 * quantization_table[8 * 1 + 1] as i32); let x1 = s01 + s11; let x3 = s01 - s11; let x0 = x0 + Wrapping(1 << (SCALE_BITS - 1)) + Wrapping(128 << SCALE_BITS); let x2 = x2 + Wrapping(1 << (SCALE_BITS - 1)) + Wrapping(128 << SCALE_BITS); // Row 0 output[0] = stbi_clamp((x0 + x1) >> SCALE_BITS); output[1] = stbi_clamp((x0 - x1) >> SCALE_BITS); // Row 1 output[output_linestride + 0] = stbi_clamp((x2 + x3) >> SCALE_BITS); output[output_linestride + 1] = stbi_clamp((x2 - x3) >> SCALE_BITS); } fn dequantize_and_idct_block_1x1(coefficients: &[i16], quantization_table: &[u16; 64], _output_linestride: usize, output: &mut [u8]) { debug_assert_eq!(coefficients.len(), 64); let s0 = (Wrapping(coefficients[0] as i32 * quantization_table[0] as i32) + Wrapping(128 * 8)) / Wrapping(8); output[0] = stbi_clamp(s0); } // take a -128..127 value and stbi__clamp it and convert to 0..255 fn stbi_clamp(x: Wrapping) -> u8 { x.0.max(0).min(255) as u8 } fn stbi_f2f(x: f32) -> Wrapping { Wrapping((x * 4096.0 + 0.5) as i32) } fn stbi_fsh(x: Wrapping) -> Wrapping { x << 12 } #[test] fn test_dequantize_and_idct_block_8x8() { let coefficients: [i16; 8 * 8] = [ -14, -39, 58, -2, 3, 3, 0, 1, 11, 27, 4, -3, 3, 0, 1, 0, -6, -13, -9, -1, -2, -1, 0, 0, -4, 0, -1, -2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, -3, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let quantization_table: [u16; 8 * 8] = [ 8, 6, 5, 8, 12, 20, 26, 31, 6, 6, 7, 10, 13, 29, 30, 28, 7, 7, 8, 12, 20, 29, 35, 28, 7, 9, 11, 15, 26, 44, 40, 31, 9, 11, 19, 28, 34, 55, 52, 39, 12, 18, 28, 32, 41, 52, 57, 46, 25, 32, 39, 44, 52, 61, 60, 51, 36, 46, 48, 49, 56, 50, 52, 50]; let output_linestride: usize = 8; let mut output = [0u8; 8 * 8]; dequantize_and_idct_block_8x8( &coefficients, &quantization_table, output_linestride, &mut output); let expected_output = [ 118, 92, 110, 83, 77, 93, 144, 198, 172, 116, 114, 87, 78, 93, 146, 191, 194, 107, 91, 76, 71, 93, 160, 198, 196, 100, 80, 74, 67, 92, 174, 209, 182, 104, 88, 81, 68, 89, 178, 206, 105, 64, 59, 59, 63, 94, 183, 201, 35, 27, 28, 37, 72, 121, 203, 204, 37, 45, 41, 47, 98, 154, 223, 208]; assert_eq!(&output[..], &expected_output[..]); } #[test] fn test_dequantize_and_idct_block_8x8_all_zero() { let mut output = [0u8; 8 * 8]; dequantize_and_idct_block_8x8( &[0; 8*8], &[666; 8*8], 8, &mut output); assert_eq!(&output[..], &[128; 8*8][..]); } #[test] fn test_dequantize_and_idct_block_8x8_saturated() { let mut output = [0u8; 8 * 8]; dequantize_and_idct_block_8x8( &[std::i16::MAX; 8*8], &[std::u16::MAX; 8*8], 8, &mut output); let expected = [ 0, 0, 0, 255, 255, 0, 0, 255, 0, 0, 215, 0, 0, 255, 255, 0, 255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 0, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 0, 0, 255, 255, 0, 255, 255, 255, 170, 0, 0, 255, 0, 0, 0, 0, 0, 255, 255, 255, 0, 255, 0, 255, 0, 0]; assert_eq!(&output[..], &expected[..]); } jpeg-decoder-0.1.22/src/lib.rs010064400017500001750000000026731400303361400142120ustar 00000000000000//! This crate contains a JPEG decoder. //! //! # Examples //! //! ``` //! use jpeg_decoder::Decoder; //! use std::fs::File; //! use std::io::BufReader; //! //! let file = File::open("tests/reftest/images/extraneous-data.jpg").expect("failed to open file"); //! let mut decoder = Decoder::new(BufReader::new(file)); //! let pixels = decoder.decode().expect("failed to decode image"); //! let metadata = decoder.info().unwrap(); //! ``` //! //! Get metadata from a file without decoding it: //! //! ``` //! use jpeg_decoder::Decoder; //! use std::fs::File; //! use std::io::BufReader; //! //! let file = File::open("tests/reftest/images/extraneous-data.jpg").expect("failed to open file"); //! let mut decoder = Decoder::new(BufReader::new(file)); //! decoder.read_info().expect("failed to read metadata"); //! let metadata = decoder.info().unwrap(); //! ``` #![deny(missing_docs)] #![forbid(unsafe_code)] #[cfg(feature="rayon")] extern crate rayon; pub use decoder::{Decoder, ImageInfo, PixelFormat}; pub use error::{Error, UnsupportedFeature}; mod decoder; mod error; mod huffman; mod idct; mod marker; mod parser; mod upsampler; mod worker; fn read_u8(reader: &mut R) -> std::io::Result { let mut buf = [0]; reader.read_exact(&mut buf)?; Ok(buf[0]) } fn read_u16_from_be(reader: &mut R) -> std::io::Result { let mut buf = [0, 0]; reader.read_exact(&mut buf)?; Ok(u16::from_be_bytes(buf)) } jpeg-decoder-0.1.22/src/marker.rs010064400017500001750000000102421367172204600147320ustar 00000000000000// Table B.1 #[derive(Clone, Copy, Debug, PartialEq)] pub enum Marker { /// Start Of Frame markers /// /// - SOF(0): Baseline DCT (Huffman coding) /// - SOF(1): Extended sequential DCT (Huffman coding) /// - SOF(2): Progressive DCT (Huffman coding) /// - SOF(3): Lossless (sequential) (Huffman coding) /// - SOF(5): Differential sequential DCT (Huffman coding) /// - SOF(6): Differential progressive DCT (Huffman coding) /// - SOF(7): Differential lossless (sequential) (Huffman coding) /// - SOF(9): Extended sequential DCT (arithmetic coding) /// - SOF(10): Progressive DCT (arithmetic coding) /// - SOF(11): Lossless (sequential) (arithmetic coding) /// - SOF(13): Differential sequential DCT (arithmetic coding) /// - SOF(14): Differential progressive DCT (arithmetic coding) /// - SOF(15): Differential lossless (sequential) (arithmetic coding) SOF(u8), /// Reserved for JPEG extensions JPG, /// Define Huffman table(s) DHT, /// Define arithmetic coding conditioning(s) DAC, /// Restart with modulo 8 count `m` RST(u8), /// Start of image SOI, /// End of image EOI, /// Start of scan SOS, /// Define quantization table(s) DQT, /// Define number of lines DNL, /// Define restart interval DRI, /// Define hierarchical progression DHP, /// Expand reference component(s) EXP, /// Reserved for application segments APP(u8), /// Reserved for JPEG extensions JPGn(u8), /// Comment COM, /// For temporary private use in arithmetic coding TEM, /// Reserved RES, } impl Marker { pub fn has_length(self) -> bool { use self::Marker::*; match self { RST(..) | SOI | EOI | TEM => false, _ => true, } } pub fn from_u8(n: u8) -> Option { use self::Marker::*; match n { 0x00 => None, // Byte stuffing 0x01 => Some(TEM), 0x02 ..= 0xBF => Some(RES), 0xC0 => Some(SOF(0)), 0xC1 => Some(SOF(1)), 0xC2 => Some(SOF(2)), 0xC3 => Some(SOF(3)), 0xC4 => Some(DHT), 0xC5 => Some(SOF(5)), 0xC6 => Some(SOF(6)), 0xC7 => Some(SOF(7)), 0xC8 => Some(JPG), 0xC9 => Some(SOF(9)), 0xCA => Some(SOF(10)), 0xCB => Some(SOF(11)), 0xCC => Some(DAC), 0xCD => Some(SOF(13)), 0xCE => Some(SOF(14)), 0xCF => Some(SOF(15)), 0xD0 => Some(RST(0)), 0xD1 => Some(RST(1)), 0xD2 => Some(RST(2)), 0xD3 => Some(RST(3)), 0xD4 => Some(RST(4)), 0xD5 => Some(RST(5)), 0xD6 => Some(RST(6)), 0xD7 => Some(RST(7)), 0xD8 => Some(SOI), 0xD9 => Some(EOI), 0xDA => Some(SOS), 0xDB => Some(DQT), 0xDC => Some(DNL), 0xDD => Some(DRI), 0xDE => Some(DHP), 0xDF => Some(EXP), 0xE0 => Some(APP(0)), 0xE1 => Some(APP(1)), 0xE2 => Some(APP(2)), 0xE3 => Some(APP(3)), 0xE4 => Some(APP(4)), 0xE5 => Some(APP(5)), 0xE6 => Some(APP(6)), 0xE7 => Some(APP(7)), 0xE8 => Some(APP(8)), 0xE9 => Some(APP(9)), 0xEA => Some(APP(10)), 0xEB => Some(APP(11)), 0xEC => Some(APP(12)), 0xED => Some(APP(13)), 0xEE => Some(APP(14)), 0xEF => Some(APP(15)), 0xF0 => Some(JPGn(0)), 0xF1 => Some(JPGn(1)), 0xF2 => Some(JPGn(2)), 0xF3 => Some(JPGn(3)), 0xF4 => Some(JPGn(4)), 0xF5 => Some(JPGn(5)), 0xF6 => Some(JPGn(6)), 0xF7 => Some(JPGn(7)), 0xF8 => Some(JPGn(8)), 0xF9 => Some(JPGn(9)), 0xFA => Some(JPGn(10)), 0xFB => Some(JPGn(11)), 0xFC => Some(JPGn(12)), 0xFD => Some(JPGn(13)), 0xFE => Some(COM), 0xFF => None, // Fill byte } } } jpeg-decoder-0.1.22/src/parser.rs010064400017500001750000000527241400303361400147420ustar 00000000000000use crate::{read_u16_from_be, read_u8}; use error::{Error, Result, UnsupportedFeature}; use huffman::{HuffmanTable, HuffmanTableClass}; use marker::Marker; use marker::Marker::*; use std::io::{self, Read}; use std::ops::Range; #[derive(Clone, Copy, Debug, PartialEq)] pub struct Dimensions { pub width: u16, pub height: u16, } #[derive(Clone, Copy, Debug, PartialEq)] pub enum EntropyCoding { Huffman, Arithmetic, } #[derive(Clone, Copy, Debug, PartialEq)] pub enum CodingProcess { DctSequential, DctProgressive, Lossless, } #[derive(Clone)] pub struct FrameInfo { pub is_baseline: bool, pub is_differential: bool, pub coding_process: CodingProcess, pub entropy_coding: EntropyCoding, pub precision: u8, pub image_size: Dimensions, pub output_size: Dimensions, pub mcu_size: Dimensions, pub components: Vec, } #[derive(Debug)] pub struct ScanInfo { pub component_indices: Vec, pub dc_table_indices: Vec, pub ac_table_indices: Vec, pub spectral_selection: Range, pub successive_approximation_high: u8, pub successive_approximation_low: u8, } #[derive(Clone, Debug)] pub struct Component { pub identifier: u8, pub horizontal_sampling_factor: u8, pub vertical_sampling_factor: u8, pub quantization_table_index: usize, pub dct_scale: usize, pub size: Dimensions, pub block_size: Dimensions, } #[derive(Debug)] pub enum AppData { Adobe(AdobeColorTransform), Jfif, Avi1, Icc(IccChunk), } // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe #[derive(Clone, Copy, Debug, PartialEq)] pub enum AdobeColorTransform { // RGB or CMYK Unknown, YCbCr, // YCbCrK YCCK, } #[derive(Debug)] pub struct IccChunk { pub num_markers: u8, pub seq_no: u8, pub data: Vec, } impl FrameInfo { pub(crate) fn update_idct_size(&mut self, idct_size: usize) -> Result<()> { for component in &mut self.components { component.dct_scale = idct_size; } update_component_sizes(self.image_size, &mut self.components)?; self.output_size = Dimensions { width: (self.image_size.width as f32 * idct_size as f32 / 8.0).ceil() as u16, height: (self.image_size.height as f32 * idct_size as f32 / 8.0).ceil() as u16 }; Ok(()) } } fn read_length(reader: &mut R, marker: Marker) -> Result { assert!(marker.has_length()); // length is including itself. let length = usize::from(read_u16_from_be(reader)?); if length < 2 { return Err(Error::Format(format!("encountered {:?} with invalid length {}", marker, length))); } Ok(length - 2) } fn skip_bytes(reader: &mut R, length: usize) -> Result<()> { let length = length as u64; let to_skip = &mut reader.by_ref().take(length); let copied = io::copy(to_skip, &mut io::sink())?; if copied < length { Err(Error::Io(io::ErrorKind::UnexpectedEof.into())) } else { Ok(()) } } // Section B.2.2 pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { let length = read_length(reader, marker)?; if length <= 6 { return Err(Error::Format("invalid length in SOF".to_owned())); } let is_baseline = marker == SOF(0); let is_differential = match marker { SOF(0 ..= 3) | SOF(9 ..= 11) => false, SOF(5 ..= 7) | SOF(13 ..= 15) => true, _ => panic!(), }; let coding_process = match marker { SOF(0) | SOF(1) | SOF(5) | SOF(9) | SOF(13) => CodingProcess::DctSequential, SOF(2) | SOF(6) | SOF(10) | SOF(14) => CodingProcess::DctProgressive, SOF(3) | SOF(7) | SOF(11) | SOF(15) => CodingProcess::Lossless, _ => panic!(), }; let entropy_coding = match marker { SOF(0 ..= 3) | SOF(5 ..= 7) => EntropyCoding::Huffman, SOF(9 ..= 11) | SOF(13 ..= 15) => EntropyCoding::Arithmetic, _ => panic!(), }; let precision = read_u8(reader)?; match precision { 8 => {}, 12 => { if is_baseline { return Err(Error::Format("12 bit sample precision is not allowed in baseline".to_owned())); } }, _ => { if coding_process != CodingProcess::Lossless { return Err(Error::Format(format!("invalid precision {} in frame header", precision))) } }, } let height = read_u16_from_be(reader)?; let width = read_u16_from_be(reader)?; // height: // "Value 0 indicates that the number of lines shall be defined by the DNL marker and // parameters at the end of the first scan (see B.2.5)." if height == 0 { return Err(Error::Unsupported(UnsupportedFeature::DNL)); } if width == 0 { return Err(Error::Format("zero width in frame header".to_owned())); } let component_count = read_u8(reader)?; if component_count == 0 { return Err(Error::Format("zero component count in frame header".to_owned())); } if coding_process == CodingProcess::DctProgressive && component_count > 4 { return Err(Error::Format("progressive frame with more than 4 components".to_owned())); } if length != 6 + 3 * component_count as usize { return Err(Error::Format("invalid length in SOF".to_owned())); } let mut components: Vec = Vec::with_capacity(component_count as usize); for _ in 0 .. component_count { let identifier = read_u8(reader)?; // Each component's identifier must be unique. if components.iter().any(|c| c.identifier == identifier) { return Err(Error::Format(format!("duplicate frame component identifier {}", identifier))); } let byte = read_u8(reader)?; let horizontal_sampling_factor = byte >> 4; let vertical_sampling_factor = byte & 0x0f; if horizontal_sampling_factor == 0 || horizontal_sampling_factor > 4 { return Err(Error::Format(format!("invalid horizontal sampling factor {}", horizontal_sampling_factor))); } if vertical_sampling_factor == 0 || vertical_sampling_factor > 4 { return Err(Error::Format(format!("invalid vertical sampling factor {}", vertical_sampling_factor))); } let quantization_table_index = read_u8(reader)?; if quantization_table_index > 3 || (coding_process == CodingProcess::Lossless && quantization_table_index != 0) { return Err(Error::Format(format!("invalid quantization table index {}", quantization_table_index))); } components.push(Component { identifier: identifier, horizontal_sampling_factor: horizontal_sampling_factor, vertical_sampling_factor: vertical_sampling_factor, quantization_table_index: quantization_table_index as usize, dct_scale: 8, size: Dimensions {width: 0, height: 0}, block_size: Dimensions {width: 0, height: 0}, }); } let mcu_size = update_component_sizes(Dimensions { width, height }, &mut components)?; Ok(FrameInfo { is_baseline: is_baseline, is_differential: is_differential, coding_process: coding_process, entropy_coding: entropy_coding, precision: precision, image_size: Dimensions { width, height }, output_size: Dimensions { width, height }, mcu_size, components: components, }) } /// Returns ceil(x/y), requires x>0 fn ceil_div(x: u32, y: u32) -> Result { if x == 0 || y == 0 { // TODO Determine how this error is reached. Can we validate input // earlier and error out then? return Err(Error::Format("invalid dimensions".to_owned())); } Ok((1 + ((x - 1) / y)) as u16) } fn update_component_sizes(size: Dimensions, components: &mut [Component]) -> Result { let h_max = components.iter().map(|c| c.horizontal_sampling_factor).max().unwrap() as u32; let v_max = components.iter().map(|c| c.vertical_sampling_factor).max().unwrap() as u32; let mcu_size = Dimensions { width: ceil_div(size.width as u32, h_max * 8)?, height: ceil_div(size.height as u32, v_max * 8)?, }; for component in components { component.size.width = ceil_div(size.width as u32 * component.horizontal_sampling_factor as u32 * component.dct_scale as u32, h_max * 8)?; component.size.height = ceil_div(size.height as u32 * component.vertical_sampling_factor as u32 * component.dct_scale as u32, v_max * 8)?; component.block_size.width = mcu_size.width * component.horizontal_sampling_factor as u16; component.block_size.height = mcu_size.height * component.vertical_sampling_factor as u16; } Ok(mcu_size) } #[test] fn test_update_component_sizes() { let mut components = [Component { identifier: 1, horizontal_sampling_factor: 2, vertical_sampling_factor: 2, quantization_table_index: 0, dct_scale: 8, size: Dimensions { width: 0, height: 0 }, block_size: Dimensions { width: 0, height: 0 }, }]; let mcu = update_component_sizes( Dimensions { width: 800, height: 280 }, &mut components).unwrap(); assert_eq!(mcu, Dimensions { width: 50, height: 18 }); assert_eq!(components[0].block_size, Dimensions { width: 100, height: 36 }); assert_eq!(components[0].size, Dimensions { width: 800, height: 280 }); } // Section B.2.3 pub fn parse_sos(reader: &mut R, frame: &FrameInfo) -> Result { let length = read_length(reader, SOS)?; if 0 == length { return Err(Error::Format("zero length in SOS".to_owned())); } let component_count = read_u8(reader)?; if component_count == 0 || component_count > 4 { return Err(Error::Format(format!("invalid component count {} in scan header", component_count))); } if length != 4 + 2 * component_count as usize { return Err(Error::Format("invalid length in SOS".to_owned())); } let mut component_indices = Vec::with_capacity(component_count as usize); let mut dc_table_indices = Vec::with_capacity(component_count as usize); let mut ac_table_indices = Vec::with_capacity(component_count as usize); for _ in 0 .. component_count { let identifier = read_u8(reader)?; let component_index = match frame.components.iter().position(|c| c.identifier == identifier) { Some(value) => value, None => return Err(Error::Format(format!("scan component identifier {} does not match any of the component identifiers defined in the frame", identifier))), }; // Each of the scan's components must be unique. if component_indices.contains(&component_index) { return Err(Error::Format(format!("duplicate scan component identifier {}", identifier))); } // "... the ordering in the scan header shall follow the ordering in the frame header." if component_index < *component_indices.iter().max().unwrap_or(&0) { return Err(Error::Format("the scan component order does not follow the order in the frame header".to_owned())); } let byte = read_u8(reader)?; let dc_table_index = byte >> 4; let ac_table_index = byte & 0x0f; if dc_table_index > 3 || (frame.is_baseline && dc_table_index > 1) { return Err(Error::Format(format!("invalid dc table index {}", dc_table_index))); } if ac_table_index > 3 || (frame.is_baseline && ac_table_index > 1) { return Err(Error::Format(format!("invalid ac table index {}", ac_table_index))); } component_indices.push(component_index); dc_table_indices.push(dc_table_index as usize); ac_table_indices.push(ac_table_index as usize); } let blocks_per_mcu = component_indices.iter().map(|&i| { frame.components[i].horizontal_sampling_factor as u32 * frame.components[i].vertical_sampling_factor as u32 }).fold(0, ::std::ops::Add::add); if component_count > 1 && blocks_per_mcu > 10 { return Err(Error::Format("scan with more than one component and more than 10 blocks per MCU".to_owned())); } let spectral_selection_start = read_u8(reader)?; let spectral_selection_end = read_u8(reader)?; let byte = read_u8(reader)?; let successive_approximation_high = byte >> 4; let successive_approximation_low = byte & 0x0f; if frame.coding_process == CodingProcess::DctProgressive { if spectral_selection_end > 63 || spectral_selection_start > spectral_selection_end || (spectral_selection_start == 0 && spectral_selection_end != 0) { return Err(Error::Format(format!("invalid spectral selection parameters: ss={}, se={}", spectral_selection_start, spectral_selection_end))); } if spectral_selection_start != 0 && component_count != 1 { return Err(Error::Format("spectral selection scan with AC coefficients can't have more than one component".to_owned())); } if successive_approximation_high > 13 || successive_approximation_low > 13 { return Err(Error::Format(format!("invalid successive approximation parameters: ah={}, al={}", successive_approximation_high, successive_approximation_low))); } // Section G.1.1.1.2 // "Each scan which follows the first scan for a given band progressively improves // the precision of the coefficients by one bit, until full precision is reached." if successive_approximation_high != 0 && successive_approximation_high != successive_approximation_low + 1 { return Err(Error::Format("successive approximation scan with more than one bit of improvement".to_owned())); } } else { if spectral_selection_start != 0 || spectral_selection_end != 63 { return Err(Error::Format("spectral selection is not allowed in non-progressive scan".to_owned())); } if successive_approximation_high != 0 || successive_approximation_low != 0 { return Err(Error::Format("successive approximation is not allowed in non-progressive scan".to_owned())); } } Ok(ScanInfo { component_indices: component_indices, dc_table_indices: dc_table_indices, ac_table_indices: ac_table_indices, spectral_selection: Range { start: spectral_selection_start, end: spectral_selection_end + 1, }, successive_approximation_high: successive_approximation_high, successive_approximation_low: successive_approximation_low, }) } // Section B.2.4.1 pub fn parse_dqt(reader: &mut R) -> Result<[Option<[u16; 64]>; 4]> { let mut length = read_length(reader, DQT)?; let mut tables = [None; 4]; // Each DQT segment may contain multiple quantization tables. while length > 0 { let byte = read_u8(reader)?; let precision = (byte >> 4) as usize; let index = (byte & 0x0f) as usize; // The combination of 8-bit sample precision and 16-bit quantization tables is explicitly // disallowed by the JPEG spec: // "An 8-bit DCT-based process shall not use a 16-bit precision quantization table." // "Pq: Quantization table element precision – Specifies the precision of the Qk // values. Value 0 indicates 8-bit Qk values; value 1 indicates 16-bit Qk values. Pq // shall be zero for 8 bit sample precision P (see B.2.2)." // libjpeg allows this behavior though, and there are images in the wild using it. So to // match libjpeg's behavior we are deviating from the JPEG spec here. if precision > 1 { return Err(Error::Format(format!("invalid precision {} in DQT", precision))); } if index > 3 { return Err(Error::Format(format!("invalid destination identifier {} in DQT", index))); } if length < 65 + 64 * precision { return Err(Error::Format("invalid length in DQT".to_owned())); } let mut table = [0u16; 64]; for item in table.iter_mut() { *item = match precision { 0 => u16::from(read_u8(reader)?), 1 => read_u16_from_be(reader)?, _ => unreachable!(), }; } if table.iter().any(|&val| val == 0) { return Err(Error::Format("quantization table contains element with a zero value".to_owned())); } tables[index] = Some(table); length -= 65 + 64 * precision; } Ok(tables) } // Section B.2.4.2 pub fn parse_dht(reader: &mut R, is_baseline: Option) -> Result<(Vec>, Vec>)> { let mut length = read_length(reader, DHT)?; let mut dc_tables = vec![None, None, None, None]; let mut ac_tables = vec![None, None, None, None]; // Each DHT segment may contain multiple huffman tables. while length > 17 { let byte = read_u8(reader)?; let class = byte >> 4; let index = (byte & 0x0f) as usize; if class != 0 && class != 1 { return Err(Error::Format(format!("invalid class {} in DHT", class))); } if is_baseline == Some(true) && index > 1 { return Err(Error::Format("a maximum of two huffman tables per class are allowed in baseline".to_owned())); } if index > 3 { return Err(Error::Format(format!("invalid destination identifier {} in DHT", index))); } let mut counts = [0u8; 16]; reader.read_exact(&mut counts)?; let size = counts.iter().map(|&val| val as usize).fold(0, ::std::ops::Add::add); if size == 0 { return Err(Error::Format("encountered table with zero length in DHT".to_owned())); } else if size > 256 { return Err(Error::Format("encountered table with excessive length in DHT".to_owned())); } else if size > length - 17 { return Err(Error::Format("invalid length in DHT".to_owned())); } let mut values = vec![0u8; size]; reader.read_exact(&mut values)?; match class { 0 => dc_tables[index] = Some(HuffmanTable::new(&counts, &values, HuffmanTableClass::DC)?), 1 => ac_tables[index] = Some(HuffmanTable::new(&counts, &values, HuffmanTableClass::AC)?), _ => unreachable!(), } length -= 17 + size; } if length != 0 { return Err(Error::Format("invalid length in DHT".to_owned())); } Ok((dc_tables, ac_tables)) } // Section B.2.4.4 pub fn parse_dri(reader: &mut R) -> Result { let length = read_length(reader, DRI)?; if length != 2 { return Err(Error::Format("DRI with invalid length".to_owned())); } Ok(read_u16_from_be(reader)?) } // Section B.2.4.5 pub fn parse_com(reader: &mut R) -> Result> { let length = read_length(reader, COM)?; let mut buffer = vec![0u8; length]; reader.read_exact(&mut buffer)?; Ok(buffer) } // Section B.2.4.6 pub fn parse_app(reader: &mut R, marker: Marker) -> Result> { let length = read_length(reader, marker)?; let mut bytes_read = 0; let mut result = None; match marker { APP(0) => { if length >= 5 { let mut buffer = [0u8; 5]; reader.read_exact(&mut buffer)?; bytes_read = buffer.len(); // http://www.w3.org/Graphics/JPEG/jfif3.pdf if &buffer[0 .. 5] == &[b'J', b'F', b'I', b'F', b'\0'] { result = Some(AppData::Jfif); // https://sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#AVI1 } else if &buffer[0 .. 5] == &[b'A', b'V', b'I', b'1', b'\0'] { result = Some(AppData::Avi1); } } } APP(2) => { if length > 14 { let mut buffer = [0u8; 14]; reader.read_exact(&mut buffer)?; bytes_read = buffer.len(); // http://www.color.org/ICC_Minor_Revision_for_Web.pdf // B.4 Embedding ICC profiles in JFIF files if &buffer[0..12] == b"ICC_PROFILE\0" { let mut data = vec![0; length - bytes_read]; reader.read_exact(&mut data)?; bytes_read += data.len(); result = Some(AppData::Icc(IccChunk { seq_no: buffer[12], num_markers: buffer[13], data, })); } } } APP(14) => { if length >= 12 { let mut buffer = [0u8; 12]; reader.read_exact(&mut buffer)?; bytes_read = buffer.len(); // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe if &buffer[0 .. 6] == &[b'A', b'd', b'o', b'b', b'e', b'\0'] { let color_transform = match buffer[11] { 0 => AdobeColorTransform::Unknown, 1 => AdobeColorTransform::YCbCr, 2 => AdobeColorTransform::YCCK, _ => return Err(Error::Format("invalid color transform in adobe app segment".to_owned())), }; result = Some(AppData::Adobe(color_transform)); } } }, _ => {}, } skip_bytes(reader, length - bytes_read)?; Ok(result) } jpeg-decoder-0.1.22/src/upsampler.rs010064400017500001750000000212521400057706300154560ustar 00000000000000use error::{Error, Result, UnsupportedFeature}; use parser::Component; pub struct Upsampler { components: Vec, line_buffer_size: usize } struct UpsamplerComponent { upsampler: Box, width: usize, height: usize, row_stride: usize, } impl Upsampler { pub fn new(components: &[Component], output_width: u16, output_height: u16) -> Result { let h_max = components.iter().map(|c| c.horizontal_sampling_factor).max().unwrap(); let v_max = components.iter().map(|c| c.vertical_sampling_factor).max().unwrap(); let mut upsampler_components = Vec::with_capacity(components.len()); for component in components { let upsampler = choose_upsampler((component.horizontal_sampling_factor, component.vertical_sampling_factor), (h_max, v_max), output_width, output_height)?; upsampler_components.push(UpsamplerComponent { upsampler: upsampler, width: component.size.width as usize, height: component.size.height as usize, row_stride: component.block_size.width as usize * component.dct_scale, }); } let buffer_size = components.iter().map(|c| c.size.width).max().unwrap() as usize * h_max as usize; Ok(Upsampler { components: upsampler_components, line_buffer_size: buffer_size }) } pub fn upsample_and_interleave_row(&self, component_data: &[Vec], row: usize, output_width: usize, output: &mut [u8]) { let component_count = component_data.len(); let mut line_buffer = vec![0u8; self.line_buffer_size]; debug_assert_eq!(component_count, self.components.len()); for (i, component) in self.components.iter().enumerate() { component.upsampler.upsample_row(&component_data[i], component.width, component.height, component.row_stride, row, output_width, &mut line_buffer); for x in 0 .. output_width { output[x * component_count + i] = line_buffer[x]; } } } } struct UpsamplerH1V1; struct UpsamplerH2V1; struct UpsamplerH1V2; struct UpsamplerH2V2; struct UpsamplerGeneric { horizontal_scaling_factor: u8, vertical_scaling_factor: u8 } fn choose_upsampler(sampling_factors: (u8, u8), max_sampling_factors: (u8, u8), output_width: u16, output_height: u16) -> Result> { let h1 = sampling_factors.0 == max_sampling_factors.0 || output_width == 1; let v1 = sampling_factors.1 == max_sampling_factors.1 || output_height == 1; let h2 = sampling_factors.0 * 2 == max_sampling_factors.0; let v2 = sampling_factors.1 * 2 == max_sampling_factors.1; if h1 && v1 { Ok(Box::new(UpsamplerH1V1)) } else if h2 && v1 { Ok(Box::new(UpsamplerH2V1)) } else if h1 && v2 { Ok(Box::new(UpsamplerH1V2)) } else if h2 && v2 { Ok(Box::new(UpsamplerH2V2)) } else { if max_sampling_factors.0 % sampling_factors.0 != 0 || max_sampling_factors.1 % sampling_factors.1 != 0 { Err(Error::Unsupported(UnsupportedFeature::NonIntegerSubsamplingRatio)) } else { Ok(Box::new(UpsamplerGeneric { horizontal_scaling_factor: max_sampling_factors.0 / sampling_factors.0, vertical_scaling_factor: max_sampling_factors.1 / sampling_factors.1 })) } } } trait Upsample { fn upsample_row(&self, input: &[u8], input_width: usize, input_height: usize, row_stride: usize, row: usize, output_width: usize, output: &mut [u8]); } impl Upsample for UpsamplerH1V1 { fn upsample_row(&self, input: &[u8], _input_width: usize, _input_height: usize, row_stride: usize, row: usize, output_width: usize, output: &mut [u8]) { let input = &input[row * row_stride ..]; output[..output_width].copy_from_slice(&input[..output_width]); } } impl Upsample for UpsamplerH2V1 { fn upsample_row(&self, input: &[u8], input_width: usize, _input_height: usize, row_stride: usize, row: usize, _output_width: usize, output: &mut [u8]) { let input = &input[row * row_stride ..]; if input_width == 1 { output[0] = input[0]; output[1] = input[0]; return; } output[0] = input[0]; output[1] = ((input[0] as u32 * 3 + input[1] as u32 + 2) >> 2) as u8; for i in 1 .. input_width - 1 { let sample = 3 * input[i] as u32 + 2; output[i * 2] = ((sample + input[i - 1] as u32) >> 2) as u8; output[i * 2 + 1] = ((sample + input[i + 1] as u32) >> 2) as u8; } output[(input_width - 1) * 2] = ((input[input_width - 1] as u32 * 3 + input[input_width - 2] as u32 + 2) >> 2) as u8; output[(input_width - 1) * 2 + 1] = input[input_width - 1]; } } impl Upsample for UpsamplerH1V2 { fn upsample_row(&self, input: &[u8], _input_width: usize, input_height: usize, row_stride: usize, row: usize, output_width: usize, output: &mut [u8]) { let row_near = row as f32 / 2.0; // If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we // want it to be the next row. let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32); let input_near = &input[row_near as usize * row_stride ..]; let input_far = &input[row_far as usize * row_stride ..]; for i in 0 .. output_width { output[i] = ((3 * input_near[i] as u32 + input_far[i] as u32 + 2) >> 2) as u8; } } } impl Upsample for UpsamplerH2V2 { fn upsample_row(&self, input: &[u8], input_width: usize, input_height: usize, row_stride: usize, row: usize, _output_width: usize, output: &mut [u8]) { let row_near = row as f32 / 2.0; // If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we // want it to be the next row. let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32); let input_near = &input[row_near as usize * row_stride ..]; let input_far = &input[row_far as usize * row_stride ..]; if input_width == 1 { let value = ((3 * input_near[0] as u32 + input_far[0] as u32 + 2) >> 2) as u8; output[0] = value; output[1] = value; return; } let mut t1 = 3 * input_near[0] as u32 + input_far[0] as u32; output[0] = ((t1 + 2) >> 2) as u8; for i in 1 .. input_width { let t0 = t1; t1 = 3 * input_near[i] as u32 + input_far[i] as u32; output[i * 2 - 1] = ((3 * t0 + t1 + 8) >> 4) as u8; output[i * 2] = ((3 * t1 + t0 + 8) >> 4) as u8; } output[input_width * 2 - 1] = ((t1 + 2) >> 2) as u8; } } impl Upsample for UpsamplerGeneric { // Uses nearest neighbor sampling fn upsample_row(&self, input: &[u8], input_width: usize, _input_height: usize, row_stride: usize, row: usize, _output_width: usize, output: &mut [u8]) { let mut index = 0; let start = (row / self.vertical_scaling_factor as usize) * row_stride; let input = &input[start..(start + input_width)]; for val in input { for _ in 0..self.horizontal_scaling_factor { output[index] = *val; index += 1; } } } } jpeg-decoder-0.1.22/src/worker/immediate.rs010064400017500001750000000055171367172151600167320ustar 00000000000000use decoder::MAX_COMPONENTS; use error::Result; use idct::dequantize_and_idct_block; use std::mem; use std::sync::Arc; use parser::Component; use super::{RowData, Worker}; pub struct ImmediateWorker { offsets: [usize; MAX_COMPONENTS], results: Vec>, components: Vec>, quantization_tables: Vec>>, } impl ImmediateWorker { pub fn new_immediate() -> ImmediateWorker { ImmediateWorker { offsets: [0; MAX_COMPONENTS], results: vec![Vec::new(); MAX_COMPONENTS], components: vec![None; MAX_COMPONENTS], quantization_tables: vec![None; MAX_COMPONENTS], } } pub fn start_immediate(&mut self, data: RowData) { assert!(self.results[data.index].is_empty()); self.offsets[data.index] = 0; self.results[data.index].resize(data.component.block_size.width as usize * data.component.block_size.height as usize * data.component.dct_scale * data.component.dct_scale, 0u8); self.components[data.index] = Some(data.component); self.quantization_tables[data.index] = Some(data.quantization_table); } pub fn append_row_immediate(&mut self, (index, data): (usize, Vec)) { // Convert coefficients from a MCU row to samples. let component = self.components[index].as_ref().unwrap(); let quantization_table = self.quantization_tables[index].as_ref().unwrap(); let block_count = component.block_size.width as usize * component.vertical_sampling_factor as usize; let line_stride = component.block_size.width as usize * component.dct_scale; assert_eq!(data.len(), block_count * 64); for i in 0..block_count { let x = (i % component.block_size.width as usize) * component.dct_scale; let y = (i / component.block_size.width as usize) * component.dct_scale; let coefficients = &data[i * 64..(i + 1) * 64]; let output = &mut self.results[index][self.offsets[index] + y * line_stride + x..]; dequantize_and_idct_block(component.dct_scale, coefficients, quantization_table, line_stride, output); } self.offsets[index] += block_count * component.dct_scale * component.dct_scale; } pub fn get_result_immediate(&mut self, index: usize) -> Vec { mem::replace(&mut self.results[index], Vec::new()) } } impl Worker for ImmediateWorker { fn new() -> Result { Ok(ImmediateWorker::new_immediate()) } fn start(&mut self, data: RowData) -> Result<()> { self.start_immediate(data); Ok(()) } fn append_row(&mut self, row: (usize, Vec)) -> Result<()> { self.append_row_immediate(row); Ok(()) } fn get_result(&mut self, index: usize) -> Result> { Ok(self.get_result_immediate(index)) } } jpeg-decoder-0.1.22/src/worker/mod.rs010064400017500001750000000013151400303361400155240ustar 00000000000000mod immediate; mod multithreaded; #[cfg(not(any(target_arch = "asmjs", target_arch = "wasm32")))] pub use self::multithreaded::MultiThreadedWorker as PlatformWorker; #[cfg(any(target_arch = "asmjs", target_arch = "wasm32"))] pub use self::immediate::ImmediateWorker as PlatformWorker; use error::Result; use parser::Component; use std::sync::Arc; pub struct RowData { pub index: usize, pub component: Component, pub quantization_table: Arc<[u16; 64]>, } pub trait Worker: Sized { fn new() -> Result; fn start(&mut self, row_data: RowData) -> Result<()>; fn append_row(&mut self, row: (usize, Vec)) -> Result<()>; fn get_result(&mut self, index: usize) -> Result>; } jpeg-decoder-0.1.22/src/worker/multithreaded.rs010064400017500001750000000066121400303361400176050ustar 00000000000000//! This module implements per-component parallelism. //! It should be possible to implement per-row parallelism as well, //! which should also boost performance of grayscale images //! and allow scaling to more cores. //! However, that would be more complex, so we use this as a starting point. use decoder::MAX_COMPONENTS; use error::Result; use std::{mem, sync::mpsc::{self, Sender}}; use std::thread; use super::{RowData, Worker}; use super::immediate::ImmediateWorker; enum WorkerMsg { Start(RowData), AppendRow(Vec), GetResult(Sender>), } pub struct MultiThreadedWorker { senders: [Option>; MAX_COMPONENTS] } impl Worker for MultiThreadedWorker { fn new() -> Result { Ok(MultiThreadedWorker { senders: [None, None, None, None] }) } fn start(&mut self, row_data: RowData) -> Result<()> { // if there is no worker thread for this component yet, start one let component = row_data.index; if let None = self.senders[component] { let sender = spawn_worker_thread(component)?; self.senders[component] = Some(sender); } // we do the "take out value and put it back in once we're done" dance here // and in all other message-passing methods because there's not that many rows // and this should be cheaper than spawning MAX_COMPONENTS many threads up front let sender = mem::replace(&mut self.senders[component], None).unwrap(); sender.send(WorkerMsg::Start(row_data)).expect("jpeg-decoder worker thread error"); self.senders[component] = Some(sender); Ok(()) } fn append_row(&mut self, row: (usize, Vec)) -> Result<()> { let component = row.0; let sender = mem::replace(&mut self.senders[component], None).unwrap(); sender.send(WorkerMsg::AppendRow(row.1)).expect("jpeg-decoder worker thread error"); self.senders[component] = Some(sender); Ok(()) } fn get_result(&mut self, index: usize) -> Result> { let (tx, rx) = mpsc::channel(); let sender = mem::replace(&mut self.senders[index], None).unwrap(); sender.send(WorkerMsg::GetResult(tx)).expect("jpeg-decoder worker thread error"); Ok(rx.recv().expect("jpeg-decoder worker thread error")) } } fn spawn_worker_thread(component: usize) -> Result> { let thread_builder = thread::Builder::new().name(format!("worker thread for component {}", component)); let (tx, rx) = mpsc::channel(); thread_builder.spawn(move || { let mut worker = ImmediateWorker::new_immediate(); while let Ok(message) = rx.recv() { match message { WorkerMsg::Start(mut data) => { // we always set component index to 0 for worker threads // because they only ever handle one per thread and we don't want them // to attempt to access nonexistent components data.index = 0; worker.start_immediate(data); }, WorkerMsg::AppendRow(row) => { worker.append_row_immediate((0, row)); }, WorkerMsg::GetResult(chan) => { let _ = chan.send(worker.get_result_immediate(0)); break; }, } } })?; Ok(tx) }