diff options
Diffstat (limited to 'vendor/gif')
26 files changed, 4022 insertions, 0 deletions
diff --git a/vendor/gif/.cargo-checksum.json b/vendor/gif/.cargo-checksum.json new file mode 100644 index 0000000..f20516c --- /dev/null +++ b/vendor/gif/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"20a658232e7d744bf594e89cdfe4ac46327c6b8bca2f90abdf9a257b411c3dd3","Cargo.toml":"7fd8ea5c4bea9a774294dd6f340a887c8144ea8beec978da3fda1395df0ee984","Changes.md":"2fe27380488b71f47c0219f4a99b73cbd0f170cbb3237626a44f2a8567774d38","LICENSE-APACHE":"769f80b5bcb42ed0af4e4d2fd74e1ac9bf843cb80c5a29219d1ef3544428a6bb","LICENSE-MIT":"77257f3d2181236b1aee78920238062ae64efe13c5d858b2db126e79c9e1b14f","README.md":"2fdb933b17e84901abbc50ff9fae053a6fb363b56b7093a26695c18863214c95","benches/decode.rs":"a2720a39e2b45022ad9d06a611a1e25d0c8b54738eb9889829b83e5ce320b8a4","benches/rgb_frame.rs":"5ccb6a8ddc14517390d90c2337948751c22e02779069483695ae9d69015d37d2","benches/samples/test.gif":"178c8642416d3f8210a206fb8fef92d307549bd48a552d50464833fa11aad4da","benches/samples/test.png":"dd61356a2c917d3c8e8b982177288768746383af9ae893720ac6465843116168","examples/check.rs":"ddd0870eea6820d3e8d3d71d44879cd0dd2bcac6ec2e91179bb5df892594bc50","examples/explode.rs":"735ae5980aa9106cedbb42cc3a7e0af5dfa3d77c35ae142ab49f499e76cb07a2","src/common.rs":"20ec5382f79999c7ad8774f506cd90987c0d422f32bdbb8abe1434092b7973b4","src/encoder.rs":"f5d5a1aaf362b528b1345503326b2a86c9c90efb1cf9507b62c1d07f5e74d43b","src/lib.rs":"a0035a39aa3649daf3d1ba3439c36e89bb2f6c1eea1aca8ada55e96ede4a3bc8","src/reader/decoder.rs":"2251591967cf56883166b49d034c14bab39b75d586f7eea1f9e81b5a84a2c8d7","src/reader/mod.rs":"d82c4b555ed78bfe860855c38181c74ce7365ea7e1cceb94560a8d2918a4a0af","src/traits.rs":"0c39abb20949ca7615cb7a2340edd2aa5e6cec5686b013352d3736018dee4a94","tests/check_testimages.rs":"dc392491ab67cda8d83a2df2cbdef6578bb88dd5ac5d036e90131c91b83029f6","tests/crashtest.rs":"e22fbda43133ff1f0b2905a6e01dc5ddd854b817b0b28dbf798ade3eb84c8315","tests/decode.rs":"9985342b5fe581e099207e25ba67031fbb46cd9158b84799ab5e9bde23ead31a","tests/results.txt":"0861feaaf22d0473d522353830e15293ba6e87bfab4848d23b8152f4f8b2a03b","tests/roundtrip.rs":"3a77998058c5ea60b092a22bd866e4587d9ad4d660a1c6ddb018efb3e44075d4","tests/stall.rs":"99a7bfd19211b3820e529a4a4dd174fffd9b8d142f3c6ab345e3faa83ff77c2e","tests/stall/issue-101-infinite-empty-loop.gif":"0a32d7e7b6e1373fd11f23b1e061e11f99cfea93c21937868fb642479bf02c0d"},"package":"80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"}
\ No newline at end of file diff --git a/vendor/gif/Cargo.lock b/vendor/gif/Cargo.lock new file mode 100644 index 0000000..32e7cd7 --- /dev/null +++ b/vendor/gif/Cargo.lock @@ -0,0 +1,668 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[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 = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "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 = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide 0.5.4", +] + +[[package]] +name = "gif" +version = "0.12.0" +dependencies = [ + "color_quant", + "criterion", + "glob", + "png", + "weezl", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +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.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "plotters" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +dependencies = [ + "bitflags", + "crc32fast", + "flate2", + "miniz_oxide 0.6.2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "itoa 1.0.4", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +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" diff --git a/vendor/gif/Cargo.toml b/vendor/gif/Cargo.toml new file mode 100644 index 0000000..5e6614f --- /dev/null +++ b/vendor/gif/Cargo.toml @@ -0,0 +1,83 @@ +# 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 are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "gif" +version = "0.12.0" +authors = ["The image-rs Developers"] +exclude = [ + "tests/crashtest/*", + "tests/samples/*", + "benches/*.gif", + "gif-afl/*", +] +description = "GIF de- and encoder" +homepage = "https://github.com/image-rs/image-gif" +documentation = "https://docs.rs/gif" +readme = "README.md" +license = "MIT/Apache-2.0" +repository = "https://github.com/image-rs/image-gif" + +[[test]] +name = "check_testimages" +required-features = ["std"] + +[[test]] +name = "crashtest" +required-features = ["std"] + +[[test]] +name = "decode" +required-features = ["std"] + +[[test]] +name = "stall" +required-features = ["std"] + +[[test]] +name = "roundtrip" +required-features = ["std"] + +[[bench]] +name = "decode" +harness = false +required-features = ["std"] + +[[bench]] +name = "rgb_frame" +harness = false +required-features = ["std, color_quant"] + +[dependencies.color_quant] +version = "1.0" +optional = true + +[dependencies.weezl] +version = "0.1.5" + +[dev-dependencies.criterion] +version = "0.3.1" + +[dev-dependencies.glob] +version = "0.3" + +[dev-dependencies.png] +version = "0.17.2" + +[features] +default = [ + "raii_no_panic", + "std", + "color_quant", +] +raii_no_panic = [] +std = [] diff --git a/vendor/gif/Changes.md b/vendor/gif/Changes.md new file mode 100644 index 0000000..19efbd5 --- /dev/null +++ b/vendor/gif/Changes.md @@ -0,0 +1,60 @@ +# v0.12.0 + +Features: +- Add compression of pre-compressed frame data, via `Encoder::write_lzw_pre_encoded_frame`. +- The `color_quant` dependency is now optional. Turning it off disables some + interfaces that would internally build quantization tables. The generic + implementation of creating such tables can be prohibitively costly compared + to specialized algorithms in some use cases. + +Optimization: +- Avoid some allocations in by replacing `flat_map` argument with arrays + +# v0.11.4 + +Bufixes: +- Fix decoding confusing superfluous image data from previous frames with + current frame data. +- Bump minimum required version of `weezl`. + +Features: +- Add `Encoder::{get_ref, get_mut, into_inner}` to access underlying stream. + +# v0.11.3 + +Bugfixes: +- Fix panic while decoding some images, has no precise cause in the file. +- Warn about `set_extensions` being unimplemented... + +Features: +- Added `StreamingDecoder::version` to query the precise version of the + standard used for encoding the file. This is merely a hint. +- Added `DecodeOptions::allow_unknown_blocks` to skip over unknown or + unspecified block kinds. + +Optimization: +- `Frame::from_rgba` now recognizes when less than 256 colors are being used, + dynamically skipping the quantization phase. +- Encoding image chunks is faster and simpler + + +# v0.11.2 + +- Fix panic when LZW code size is invalid +- Added option to omit check for lzw end code + +# v0.11.1 + +- Frames out-of-bounds of the screen descriptor are again accepted by default. +- Added `DecodeOptions::check_frame_consistency` to turn this validation on. + +# v0.11 + +- Rename `Reader` to `Decoder`. +- Reworked `Decoder` into `DecodeOptions`. +- The decoding error is now opaque and no longer allocates a string. Adding + more information or more error conditions is forward compatible. +- Replace the lzw decoder with `weezl`, up to +350% throughput. +- The dysfunctional C-API has been (temporarily?) removed + - It may get reintroduced as a separate crate at some point +- Added a `std` feature. It must be active for now. diff --git a/vendor/gif/LICENSE-APACHE b/vendor/gif/LICENSE-APACHE new file mode 100644 index 0000000..f8e5e5e --- /dev/null +++ b/vendor/gif/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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.
\ No newline at end of file diff --git a/vendor/gif/LICENSE-MIT b/vendor/gif/LICENSE-MIT new file mode 100644 index 0000000..44669a4 --- /dev/null +++ b/vendor/gif/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 nwin + +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. diff --git a/vendor/gif/README.md b/vendor/gif/README.md new file mode 100644 index 0000000..3202b8d --- /dev/null +++ b/vendor/gif/README.md @@ -0,0 +1,83 @@ +# GIF en- and decoding library [![Build Status](https://github.com/image-rs/image-gif/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/image-gif/actions) + +GIF en- and decoder written in Rust ([API Documentation](https://docs.rs/gif/)). + +# GIF encoding and decoding library + +This library provides all functions necessary to de- and encode GIF files. + +## High level interface + +The high level interface consists of the two types +[`Encoder`](https://docs.rs/gif/*/gif/struct.Encoder.html) and [`Decoder`](https://docs.rs/gif/*/gif/struct.Decoder.html). + +### Decoding GIF files + +```rust +// Open the file +use std::fs::File; +let input = File::open("tests/samples/sample_1.gif").unwrap(); +// Configure the decoder such that it will expand the image to RGBA. +let mut options = gif::DecodeOptions::new(); +options.set_color_output(gif::ColorOutput::RGBA); +// Read the file header +let mut decoder = options.read_info(input).unwrap(); +while let Some(frame) = decoder.read_next_frame().unwrap() { + // Process every frame +} +``` + +### Encoding GIF files + +The encoder can be used to save simple computer generated images: + +```rust +use gif::{Frame, Encoder, Repeat}; +use std::fs::File; +use std::borrow::Cow; + +let color_map = &[0xFF, 0xFF, 0xFF, 0, 0, 0]; +let (width, height) = (6, 6); +let beacon_states = [[ + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, +], [ + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, +]]; +let mut image = File::create("target/beacon.gif").unwrap(); +let mut encoder = Encoder::new(&mut image, width, height, color_map).unwrap(); +encoder.set_repeat(Repeat::Infinite).unwrap(); +for state in &beacon_states { + let mut frame = Frame::default(); + frame.width = width; + frame.height = height; + frame.buffer = Cow::Borrowed(&*state); + encoder.write_frame(&frame).unwrap(); +} +``` + +[`Frame::from_*`](https://docs.rs/gif/*/gif/struct.Frame.html) can be used to convert a true color image to a paletted +image with a maximum of 256 colors: + +```rust +use std::fs::File; + +// Get pixel data from some source +let mut pixels: Vec<u8> = vec![0; 30_000]; +// Create frame from data +let frame = gif::Frame::from_rgb(100, 100, &mut *pixels); +// Create encoder +let mut image = File::create("target/indexed_color.gif").unwrap(); +let mut encoder = gif::Encoder::new(&mut image, frame.width, frame.height, &[]).unwrap(); +// Write frame to file +encoder.write_frame(&frame).unwrap(); +``` diff --git a/vendor/gif/benches/decode.rs b/vendor/gif/benches/decode.rs new file mode 100644 index 0000000..8272cee --- /dev/null +++ b/vendor/gif/benches/decode.rs @@ -0,0 +1,81 @@ +use criterion::{black_box, BenchmarkId, BenchmarkGroup, Criterion, Throughput, measurement::Measurement}; +use gif::Decoder; + +fn read_image(image: &[u8]) -> Option<Vec<u8>> { + let decoder = Decoder::new(black_box(image)); + //decoder.set_param(gif::ColorOutput::RGBA); + let mut reader = decoder.unwrap(); + + while let Some(_) = reader.next_frame_info().unwrap() { + let mut v = vec![0; reader.buffer_size()]; + reader.fill_buffer(&mut v).unwrap(); + return Some(v); + } + None +} + +fn read_metadata(image: &[u8]) { + let decoder = Decoder::new(black_box(image)); + decoder.unwrap(); +} + +fn main() { + struct BenchDef { + data: &'static [u8], + id: &'static str, + sample_size: usize, + } + + fn run_bench_def<M: Measurement>(group: &mut BenchmarkGroup<M>, def: BenchDef) { + group + .sample_size(def.sample_size) + .throughput(Throughput::Bytes(def.data.len() as u64)) + .bench_with_input( + BenchmarkId::new(def.id, def.data.len()), + def.data, + |b, input| { + b.iter(|| read_image(input)) + } + ); + } + + let mut c = Criterion::default().configure_from_args(); + let mut group = c.benchmark_group("gif"); + + run_bench_def(&mut group, BenchDef { + data: include_bytes!("note.gif"), + id: "note.gif", + sample_size: 100, + }); + + run_bench_def(&mut group, BenchDef { + data: include_bytes!("photo.gif"), + id: "photo.gif", + sample_size: 20, + }); + + run_bench_def(&mut group, BenchDef { + data: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/samples/sample_1.gif")), + id: "sample_1.gif", + sample_size: 100, + }); + + run_bench_def(&mut group, BenchDef { + data: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/samples/sample_big.gif")), + id: "sample_big.gif", + sample_size: 20, + }); + + group + .bench_with_input( + "extract-metadata-note", + include_bytes!("note.gif"), + |b, input| { + b.iter(|| read_metadata(input)) + } + ); + + group.finish(); + + c.final_summary(); +} diff --git a/vendor/gif/benches/rgb_frame.rs b/vendor/gif/benches/rgb_frame.rs new file mode 100644 index 0000000..a0d1934 --- /dev/null +++ b/vendor/gif/benches/rgb_frame.rs @@ -0,0 +1,73 @@ +use std::fs; + +use criterion::{Criterion, Throughput}; +use gif::{Encoder, Frame, Repeat}; +use png; + +const DIR: &str = "benches/samples"; + +fn main() +{ + let mut c = Criterion::default().configure_from_args(); + let mut group = c.benchmark_group("rgb_frame"); + + let dir = fs::read_dir(DIR).expect("Cant'r read dir:\n{}"); + + for path in dir { + let path = path.expect("Can't read path:\n{}").path(); + if path.extension().unwrap() != "png" { + continue; + } + + let mut reader = { + let input = fs::File::open(&path).unwrap(); + let decoder = png::Decoder::new(input); + decoder.read_info().unwrap() + }; + + let mut buf = vec![0; reader.output_buffer_size()]; + let info = reader.next_frame(&mut buf).unwrap(); + + let (w, h, size) = { + // could use try_into().unwrap() but probably no need + (info.width as u16, info.height as u16, info.buffer_size()) + }; + + //size might have to be adjusted for large images + group + .sample_size(50) + .throughput(Throughput::Bytes(size as u64)) + .bench_function(path.file_name().unwrap().to_str().unwrap(), + |b| { + match info.color_type { + png::ColorType::Rgb => b.iter(|| { + Frame::from_rgb_speed(w, h, &mut buf[..size], 30) + }), + png::ColorType::Rgba => b.iter(|| { + Frame::from_rgba_speed(w, h, &mut buf[..size], 30) + }), + c => { + println!("Image has wrong color type: {:?}", c); + } + } + }); + + // actually write the image as a singe frame gif... while MSE can be used + // for quality check, it might not be as good as visual inspection + let mut encoder = { + let output = fs::File::create(path.with_extension("gif")).unwrap(); + Encoder::new(output, w, h, &[]).unwrap() + }; + encoder.set_repeat(Repeat::Finite(0)).unwrap(); + + let frame = match info.color_type { + png::ColorType::Rgb => Frame::from_rgb(w, h, &mut buf[..size]), + png::ColorType::Rgba => Frame::from_rgba(w, h, &mut buf[..size]), + _ => continue, + }; + + encoder.write_frame(&frame).unwrap(); + } + group.finish(); + c.final_summary(); +} diff --git a/vendor/gif/benches/samples/test.gif b/vendor/gif/benches/samples/test.gif Binary files differnew file mode 100644 index 0000000..a2e032f --- /dev/null +++ b/vendor/gif/benches/samples/test.gif diff --git a/vendor/gif/benches/samples/test.png b/vendor/gif/benches/samples/test.png Binary files differnew file mode 100644 index 0000000..ec11a5b --- /dev/null +++ b/vendor/gif/benches/samples/test.png diff --git a/vendor/gif/examples/check.rs b/vendor/gif/examples/check.rs new file mode 100644 index 0000000..89a6123 --- /dev/null +++ b/vendor/gif/examples/check.rs @@ -0,0 +1,41 @@ +use std::{env, fs, process}; + +fn main() { + let file = env::args().nth(1) + .unwrap_or_else(|| explain_usage()); + let file = fs::File::open(&file) + .expect("failed to open input file"); + let mut reader = { + let mut options = gif::DecodeOptions::new(); + options.allow_unknown_blocks(true); + options.read_info(file).unwrap() + }; + + loop { + let frame = match reader.read_next_frame() { + Ok(Some(frame)) => frame, + Ok(None) => break, + Err(error) => { + println!("Error: {:?}", error); + break; + } + }; + + println!( + " Frame:\n \ + delay: {:?}\n \ + canvas: {}x{}+{}+{}\n \ + dispose: {:?}\n \ + needs_input: {:?}", + frame.delay, + frame.width, frame.height, frame.left, frame.top, + frame.dispose, + frame.needs_user_input + ); + } +} + +fn explain_usage() -> ! { + println!("Print information on the frames of a gif.\n\nUsage: check <file>"); + process::exit(1) +} diff --git a/vendor/gif/examples/explode.rs b/vendor/gif/examples/explode.rs new file mode 100644 index 0000000..caa3c34 --- /dev/null +++ b/vendor/gif/examples/explode.rs @@ -0,0 +1,44 @@ +//! Exports each GIF frame as a separate image. + +use std::env; +use std::fs::File; +use std::path::PathBuf; + +fn main() -> Result<(), Box<dyn std::error::Error>> { + let input_path = PathBuf::from( + env::args_os() + .nth(1) + .ok_or("Specify a GIF path as the first argument")?, + ); + + let input = File::open(&input_path)?; + let mut options = gif::DecodeOptions::new(); + options.set_color_output(gif::ColorOutput::Indexed); + let mut decoder = options.read_info(input)?; + let screen_width = decoder.width(); + let screen_height = decoder.height(); + let global_pal = decoder.global_palette().unwrap_or_default().to_vec(); + + let output_file_stem = input_path.file_stem().unwrap().to_str().unwrap(); + let mut frame_number = 1; + while let Some(frame) = decoder.read_next_frame()? { + let output_path = format!("{}.{:03}.gif", output_file_stem, frame_number); + let mut output = File::create(&output_path)?; + let mut encoder = gif::Encoder::new(&mut output, screen_width, screen_height, &global_pal)?; + encoder.write_frame(&frame)?; + frame_number += 1; + + use gif::DisposalMethod::*; + let disposal = match frame.dispose { + Any => "any", + Keep => "keep", + Background => "background", + Previous => "previous", + }; + eprintln!( + "Written {} ({}x{}@{}x{} delay={} {})", + output_path, frame.width, frame.height, frame.top, frame.left, frame.delay, disposal + ); + } + Ok(()) +} diff --git a/vendor/gif/src/common.rs b/vendor/gif/src/common.rs new file mode 100644 index 0000000..20ba99b --- /dev/null +++ b/vendor/gif/src/common.rs @@ -0,0 +1,346 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::collections::HashSet; + +/// Disposal method +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum DisposalMethod { + /// StreamingDecoder is not required to take any action. + Any = 0, + /// Do not dispose. + Keep = 1, + /// Restore to background color. + Background = 2, + /// Restore to previous. + Previous = 3, +} + +impl DisposalMethod { + /// Converts `u8` to `Option<Self>` + pub fn from_u8(n: u8) -> Option<DisposalMethod> { + match n { + 0 => Some(DisposalMethod::Any), + 1 => Some(DisposalMethod::Keep), + 2 => Some(DisposalMethod::Background), + 3 => Some(DisposalMethod::Previous), + _ => None + } + } +} + +/// Known GIF block labels. +/// +/// Note that the block uniquely specifies the layout of bytes that follow and how they are +/// framed. For example, the header always has a fixed length but is followed by a variable amount +/// of additional data. An image descriptor may be followed by a local color table depending on +/// information read in it. Therefore, it doesn't make sense to continue parsing after encountering +/// an unknown block as the semantics of following bytes are unclear. +/// +/// The extension block provides a common framing for an arbitrary amount of application specific +/// data which may be ignored. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum Block { + /// Image block. + Image = 0x2C, + /// Extension block. + Extension = 0x21, + /// Image trailer. + Trailer = 0x3B, +} + +impl Block { + /// Converts `u8` to `Option<Self>` + pub fn from_u8(n: u8) -> Option<Block> { + match n { + 0x2C => Some(Block::Image), + 0x21 => Some(Block::Extension), + 0x3B => Some(Block::Trailer), + _ => None + } + } +} + +/// A newtype wrapper around an arbitrary extension ID. +/// +/// An extension is some amount of byte data organized in sub-blocks so that one can skip over it +/// without knowing the semantics. Though technically you likely want to use a `Application` +/// extension, the library tries to stay flexible here. +/// +/// This allows us to customize the set of impls compared to a raw `u8`. It also clarifies the +/// intent and gives some inherent methods for interoperability with known extension types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct AnyExtension(pub u8); + +/// Known GIF extension labels. +/// +/// These are extensions which may be interpreted by the library and to which a specification with +/// the internal data layout is known. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum Extension { + /// Plain Text extension. + /// + /// This instructs the decoder to render a text as characters in a grid of cells, in a + /// mono-spaced font of its choosing. This is seldom actually implemented and ignored by + /// ImageMagick. The color is always taken from the global table which further complicates any + /// use. No real information on the frame sequencing of this block is available in the + /// standard. + Text = 0x01, + /// Control extension. + Control = 0xF9, + /// Comment extension. + Comment = 0xFE, + /// Application extension. + /// + /// See [ImageMagick] for an idea of commonly recognized extensions. + /// + /// [ImageMagick]: https://github.com/ImageMagick/ImageMagick/blob/b0b58c6303195928060f55f9c3ca8233ab7f7733/coders/gif.c#L1128 + Application = 0xFF, +} + +impl AnyExtension { + /// Decode the label as a known extension. + pub fn into_known(self) -> Option<Extension> { + Extension::from_u8(self.0) + } +} + +impl From<Extension> for AnyExtension { + fn from(ext: Extension) -> Self { + AnyExtension(ext as u8) + } +} + +impl Extension { + /// Converts `u8` to a `Extension` if it is known. + pub fn from_u8(n: u8) -> Option<Extension> { + match n { + 0x01 => Some(Extension::Text), + 0xF9 => Some(Extension::Control), + 0xFE => Some(Extension::Comment), + 0xFF => Some(Extension::Application), + _ => None + } + } +} + +/// A GIF frame +#[derive(Debug, Clone)] +pub struct Frame<'a> { + /// Frame delay in units of 10 ms. + pub delay: u16, + /// Disposal method. + pub dispose: DisposalMethod, + /// Transparent index (if available). + pub transparent: Option<u8>, + /// True if the frame needs user input to be displayed. + pub needs_user_input: bool, + /// Offset from the top border of the canvas. + pub top: u16, + /// Offset from the left border of the canvas. + pub left: u16, + /// Width of the frame. + pub width: u16, + /// Height of the frame. + pub height: u16, + /// True if the image is interlaced. + pub interlaced: bool, + /// Frame local color palette if available. + pub palette: Option<Vec<u8>>, + /// Buffer containing the image data. + /// Only indices unless configured differently. + pub buffer: Cow<'a, [u8]> +} + +impl<'a> Default for Frame<'a> { + fn default() -> Frame<'a> { + Frame { + delay: 0, + dispose: DisposalMethod::Keep, + transparent: None, + needs_user_input: false, + top: 0, + left: 0, + width: 0, + height: 0, + interlaced: false, + palette: None, + buffer: Cow::Borrowed(&[]) + } + } +} + +impl Frame<'static> { + /// Creates a frame from pixels in RGBA format. + /// + /// This is a lossy method. The `gif` format does not support arbitrary alpha but only a 1-bit + /// transparency mask per pixel. Any non-zero alpha value will be interpreted as a fully opaque + /// pixel. Additionally, only 256 colors can appear in a single frame. The palette will be + /// reduced by the NeuQuant algorithm if necessary. Different frames have independent palettes. + /// + /// *Note: This method is not optimized for speed.* + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height * 4`. + #[cfg(feature = "color_quant")] + pub fn from_rgba(width: u16, height: u16, pixels: &mut [u8]) -> Frame<'static> { + Frame::from_rgba_speed(width, height, pixels, 1) + } + + /// Creates a frame from pixels in RGBA format. + /// + /// `speed` is a value in the range [1, 30]. + /// The higher the value the faster it runs at the cost of image quality. + /// A `speed` of 10 is a good compromise between speed and quality. + /// + /// This is a lossy method. The `gif` format does not support arbitrary alpha but only a 1-bit + /// transparency mask per pixel. Any non-zero alpha value will be interpreted as a fully opaque + /// pixel. Additionally, only 256 colors can appear in a single frame. The palette will be + /// reduced by the NeuQuant algorithm if necessary. Different frames have independent palettes. + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height * 4`. + /// * If `speed < 1` or `speed > 30` + #[cfg(feature = "color_quant")] + pub fn from_rgba_speed(width: u16, height: u16, pixels: &mut [u8], speed: i32) -> Frame<'static> { + assert_eq!(width as usize * height as usize * 4, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame"); + assert!(speed >= 1 && speed <= 30, "speed needs to be in the range [1, 30]"); + let mut transparent = None; + for pix in pixels.chunks_exact_mut(4) { + if pix[3] != 0 { + pix[3] = 0xFF; + } else { + transparent = Some([pix[0], pix[1], pix[2], pix[3]]) + } + } + + // Attempt to build a palette of all colors. If we go over 256 colors, + // switch to the NeuQuant algorithm. + let mut colors: HashSet<(u8, u8, u8, u8)> = HashSet::new(); + for pixel in pixels.chunks_exact(4) { + if colors.insert((pixel[0], pixel[1], pixel[2], pixel[3])) && colors.len() > 256 { + // > 256 colours, let's use NeuQuant. + let nq = color_quant::NeuQuant::new(speed, 256, pixels); + + return Frame { + width, + height, + buffer: Cow::Owned(pixels.chunks_exact(4).map(|pix| nq.index_of(pix) as u8).collect()), + palette: Some(nq.color_map_rgb()), + transparent: transparent.map(|t| nq.index_of(&t) as u8), + ..Frame::default() + }; + } + } + + // Palette size <= 256 elements, we can build an exact palette. + let mut colors_vec: Vec<(u8, u8, u8, u8)> = colors.into_iter().collect(); + colors_vec.sort(); + let palette = colors_vec.iter().flat_map(|&(r, g, b, _a)| [r, g, b]).collect(); + let colors_lookup: HashMap<(u8, u8, u8, u8), u8> = colors_vec.into_iter().zip(0..=255).collect(); + + let index_of = | pixel: &[u8] | + *colors_lookup.get(&(pixel[0], pixel[1], pixel[2], pixel[3])).unwrap(); + + return Frame { + width, + height, + buffer: Cow::Owned(pixels.chunks_exact(4).map(|pix| index_of(pix)).collect()), + palette: Some(palette), + transparent: transparent.map(|t| index_of(&t)), + ..Frame::default() + } + } + + /// Creates a frame from a palette and indexed pixels. + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height`. + /// * If the length of palette > `256 * 3`. + pub fn from_palette_pixels(width: u16, height: u16, pixels: &[u8], palette: &[u8], transparent: Option<u8>) -> Frame<'static> { + assert_eq!(width as usize * height as usize, pixels.len(), "Too many or too little pixels for the given width and height to create a GIF Frame"); + assert!(palette.len() <= 256*3, "Too many palette values to create a GIF Frame"); + + Frame { + width, + height, + buffer: Cow::Owned(pixels.to_vec()), + palette: Some(palette.to_vec()), + transparent, + ..Frame::default() + } + } + + /// Creates a frame from indexed pixels in the global palette. + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height`. + pub fn from_indexed_pixels(width: u16, height: u16, pixels: &[u8], transparent: Option<u8>) -> Frame<'static> { + assert_eq!(width as usize * height as usize, pixels.len(), "Too many or too little pixels for the given width and height to create a GIF Frame"); + + Frame { + width, + height, + buffer: Cow::Owned(pixels.to_vec()), + palette: None, + transparent, + ..Frame::default() + } + } + + /// Creates a frame from pixels in RGB format. + /// + /// This is a lossy method. In the `gif` format only 256 colors can appear in a single frame. + /// The palette will be reduced by the NeuQuant algorithm if necessary. Different frames have + /// independent palettes. + /// + /// *Note: This method is not optimized for speed.* + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height * 3`. + #[cfg(feature = "color_quant")] + pub fn from_rgb(width: u16, height: u16, pixels: &[u8]) -> Frame<'static> { + Frame::from_rgb_speed(width, height, pixels, 1) + } + + /// Creates a frame from pixels in RGB format. + /// + /// `speed` is a value in the range [1, 30]. + /// + /// This is a lossy method. In the `gif` format only 256 colors can appear in a single frame. + /// The palette will be reduced by the NeuQuant algorithm if necessary. Different frames have + /// independent palettes. + /// + /// The higher the value the faster it runs at the cost of image quality. + /// A `speed` of 10 is a good compromise between speed and quality. + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height * 3`. + /// * If `speed < 1` or `speed > 30` + #[cfg(feature = "color_quant")] + pub fn from_rgb_speed(width: u16, height: u16, pixels: &[u8], speed: i32) -> Frame<'static> { + assert_eq!(width as usize * height as usize * 3, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame"); + let mut vec: Vec<u8> = Vec::with_capacity(pixels.len() + width as usize * height as usize); + for v in pixels.chunks_exact(3) { + vec.extend_from_slice(&[v[0], v[1], v[2], 0xFF]) + } + Frame::from_rgba_speed(width, height, &mut vec, speed) + } + + pub(crate) fn required_bytes(&self) -> usize { + usize::from(self.width) * usize::from(self.height) + } +} + +#[test] +#[cfg(feature = "color_quant")] +// Creating the `colors_lookup` hashmap in Frame::from_rgba_speed panics due to +// overflow while bypassing NeuQuant and zipping a RangeFrom with 256 colors. +// Changing .zip(0_u8..) to .zip(0_u8..=255) fixes this issue. +fn rgba_speed_avoid_panic_256_colors() { + let side = 16; + let pixel_data: Vec<u8> = (0..=255).map(|a| vec![a, a, a]).flatten().collect(); + Frame::from_rgb(side, side, &pixel_data); +} diff --git a/vendor/gif/src/encoder.rs b/vendor/gif/src/encoder.rs new file mode 100644 index 0000000..693a8cb --- /dev/null +++ b/vendor/gif/src/encoder.rs @@ -0,0 +1,434 @@ +//! # Minimal gif encoder +use std::io; +use std::io::prelude::*; +use std::fmt; +use std::error; +use std::borrow::Cow; + +use weezl::{BitOrder, encode::Encoder as LzwEncoder}; + +use crate::traits::{WriteBytesExt}; +use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame}; + +#[derive(Debug)] +enum FormatErrorKind { + /// The image has too many colors. + TooManyColors, + /// The image has no color palette which is required. + MissingColorPalette, +} + +/// The image has incorrect properties, making it impossible to encode as a gif. +#[derive(Debug)] +pub struct EncodingFormatError { + kind: FormatErrorKind +} + +impl error::Error for EncodingFormatError {} +impl fmt::Display for EncodingFormatError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + FormatErrorKind::TooManyColors => write!(fmt, "the image has too many colors"), + FormatErrorKind::MissingColorPalette => write!(fmt, "the GIF format requires a color palette but none was given") + } + } +} + +impl From<FormatErrorKind> for EncodingFormatError { + fn from(kind: FormatErrorKind) -> Self { + EncodingFormatError { kind } + } +} + +#[derive(Debug)] +/// Encoding error. +pub enum EncodingError { + /// Returned if the to image is not encodable as a gif. + Format(EncodingFormatError), + /// Wraps `std::io::Error`. + Io(io::Error), +} + +impl fmt::Display for EncodingError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + EncodingError::Io(err) => err.fmt(fmt), + EncodingError::Format(err) => err.fmt(fmt), + } + } +} + +impl error::Error for EncodingError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + EncodingError::Io(err) => Some(err), + EncodingError::Format(err) => Some(err), + } + } +} + +impl From<io::Error> for EncodingError { + fn from(err: io::Error) -> Self { + EncodingError::Io(err) + } +} + +impl From<EncodingFormatError> for EncodingError { + fn from(err: EncodingFormatError) -> Self { + EncodingError::Format(err) + } +} + +impl From<FormatErrorKind> for EncodingError { + fn from(kind: FormatErrorKind) -> Self { + EncodingError::Format(kind.into()) + } +} + + +/// Number of repetitions +#[derive(Copy, Clone, Debug)] +pub enum Repeat { + /// Finite number of repetitions + Finite(u16), + /// Infinite number of repetitions + Infinite +} + +/// Extension data. +pub enum ExtensionData { + /// Control extension. Use `ExtensionData::new_control_ext` to construct. + Control { + /// Flags. + flags: u8, + /// Frame delay. + delay: u16, + /// Transparent index. + trns: u8 + }, + /// Sets the number of repetitions + Repetitions(Repeat) +} + +impl ExtensionData { + /// Constructor for control extension data. + /// + /// `delay` is given in units of 10 ms. + pub fn new_control_ext(delay: u16, dispose: DisposalMethod, + needs_user_input: bool, trns: Option<u8>) -> ExtensionData { + let mut flags = 0; + let trns = match trns { + Some(trns) => { + flags |= 1; + trns as u8 + }, + None => 0 + }; + flags |= (needs_user_input as u8) << 1; + flags |= (dispose as u8) << 2; + ExtensionData::Control { + flags: flags, + delay: delay, + trns: trns + } + } +} + +impl<W: Write> Encoder<W> { + /// Creates a new encoder. + /// + /// `global_palette` gives the global color palette in the format `[r, g, b, ...]`, + /// if no global palette shall be used an empty slice may be supplied. + pub fn new(w: W, width: u16, height: u16, global_palette: &[u8]) -> Result<Self, EncodingError> { + let buffer_size = (width as usize) * (height as usize); + Encoder { + w: Some(w), + global_palette: false, + width: width, + height: height, + buffer: Vec::with_capacity(buffer_size) + }.write_global_palette(global_palette) + } + + /// Write an extension block that signals a repeat behaviour. + pub fn set_repeat(&mut self, repeat: Repeat) -> Result<(), EncodingError> { + self.write_extension(ExtensionData::Repetitions(repeat)) + } + + /// Writes the global color palette. + pub fn write_global_palette(mut self, palette: &[u8]) -> Result<Self, EncodingError> { + self.global_palette = true; + let mut flags = 0; + flags |= 0b1000_0000; + let num_colors = palette.len() / 3; + if num_colors > 256 { + return Err(EncodingError::from(FormatErrorKind::TooManyColors)); + } + // Size of global color table. + flags |= flag_size(num_colors); + // Color resolution .. FIXME. This is mostly ignored (by ImageMagick at least) but hey, we + // should use some sensible value here or even allow configuring it? + flags |= flag_size(num_colors) << 4; // wtf flag + self.write_screen_desc(flags)?; + self.write_color_table(palette)?; + Ok(self) + } + + /// Writes a frame to the image. + /// + /// Note: This function also writes a control extension if necessary. + pub fn write_frame(&mut self, frame: &Frame) -> Result<(), EncodingError> { + self.write_frame_header(frame)?; + self.write_image_block(&frame.buffer) + } + + fn write_frame_header(&mut self, frame: &Frame) -> Result<(), EncodingError> { + // TODO commented off to pass test in lib.rs + //if frame.delay > 0 || frame.transparent.is_some() { + self.write_extension(ExtensionData::new_control_ext( + frame.delay, + frame.dispose, + frame.needs_user_input, + frame.transparent + + ))?; + //} + let writer = self.w.as_mut().unwrap(); + writer.write_le(Block::Image as u8)?; + writer.write_le(frame.left)?; + writer.write_le(frame.top)?; + writer.write_le(frame.width)?; + writer.write_le(frame.height)?; + let mut flags = 0; + if frame.interlaced { + flags |= 0b0100_0000; + } + match frame.palette { + Some(ref palette) => { + flags |= 0b1000_0000; + let num_colors = palette.len() / 3; + if num_colors > 256 { + return Err(EncodingError::from(FormatErrorKind::TooManyColors)); + } + flags |= flag_size(num_colors); + writer.write_le(flags)?; + self.write_color_table(palette) + }, + None => if !self.global_palette { + Err(EncodingError::from(FormatErrorKind::MissingColorPalette)) + } else { + writer.write_le(flags).map_err(Into::into) + } + } + } + + fn write_image_block(&mut self, data: &[u8]) -> Result<(), EncodingError> { + self.buffer.clear(); + lzw_encode(data, &mut self.buffer); + + let writer = self.w.as_mut().unwrap(); + Self::write_encoded_image_block(writer, &self.buffer) + } + + fn write_encoded_image_block(writer: &mut W, data_with_min_code_size: &[u8]) -> Result<(), EncodingError> { + let (&min_code_size, data) = data_with_min_code_size.split_first().unwrap_or((&2, &[])); + writer.write_le(min_code_size)?; + + // Write blocks. `chunks_exact` seems to be slightly faster + // than `chunks` according to both Rust docs and benchmark results. + let mut iter = data.chunks_exact(0xFF); + while let Some(full_block) = iter.next() { + writer.write_le(0xFFu8)?; + writer.write_all(full_block)?; + } + let last_block = iter.remainder(); + if !last_block.is_empty() { + writer.write_le(last_block.len() as u8)?; + writer.write_all(last_block)?; + } + writer.write_le(0u8).map_err(Into::into) + } + + fn write_color_table(&mut self, table: &[u8]) -> Result<(), EncodingError> { + let writer = self.w.as_mut().unwrap(); + let num_colors = table.len() / 3; + if num_colors > 256 { + return Err(EncodingError::from(FormatErrorKind::TooManyColors)); + } + let size = flag_size(num_colors); + writer.write_all(&table[..num_colors * 3])?; + // Waste some space as of gif spec + for _ in 0..((2 << size) - num_colors) { + writer.write_all(&[0, 0, 0])? + } + Ok(()) + } + + /// Writes an extension to the image. + /// + /// It is normally not necessary to call this method manually. + pub fn write_extension(&mut self, extension: ExtensionData) -> Result<(), EncodingError> { + use self::ExtensionData::*; + // 0 finite repetitions can only be achieved + // if the corresponting extension is not written + if let Repetitions(Repeat::Finite(0)) = extension { + return Ok(()) + } + let writer = self.w.as_mut().unwrap(); + writer.write_le(Block::Extension as u8)?; + match extension { + Control { flags, delay, trns } => { + writer.write_le(Extension::Control as u8)?; + writer.write_le(4u8)?; + writer.write_le(flags)?; + writer.write_le(delay)?; + writer.write_le(trns)?; + } + Repetitions(repeat) => { + writer.write_le(Extension::Application as u8)?; + writer.write_le(11u8)?; + writer.write_all(b"NETSCAPE2.0")?; + writer.write_le(3u8)?; + writer.write_le(1u8)?; + match repeat { + Repeat::Finite(no) => writer.write_le(no)?, + Repeat::Infinite => writer.write_le(0u16)?, + } + } + } + writer.write_le(0u8).map_err(Into::into) + } + + /// Writes a raw extension to the image. + /// + /// This method can be used to write an unsupported extension to the file. `func` is the extension + /// identifier (e.g. `Extension::Application as u8`). `data` are the extension payload blocks. If any + /// contained slice has a lenght > 255 it is automatically divided into sub-blocks. + pub fn write_raw_extension(&mut self, func: AnyExtension, data: &[&[u8]]) -> io::Result<()> { + let writer = self.w.as_mut().unwrap(); + writer.write_le(Block::Extension as u8)?; + writer.write_le(func.0)?; + for block in data { + for chunk in block.chunks(0xFF) { + writer.write_le(chunk.len() as u8)?; + writer.write_all(chunk)?; + } + } + writer.write_le(0u8) + } + + /// Writes a frame to the image, but expects `Frame.buffer` to contain LZW-encoded data + /// from [`Frame::make_lzw_pre_encoded`]. + /// + /// Note: This function also writes a control extension if necessary. + pub fn write_lzw_pre_encoded_frame(&mut self, frame: &Frame) -> Result<(), EncodingError> { + self.write_frame_header(frame)?; + let writer = self.w.as_mut().unwrap(); + Self::write_encoded_image_block(writer, &frame.buffer) + } + + /// Writes the logical screen desriptor + fn write_screen_desc(&mut self, flags: u8) -> io::Result<()> { + let writer = self.w.as_mut().unwrap(); + writer.write_all(b"GIF89a")?; + writer.write_le(self.width)?; + writer.write_le(self.height)?; + writer.write_le(flags)?; // packed field + writer.write_le(0u8)?; // bg index + writer.write_le(0u8) // aspect ratio + } + + /// Gets a reference to the writer instance used by this encoder. + pub fn get_ref(&self) -> &W { + self.w.as_ref().unwrap() + } + + /// Gets a mutable reference to the writer instance used by this encoder. + /// + /// It is inadvisable to directly write to the underlying writer. + pub fn get_mut(&mut self) -> &mut W { + self.w.as_mut().unwrap() + } + + /// Returns writer instance used by this encoder + pub fn into_inner(mut self) -> io::Result<W> { + self.write_trailer()?; + Ok(self.w.take().unwrap()) + } + + /// Write the final tailer. + fn write_trailer(&mut self) -> io::Result<()> { + self.w.as_mut().unwrap().write_le(Block::Trailer as u8) + } +} + +/// Encodes the data into the provided buffer. +/// +/// The first byte is the minimum code size, followed by LZW data. +fn lzw_encode(data: &[u8], buffer: &mut Vec<u8>) { + let min_code_size = match flag_size(1 + data.iter().copied().max().unwrap_or(0) as usize) + 1 { + 1 => 2, // As per gif spec: The minimal code size has to be >= 2 + n => n + }; + buffer.push(min_code_size); + let mut enc = LzwEncoder::new(BitOrder::Lsb, min_code_size); + let len = enc.into_vec(buffer).encode_all(data).consumed_out; + buffer.truncate(len+1); +} + +impl Frame<'_> { + /// Replace frame's buffer with a LZW-compressed one for use with [`Encoder::write_lzw_pre_encoded_frame`]. + /// + /// Frames can be compressed in any order, separately from the `Encoder`, which can be used to compress frames in parallel. + pub fn make_lzw_pre_encoded(&mut self) { + let mut buffer = Vec::with_capacity(self.buffer.len() / 2); + lzw_encode(&self.buffer, &mut buffer); + self.buffer = Cow::Owned(buffer); + } +} + +/// GIF encoder. +pub struct Encoder<W: Write> { + w: Option<W>, + global_palette: bool, + width: u16, + height: u16, + buffer: Vec<u8> +} + +impl<W: Write> Drop for Encoder<W> { + + #[cfg(feature = "raii_no_panic")] + fn drop(&mut self) { + if self.w.is_some() { + let _ = self.write_trailer(); + } + } + + #[cfg(not(feature = "raii_no_panic"))] + fn drop(&mut self) { + if self.w.is_some() { + self.write_trailer().unwrap(); + } + } +} + +// Color table size converted to flag bits +fn flag_size(size: usize) -> u8 { + match size { + 0 ..=2 => 0, + 3 ..=4 => 1, + 5 ..=8 => 2, + 9 ..=16 => 3, + 17 ..=32 => 4, + 33 ..=64 => 5, + 65 ..=128 => 6, + 129..=256 => 7, + _ => 7 + } +} + +#[test] +fn error_cast() { + let _ : Box<dyn error::Error> = EncodingError::from(FormatErrorKind::MissingColorPalette).into(); +} diff --git a/vendor/gif/src/lib.rs b/vendor/gif/src/lib.rs new file mode 100644 index 0000000..8eb2a61 --- /dev/null +++ b/vendor/gif/src/lib.rs @@ -0,0 +1,154 @@ +//! # GIF en- and decoding library [![Build Status](https://github.com/image-rs/image-gif/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/image-gif/actions) +//! +//! GIF en- and decoder written in Rust ([API Documentation](https://docs.rs/gif)). +//! +//! # GIF encoding and decoding library +//! +//! This library provides all functions necessary to de- and encode GIF files. +//! +//! ## High level interface +//! +//! The high level interface consists of the two types +//! [`Encoder`](struct.Encoder.html) and [`Decoder`](struct.Decoder.html). +//! +//! ### Decoding GIF files +//! +//! ```rust +//! // Open the file +//! use std::fs::File; +//! let mut decoder = gif::DecodeOptions::new(); +//! // Configure the decoder such that it will expand the image to RGBA. +//! decoder.set_color_output(gif::ColorOutput::RGBA); +//! // Read the file header +//! let file = File::open("tests/samples/sample_1.gif").unwrap(); +//! let mut decoder = decoder.read_info(file).unwrap(); +//! while let Some(frame) = decoder.read_next_frame().unwrap() { +//! // Process every frame +//! } +//! ``` +//! +//! +//! +//! ### Encoding GIF files +//! +//! The encoder can be used so save simple computer generated images: +//! +//! ```rust +//! use gif::{Frame, Encoder, Repeat}; +//! use std::fs::File; +//! use std::borrow::Cow; +//! +//! let color_map = &[0xFF, 0xFF, 0xFF, 0, 0, 0]; +//! let (width, height) = (6, 6); +//! let mut beacon_states = [[ +//! 0, 0, 0, 0, 0, 0, +//! 0, 1, 1, 0, 0, 0, +//! 0, 1, 1, 0, 0, 0, +//! 0, 0, 0, 1, 1, 0, +//! 0, 0, 0, 1, 1, 0, +//! 0, 0, 0, 0, 0, 0, +//! ], [ +//! 0, 0, 0, 0, 0, 0, +//! 0, 1, 1, 0, 0, 0, +//! 0, 1, 0, 0, 0, 0, +//! 0, 0, 0, 0, 1, 0, +//! 0, 0, 0, 1, 1, 0, +//! 0, 0, 0, 0, 0, 0, +//! ]]; +//! let mut image = File::create("tests/samples/beacon.gif").unwrap();; +//! let mut encoder = Encoder::new(&mut image, width, height, color_map).unwrap(); +//! encoder.set_repeat(Repeat::Infinite).unwrap(); +//! for state in &beacon_states { +//! let mut frame = Frame::default(); +//! frame.width = width; +//! frame.height = height; +//! frame.buffer = Cow::Borrowed(&*state); +//! encoder.write_frame(&frame).unwrap(); +//! } +//! ``` +//! +//! [`Frame::from_*`](struct.Frame.html) can be used to convert a true color image to a paletted +//! image with a maximum of 256 colors: +//! +//! ```rust +//! # #[cfg(feature = "color_quant")] { +//! use std::fs::File; +//! +//! // Get pixel data from some source +//! let mut pixels: Vec<u8> = vec![0; 30_000]; +//! // Create frame from data +//! let frame = gif::Frame::from_rgb(100, 100, &mut *pixels); +//! // Create encoder +//! let mut image = File::create("target/indexed_color.gif").unwrap(); +//! let mut encoder = gif::Encoder::new(&mut image, frame.width, frame.height, &[]).unwrap(); +//! // Write frame to file +//! encoder.write_frame(&frame).unwrap(); +//! # } +//! ``` + +// TODO: make this compile +// ```rust +// use gif::{Frame, Encoder}; +// use std::fs::File; +// let color_map = &[0, 0, 0, 0xFF, 0xFF, 0xFF]; +// let mut frame = Frame::default(); +// // Generate checkerboard lattice +// for (i, j) in (0..10).zip(0..10) { +// frame.buffer.push(if (i * j) % 2 == 0 { +// 1 +// } else { +// 0 +// }) +// } +// # (|| { +// { +// let mut file = File::create("test.gif")?; +// let mut encoder = Encoder::new(&mut file, 100, 100); +// encoder.write_global_palette(color_map)?.write_frame(&frame) +// } +// # })().unwrap(); +// ``` +#![deny(missing_docs)] +#![cfg(feature = "std")] + +mod traits; +mod common; +mod reader; +mod encoder; + +pub use crate::common::{AnyExtension, Block, Extension, DisposalMethod, Frame}; + +pub use crate::reader::{StreamingDecoder, Decoded, DecodingError, DecodingFormatError}; +/// StreamingDecoder configuration parameters +pub use crate::reader::{ColorOutput, MemoryLimit, Extensions}; +pub use crate::reader::{DecodeOptions, Decoder, Version}; + +pub use crate::encoder::{Encoder, ExtensionData, Repeat, EncodingError}; + +#[cfg(test)] +#[test] +fn round_trip() { + use std::io::prelude::*; + use std::fs::File; + let mut data = vec![]; + File::open("tests/samples/sample_1.gif").unwrap().read_to_end(&mut data).unwrap(); + let mut decoder = Decoder::new(&*data).unwrap(); + let palette: Vec<u8> = decoder.palette().unwrap().into(); + let frame = decoder.read_next_frame().unwrap().unwrap(); + let mut data2 = vec![]; + { + let mut encoder = Encoder::new(&mut data2, frame.width, frame.height, &palette).unwrap(); + encoder.write_frame(frame).unwrap(); + } + assert_eq!(&data[..], &data2[..]) +} + +macro_rules! insert_as_doc { + { $content:expr } => { + #[doc = $content] extern { } + } +} + +// Provides the README.md as doc, to ensure the example works! +#[cfg(feature = "color_quant")] +insert_as_doc!(include_str!("../README.md")); diff --git a/vendor/gif/src/reader/decoder.rs b/vendor/gif/src/reader/decoder.rs new file mode 100644 index 0000000..f0f8eea --- /dev/null +++ b/vendor/gif/src/reader/decoder.rs @@ -0,0 +1,724 @@ +use std::cmp; +use std::error; +use std::fmt; +use std::io; +use std::mem; +use std::default::Default; + +use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame}; +use crate::reader::DecodeOptions; + +use weezl::{BitOrder, decode::Decoder as LzwDecoder, LzwStatus}; + +/// GIF palettes are RGB +pub const PLTE_CHANNELS: usize = 3; + +/// An error returned in the case of the image not being formatted properly. +#[derive(Debug)] +pub struct DecodingFormatError { + underlying: Box<dyn error::Error + Send + Sync + 'static> +} + +impl fmt::Display for DecodingFormatError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&*self.underlying, fmt) + } +} + +impl error::Error for DecodingFormatError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(&*self.underlying as _) + } +} + +impl DecodingFormatError { + fn new( + err: impl Into<Box<dyn error::Error + Send + Sync>>, + ) -> Self { + DecodingFormatError { + underlying: err.into(), + } + } +} + +#[derive(Debug)] +/// Decoding error. +pub enum DecodingError { + /// Returned if the image is found to be malformed. + Format(DecodingFormatError), + /// Wraps `std::io::Error`. + Io(io::Error), +} + +impl DecodingError { + #[inline] + pub(crate) fn format( + err: impl Into<Box<dyn error::Error + Send + Sync>>, + ) -> Self { + DecodingError::Format(DecodingFormatError::new(err)) + } +} + +impl fmt::Display for DecodingError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + DecodingError::Format(ref d) => d.fmt(fmt), + DecodingError::Io(ref err) => err.fmt(fmt), + } + } +} + +impl error::Error for DecodingError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + DecodingError::Format(ref err) => Some(err), + DecodingError::Io(ref err) => Some(err), + } + } +} + +impl From<io::Error> for DecodingError { + fn from(err: io::Error) -> Self { + DecodingError::Io(err) + } +} + +impl From<DecodingFormatError> for DecodingError { + fn from(err: DecodingFormatError) -> Self { + DecodingError::Format(err) + } +} + +/// Configures how extensions should be handled +#[derive(PartialEq, Debug)] +pub enum Extensions { + /// Saves all extention data + Save, + /// Skips the data of unknown extensions + /// and extracts the data from known ones + Skip +} + +/// Indicates whether a certain object has been decoded +#[derive(Debug)] +pub enum Decoded<'a> { + /// Decoded nothing. + Nothing, + /// Global palette. + GlobalPalette(Vec<u8>), + /// Index of the background color in the global palette. + BackgroundColor(u8), + /// Decoded the image trailer. + Trailer, + /// The start of a block. + BlockStart(Block), + /// Decoded a sub-block. More sub-block are available. + /// + /// Indicates the label of the extension which might be unknown. A label of `0` is used when + /// the sub block does not belong to an extension. + SubBlockFinished(AnyExtension, &'a [u8]), + /// Decoded the last (or only) sub-block of a block. + /// + /// Indicates the label of the extension which might be unknown. A label of `0` is used when + /// the sub block does not belong to an extension. + BlockFinished(AnyExtension, &'a [u8]), + /// Decoded all information of the next frame. + /// + /// The returned frame does **not** contain any owned image data. + Frame(&'a Frame<'static>), + /// Decoded some data of the current frame. + Data(&'a [u8]), + /// No more data available the current frame. + DataEnd, + +} + +/// Internal state of the GIF decoder +#[derive(Debug)] +enum State { + Magic(usize, [u8; 6]), + U16Byte1(U16Value, u8), + U16(U16Value), + Byte(ByteValue), + GlobalPalette(usize), + BlockStart(Option<Block>), + /// Block end, with remaining expected data. NonZero for invalid EOF. + BlockEnd(u8), + ExtensionBlock(AnyExtension), + SkipBlock(usize), + LocalPalette(usize), + LzwInit(u8), + DecodeSubBlock(usize), + FrameDecoded, + Trailer +} +use self::State::*; + +/// U16 values that may occur in a GIF image +#[derive(Debug)] +enum U16Value { + /// Logical screen descriptor width + ScreenWidth, + /// Logical screen descriptor height + ScreenHeight, + /// Delay time + Delay, + /// Left frame offset + ImageLeft, + /// Top frame offset + ImageTop, + /// Frame width + ImageWidth, + /// Frame height + ImageHeight, +} + +/// Single byte screen descriptor values +#[derive(Debug)] +enum ByteValue { + GlobalFlags, + Background { table_size: usize }, + AspectRatio { table_size: usize }, + ControlFlags, + ImageFlags, + TransparentIdx, + CodeSize, +} + +/// GIF decoder which supports streaming +pub struct StreamingDecoder { + state: Option<State>, + lzw_reader: Option<LzwDecoder>, + decode_buffer: Vec<u8>, + skip_extensions: bool, + check_frame_consistency: bool, + check_for_end_code: bool, + allow_unknown_blocks: bool, + version: Version, + width: u16, + height: u16, + global_color_table: Vec<u8>, + background_color: [u8; 4], + /// ext buffer + ext: ExtensionData, + /// Frame data + current: Option<Frame<'static>>, +} + +/// One version number of the GIF standard. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum Version { + /// Version 87a, from May 1987. + V87a, + /// Version 89a, from July 1989. + V89a, +} + +struct ExtensionData { + id: AnyExtension, + data: Vec<u8>, + is_block_end: bool, +} + +impl StreamingDecoder { + /// Creates a new streaming decoder + pub fn new() -> StreamingDecoder { + let options = DecodeOptions::new(); + Self::with_options(&options) + } + + pub(crate) fn with_options(options: &DecodeOptions) -> Self { + StreamingDecoder { + state: Some(Magic(0, [0; 6])), + lzw_reader: None, + decode_buffer: vec![], + skip_extensions: true, + check_frame_consistency: options.check_frame_consistency, + check_for_end_code: options.check_for_end_code, + allow_unknown_blocks: options.allow_unknown_blocks, + version: Version::V87a, + width: 0, + height: 0, + global_color_table: Vec::new(), + background_color: [0, 0, 0, 0xFF], + ext: ExtensionData { + id: AnyExtension(0), + data: Vec::with_capacity(256), // 0xFF + 1 byte length + is_block_end: true, + }, + current: None + } + } + + /// Updates the internal state of the decoder. + /// + /// Returns the number of bytes consumed from the input buffer + /// and the last decoding result. + pub fn update<'a>(&'a mut self, mut buf: &[u8]) + -> Result<(usize, Decoded<'a>), DecodingError> { + // NOTE: Do not change the function signature without double-checking the + // unsafe block! + let len = buf.len(); + while buf.len() > 0 && self.state.is_some() { + match self.next_state(buf) { + Ok((bytes, Decoded::Nothing)) => { + buf = &buf[bytes..] + } + Ok((bytes, Decoded::Trailer)) => { + buf = &buf[bytes..]; + break + } + Ok((bytes, result)) => { + buf = &buf[bytes..]; + return Ok( + (len-buf.len(), + // This transmute just casts the lifetime away. Since Rust only + // has SESE regions, this early return cannot be worked out and + // such that the borrow region of self includes the whole block. + // The explixit lifetimes in the function signature ensure that + // this is safe. + // ### NOTE + // To check that everything is sound, return the result without + // the match (e.g. `return Ok(self.next_state(buf)?)`). If + // it compiles the returned lifetime is correct. + unsafe { + mem::transmute::<Decoded, Decoded>(result) + } + )) + } + Err(err) => return Err(err) + } + } + Ok((len-buf.len(), Decoded::Nothing)) + + } + + /// Returns the data of the last extension that has been decoded. + pub fn last_ext(&self) -> (AnyExtension, &[u8], bool) { + (self.ext.id, &self.ext.data, self.ext.is_block_end) + } + + #[inline(always)] + /// Current frame info as a mutable ref. + pub fn current_frame_mut<'a>(&'a mut self) -> &'a mut Frame<'static> { + self.current.as_mut().unwrap() + } + + #[inline(always)] + /// Current frame info as a ref. + pub fn current_frame<'a>(&'a self) -> &'a Frame<'static> { + self.current.as_ref().unwrap() + } + + /// Width of the image + pub fn width(&self) -> u16 { + self.width + } + + /// Height of the image + pub fn height(&self) -> u16 { + self.height + } + + /// The version number of the GIF standard used in this image. + /// + /// We suppose a minimum of `V87a` compatibility. This value will be reported until we have + /// read the version information in the magic header bytes. + pub fn version(&self) -> Version { + self.version + } + + /// Configure whether extensions are saved or skipped. + #[deprecated = "Does not work as intended. In fact, doesn't do anything. This may disappear soon."] + pub fn set_extensions(&mut self, extensions: Extensions) { + self.skip_extensions = match extensions { + Extensions::Skip => true, + Extensions::Save => false, + } + } + + fn next_state<'a>(&'a mut self, buf: &[u8]) -> Result<(usize, Decoded<'a>), DecodingError> { + macro_rules! goto ( + ($n:expr, $state:expr) => ({ + self.state = Some($state); + Ok(($n, Decoded::Nothing)) + }); + ($state:expr) => ({ + self.state = Some($state); + Ok((1, Decoded::Nothing)) + }); + ($n:expr, $state:expr, emit $res:expr) => ({ + self.state = Some($state); + Ok(($n, $res)) + }); + ($state:expr, emit $res:expr) => ({ + self.state = Some($state); + Ok((1, $res)) + }) + ); + + let b = buf[0]; + + // Driver should ensure that state is never None + let state = self.state.take().unwrap(); + //println!("{:?}", state); + + match state { + Magic(i, mut version) => if i < 6 { + version[i] = b; + goto!(Magic(i+1, version)) + } else if &version[..3] == b"GIF" { + self.version = match &version[3..] { + b"87a" => Version::V87a, + b"89a" => Version::V89a, + _ => return Err(DecodingError::format("unsupported GIF version")) + }; + goto!(U16Byte1(U16Value::ScreenWidth, b)) + } else { + Err(DecodingError::format("malformed GIF header")) + }, + U16(next) => goto!(U16Byte1(next, b)), + U16Byte1(next, value) => { + use self::U16Value::*; + let value = ((b as u16) << 8) | value as u16; + match (next, value) { + (ScreenWidth, width) => { + self.width = width; + goto!(U16(U16Value::ScreenHeight)) + }, + (ScreenHeight, height) => { + self.height = height; + goto!(Byte(ByteValue::GlobalFlags)) + }, + (Delay, delay) => { + self.ext.data.push(value as u8); + self.ext.data.push(b); + self.current_frame_mut().delay = delay; + goto!(Byte(ByteValue::TransparentIdx)) + }, + (ImageLeft, left) => { + self.current_frame_mut().left = left; + goto!(U16(U16Value::ImageTop)) + }, + (ImageTop, top) => { + self.current_frame_mut().top = top; + goto!(U16(U16Value::ImageWidth)) + }, + (ImageWidth, width) => { + self.current_frame_mut().width = width; + goto!(U16(U16Value::ImageHeight)) + }, + (ImageHeight, height) => { + self.current_frame_mut().height = height; + goto!(Byte(ByteValue::ImageFlags)) + } + } + } + Byte(value) => { + use self::ByteValue::*; + match value { + GlobalFlags => { + let global_table = b & 0x80 != 0; + let entries = if global_table { + let entries = PLTE_CHANNELS*(1 << ((b & 0b111) + 1) as usize); + self.global_color_table.reserve_exact(entries); + entries + } else { + 0usize + }; + goto!(Byte(Background { table_size: entries })) + }, + Background { table_size } => { + goto!( + Byte(AspectRatio { table_size: table_size }), + emit Decoded::BackgroundColor(b) + ) + }, + AspectRatio { table_size } => { + goto!(GlobalPalette(table_size)) + }, + ControlFlags => { + self.ext.data.push(b); + let control_flags = b; + if control_flags & 1 != 0 { + // Set to Some(...), gets overwritten later + self.current_frame_mut().transparent = Some(0) + } + self.current_frame_mut().needs_user_input = + control_flags & 0b10 != 0; + self.current_frame_mut().dispose = match DisposalMethod::from_u8( + (control_flags & 0b11100) >> 2 + ) { + Some(method) => method, + None => DisposalMethod::Any + }; + goto!(U16(U16Value::Delay)) + } + TransparentIdx => { + self.ext.data.push(b); + if let Some(ref mut idx) = self.current_frame_mut().transparent { + *idx = b + } + goto!(SkipBlock(0)) + //goto!(AwaitBlockEnd) + } + ImageFlags => { + let local_table = (b & 0b1000_0000) != 0; + let interlaced = (b & 0b0100_0000) != 0; + let table_size = b & 0b0000_0111; + + self.current_frame_mut().interlaced = interlaced; + + if self.check_frame_consistency { + // Consistency checks. + let (width, height) = (self.width, self.height); + let frame = self.current_frame_mut(); + if width.checked_sub(frame.width) < Some(frame.left) + || height.checked_sub(frame.height) < Some(frame.top) + { + return Err(DecodingError::format("frame descriptor is out-of-bounds")) + } + } + + if local_table { + let entries = PLTE_CHANNELS * (1 << (table_size + 1)); + + self.current_frame_mut().palette = + Some(Vec::with_capacity(entries)); + goto!(LocalPalette(entries)) + } else { + goto!(Byte(CodeSize)) + } + }, + CodeSize => goto!(LzwInit(b)) + } + } + GlobalPalette(left) => { + let n = cmp::min(left, buf.len()); + if left > 0 { + self.global_color_table.extend_from_slice(&buf[..n]); + goto!(n, GlobalPalette(left - n)) + } else { + let idx = self.background_color[0]; + match self.global_color_table.chunks(PLTE_CHANNELS).nth(idx as usize) { + Some(chunk) => self.background_color[..PLTE_CHANNELS] + .copy_from_slice(&chunk[..PLTE_CHANNELS]), + None => self.background_color[0] = 0 + } + goto!(BlockStart(Block::from_u8(b)), emit Decoded::GlobalPalette( + mem::replace(&mut self.global_color_table, Vec::new()) + )) + } + } + BlockStart(type_) => { + match type_ { + Some(Block::Image) => { + self.add_frame(); + goto!(U16Byte1(U16Value::ImageLeft, b), emit Decoded::BlockStart(Block::Image)) + } + Some(Block::Extension) => { + goto!(ExtensionBlock(AnyExtension(b)), emit Decoded::BlockStart(Block::Extension)) + } + Some(Block::Trailer) => { + goto!(0, State::Trailer, emit Decoded::BlockStart(Block::Trailer)) + } + None => { + if self.allow_unknown_blocks { + goto!(SkipBlock(b as usize)) + } else { + Err(DecodingError::format("unknown block type encountered")) + } + } + } + } + BlockEnd(terminator) => { + if terminator == 0 { + if b == Block::Trailer as u8 { + goto!(0, BlockStart(Some(Block::Trailer))) + } else { + goto!(BlockStart(Block::from_u8(b))) + } + } else { + return Err(DecodingError::format( + "expected block terminator not found" + )) + } + } + ExtensionBlock(id) => { + use Extension::*; + self.ext.id = id; + self.ext.data.clear(); + self.ext.data.push(b); + if let Some(ext) = Extension::from_u8(id.0) { + match ext { + Control => { + goto!(self.read_control_extension(b)?) + } + Text | Comment | Application => { + goto!(SkipBlock(b as usize)) + } + } + } else { + return Err(DecodingError::format( + "unknown extention block encountered" + )) + } + } + SkipBlock(left) => { + let n = cmp::min(left, buf.len()); + if left > 0 { + self.ext.data.extend_from_slice(&buf[..n]); + goto!(n, SkipBlock(left - n)) + } else { + if b == 0 { + self.ext.is_block_end = true; + goto!(BlockEnd(b), emit Decoded::BlockFinished(self.ext.id, &self.ext.data)) + } else { + self.ext.is_block_end = false; + goto!(SkipBlock(b as usize), emit Decoded::SubBlockFinished(self.ext.id, &self.ext.data)) + } + } + } + LocalPalette(left) => { + let n = cmp::min(left, buf.len()); + if left > 0 { + + self.current_frame_mut().palette + .as_mut().unwrap().extend(buf[..n].iter().cloned()); + goto!(n, LocalPalette(left - n)) + } else { + goto!(LzwInit(b)) + } + } + LzwInit(code_size) => { + // LZW spec: max 12 bits per code + if code_size > 11 { + return Err(DecodingError::format( + "invalid minimal code size" + )) + } + self.lzw_reader = Some(LzwDecoder::new(BitOrder::Lsb, code_size)); + goto!(DecodeSubBlock(b as usize), emit Decoded::Frame(self.current_frame_mut())) + } + DecodeSubBlock(left) => { + if left > 0 { + let n = cmp::min(left, buf.len()); + let max_bytes = self.current_frame().required_bytes(); + let decoder = self.lzw_reader.as_mut().unwrap(); + if decoder.has_ended() { + debug_assert!(n > 0, "Made forward progress after LZW end"); + return goto!(n, DecodeSubBlock(0), emit Decoded::Data(&[])); + } + + let mut dummy_target; + let decode_target; + + if self.decode_buffer.is_empty() { + let size = (1 << 14).min(max_bytes); + self.decode_buffer = vec![0; size]; + } + + if max_bytes == 0 { + dummy_target = [0; 16]; + decode_target = &mut dummy_target[..]; + } else { + decode_target = self.decode_buffer.as_mut_slice(); + } + + debug_assert!(!decode_target.is_empty(), "LZW decoding can make forward progress."); + let decoded = decoder.decode_bytes(&buf[..n], decode_target); + + if let Err(err) = decoded.status { + return Err(io::Error::new(io::ErrorKind::InvalidData, &*format!("{:?}", err)).into()); + } + + let bytes = &self.decode_buffer[..decoded.consumed_out.min(max_bytes)]; + let consumed = decoded.consumed_in; + goto!(consumed, DecodeSubBlock(left - consumed), emit Decoded::Data(bytes)) + } else if b != 0 { // decode next sub-block + goto!(DecodeSubBlock(b as usize)) + } else { + let max_bytes = self.current_frame().required_bytes(); + // The end of the lzw stream is only reached if left == 0 and an additional call + // to `decode_bytes` results in an empty slice. + let decoder = self.lzw_reader.as_mut().unwrap(); + // Some mutable bytes to decode into. We need this for forward progress in + // `lzw`. However, in some cases we do not actually need any bytes, when + // `max_bytes` is `0`. + let mut dummy_target; + let decode_target; + + if self.decode_buffer.is_empty() { + let size = (1 << 14).min(max_bytes); + self.decode_buffer = vec![0; size]; + } + + if max_bytes == 0 { + dummy_target = [0; 16]; + decode_target = &mut dummy_target[..]; + } else { + decode_target = self.decode_buffer.as_mut_slice(); + } + + debug_assert!(!decode_target.is_empty(), "LZW decoding can make forward progress."); + let decoded = decoder.decode_bytes(&[], decode_target); + + match decoded.status { + Ok(LzwStatus::Done) | Ok(LzwStatus::Ok) => {}, + Ok(LzwStatus::NoProgress) => { + if self.check_for_end_code { + return Err(io::Error::new(io::ErrorKind::InvalidData, "No end code in lzw stream").into()); + } else { + self.current = None; + return goto!(0, FrameDecoded, emit Decoded::DataEnd); + } + }, + Err(err) => { + return Err(io::Error::new(io::ErrorKind::InvalidData, &*format!("{:?}", err)).into()); + } + } + let bytes = &self.decode_buffer[..decoded.consumed_out.min(max_bytes)]; + + if bytes.len() > 0 { + goto!(0, DecodeSubBlock(0), emit Decoded::Data(bytes)) + } else { + // end of image data reached + self.current = None; + goto!(0, FrameDecoded, emit Decoded::DataEnd) + } + } + } + FrameDecoded => { + goto!(BlockEnd(b)) + } + Trailer => { + self.state = None; + Ok((1, Decoded::Trailer)) + //panic!("EOF {:?}", self) + } + } + } + + fn read_control_extension(&mut self, b: u8) -> Result<State, DecodingError> { + self.add_frame(); + self.ext.data.push(b); + if b != 4 { + return Err(DecodingError::format( + "control extension has wrong length" + )) + } + Ok(Byte(ByteValue::ControlFlags)) + } + + fn add_frame(&mut self) { + if self.current.is_none() { + self.current = Some(Frame::default()) + } + } +} + +#[test] +fn error_cast() { + let _ : Box<dyn error::Error> = DecodingError::Format(DecodingFormatError::new("testing")).into(); +} diff --git a/vendor/gif/src/reader/mod.rs b/vendor/gif/src/reader/mod.rs new file mode 100644 index 0000000..a453e79 --- /dev/null +++ b/vendor/gif/src/reader/mod.rs @@ -0,0 +1,522 @@ +use std::borrow::Cow; +use std::io; +use std::cmp; +use std::mem; +use std::iter; +use std::io::prelude::*; + +use crate::common::{Block, Frame}; + +mod decoder; +pub use self::decoder::{ + PLTE_CHANNELS, StreamingDecoder, Decoded, DecodingError, DecodingFormatError, Extensions, + Version +}; + +const N_CHANNELS: usize = 4; + +/// Output mode for the image data +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +pub enum ColorOutput { + /// The decoder expands the image data to 32bit RGBA. + /// This affects: + /// + /// - The buffer buffer of the `Frame` returned by `Decoder::read_next_frame`. + /// - `Decoder::fill_buffer`, `Decoder::buffer_size` and `Decoder::line_length`. + RGBA = 0, + /// The decoder returns the raw indexed data. + Indexed = 1, +} + +#[derive(Clone, Debug)] +/// Memory limit in bytes. `MemoryLimit(0)` means +/// that there is no memory limit set. +pub struct MemoryLimit(pub u32); + +impl MemoryLimit { + /// Enforce no memory limit. + /// + /// If you intend to process images from unknown origins this is a potentially dangerous + /// constant to use, as your program could be vulnerable to decompression bombs. That is, + /// malicious images crafted specifically to require an enormous amount of memory to process + /// while having a disproportionately small file size. + /// + /// The risks for modern machines are a bit smaller as the dimensions of each frame can not + /// exceed `u32::MAX` (~4Gb) but this is still a significant amount of memory. + pub const NONE: MemoryLimit = MemoryLimit(0); + + fn buffer_size(&self, color: ColorOutput, width: u16, height: u16) -> Option<usize> { + let pixels = u32::from(width) * u32::from(height); + + let bytes_per_pixel = match color { + ColorOutput::Indexed => 1, + ColorOutput::RGBA => 4, + }; + + if self.0 > 0 && pixels > self.0 / bytes_per_pixel { + None + } else { + Some(pixels as usize * bytes_per_pixel as usize) + } + } +} + +/// Options for opening a GIF decoder. +#[derive(Clone, Debug)] +pub struct DecodeOptions { + memory_limit: MemoryLimit, + color_output: ColorOutput, + check_frame_consistency: bool, + check_for_end_code: bool, + allow_unknown_blocks: bool, +} + +impl DecodeOptions { + /// Creates a new decoder builder + pub fn new() -> DecodeOptions { + DecodeOptions { + memory_limit: MemoryLimit(50_000_000), // 50 MB + color_output: ColorOutput::Indexed, + check_frame_consistency: false, + check_for_end_code: false, + allow_unknown_blocks: false, + } + } + + /// Configure how color data is decoded. + pub fn set_color_output(&mut self, color: ColorOutput) { + self.color_output = color; + } + + /// Configure a memory limit for decoding. + pub fn set_memory_limit(&mut self, limit: MemoryLimit) { + self.memory_limit = limit; + } + + /// Configure if frames must be within the screen descriptor. + /// + /// The default is `false`. + /// + /// When turned on, all frame descriptors being read must fit within the screen descriptor or + /// otherwise an error is returned and the stream left in an unspecified state. + /// + /// When turned off, frames may be arbitrarily larger or offset in relation to the screen. Many + /// other decoder libraries handle this in highly divergent ways. This moves all checks to the + /// caller, for example to emulate a specific style. + pub fn check_frame_consistency(&mut self, check: bool) { + self.check_frame_consistency = check; + } + + /// Configure if LZW encoded blocks must end with a marker end code. + /// + /// The default is `false`. + /// + /// When turned on, all image data blocks—which are LZW encoded—must contain a special bit + /// sequence signalling the end of the data. LZW processing terminates when this code is + /// encountered. The specification states that it must be the last code output by the encoder + /// for an image. + /// + /// When turned off then image data blocks can simply end. Note that this might silently ignore + /// some bits of the last or second to last byte. + pub fn check_lzw_end_code(&mut self, check: bool) { + self.check_for_end_code = check; + } + + /// Configure if unknown blocks are allowed to be decoded. + /// + /// The default is `false`. + /// + /// When turned on, the decoder will allow unknown blocks to be in the + /// `BlockStart` position. + /// + /// When turned off, decoded block starts must mark an `Image`, `Extension`, + /// or `Trailer` block. Otherwise, the decoded image will return an error. + /// If an unknown block error is returned from decoding, enabling this + /// setting may allow for a further state of decoding on the next attempt. + pub fn allow_unknown_blocks(&mut self, check: bool) { + self.allow_unknown_blocks = check; + } + + /// Reads the logical screen descriptor including the global color palette + /// + /// Returns a `Decoder`. All decoder configuration has to be done beforehand. + pub fn read_info<R: Read>(self, r: R) -> Result<Decoder<R>, DecodingError> { + Decoder::with_no_init(r, StreamingDecoder::with_options(&self), self).init() + } +} + +struct ReadDecoder<R: Read> { + reader: io::BufReader<R>, + decoder: StreamingDecoder, + at_eof: bool +} + +impl<R: Read> ReadDecoder<R> { + fn decode_next(&mut self) -> Result<Option<Decoded>, DecodingError> { + while !self.at_eof { + let (consumed, result) = { + let buf = self.reader.fill_buf()?; + if buf.len() == 0 { + return Err(DecodingError::format( + "unexpected EOF" + )) + } + self.decoder.update(buf)? + }; + self.reader.consume(consumed); + match result { + Decoded::Nothing => (), + Decoded::BlockStart(Block::Trailer) => { + self.at_eof = true + }, + result => return Ok(unsafe{ + // FIXME: #6393 + Some(mem::transmute::<Decoded, Decoded>(result)) + }), + } + } + Ok(None) + } +} + +#[allow(dead_code)] +/// GIF decoder +pub struct Decoder<R: Read> { + decoder: ReadDecoder<R>, + color_output: ColorOutput, + memory_limit: MemoryLimit, + bg_color: Option<u8>, + global_palette: Option<Vec<u8>>, + current_frame: Frame<'static>, + buffer: Vec<u8>, +} + +impl<R> Decoder<R> where R: Read { + /// Create a new decoder with default options. + pub fn new(reader: R) -> Result<Self, DecodingError> { + DecodeOptions::new().read_info(reader) + } + + /// Return a builder that allows configuring limits etc. + pub fn build() -> DecodeOptions { + DecodeOptions::new() + } + + fn with_no_init(reader: R, decoder: StreamingDecoder, options: DecodeOptions) -> Decoder<R> { + Decoder { + decoder: ReadDecoder { + reader: io::BufReader::new(reader), + decoder, + at_eof: false + }, + bg_color: None, + global_palette: None, + buffer: Vec::with_capacity(32), + color_output: options.color_output, + memory_limit: options.memory_limit, + current_frame: Frame::default(), + } + } + + fn init(mut self) -> Result<Self, DecodingError> { + loop { + match self.decoder.decode_next()? { + Some(Decoded::BackgroundColor(bg_color)) => { + self.bg_color = Some(bg_color) + } + Some(Decoded::GlobalPalette(palette)) => { + self.global_palette = if palette.len() > 0 { + Some(palette) + } else { + None + }; + break + }, + Some(_) => { + // Unreachable since this loop exists after the global + // palette has been read. + unreachable!() + }, + None => return Err(DecodingError::format( + "file does not contain any image data" + )) + } + } + // If the background color is invalid, ignore it + if let Some(ref palette) = self.global_palette { + if self.bg_color.unwrap_or(0) as usize >= (palette.len() / PLTE_CHANNELS) { + self.bg_color = None; + } + } + Ok(self) + } + + /// Returns the next frame info + pub fn next_frame_info(&mut self) -> Result<Option<&Frame<'static>>, DecodingError> { + if !self.buffer.is_empty() { + // FIXME: Warn about discarding data? + self.buffer.clear(); + } + + loop { + match self.decoder.decode_next()? { + Some(Decoded::Frame(frame)) => { + self.current_frame = frame.clone(); + if frame.palette.is_none() && self.global_palette.is_none() { + return Err(DecodingError::format( + "no color table available for current frame" + )) + } + break + }, + Some(_) => (), + None => return Ok(None) + + } + } + Ok(Some(&self.current_frame)) + } + + /// Reads the next frame from the image. + /// + /// Do not call `Self::next_frame_info` beforehand. + /// Deinterlaces the result. + pub fn read_next_frame(&mut self) -> Result<Option<&Frame<'static>>, DecodingError> { + if let Some(frame) = self.next_frame_info()? { + let (width, height) = (frame.width, frame.height); + let pixel_bytes = self.memory_limit + .buffer_size(self.color_output, width, height) + .ok_or_else(|| { + DecodingError::format("image is too large to decode") + })?; + + debug_assert_eq!( + pixel_bytes, self.buffer_size(), + "Checked computation diverges from required buffer size" + ); + + let mut vec = vec![0; pixel_bytes]; + self.read_into_buffer(&mut vec)?; + self.current_frame.buffer = Cow::Owned(vec); + self.current_frame.interlaced = false; + Ok(Some(&self.current_frame)) + } else { + Ok(None) + } + } + + /// Reads the data of the current frame into a pre-allocated buffer. + /// + /// `Self::next_frame_info` needs to be called beforehand. + /// The length of `buf` must be at least `Self::buffer_size`. + /// Deinterlaces the result. + pub fn read_into_buffer(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> { + if self.current_frame.interlaced { + let width = self.line_length(); + let height = self.current_frame.height as usize; + for row in (InterlaceIterator { len: height, next: 0, pass: 0 }) { + if !self.fill_buffer(&mut buf[row*width..][..width])? { + return Err(DecodingError::format("image truncated")) + } + } + } else { + let buf = &mut buf[..self.buffer_size()]; + if !self.fill_buffer(buf)? { + return Err(DecodingError::format("image truncated")) + } + }; + Ok(()) + } + + /// Reads data of the current frame into a pre-allocated buffer until the buffer has been + /// filled completely. + /// + /// `Self::next_frame_info` needs to be called beforehand. Returns `true` if the supplied + /// buffer could be filled completely. Should not be called after `false` had been returned. + pub fn fill_buffer(&mut self, mut buf: &mut [u8]) -> Result<bool, DecodingError> { + use self::ColorOutput::*; + const PLTE_CHANNELS: usize = 3; + macro_rules! handle_data( + ($data:expr) => { + match self.color_output { + RGBA => { + let transparent = self.current_frame.transparent; + let palette: &[u8] = match self.current_frame.palette { + Some(ref table) => &*table, + None => &*self.global_palette.as_ref().unwrap(), + }; + let len = cmp::min(buf.len()/N_CHANNELS, $data.len()); + for (rgba, &idx) in buf[..len*N_CHANNELS].chunks_mut(N_CHANNELS).zip($data.iter()) { + let plte_offset = PLTE_CHANNELS * idx as usize; + if palette.len() >= plte_offset + PLTE_CHANNELS { + let colors = &palette[plte_offset..]; + rgba[0] = colors[0]; + rgba[1] = colors[1]; + rgba[2] = colors[2]; + rgba[3] = if let Some(t) = transparent { + if t == idx { 0x00 } else { 0xFF } + } else { + 0xFF + } + } + } + (len, N_CHANNELS) + }, + Indexed => { + let len = cmp::min(buf.len(), $data.len()); + buf[..len].copy_from_slice(&$data[..len]); + (len, 1) + } + } + } + ); + let buf_len = self.buffer.len(); + if buf_len > 0 { + let (len, channels) = handle_data!(&self.buffer); + let _ = self.buffer.drain(..len); + buf = &mut buf[len*channels..]; + if buf.len() == 0 { + return Ok(true) + } + } + loop { + match self.decoder.decode_next()? { + Some(Decoded::Data(data)) => { + let (len, channels) = handle_data!(data); + buf = &mut buf[len*channels..]; // shorten buf + if buf.len() > 0 { + continue + } else if len < data.len() { + self.buffer.extend_from_slice(&data[len..]); + } + return Ok(true) + }, + Some(_) => return Ok(false), // make sure that no important result is missed + None => return Ok(false) + + } + } + } + + /// Output buffer size + pub fn buffer_size(&self) -> usize { + self.line_length() * self.current_frame.height as usize + } + + /// Line length of the current frame + pub fn line_length(&self) -> usize { + use self::ColorOutput::*; + match self.color_output { + RGBA => self.current_frame.width as usize * N_CHANNELS, + Indexed => self.current_frame.width as usize + } + } + + /// Returns the color palette relevant for the current (next) frame + pub fn palette(&self) -> Result<&[u8], DecodingError> { + // TODO prevent planic + Ok(match self.current_frame.palette { + Some(ref table) => &*table, + None => &*self.global_palette.as_ref().ok_or(DecodingError::format( + "no color table available for current frame" + ))?, + }) + } + + /// The global color palette + pub fn global_palette(&self) -> Option<&[u8]> { + self.global_palette.as_ref().map(|v| &**v) + } + + /// Width of the image + pub fn width(&self) -> u16 { + self.decoder.decoder.width() + } + + /// Height of the image + pub fn height(&self) -> u16 { + self.decoder.decoder.height() + } + + /// Index of the background color in the global palette + pub fn bg_color(&self) -> Option<usize> { + self.bg_color.map(|v| v as usize) + } +} + +struct InterlaceIterator { + len: usize, + next: usize, + pass: usize +} + +impl iter::Iterator for InterlaceIterator { + type Item = usize; + + fn next(&mut self) -> Option<Self::Item> { + if self.len == 0 || self.pass > 3 { + return None + } + let mut next = self.next + [8, 8, 4, 2][self.pass]; + while next >= self.len { + next = [4, 2, 1, 0][self.pass]; + self.pass += 1; + } + mem::swap(&mut next, &mut self.next); + Some(next) + } +} + +#[cfg(test)] +mod test { + use std::fs::File; + + use super::{Decoder, InterlaceIterator}; + + #[test] + fn test_simple_indexed() { + let mut decoder = Decoder::new(File::open("tests/samples/sample_1.gif").unwrap()).unwrap(); + let frame = decoder.read_next_frame().unwrap().unwrap(); + assert_eq!(&*frame.buffer, &[ + 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, + 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, + 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, + 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, + 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 1, 1, 1, 1, 1 + ][..]) + } + + #[test] + fn test_interlace_iterator() { + for &(len, expect) in &[ + (0, &[][..]), + (1, &[0][..]), + (2, &[0, 1][..]), + (3, &[0, 2, 1][..]), + (4, &[0, 2, 1, 3][..]), + (5, &[0, 4, 2, 1, 3][..]), + (6, &[0, 4, 2, 1, 3, 5][..]), + (7, &[0, 4, 2, 6, 1, 3, 5][..]), + (8, &[0, 4, 2, 6, 1, 3, 5, 7][..]), + (9, &[0, 8, 4, 2, 6, 1, 3, 5, 7][..]), + (10, &[0, 8, 4, 2, 6, 1, 3, 5, 7, 9][..]), + (11, &[0, 8, 4, 2, 6, 10, 1, 3, 5, 7, 9][..]), + (12, &[0, 8, 4, 2, 6, 10, 1, 3, 5, 7, 9, 11][..]), + (13, &[0, 8, 4, 12, 2, 6, 10, 1, 3, 5, 7, 9, 11][..]), + (14, &[0, 8, 4, 12, 2, 6, 10, 1, 3, 5, 7, 9, 11, 13][..]), + (15, &[0, 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13][..]), + (16, &[0, 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15][..]), + (17, &[0, 8, 16, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15][..]), + ] { + let iter = InterlaceIterator { len: len, next: 0, pass: 0 }; + let lines = iter.collect::<Vec<_>>(); + assert_eq!(lines, expect); + } + } +} diff --git a/vendor/gif/src/traits.rs b/vendor/gif/src/traits.rs new file mode 100644 index 0000000..7fe326c --- /dev/null +++ b/vendor/gif/src/traits.rs @@ -0,0 +1,49 @@ +//! Traits used in this library +use std::io; + +/// Writer extension to write little endian data +pub trait WriteBytesExt<T> { + /// Writes `T` to a bytes stream. Least significant byte first. + fn write_le(&mut self, n: T) -> io::Result<()>; + + /* + #[inline] + fn write_byte(&mut self, n: u8) -> io::Result<()> where Self: Write { + self.write_all(&[n]) + } + */ +} + +impl<W: io::Write + ?Sized> WriteBytesExt<u8> for W { + #[inline] + fn write_le(&mut self, n: u8) -> io::Result<()> { + self.write_all(&[n]) + + } +} + +impl<W: io::Write + ?Sized> WriteBytesExt<u16> for W { + #[inline] + fn write_le(&mut self, n: u16) -> io::Result<()> { + self.write_all(&[n as u8, (n>>8) as u8]) + + } +} + +impl<W: io::Write + ?Sized> WriteBytesExt<u32> for W { + #[inline] + fn write_le(&mut self, n: u32) -> io::Result<()> { + self.write_le(n as u16)?; + self.write_le((n >> 16) as u16) + + } +} + +impl<W: io::Write + ?Sized> WriteBytesExt<u64> for W { + #[inline] + fn write_le(&mut self, n: u64) -> io::Result<()> { + self.write_le(n as u32)?; + self.write_le((n >> 32) as u32) + + } +} diff --git a/vendor/gif/tests/check_testimages.rs b/vendor/gif/tests/check_testimages.rs new file mode 100644 index 0000000..49f0d05 --- /dev/null +++ b/vendor/gif/tests/check_testimages.rs @@ -0,0 +1,170 @@ +extern crate gif; +extern crate glob; + +use std::collections::HashMap; +use std::fs::File; +use std::path::PathBuf; + +use std::io::BufReader; +use std::io::prelude::*; + +const BASE_PATH: [&'static str; 2] = [".", "tests"]; + +fn process_images<F>(func: F) +where F: Fn(PathBuf) -> Result<u32, gif::DecodingError> { + let base: PathBuf = BASE_PATH.iter().collect(); + let test_suites = &["samples"]; + let mut results = HashMap::new(); + let mut expected_failures = 0; + for suite in test_suites { + let mut path = base.clone(); + path.push(suite); + path.push("*.gif"); + let pattern = &*format!("{}", path.display()); + for path in glob::glob(pattern).unwrap().filter_map(Result::ok) { + print!("{}: ", path.to_string_lossy()); + match func(path.clone()) { + Ok(crc) => { + results.insert(path, format!("{}", crc)); + println!("{}", crc) + }, + Err(_) if path.file_name().unwrap().to_str().unwrap().starts_with("x") => { + expected_failures += 1; + println!("Expected failure") + }, + err => panic!("{:?}", err) + } + } + } + let mut path = base.clone(); + path.push("results.txt"); + let mut ref_results = HashMap::new(); + let mut failures = 0; + for line in BufReader::new(File::open(path).unwrap()).lines() { + let line = line.unwrap(); + let parts: Vec<_> = line.split(": ").collect(); + if parts[1] == "Expected failure" { + failures += 1; + } else { + ref_results.insert(PathBuf::from(parts[0]), parts[1].to_string()); + } + } + assert_eq!(expected_failures, failures); + for (path, crc) in results.iter() { + assert_eq!( + ref_results.get(path).expect(&format!("reference for {:?} is missing", path)), + crc + ) + } +} + +#[test] +fn render_images() { + process_images(|path| { + let mut decoder = gif::DecodeOptions::new(); + decoder.set_color_output(gif::ColorOutput::RGBA); + let file = File::open(path)?; + let mut decoder = decoder.read_info(file)?; + let mut crc = Crc32::new(); + while let Some(frame) = decoder.read_next_frame()? { + // First sanity check: + assert_eq!( + frame.buffer.len(), + frame.width as usize + * frame.height as usize + * 4 + ); + crc.update(&*frame.buffer); + } + Ok(crc.checksum()) + }) +} + + +const CRC_TABLE: [u32; 256] = [ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, + 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, + 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, + 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, + 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, + 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, + 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, + 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, + 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, + 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, + 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, + 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, + 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, + 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, + 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, + 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, + 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, + 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, + 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, + 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, + 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, + 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, + 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, + 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, + 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, + 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, + 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, + 0x2d02ef8d +]; + +/// Crc-32 checksum calculation +#[derive(Debug)] +pub struct Crc32 { + crc: u32, +} + +impl Crc32 { + /// Create a new hasher. + pub fn new() -> Crc32 { + Crc32 {crc: 0xFFFFFFFF} + } + + /// Resets the hasher. + pub fn reset(&mut self) { + *self = Self::new() + } + + /// Update the internal hasher with the bytes from ```buf``` + pub fn update(&mut self, buf: &[u8]) { + for &byte in buf { + let a = (self.crc ^ byte as u32) & 0xFF; + let b = self.crc >> 8; + + self.crc = CRC_TABLE[a as usize] ^ b; + } + } + + /// Return the computed hash. + pub fn checksum(&self) -> u32 { + self.crc ^ 0xFFFFFFFF + } +} diff --git a/vendor/gif/tests/crashtest.rs b/vendor/gif/tests/crashtest.rs new file mode 100644 index 0000000..ef67cb3 --- /dev/null +++ b/vendor/gif/tests/crashtest.rs @@ -0,0 +1,28 @@ +use std::{fs, io}; +use gif::DecodeOptions; + +#[test] +fn try_decode_crash_regression() { + let files = fs::read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/crashtest")).unwrap(); + let options = DecodeOptions::new(); + + for entry in files { + let entry = entry.unwrap(); + if let Some(ext) = entry.path().extension() { + if ext.to_str() != Some("gif") { + panic!("Unexpected file {} in crashtests, should end with .gif", entry.path().display()); + } + } else { + panic!("Unexpected file {} in crashtests, should end with .gif", entry.path().display()); + } + + let file_data = fs::read(entry.path()).unwrap(); + let _ = try_decode_file(&options, file_data); + } +} + +fn try_decode_file(options: &DecodeOptions, data: Vec<u8>) -> Result<(), gif::DecodingError> { + let mut reader = options.clone().read_info(io::Cursor::new(data))?; + while reader.read_next_frame()?.is_some() {} + Ok(()) +} diff --git a/vendor/gif/tests/decode.rs b/vendor/gif/tests/decode.rs new file mode 100644 index 0000000..6fc3aab --- /dev/null +++ b/vendor/gif/tests/decode.rs @@ -0,0 +1,84 @@ +use gif::{DecodeOptions, DisposalMethod, Encoder, Frame}; + +#[test] +fn frame_consistency_is_configurable() { + let image = create_image_with_oob_frames(); + + { + let options = DecodeOptions::new(); + let mut data = image.as_slice(); + let mut decoder = options.clone().read_info(&mut data).unwrap(); + assert!(decoder.read_next_frame().is_ok()); + assert!(decoder.read_next_frame().is_ok()); + } + + { + let mut options = DecodeOptions::new(); + options.check_frame_consistency(true); + let mut data = image.as_slice(); + let mut decoder = options.clone().read_info(&mut data).unwrap(); + assert!(decoder.read_next_frame().is_ok()); + assert!(decoder.read_next_frame().is_err()); + } + + { + let mut options = DecodeOptions::new(); + options.check_frame_consistency(false); + let mut data = image.as_slice(); + let mut decoder = options.clone().read_info(&mut data).unwrap(); + assert!(decoder.read_next_frame().is_ok()); + assert!(decoder.read_next_frame().is_ok()); + } +} + +fn create_image_with_oob_frames() -> Vec<u8> { + let mut data = vec![]; + let mut encoder = Encoder::new(&mut data, 2, 2, &[0, 0, 0]).unwrap(); + + let mut frame = Frame { + delay: 1, + dispose: DisposalMethod::Any, + transparent: None, + needs_user_input: false, + top: 0, + left: 0, + width: 2, + height: 2, + interlaced: false, + palette: None, + buffer: vec![0, 0, 0, 0].into(), + }; + + encoder.write_frame(&frame).unwrap(); + frame.top = 1; + frame.left = 1; + encoder.write_frame(&frame).unwrap(); + + drop(encoder); + data +} + +#[test] +fn check_for_end_code_is_configurable() { + // In this particular image, the image data of the 62nd frame has no end code. + let image: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/samples/gifplayer-muybridge.gif")); + + { + let options = DecodeOptions::new(); + let mut decoder = options.clone().read_info(&image[..]).unwrap(); + for _ in 0..61 { + assert!(decoder.read_next_frame().is_ok()); + } + assert!(decoder.read_next_frame().is_ok()); + } + + { + let mut options = DecodeOptions::new(); + options.check_lzw_end_code(true); + let mut decoder = options.clone().read_info(&image[..]).unwrap(); + for _ in 0..61 { + assert!(decoder.read_next_frame().is_ok()); + } + assert!(decoder.read_next_frame().is_err()); + } +} diff --git a/vendor/gif/tests/results.txt b/vendor/gif/tests/results.txt new file mode 100644 index 0000000..131b59d --- /dev/null +++ b/vendor/gif/tests/results.txt @@ -0,0 +1,10 @@ +tests/samples/alpha_gif_a.gif: 3871893825 +tests/samples/anim-gr.gif: 291646878 +tests/samples/beacon.gif: 2462153529 +tests/samples/interlaced.gif: 2115495372 +tests/samples/moon_impact.gif: 2438689726 +tests/samples/sample_1.gif: 3275424619 +tests/samples/2x2.gif: 3802149240 +tests/samples/sample_big.gif: 4184562096 +tests/samples/gifplayer-muybridge.gif: 4267078865 +tests/samples/set_hsts.gif: 224161812 diff --git a/vendor/gif/tests/roundtrip.rs b/vendor/gif/tests/roundtrip.rs new file mode 100644 index 0000000..c121508 --- /dev/null +++ b/vendor/gif/tests/roundtrip.rs @@ -0,0 +1,103 @@ +use gif::{ColorOutput, Decoder, Encoder, Frame}; + +#[test] +fn encode_roundtrip() { + const ORIGINAL: &'static [u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/samples/2x2.gif")); + round_trip_from_image(ORIGINAL); +} + +fn round_trip_from_image(original: &[u8]) { + let (width, height, global_palette); + let frames: Vec<Frame> = { + let mut decoder = Decoder::new(original).unwrap(); + width = decoder.width(); + height = decoder.height(); + global_palette = decoder + .global_palette() + .unwrap_or_default() + .to_vec(); + core::iter::from_fn(move || { + decoder.read_next_frame().unwrap().cloned() + }).collect() + }; + + let mut encoder = Encoder::new(vec![], width, height, &global_palette).unwrap(); + for frame in &frames { + encoder.write_frame(frame).unwrap(); + } + let buffer = encoder.into_inner().unwrap(); + + { + let mut decoder = Decoder::new(&buffer[..]).expect("Invalid info encoded"); + assert_eq!(decoder.width(), width); + assert_eq!(decoder.height(), height); + assert_eq!(global_palette, decoder.global_palette().unwrap_or_default()); + let new_frames: Vec<_> = core::iter::from_fn(move || { + decoder.read_next_frame().unwrap().cloned() + }).collect(); + assert_eq!(new_frames.len(), frames.len(), "Diverging number of frames"); + for (new, reference) in new_frames.iter().zip(&frames) { + assert_eq!(new.delay, reference.delay); + assert_eq!(new.dispose, reference.dispose); + assert_eq!(new.transparent, reference.transparent); + assert_eq!(new.needs_user_input, reference.needs_user_input); + assert_eq!(new.top, reference.top); + assert_eq!(new.left, reference.left); + assert_eq!(new.width, reference.width); + assert_eq!(new.height, reference.height); + assert_eq!(new.interlaced, reference.interlaced); + assert_eq!(new.palette, reference.palette); + assert_eq!(new.buffer, reference.buffer); + } + } +} + +#[test] +#[cfg(feature = "color_quant")] +fn encode_roundtrip_few_colors() { + const WIDTH: u16 = 128; + const HEIGHT: u16 = 128; + + // Build an image with a single red pixel, that NeuQuant won't + // sample, in order to check that we do appropriatelyq specialise the + // few-colors case. + let mut pixels: Vec<u8> = vec![255; WIDTH as usize * HEIGHT as usize * 4]; + // Top-left pixel is always sampled, so use the second pixel. + pixels[5] = 0; + pixels[6] = 0; + // Set speed to 30 to handily avoid sampling that one pixel. + // + // We clone "pixels", since the parameter is replaced with a + // paletted version, and later we want to compare the output with + // the original RGBA image. + let mut frame = Frame::from_rgba_speed(WIDTH, HEIGHT, &mut pixels.clone(), 30); + + let mut buffer = vec![]; + { + let mut encoder = Encoder::new(&mut buffer, WIDTH, HEIGHT, &[]).unwrap(); + encoder.write_frame(&frame).unwrap(); + + frame.make_lzw_pre_encoded(); + encoder.write_lzw_pre_encoded_frame(&frame).unwrap(); + } + + { + let mut decoder = { + let mut builder = Decoder::<&[u8]>::build(); + builder.set_color_output(ColorOutput::RGBA); + builder.read_info(&buffer[..]).expect("Invalid info encoded") + }; + + // Only check key fields, assuming "round_trip_from_image" + // covers the rest. We are primarily concerned with quantisation. + assert_eq!(decoder.width(), WIDTH); + assert_eq!(decoder.height(), HEIGHT); + let new_frames: Vec<_> = core::iter::from_fn(move || { + decoder.read_next_frame().unwrap().cloned() + }).collect(); + assert_eq!(new_frames.len(), 2, "Diverging number of frames"); + // NB: reference.buffer can't be used as it contains the palette version. + assert_eq!(new_frames[0].buffer, pixels); + assert_eq!(new_frames[1].buffer, pixels); + } +} diff --git a/vendor/gif/tests/stall.rs b/vendor/gif/tests/stall.rs new file mode 100644 index 0000000..3d51a17 --- /dev/null +++ b/vendor/gif/tests/stall.rs @@ -0,0 +1,42 @@ +use std::{fs, sync::mpsc, thread, time::Duration}; + +#[test] +fn try_decode_crash_regression() { + let files = fs::read_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/stall")).unwrap(); + + for entry in files { + let entry = entry.unwrap(); + if let Some(ext) = entry.path().extension() { + if ext.to_str() != Some("gif") { + panic!("Unexpected file {} in crashtests, should end with .gif", entry.path().display()); + } + } else { + panic!("Unexpected file {} in crashtests, should end with .gif", entry.path().display()); + } + + let file_data = fs::read(entry.path()).unwrap(); + let _ = decode_on_timer(file_data); + } +} + +fn decode_on_timer(data: Vec<u8>) { + let (send, recv) = mpsc::channel(); + + thread::spawn(move || { + let result = decode(&data); + send.send(result).expect("still waiting"); + }); + + let _ = recv.recv_timeout(Duration::from_secs(1)) + .expect("any result"); +} + +fn decode(data: &[u8]) -> Result<(), gif::DecodingError> { + let mut options = gif::DecodeOptions::new(); + options.set_color_output(gif::ColorOutput::RGBA); + + let mut decoder = options.read_info(data)?; + while let Some(_frame) = decoder.read_next_frame()? {} + + Ok(()) +} diff --git a/vendor/gif/tests/stall/issue-101-infinite-empty-loop.gif b/vendor/gif/tests/stall/issue-101-infinite-empty-loop.gif Binary files differnew file mode 100644 index 0000000..270ad07 --- /dev/null +++ b/vendor/gif/tests/stall/issue-101-infinite-empty-loop.gif |