Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
This commit is contained in:
1
vendor/gif/.cargo-checksum.json
vendored
Normal file
1
vendor/gif/.cargo-checksum.json
vendored
Normal file
@ -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"}
|
668
vendor/gif/Cargo.lock
generated
vendored
Normal file
668
vendor/gif/Cargo.lock
generated
vendored
Normal file
@ -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"
|
83
vendor/gif/Cargo.toml
vendored
Normal file
83
vendor/gif/Cargo.toml
vendored
Normal file
@ -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 = []
|
60
vendor/gif/Changes.md
vendored
Normal file
60
vendor/gif/Changes.md
vendored
Normal file
@ -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.
|
201
vendor/gif/LICENSE-APACHE
vendored
Normal file
201
vendor/gif/LICENSE-APACHE
vendored
Normal file
@ -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.
|
21
vendor/gif/LICENSE-MIT
vendored
Normal file
21
vendor/gif/LICENSE-MIT
vendored
Normal file
@ -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.
|
83
vendor/gif/README.md
vendored
Normal file
83
vendor/gif/README.md
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
# GIF en- and decoding library [](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();
|
||||
```
|
81
vendor/gif/benches/decode.rs
vendored
Normal file
81
vendor/gif/benches/decode.rs
vendored
Normal file
@ -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();
|
||||
}
|
73
vendor/gif/benches/rgb_frame.rs
vendored
Normal file
73
vendor/gif/benches/rgb_frame.rs
vendored
Normal file
@ -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();
|
||||
}
|
BIN
vendor/gif/benches/samples/test.gif
vendored
Normal file
BIN
vendor/gif/benches/samples/test.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
BIN
vendor/gif/benches/samples/test.png
vendored
Normal file
BIN
vendor/gif/benches/samples/test.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 419 KiB |
41
vendor/gif/examples/check.rs
vendored
Normal file
41
vendor/gif/examples/check.rs
vendored
Normal file
@ -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)
|
||||
}
|
44
vendor/gif/examples/explode.rs
vendored
Normal file
44
vendor/gif/examples/explode.rs
vendored
Normal file
@ -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(())
|
||||
}
|
346
vendor/gif/src/common.rs
vendored
Normal file
346
vendor/gif/src/common.rs
vendored
Normal file
@ -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);
|
||||
}
|
434
vendor/gif/src/encoder.rs
vendored
Normal file
434
vendor/gif/src/encoder.rs
vendored
Normal file
@ -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();
|
||||
}
|
154
vendor/gif/src/lib.rs
vendored
Normal file
154
vendor/gif/src/lib.rs
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
//! # GIF en- and decoding library [](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"));
|
724
vendor/gif/src/reader/decoder.rs
vendored
Normal file
724
vendor/gif/src/reader/decoder.rs
vendored
Normal file
@ -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();
|
||||
}
|
522
vendor/gif/src/reader/mod.rs
vendored
Normal file
522
vendor/gif/src/reader/mod.rs
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
49
vendor/gif/src/traits.rs
vendored
Normal file
49
vendor/gif/src/traits.rs
vendored
Normal file
@ -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)
|
||||
|
||||
}
|
||||
}
|
170
vendor/gif/tests/check_testimages.rs
vendored
Normal file
170
vendor/gif/tests/check_testimages.rs
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
28
vendor/gif/tests/crashtest.rs
vendored
Normal file
28
vendor/gif/tests/crashtest.rs
vendored
Normal file
@ -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(())
|
||||
}
|
84
vendor/gif/tests/decode.rs
vendored
Normal file
84
vendor/gif/tests/decode.rs
vendored
Normal file
@ -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());
|
||||
}
|
||||
}
|
10
vendor/gif/tests/results.txt
vendored
Normal file
10
vendor/gif/tests/results.txt
vendored
Normal file
@ -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
|
103
vendor/gif/tests/roundtrip.rs
vendored
Normal file
103
vendor/gif/tests/roundtrip.rs
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
42
vendor/gif/tests/stall.rs
vendored
Normal file
42
vendor/gif/tests/stall.rs
vendored
Normal file
@ -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(())
|
||||
}
|
BIN
vendor/gif/tests/stall/issue-101-infinite-empty-loop.gif
vendored
Normal file
BIN
vendor/gif/tests/stall/issue-101-infinite-empty-loop.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 B |
Reference in New Issue
Block a user