gif-0.13.1/.cargo_vcs_info.json0000644000000001360000000000100116630ustar { "git": { "sha1": "70a1d31ec5719da10c6eb8163ed4283747d56f7c" }, "path_in_vcs": "" }gif-0.13.1/Cargo.lock0000644000000455150000000000100076500ustar # 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 = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[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 = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bumpalo" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[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 = "ciborium" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[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.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fdeflate" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "gif" version = "0.13.1" dependencies = [ "color_quant", "criterion", "glob", "png", "rayon", "weezl", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[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.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "is-terminal" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ "hermit-abi", "rustix", "windows-sys", ] [[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 = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", "simd-adler32", ] [[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "png" version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "proc-macro2" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "regex" version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustix" version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[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 = "serde" version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "syn" version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[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.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "walkdir" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "web-sys" version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "weezl" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 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" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" gif-0.13.1/Cargo.toml0000644000000027100000000000100076610ustar # 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 = "2021" name = "gif" version = "0.13.1" authors = ["The image-rs Developers"] include = [ "src/**", "LICENSE-*", "README.md", "benches/*.rs", ] 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" [lib] bench = false [[bench]] name = "decode" harness = false required-features = ["std"] [[bench]] name = "rgb_frame" harness = false required-features = [ "std", "color_quant", ] [dependencies.color_quant] version = "1.1" optional = true [dependencies.weezl] version = "0.1.8" [dev-dependencies.criterion] version = "0.5.1" [dev-dependencies.glob] version = "0.3" [dev-dependencies.png] version = "0.17.10" [dev-dependencies.rayon] version = "1.8.0" [features] color_quant = ["dep:color_quant"] default = [ "raii_no_panic", "std", "color_quant", ] raii_no_panic = [] std = [] gif-0.13.1/Cargo.toml.orig000064400000000000000000000021361046102023000133440ustar 00000000000000[package] name = "gif" license = "MIT/Apache-2.0" version = "0.13.1" description = "GIF de- and encoder" authors = ["The image-rs Developers"] readme = "README.md" homepage = "https://github.com/image-rs/image-gif" repository = "https://github.com/image-rs/image-gif" documentation = "https://docs.rs/gif" edition = "2021" include = ["src/**", "LICENSE-*", "README.md", "benches/*.rs"] [lib] bench = false [dependencies] weezl = "0.1.8" color_quant = { version = "1.1", optional = true } [dev-dependencies] glob = "0.3" criterion = "0.5.1" png = "0.17.10" rayon = "1.8.0" # for parallel reencoding example [features] default = ["raii_no_panic", "std", "color_quant"] # The `Encoder` finishes writing in `Drop`, and if the write fails, it either ignores the error (`raii_no_panic`) or panics. # Use `Encoder::into_inner` to avoid the issue entirely raii_no_panic = [] color_quant = ["dep:color_quant"] # Reservation for a feature turning off std std = [] [[bench]] name = "decode" harness = false required-features = ["std"] [[bench]] name = "rgb_frame" harness = false required-features = ["std", "color_quant"] gif-0.13.1/LICENSE-APACHE000064400000000000000000000251361046102023000124060ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.gif-0.13.1/LICENSE-MIT000064400000000000000000000020571046102023000121130ustar 00000000000000The 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. gif-0.13.1/README.md000064400000000000000000000050011046102023000117260ustar 00000000000000# GIF en- and decoding library [![Build Status](https://github.com/image-rs/image-gif/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/image-gif/actions) GIF en- and decoder written in Rust ([API Documentation](https://docs.rs/gif/)). # GIF encoding and decoding library This library provides all functions necessary to de- and encode GIF files. ## High level interface The high level interface consists of the two types [`Encoder`](https://docs.rs/gif/*/gif/struct.Encoder.html) and [`Decoder`](https://docs.rs/gif/*/gif/struct.Decoder.html). ### Decoding GIF files ```rust // Open the file use std::fs::File; let input = File::open("tests/samples/sample_1.gif").unwrap(); // Configure the decoder such that it will expand the image to RGBA. let mut options = gif::DecodeOptions::new(); options.set_color_output(gif::ColorOutput::RGBA); // Read the file header let mut decoder = options.read_info(input).unwrap(); while let Some(frame) = decoder.read_next_frame().unwrap() { // Process every frame } ``` ### Encoding GIF files The encoder can be used to save simple computer generated images: ```rust use gif::{Frame, Encoder, Repeat}; use std::fs::File; use std::borrow::Cow; let color_map = &[0xFF, 0xFF, 0xFF, 0, 0, 0]; let (width, height) = (6, 6); let beacon_states = [[ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, ], [ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, ]]; let mut image = File::create("target/beacon.gif").unwrap(); let mut encoder = Encoder::new(&mut image, width, height, color_map).unwrap(); encoder.set_repeat(Repeat::Infinite).unwrap(); for state in &beacon_states { let mut frame = Frame::default(); frame.width = width; frame.height = height; frame.buffer = Cow::Borrowed(&*state); encoder.write_frame(&frame).unwrap(); } ``` [`Frame::from_*`](https://docs.rs/gif/*/gif/struct.Frame.html) can be used to convert a true color image to a paletted image with a maximum of 256 colors: ```rust use std::fs::File; // Get pixel data from some source let mut pixels: Vec = 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(); ``` gif-0.13.1/benches/decode.rs000064400000000000000000000041771046102023000136640ustar 00000000000000use criterion::{black_box, BenchmarkId, BenchmarkGroup, Criterion, Throughput, measurement::Measurement}; use gif::Decoder; fn read_image(image: &[u8]) -> Option> { 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(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!("../tests/samples/sample_1.gif"), id: "sample_1.gif", sample_size: 100, }); run_bench_def(&mut group, BenchDef { data: include_bytes!("../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(); } gif-0.13.1/benches/rgb_frame.rs000064400000000000000000000046271046102023000143650ustar 00000000000000use 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(); } gif-0.13.1/src/common.rs000064400000000000000000000333331046102023000131050ustar 00000000000000use std::borrow::Cow; #[cfg(feature = "color_quant")] use std::collections::{HashMap, 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` #[must_use] pub fn from_u8(n: u8) -> Option { 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` #[must_use] pub fn from_u8(n: u8) -> Option { 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. #[must_use] pub fn into_known(self) -> Option { Extension::from_u8(self.0) } } impl From for AnyExtension { fn from(ext: Extension) -> Self { AnyExtension(ext as u8) } } impl Extension { /// Converts `u8` to a `Extension` if it is known. #[must_use] pub fn from_u8(n: u8) -> Option { 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, /// 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>, /// 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_unstable(); 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])).copied().unwrap_or(0); return Frame { width, height, buffer: Cow::Owned(pixels.chunks_exact(4).map(index_of).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: impl Into>, palette: impl Into>, transparent: Option) -> Frame<'static> { let pixels = pixels.into(); let palette = palette.into(); 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), palette: Some(palette), 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: impl Into>, transparent: Option) -> Frame<'static> { let pixels = pixels.into(); 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.clone()), 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")] #[must_use] 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")] #[must_use] 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 = Vec::new(); vec.try_reserve_exact(pixels.len() + width as usize * height as usize).expect("OOM"); 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) } /// Leaves empty buffer and empty palette behind #[inline] pub(crate) fn take(&mut self) -> Self { Frame { delay: self.delay, dispose: self.dispose, transparent: self.transparent, needs_user_input: self.needs_user_input, top: self.top, left: self.left, width: self.width, height: self.height, interlaced: self.interlaced, palette: std::mem::take(&mut self.palette), buffer: std::mem::replace(&mut self.buffer, Cow::Borrowed(&[])), } } } #[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 = (0..=255).map(|a| vec![a, a, a]).flatten().collect(); let _ = Frame::from_rgb(side, side, &pixel_data); } gif-0.13.1/src/encoder.rs000064400000000000000000000407041046102023000132340ustar 00000000000000//! # 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}; /// The image has incorrect properties, making it impossible to encode as a gif. #[derive(Debug)] #[non_exhaustive] pub enum EncodingFormatError { /// The image has too many colors. TooManyColors, /// The image has no color palette which is required. MissingColorPalette, /// LZW data is not valid for GIF. This may happen when wrong buffer is given to `write_lzw_pre_encoded_frame` InvalidMinCodeSize, } impl error::Error for EncodingFormatError {} impl fmt::Display for EncodingFormatError { #[cold] fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::TooManyColors => write!(fmt, "the image has too many colors"), Self::MissingColorPalette => write!(fmt, "the GIF format requires a color palette but none was given"), Self::InvalidMinCodeSize => write!(fmt, "LZW data is invalid"), } } } #[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 { #[cold] 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 { #[cold] fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { EncodingError::Io(err) => Some(err), EncodingError::Format(err) => Some(err), } } } impl From for EncodingError { #[cold] fn from(err: io::Error) -> Self { EncodingError::Io(err) } } impl From for EncodingError { #[cold] fn from(err: EncodingFormatError) -> Self { EncodingError::Format(err) } } /// Number of repetitions #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Repeat { /// Finite number of repetitions Finite(u16), /// Infinite number of repetitions Infinite, } impl Default for Repeat { fn default() -> Self { Self::Finite(0) } } /// Extension data. #[non_exhaustive] 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. #[must_use] pub fn new_control_ext(delay: u16, dispose: DisposalMethod, needs_user_input: bool, trns: Option) -> ExtensionData { let mut flags = 0; let trns = match trns { Some(trns) => { flags |= 1; trns }, None => 0 }; flags |= u8::from(needs_user_input) << 1; flags |= (dispose as u8) << 2; ExtensionData::Control { flags, delay, trns } } } impl Encoder { /// 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 { Encoder { w: Some(w), global_palette: false, width, height, buffer: Vec::new(), }.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. fn write_global_palette(mut self, palette: &[u8]) -> Result { let mut flags = 0; flags |= 0b1000_0000; let (palette, padding, table_size) = Self::check_color_table(palette)?; self.global_palette = !palette.is_empty(); // Size of global color table. flags |= table_size; // 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 |= table_size << 4; // wtf flag self.write_screen_desc(flags)?; Self::write_color_table(self.writer()?, palette, padding)?; 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> { if usize::from(frame.width).checked_mul(usize::from(frame.height)).map_or(true, |size| frame.buffer.len() < size) { return Err(io::Error::new(io::ErrorKind::InvalidInput, "frame.buffer is too small for its width/height").into()); } debug_assert!((frame.width > 0 && frame.height > 0) || frame.buffer.is_empty(), "the frame has 0 pixels, but non-empty buffer"); self.write_frame_header(frame)?; self.write_image_block(&frame.buffer) } fn write_frame_header(&mut self, frame: &Frame<'_>) -> Result<(), EncodingError> { self.write_extension(ExtensionData::new_control_ext( frame.delay, frame.dispose, frame.needs_user_input, frame.transparent, ))?; let mut flags = 0; if frame.interlaced { flags |= 0b0100_0000; } let palette = match frame.palette { Some(ref palette) => { flags |= 0b1000_0000; let (palette, padding, table_size) = Self::check_color_table(&palette)?; flags |= table_size; Some((palette, padding)) }, None if self.global_palette => None, _ => return Err(EncodingError::from(EncodingFormatError::MissingColorPalette)) }; let mut tmp = tmp_buf::<10>(); tmp.write_le(Block::Image as u8)?; tmp.write_le(frame.left)?; tmp.write_le(frame.top)?; tmp.write_le(frame.width)?; tmp.write_le(frame.height)?; tmp.write_le(flags)?; let writer = self.writer()?; tmp.finish(&mut *writer)?; if let Some((palette, padding)) = palette { Self::write_color_table(writer, palette, padding)?; } Ok(()) } fn write_image_block(&mut self, data: &[u8]) -> Result<(), EncodingError> { self.buffer.clear(); self.buffer.try_reserve(data.len() / 4) .map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?; lzw_encode(data, &mut self.buffer); let writer = self.w.as_mut().ok_or(io::Error::from(io::ErrorKind::Unsupported))?; 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); for full_block in iter.by_ref() { 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(writer: &mut W, table: &[u8], padding: usize) -> Result<(), EncodingError> { writer.write_all(&table)?; // Waste some space as of gif spec for _ in 0..padding { writer.write_all(&[0, 0, 0])?; } Ok(()) } /// returns rounded palette size, number of missing colors, and table size flag fn check_color_table(table: &[u8]) -> Result<(&[u8], usize, u8), EncodingError> { let num_colors = table.len() / 3; if num_colors > 256 { return Err(EncodingError::from(EncodingFormatError::TooManyColors)); } let table_size = flag_size(num_colors); let padding = (2 << table_size) - num_colors; Ok((&table[..num_colors * 3], padding, table_size)) } /// 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.writer()?; writer.write_le(Block::Extension as u8)?; match extension { Control { flags, delay, trns } => { let mut tmp = tmp_buf::<6>(); tmp.write_le(Extension::Control as u8)?; tmp.write_le(4u8)?; tmp.write_le(flags)?; tmp.write_le(delay)?; tmp.write_le(trns)?; tmp.finish(&mut *writer)?; } Repetitions(repeat) => { let mut tmp = tmp_buf::<17>(); tmp.write_le(Extension::Application as u8)?; tmp.write_le(11u8)?; tmp.write_all(b"NETSCAPE2.0")?; tmp.write_le(3u8)?; tmp.write_le(1u8)?; tmp.write_le(match repeat { Repeat::Finite(no) => no, Repeat::Infinite => 0u16, })?; tmp.finish(&mut *writer)?; } } 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.writer()?; 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> { // empty data is allowed if let Some(&min_code_size) = frame.buffer.get(0) { if min_code_size > 11 || min_code_size < 2 { return Err(EncodingError::Format(EncodingFormatError::InvalidMinCodeSize)); } } self.write_frame_header(frame)?; let writer = self.writer()?; Self::write_encoded_image_block(writer, &frame.buffer) } /// Writes the logical screen desriptor fn write_screen_desc(&mut self, flags: u8) -> io::Result<()> { let mut tmp = tmp_buf::<13>(); tmp.write_all(b"GIF89a")?; tmp.write_le(self.width)?; tmp.write_le(self.height)?; tmp.write_le(flags)?; // packed field tmp.write_le(0u8)?; // bg index tmp.write_le(0u8)?; // aspect ratio tmp.finish(self.writer()?) } /// 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() } /// Finishes writing, and returns the `io::Write` instance used by this encoder pub fn into_inner(mut self) -> io::Result { self.write_trailer()?; self.w.take().ok_or(io::Error::from(io::ErrorKind::Unsupported)) } /// Write the final tailer. fn write_trailer(&mut self) -> io::Result<()> { self.writer()?.write_le(Block::Trailer as u8) } #[inline] fn writer(&mut self) -> io::Result<&mut W> { self.w.as_mut().ok_or(io::Error::from(io::ErrorKind::Unsupported)) } } /// 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) { let mut max_byte = 0; for &byte in data { if byte > max_byte { max_byte = byte; // code size is the same after that if byte > 127 { break; } } } let palette_min_len = max_byte as u32 + 1; // As per gif spec: The minimal code size has to be >= 2 let min_code_size = palette_min_len.max(4).next_power_of_two().trailing_zeros() as u8; 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::new(); buffer.try_reserve(self.buffer.len() / 2).expect("OOM"); lzw_encode(&self.buffer, &mut buffer); self.buffer = Cow::Owned(buffer); } } /// GIF encoder. pub struct Encoder { w: Option, global_palette: bool, width: u16, height: u16, buffer: Vec, } impl Drop for Encoder { #[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 { (size.max(2).min(255).next_power_of_two().trailing_zeros()-1) as u8 } #[test] fn test_flag_size() { fn expected(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 } } for i in 0..300 { assert_eq!(flag_size(i), expected(i)); } for i in 4..=255u8 { let expected = match flag_size(1 + i as usize) + 1 { 1 => 2, n => n, }; let actual = (i as u32 + 1).max(4).next_power_of_two().trailing_zeros() as u8; assert_eq!(actual, expected); } } struct Buf { buf: [u8; N], pos: usize } impl Write for Buf { #[inline(always)] fn write(&mut self, buf: &[u8]) -> io::Result { let len = buf.len(); let pos = self.pos; self.buf[pos.. pos + len].copy_from_slice(buf); self.pos += len; Ok(len) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } fn tmp_buf() -> Buf { Buf { buf: [0; N], pos: 0 } } impl Buf { #[inline(always)] fn finish(&mut self, mut w: impl Write) -> io::Result<()> { debug_assert_eq!(self.pos, N); w.write_all(&self.buf) } } #[test] fn error_cast() { let _ : Box = EncodingError::from(EncodingFormatError::MissingColorPalette).into(); } gif-0.13.1/src/lib.rs000064400000000000000000000105571046102023000123660ustar 00000000000000#![forbid(unsafe_code)] //! # GIF en- and decoding library [![Build Status](https://github.com/image-rs/image-gif/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/image-gif/actions) //! //! GIF en- and decoder written in Rust ([API Documentation](https://docs.rs/gif)). //! //! # GIF encoding and decoding library //! //! This library provides all functions necessary to de- and encode GIF files. //! //! ## High level interface //! //! The high level interface consists of the two types //! [`Encoder`](struct.Encoder.html) and [`Decoder`](struct.Decoder.html). //! //! ### Decoding GIF files //! //! ```rust //! // Open the file //! use std::fs::File; //! let mut decoder = gif::DecodeOptions::new(); //! // Configure the decoder such that it will expand the image to RGBA. //! decoder.set_color_output(gif::ColorOutput::RGBA); //! // Read the file header //! let file = File::open("tests/samples/sample_1.gif").unwrap(); //! let mut decoder = decoder.read_info(file).unwrap(); //! while let Some(frame) = decoder.read_next_frame().unwrap() { //! // Process every frame //! } //! ``` //! //! //! //! ### Encoding GIF files //! //! The encoder can be used so save simple computer generated images: //! //! ```rust //! use gif::{Frame, Encoder, Repeat}; //! use std::fs::File; //! use std::borrow::Cow; //! //! let color_map = &[0xFF, 0xFF, 0xFF, 0, 0, 0]; //! let (width, height) = (6, 6); //! let mut beacon_states = [[ //! 0, 0, 0, 0, 0, 0, //! 0, 1, 1, 0, 0, 0, //! 0, 1, 1, 0, 0, 0, //! 0, 0, 0, 1, 1, 0, //! 0, 0, 0, 1, 1, 0, //! 0, 0, 0, 0, 0, 0, //! ], [ //! 0, 0, 0, 0, 0, 0, //! 0, 1, 1, 0, 0, 0, //! 0, 1, 0, 0, 0, 0, //! 0, 0, 0, 0, 1, 0, //! 0, 0, 0, 1, 1, 0, //! 0, 0, 0, 0, 0, 0, //! ]]; //! let mut image = File::create("tests/samples/beacon.gif").unwrap();; //! let mut encoder = Encoder::new(&mut image, width, height, color_map).unwrap(); //! encoder.set_repeat(Repeat::Infinite).unwrap(); //! for state in &beacon_states { //! let mut frame = Frame::default(); //! frame.width = width; //! frame.height = height; //! frame.buffer = Cow::Borrowed(&*state); //! encoder.write_frame(&frame).unwrap(); //! } //! ``` //! //! [`Frame::from_*`](struct.Frame.html) can be used to convert a true color image to a paletted //! image with a maximum of 256 colors: //! //! ```rust //! # #[cfg(feature = "color_quant")] { //! use std::fs::File; //! //! // Get pixel data from some source //! let mut pixels: Vec = 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, Extension, DisposalMethod, Frame}; pub use crate::reader::{DecodingError, DecodingFormatError}; pub use crate::reader::{ColorOutput, MemoryLimit}; pub use crate::reader::{DecodeOptions, Decoder, Version}; pub use crate::encoder::{Encoder, ExtensionData, Repeat, EncodingError, EncodingFormatError}; /// Low-level, advanced decoder. Prefer [`Decoder`] instead, which can stream frames too. pub mod streaming_decoder { pub use crate::common::Block; pub use crate::reader::{Decoded, FrameDataType, FrameDecoder, OutputBuffer, StreamingDecoder}; } #[cfg(feature = "color_quant")] macro_rules! insert_as_doc { { $content:expr } => { #[allow(unused_doc_comments)] #[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")); gif-0.13.1/src/reader/converter.rs000064400000000000000000000230721046102023000150650ustar 00000000000000use std::borrow::Cow; use std::io; use std::mem; use std::iter; use crate::common::Frame; use crate::MemoryLimit; use super::decoder::{ PLTE_CHANNELS, DecodingError, OutputBuffer }; pub(crate) 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, } pub(crate) type FillBufferCallback<'a> = &'a mut dyn FnMut(&mut OutputBuffer<'_>) -> Result; /// Deinterlaces and expands to RGBA if needed pub(crate) struct PixelConverter { memory_limit: MemoryLimit, color_output: ColorOutput, buffer: Vec, global_palette: Option>, } impl PixelConverter { pub(crate) fn new(color_output: ColorOutput, memory_limit: MemoryLimit) -> Self { Self { memory_limit, color_output, buffer: Vec::new(), global_palette: None, } } pub(crate) fn check_buffer_size(&mut self, frame: &Frame<'_>) -> Result { let pixel_bytes = self.memory_limit .buffer_size(self.color_output, frame.width, frame.height) .ok_or_else(|| io::Error::new(io::ErrorKind::OutOfMemory, "image is too large"))?; debug_assert_eq!( pixel_bytes, self.buffer_size(frame).unwrap(), "Checked computation diverges from required buffer size" ); Ok(pixel_bytes) } #[inline] pub(crate) fn read_frame(&mut self, frame: &mut Frame<'_>, data_callback: FillBufferCallback<'_>) -> Result<(), DecodingError> { let pixel_bytes = self.check_buffer_size(frame)?; let mut vec = match mem::replace(&mut frame.buffer, Cow::Borrowed(&[])) { // reuse buffer if possible without reallocating Cow::Owned(mut vec) if vec.capacity() >= pixel_bytes => { vec.resize(pixel_bytes, 0); vec }, // resizing would realloc anyway, and 0-init is faster than a copy _ => vec![0; pixel_bytes], }; self.read_into_buffer(frame, &mut vec, data_callback)?; frame.buffer = Cow::Owned(vec); frame.interlaced = false; Ok(()) } #[inline] pub(crate) fn buffer_size(&self, frame: &Frame<'_>) -> Option { self.line_length(frame).checked_mul(frame.height as usize) } #[inline] pub(crate) fn line_length(&self, frame: &Frame<'_>) -> usize { use self::ColorOutput::*; match self.color_output { RGBA => frame.width as usize * N_CHANNELS, Indexed => frame.width as usize, } } /// Use `read_into_buffer` to deinterlace #[inline(never)] pub(crate) fn fill_buffer(&mut self, current_frame: &Frame<'_>, mut buf: &mut [u8], data_callback: FillBufferCallback<'_>) -> Result { loop { let decode_into = match self.color_output { // When decoding indexed data, LZW can write the pixels directly ColorOutput::Indexed => &mut buf[..], // When decoding RGBA, the pixel data will be expanded by a factor of 4, // and it's simpler to decode indexed pixels to another buffer first ColorOutput::RGBA => { let buffer_size = buf.len() / N_CHANNELS; if buffer_size == 0 { return Err(DecodingError::format("odd-sized buffer")); } if self.buffer.len() < buffer_size { self.buffer.resize(buffer_size, 0); } &mut self.buffer[..buffer_size] } }; match data_callback(&mut OutputBuffer::Slice(decode_into))? { 0 => return Ok(false), bytes_decoded => { match self.color_output { ColorOutput::RGBA => { let transparent = current_frame.transparent; let palette: &[u8] = current_frame.palette.as_deref() .or(self.global_palette.as_deref()) .unwrap_or_default(); // next_frame_info already checked it won't happen let (pixels, rest) = buf.split_at_mut(bytes_decoded * N_CHANNELS); buf = rest; for (rgba, idx) in pixels.chunks_exact_mut(N_CHANNELS).zip(self.buffer.iter().copied().take(bytes_decoded)) { let plte_offset = PLTE_CHANNELS * idx as usize; if let Some(colors) = palette.get(plte_offset..plte_offset+PLTE_CHANNELS) { 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 }; } } }, ColorOutput::Indexed => { buf = &mut buf[bytes_decoded..]; } } if buf.is_empty() { return Ok(true); } }, } } } pub(crate) fn global_palette(&self) -> Option<&[u8]> { self.global_palette.as_deref() } pub(crate) fn set_global_palette(&mut self, palette: Vec) { self.global_palette = if !palette.is_empty() { Some(palette) } else { None }; } /// Applies deinterlacing /// /// Set `frame.interlaced = false` afterwards if you're putting the buffer back into the `Frame` pub(crate) fn read_into_buffer(&mut self, frame: &Frame<'_>, buf: &mut [u8], data_callback: FillBufferCallback<'_>) -> Result<(), DecodingError> { if frame.interlaced { let width = self.line_length(frame); for row in (InterlaceIterator { len: frame.height, next: 0, pass: 0 }) { // this can't overflow 32-bit, because row never equals (maximum) height let start = row * width; // Handle a too-small buffer and 32-bit usize overflow without panicking let line = buf.get_mut(start..).and_then(|b| b.get_mut(..width)) .ok_or_else(|| DecodingError::format("buffer too small"))?; if !self.fill_buffer(frame, line, data_callback)? { return Err(DecodingError::format("image truncated")); } } } else { let buf = self.buffer_size(frame).and_then(|buffer_size| buf.get_mut(..buffer_size)) .ok_or_else(|| DecodingError::format("buffer too small"))?; if !self.fill_buffer(frame, buf, data_callback)? { return Err(DecodingError::format("image truncated")); } }; Ok(()) } } struct InterlaceIterator { len: u16, next: usize, pass: usize, } impl iter::Iterator for InterlaceIterator { type Item = usize; #[inline] fn next(&mut self) -> Option { if self.len == 0 { return None; } // although the pass never goes out of bounds thanks to len==0, // the optimizer doesn't see it. get()? avoids costlier panicking code. let mut next = self.next + *[8, 8, 4, 2].get(self.pass)?; while next >= self.len as usize { debug_assert!(self.pass < 4); next = *[4, 2, 1, 0].get(self.pass)?; self.pass += 1; } mem::swap(&mut next, &mut self.next); Some(next) } } #[cfg(test)] mod test { use super::InterlaceIterator; #[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, next: 0, pass: 0 }; let lines = iter.collect::>(); assert_eq!(lines, expect); } } #[test] fn interlace_max() { let iter = InterlaceIterator { len: 0xFFFF, next: 0, pass: 0 }; assert_eq!(65533, iter.last().unwrap()); } } gif-0.13.1/src/reader/decoder.rs000064400000000000000000000760001046102023000144620ustar 00000000000000use std::borrow::Cow; use std::cmp; use std::error; use std::fmt; use std::io; use std::mem; use std::default::Default; use std::num::NonZeroUsize; use crate::Repeat; use crate::MemoryLimit; use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame}; use crate::reader::DecodeOptions; use weezl::{BitOrder, decode::Decoder as LzwDecoder, LzwError, 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, } impl fmt::Display for DecodingFormatError { #[cold] fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&*self.underlying, fmt) } } impl error::Error for DecodingFormatError { #[cold] fn source(&self) -> Option<&(dyn error::Error + 'static)> { Some(&*self.underlying as _) } } #[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 { #[cold] pub(crate) fn format(err: &'static str) -> Self { DecodingError::Format(DecodingFormatError { underlying: err.into(), }) } } impl fmt::Display for DecodingError { #[cold] 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 { #[cold] fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { DecodingError::Format(ref err) => Some(err), DecodingError::Io(ref err) => Some(err), } } } impl From for DecodingError { #[inline] fn from(err: io::Error) -> Self { DecodingError::Io(err) } } impl From for DecodingError { #[cold] fn from(err: io::ErrorKind) -> Self { DecodingError::Io(io::Error::from(err)) } } impl From for DecodingError { #[inline] fn from(err: DecodingFormatError) -> Self { DecodingError::Format(err) } } /// Varies depending on `skip_frame_decoding` #[derive(Debug, Copy, Clone)] pub enum FrameDataType { /// `Frame.buffer` will be regular pixel data Pixels, /// Raw LZW data Lzw { /// Needed for decoding min_code_size: u8, }, } /// Indicates whether a certain object has been decoded #[derive(Debug)] #[non_exhaustive] pub enum Decoded { /// Decoded nothing. Nothing, /// Global palette. GlobalPalette(Box<[u8]>), /// Index of the background color in the global palette. BackgroundColor(u8), /// Loop count is known Repetitions(Repeat), /// Palette and optional `Application` extension have been parsed, /// reached frame data. HeaderEnd, /// The start of a block. /// `BlockStart(Block::Trailer)` is the very last decode event 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. /// /// Call `last_ext()` to get the data SubBlockFinished(AnyExtension), /// 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. /// /// Call `last_ext()` to get the data BlockFinished(AnyExtension), /// Decoded all information of the next frame, except the image data. /// /// The returned frame does **not** contain any owned image data. /// /// Call `current_frame_mut()` to access the frame info. FrameMetadata(FrameDataType), /// Decoded some data of the current frame. Size is in bytes, always > 0 BytesDecoded(NonZeroUsize), /// Copied (or consumed and discarded) compressed data of the current frame. In bytes. LzwDataCopied(usize), /// No more data available the current frame. DataEnd, } /// Internal state of the GIF decoder #[derive(Debug, Copy, Clone)] enum State { Magic(u8, [u8; 6]), U16Byte1(U16Value, u8), U16(U16Value), Byte(ByteValue), GlobalPalette(usize), BlockStart(u8), BlockEnd, ExtensionBlock(AnyExtension), /// Collects data in ext.data ExtensionDataBlock(usize), ApplicationExtension, LocalPalette(usize), LzwInit(u8), /// Decompresses LZW DecodeSubBlock(usize), /// Keeps LZW compressed CopySubBlock(usize), FrameDecoded, Trailer, } use self::State::*; use super::converter::PixelConverter; /// U16 values that may occur in a GIF image #[derive(Debug, Copy, Clone)] 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, Copy, Clone)] enum ByteValue { GlobalFlags, Background { global_flags: u8 }, AspectRatio { global_flags: u8 }, ControlFlags, ImageFlags, TransparentIdx, CodeSize, } /// Decoder for `Frame::make_lzw_pre_encoded` pub struct FrameDecoder { lzw_reader: LzwReader, pixel_converter: PixelConverter, } impl FrameDecoder { /// See also `set_global_palette` #[inline] #[must_use] pub fn new(options: DecodeOptions) -> Self { Self { lzw_reader: LzwReader::new(options.check_for_end_code), pixel_converter: PixelConverter::new(options.color_output, options.memory_limit), } } /// Palette used for RGBA conversion #[inline] pub fn set_global_palette(&mut self, palette: Vec) { self.pixel_converter.set_global_palette(palette); } /// Converts the frame in-place, replacing its LZW buffer with pixels. /// /// If you get an error about invalid min code size, the buffer was probably pixels, not compressed data. #[inline] pub fn decode_lzw_encoded_frame(&mut self, frame: &mut Frame<'_>) -> Result<(), DecodingError> { let pixel_bytes = self.pixel_converter.check_buffer_size(frame)?; let mut vec = vec![0; pixel_bytes]; self.decode_lzw_encoded_frame_into_buffer(frame, &mut vec)?; frame.buffer = Cow::Owned(vec); frame.interlaced = false; Ok(()) } /// Converts into the given buffer. It must be [`buffer_size()`] bytes large. /// /// Pixels are always deinterlaced, so update `frame.interlaced` afterwards if you're putting the buffer back into the frame. pub fn decode_lzw_encoded_frame_into_buffer(&mut self, frame: &Frame<'_>, buf: &mut [u8]) -> Result<(), DecodingError> { let (&min_code_size, mut data) = frame.buffer.split_first().unwrap_or((&2, &[])); self.lzw_reader.reset(min_code_size)?; let lzw_reader = &mut self.lzw_reader; self.pixel_converter.read_into_buffer(frame, buf, &mut move |out| { loop { let (bytes_read, bytes_written) = lzw_reader.decode_bytes(data, out)?; data = &data.get(bytes_read..).unwrap_or_default(); if bytes_written > 0 || bytes_read == 0 || data.is_empty() { return Ok(bytes_written) } } })?; Ok(()) } /// Number of bytes required for `decode_lzw_encoded_frame_into_buffer` #[inline] #[must_use] pub fn buffer_size(&self, frame: &Frame<'_>) -> usize { self.pixel_converter.buffer_size(frame).unwrap() } } struct LzwReader { decoder: Option, min_code_size: u8, check_for_end_code: bool, } impl LzwReader { pub fn new(check_for_end_code: bool) -> Self { Self { decoder: None, min_code_size: 0, check_for_end_code, } } pub fn check_code_size(min_code_size: u8) -> Result<(), DecodingError> { // LZW spec: max 12 bits per code. This check helps catch confusion // between LZW-compressed buffers and raw pixel data if min_code_size > 11 || min_code_size < 1 { return Err(DecodingError::format("invalid minimal code size")); } Ok(()) } pub fn reset(&mut self, min_code_size: u8) -> Result<(), DecodingError> { Self::check_code_size(min_code_size)?; // The decoder can be reused if the code size stayed the same if self.min_code_size != min_code_size || self.decoder.is_none() { self.min_code_size = min_code_size; self.decoder = Some(LzwDecoder::new(BitOrder::Lsb, min_code_size)); } else { self.decoder.as_mut().ok_or_else(|| DecodingError::format("bad state"))?.reset(); } Ok(()) } pub fn has_ended(&self) -> bool { self.decoder.as_ref().map_or(true, |e| e.has_ended()) } pub fn decode_bytes(&mut self, lzw_data: &[u8], decode_buffer: &mut OutputBuffer<'_>) -> io::Result<(usize, usize)> { let decoder = self.decoder.as_mut().ok_or(io::ErrorKind::Unsupported)?; let decode_buffer = match decode_buffer { OutputBuffer::Slice(buf) => &mut **buf, OutputBuffer::None => &mut [], OutputBuffer::Vec(_) => return Err(io::Error::from(io::ErrorKind::Unsupported)), }; let decoded = decoder.decode_bytes(lzw_data, decode_buffer); match decoded.status { Ok(LzwStatus::Done | 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")); } }, Err(err @ LzwError::InvalidCode) => { return Err(io::Error::new(io::ErrorKind::InvalidData, err)); } } Ok((decoded.consumed_in, decoded.consumed_out)) } } /// GIF decoder which emits [low-level events](Decoded) for items in the GIF file /// /// To just get GIF frames, use [`crate::Decoder`] instead. pub struct StreamingDecoder { state: State, lzw_reader: LzwReader, skip_frame_decoding: bool, check_frame_consistency: bool, allow_unknown_blocks: bool, memory_limit: MemoryLimit, version: Version, width: u16, height: u16, global_color_table: Vec, background_color: [u8; 4], /// ext buffer ext: ExtensionData, /// Frame data current: Option>, /// Needs to emit `HeaderEnd` once header_end_reached: bool, } /// One version number of the GIF standard. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Version { /// Version 87a, from May 1987. V87a, /// Version 89a, from July 1989. V89a, } struct ExtensionData { id: AnyExtension, data: Vec, is_block_end: bool, } /// Destination to write to for `StreamingDecoder::update` pub enum OutputBuffer<'a> { /// Overwrite bytes Slice(&'a mut [u8]), /// Append LZW bytes Vec(&'a mut Vec), /// Discard bytes None, } impl<'a> OutputBuffer<'a> { fn append(&mut self, buf: &[u8], memory_limit: &MemoryLimit) -> Result<(usize, usize), DecodingError> { let (consumed, copied) = match self { OutputBuffer::Slice(slice) => { let len = cmp::min(buf.len(), slice.len()); slice[..len].copy_from_slice(&buf[..len]); (len, len) }, OutputBuffer::Vec(vec) => { let vec: &mut Vec = vec; let len = buf.len(); memory_limit.check_size(vec.len() + len)?; vec.try_reserve(len).map_err(|_| io::ErrorKind::OutOfMemory)?; if vec.capacity() - vec.len() >= len { vec.extend_from_slice(buf); } (len, len) }, // It's valid that bytes are discarded. For example, // when using next_frame_info() with skip_frame_decoding to only get metadata. OutputBuffer::None => (buf.len(), 0), }; Ok((consumed, copied)) } } impl StreamingDecoder { /// Creates a new streaming decoder #[must_use] pub fn new() -> StreamingDecoder { let options = DecodeOptions::new(); Self::with_options(&options) } pub(crate) fn with_options(options: &DecodeOptions) -> Self { StreamingDecoder { state: Magic(0, [0; 6]), lzw_reader: LzwReader::new(options.check_for_end_code), skip_frame_decoding: options.skip_frame_decoding, check_frame_consistency: options.check_frame_consistency, allow_unknown_blocks: options.allow_unknown_blocks, memory_limit: options.memory_limit.clone(), 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, header_end_reached: false, } } /// 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], write_into: &mut OutputBuffer<'_>, ) -> Result<(usize, Decoded), DecodingError> { let len = buf.len(); while !buf.is_empty() { let (bytes, decoded) = self.next_state(buf, write_into)?; buf = buf.get(bytes..).unwrap_or_default(); match decoded { Decoded::Nothing => {}, result => { return Ok((len-buf.len(), result)); }, }; } Ok((len - buf.len(), Decoded::Nothing)) } /// Returns the data of the last extension that has been decoded. #[must_use] pub fn last_ext(&self) -> (AnyExtension, &[u8], bool) { (self.ext.id, &self.ext.data, self.ext.is_block_end) } /// Current frame info as a mutable ref. #[must_use] #[track_caller] pub fn current_frame_mut(&mut self) -> &mut Frame<'static> { self.current.as_mut().unwrap() } /// Current frame info as a ref. #[track_caller] #[must_use] pub fn current_frame(&self) -> &Frame<'static> { self.current.as_ref().unwrap() } /// Current frame info as a mutable ref. #[inline(always)] fn try_current_frame(&mut self) -> Result<&mut Frame<'static>, DecodingError> { self.current.as_mut().ok_or_else(|| DecodingError::format("bad state")) } /// Width of the image #[must_use] pub fn width(&self) -> u16 { self.width } /// Height of the image #[must_use] 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. #[must_use] pub fn version(&self) -> Version { self.version } #[inline] fn next_state(&mut self, buf: &[u8], write_into: &mut OutputBuffer<'_>) -> Result<(usize, Decoded), DecodingError> { macro_rules! goto ( ($n:expr, $state:expr) => ({ self.state = $state; Ok(($n, Decoded::Nothing)) }); ($state:expr) => ({ self.state = $state; Ok((1, Decoded::Nothing)) }); ($n:expr, $state:expr, emit $res:expr) => ({ self.state = $state; Ok(($n, $res)) }); ($state:expr, emit $res:expr) => ({ self.state = $state; Ok((1, $res)) }) ); let b = *buf.get(0).ok_or(io::ErrorKind::UnexpectedEof)?; match self.state { Magic(i, mut version) => if i < 6 { version[i as usize] = 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("malformed GIF header")) }; goto!(U16Byte1(U16Value::ScreenWidth, b)) } else { Err(DecodingError::format("malformed GIF header")) }, Byte(value) => { use self::ByteValue::*; match value { GlobalFlags => { goto!(Byte(Background { global_flags: b })) }, Background { global_flags } => { goto!( Byte(AspectRatio { global_flags }), emit Decoded::BackgroundColor(b) ) }, AspectRatio { global_flags } => { let global_table = global_flags & 0x80 != 0; let table_size = if global_table { let table_size = PLTE_CHANNELS * (1 << ((global_flags & 0b111) + 1) as usize); self.global_color_table.try_reserve_exact(table_size).map_err(|_| io::ErrorKind::OutOfMemory)?; table_size } else { 0usize }; goto!(GlobalPalette(table_size)) }, ControlFlags => { self.ext.data.push(b); let frame = self.try_current_frame()?; let control_flags = b; if control_flags & 1 != 0 { // Set to Some(...), gets overwritten later frame.transparent = Some(0); } frame.needs_user_input = control_flags & 0b10 != 0; frame.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.try_current_frame()?.transparent { *idx = b; } goto!(ExtensionDataBlock(0)) } ImageFlags => { let local_table = (b & 0b1000_0000) != 0; let interlaced = (b & 0b0100_0000) != 0; let table_size = b & 0b0000_0111; let check_frame_consistency = self.check_frame_consistency; let (width, height) = (self.width, self.height); let frame = self.try_current_frame()?; frame.interlaced = interlaced; if check_frame_consistency { // Consistency checks. 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)); let mut pal = Vec::new(); pal.try_reserve_exact(entries).map_err(|_| io::ErrorKind::OutOfMemory)?; frame.palette = Some(pal); 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_exact(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(b), emit Decoded::GlobalPalette( mem::take(&mut self.global_color_table).into_boxed_slice() )) } } BlockStart(type_) => { if !self.header_end_reached && type_ != Block::Extension as u8 { self.header_end_reached = true; return goto!(0, BlockStart(type_), emit Decoded::HeaderEnd); } match Block::from_u8(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) => { // The `Trailer` is the final state, and isn't reachable without extraneous data after the end of file goto!(Trailer, emit Decoded::BlockStart(Block::Trailer)) } None => { if self.allow_unknown_blocks { goto!(ExtensionDataBlock(b as usize)) } else { Err(DecodingError::format("unknown block type encountered")) } } } }, BlockEnd => { if b == Block::Trailer as u8 { // can't consume yet, because the trailer is not a real block, // and won't have futher data for BlockStart goto!(0, BlockStart(b)) } else { goto!(BlockStart(b)) } } 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!(ExtensionDataBlock(b as usize)) } } } else { Err(DecodingError::format("unknown block type encountered")) } } ExtensionDataBlock(left) => { if left > 0 { let n = cmp::min(left, buf.len()); self.memory_limit.check_size(self.ext.data.len() + n)?; self.ext.data.try_reserve(n).map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?; self.ext.data.extend_from_slice(&buf[..n]); goto!(n, ExtensionDataBlock(left - n)) } else if b == 0 { self.ext.is_block_end = true; if self.ext.id.into_known() == Some(Extension::Application) { goto!(0, ApplicationExtension, emit Decoded::BlockFinished(self.ext.id)) } else { goto!(BlockEnd, emit Decoded::BlockFinished(self.ext.id)) } } else { self.ext.is_block_end = false; goto!(ExtensionDataBlock(b as usize), emit Decoded::SubBlockFinished(self.ext.id)) } } ApplicationExtension => { debug_assert_eq!(0, b); // the parser removes sub-block lenghts, so app name and data are concatenated if self.ext.data.len() >= 15 && &self.ext.data[1..13] == b"NETSCAPE2.0\x01" { let repeat = &self.ext.data[13..15]; let repeat = u16::from(repeat[0]) | u16::from(repeat[1]) << 8; goto!(BlockEnd, emit Decoded::Repetitions(if repeat == 0 { Repeat::Infinite } else { Repeat::Finite(repeat) })) } else { goto!(BlockEnd) } } LocalPalette(left) => { let n = cmp::min(left, buf.len()); if left > 0 { let src = &buf[..n]; if let Some(pal) = self.try_current_frame()?.palette.as_mut() { // capacity has already been reserved in ImageFlags if pal.capacity() - pal.len() >= src.len() { pal.extend_from_slice(src); } } goto!(n, LocalPalette(left - n)) } else { goto!(LzwInit(b)) } } LzwInit(min_code_size) => { if !self.skip_frame_decoding { // Reset validates the min code size self.lzw_reader.reset(min_code_size)?; goto!(DecodeSubBlock(b as usize), emit Decoded::FrameMetadata(FrameDataType::Pixels)) } else { LzwReader::check_code_size(min_code_size)?; goto!(CopySubBlock(b as usize), emit Decoded::FrameMetadata(FrameDataType::Lzw { min_code_size })) } } CopySubBlock(left) => { debug_assert!(self.skip_frame_decoding); if left > 0 { let n = cmp::min(left, buf.len()); let (consumed, copied) = write_into.append(&buf[..n], &self.memory_limit)?; goto!(consumed, CopySubBlock(left - consumed), emit Decoded::LzwDataCopied(copied)) } else if b != 0 { goto!(CopySubBlock(b as usize)) } else { goto!(0, FrameDecoded) } } DecodeSubBlock(left) => { debug_assert!(!self.skip_frame_decoding); if left > 0 { let n = cmp::min(left, buf.len()); if self.lzw_reader.has_ended() || matches!(write_into, OutputBuffer::None) { return goto!(n, DecodeSubBlock(left - n), emit Decoded::Nothing); } let (mut consumed, bytes_len) = self.lzw_reader.decode_bytes(&buf[..n], write_into)?; // skip if can't make progress (decode would fail if check_for_end_code was set) if consumed == 0 && bytes_len == 0 { consumed = n; } let decoded = if let Some(bytes_len) = NonZeroUsize::new(bytes_len) { Decoded::BytesDecoded(bytes_len) } else { Decoded::Nothing }; goto!(consumed, DecodeSubBlock(left - consumed), emit decoded) } else if b != 0 { // decode next sub-block goto!(DecodeSubBlock(b as usize)) } else { let (_, bytes_len) = self.lzw_reader.decode_bytes(&[], write_into)?; if let Some(bytes_len) = NonZeroUsize::new(bytes_len) { goto!(0, DecodeSubBlock(0), emit Decoded::BytesDecoded(bytes_len)) } else { goto!(0, FrameDecoded) } } } U16(next) => goto!(U16Byte1(next, b)), U16Byte1(next, value) => { goto!(self.read_second_byte(next, value, b)?) } FrameDecoded => { // end of image data reached self.current = None; debug_assert_eq!(0, b); goto!(BlockEnd, emit Decoded::DataEnd) } Trailer => goto!(0, Trailer, emit Decoded::Nothing), } } fn read_second_byte(&mut self, next: U16Value, value: u8, b: u8) -> Result { use self::U16Value::*; let value = (u16::from(b) << 8) | u16::from(value); Ok(match (next, value) { (ScreenWidth, width) => { self.width = width; U16(U16Value::ScreenHeight) }, (ScreenHeight, height) => { self.height = height; Byte(ByteValue::GlobalFlags) }, (Delay, delay) => { self.try_current_frame()?.delay = delay; self.ext.data.push(value as u8); self.ext.data.push(b); Byte(ByteValue::TransparentIdx) }, (ImageLeft, left) => { self.try_current_frame()?.left = left; U16(U16Value::ImageTop) }, (ImageTop, top) => { self.try_current_frame()?.top = top; U16(U16Value::ImageWidth) }, (ImageWidth, width) => { self.try_current_frame()?.width = width; U16(U16Value::ImageHeight) }, (ImageHeight, height) => { self.try_current_frame()?.height = height; Byte(ByteValue::ImageFlags) } }) } fn read_control_extension(&mut self, b: u8) -> Result { 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 = DecodingError::format("testing").into(); } gif-0.13.1/src/reader/mod.rs000064400000000000000000000434351046102023000136420ustar 00000000000000use std::borrow::Cow; use std::io; use std::iter::FusedIterator; use std::mem; use std::io::prelude::*; use std::num::NonZeroU64; use std::convert::{TryFrom, TryInto}; use crate::Repeat; use crate::common::{Block, Frame}; mod decoder; mod converter; pub use self::decoder::{ PLTE_CHANNELS, StreamingDecoder, Decoded, DecodingError, DecodingFormatError, Version, FrameDataType, OutputBuffer, FrameDecoder }; use self::converter::PixelConverter; pub use self::converter::ColorOutput; #[derive(Clone, Debug)] /// The maximum amount of memory the decoder is allowed to use for each frame pub enum 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 size of each frame cannot /// exceed 16GiB, but this is still a significant amount of memory. Unlimited, /// Limit the amount of memory that can be used for a single frame to this many bytes. /// /// It may not be enforced precisely due to allocator overhead /// and the decoder potentially allocating small auxiliary buffers, /// but it will precisely limit the size of the output buffer for each frame. // // The `NonZero` type is used to make FFI simpler. // Due to the guaranteed niche optimization, `Unlimited` will be represented as `0`, // and the whole enum as a simple `u64`. Bytes(NonZeroU64), } impl MemoryLimit { fn check_size(&self, size: usize) -> Result<(), DecodingError> { match self { MemoryLimit::Unlimited => Ok(()), MemoryLimit::Bytes(limit) => { if size as u64 <= limit.get() { Ok(()) } else { Err(DecodingError::format("memory limit reached")) } }, } } fn buffer_size(&self, color: ColorOutput, width: u16, height: u16) -> Option { let pixels = u64::from(width) * u64::from(height); let bytes_per_pixel = match color { ColorOutput::Indexed => 1, ColorOutput::RGBA => 4, }; // This cannot overflow because the maximum possible value is 16GiB, well within u64 range let total_bytes = pixels * bytes_per_pixel; // On 32-bit platforms the size of the output buffer may not be representable let usize_bytes = usize::try_from(total_bytes).ok()?; match self { MemoryLimit::Unlimited => Some(usize_bytes), MemoryLimit::Bytes(limit) => { if total_bytes > limit.get() { None } else { Some(usize_bytes) } }, } } } /// Options for opening a GIF decoder. [`DecodeOptions::read_info`] will start the decoder. #[derive(Clone, Debug)] pub struct DecodeOptions { memory_limit: MemoryLimit, color_output: ColorOutput, check_frame_consistency: bool, skip_frame_decoding: bool, check_for_end_code: bool, allow_unknown_blocks: bool, } impl Default for DecodeOptions { fn default() -> Self { Self::new() } } impl DecodeOptions { /// Creates a new decoder builder #[must_use] #[inline] pub fn new() -> DecodeOptions { DecodeOptions { memory_limit: MemoryLimit::Bytes(50_000_000.try_into().unwrap()), // 50 MB color_output: ColorOutput::Indexed, check_frame_consistency: false, skip_frame_decoding: false, check_for_end_code: false, allow_unknown_blocks: false, } } /// Configure how color data is decoded. #[inline] 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 whether to skip decoding frames. /// /// The default is false. /// /// When turned on, LZW decoding is skipped. [`Decoder::read_next_frame`] will return /// compressed LZW bytes in frame's data. /// [`Decoder::next_frame_info`] will return the metadata of the next frame as usual. /// This is useful to count frames without incurring the overhead of decoding. pub fn skip_frame_decoding(&mut self, skip: bool) { self.skip_frame_decoding = skip; } /// 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(self, r: R) -> Result, DecodingError> { Decoder::with_no_init(r, StreamingDecoder::with_options(&self), self).init() } } struct ReadDecoder { reader: io::BufReader, decoder: StreamingDecoder, at_eof: bool, } impl ReadDecoder { #[inline(never)] fn decode_next(&mut self, write_into: &mut OutputBuffer<'_>) -> Result, DecodingError> { while !self.at_eof { let (consumed, result) = { let buf = self.reader.fill_buf()?; if buf.is_empty() { return Err(io::ErrorKind::UnexpectedEof.into()); } self.decoder.update(buf, write_into)? }; self.reader.consume(consumed); match result { Decoded::Nothing => (), Decoded::BlockStart(Block::Trailer) => { self.at_eof = true; }, result => return Ok(Some(result)) } } Ok(None) } fn into_inner(self) -> io::BufReader { self.reader } fn decode_next_bytes(&mut self, out: &mut OutputBuffer<'_>) -> Result { match self.decode_next(out)? { Some(Decoded::BytesDecoded(len)) => Ok(len.get()), Some(Decoded::DataEnd) => Ok(0), _ => Err(DecodingError::format("unexpected data")), } } } #[allow(dead_code)] /// GIF decoder. Create [`DecodeOptions`] to get started, and call [`DecodeOptions::read_info`]. pub struct Decoder { decoder: ReadDecoder, pixel_converter: PixelConverter, bg_color: Option, repeat: Repeat, current_frame: Frame<'static>, current_frame_data_type: FrameDataType, } impl Decoder where R: Read { /// Create a new decoder with default options. #[inline] pub fn new(reader: R) -> Result { DecodeOptions::new().read_info(reader) } /// Return a builder that allows configuring limits etc. #[must_use] #[inline] pub fn build() -> DecodeOptions { DecodeOptions::new() } fn with_no_init(reader: R, decoder: StreamingDecoder, options: DecodeOptions) -> Decoder { Decoder { decoder: ReadDecoder { reader: io::BufReader::new(reader), decoder, at_eof: false, }, bg_color: None, pixel_converter: PixelConverter::new(options.color_output, options.memory_limit), repeat: Repeat::default(), current_frame: Frame::default(), current_frame_data_type: FrameDataType::Pixels, } } fn init(mut self) -> Result { loop { match self.decoder.decode_next(&mut OutputBuffer::None)? { Some(Decoded::BackgroundColor(bg_color)) => { self.bg_color = Some(bg_color); } Some(Decoded::GlobalPalette(palette)) => { self.pixel_converter.set_global_palette(palette.into()); }, Some(Decoded::Repetitions(repeat)) => { self.repeat = repeat; }, Some(Decoded::HeaderEnd) => { break }, Some(_) => { // There will be extra events when parsing application extension continue }, None => return Err(DecodingError::format( "file does not contain any image data" )) } } // If the background color is invalid, ignore it if let Some(palette) = self.pixel_converter.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>, DecodingError> { loop { match self.decoder.decode_next(&mut OutputBuffer::None)? { Some(Decoded::FrameMetadata(frame_data_type)) => { self.current_frame = self.decoder.decoder.current_frame_mut().take(); self.current_frame_data_type = frame_data_type; if self.current_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. /// /// You can also call `.into_iter()` on the decoder to use it as a regular iterator. pub fn read_next_frame(&mut self) -> Result>, DecodingError> { if let Some(_) = self.next_frame_info()? { match self.current_frame_data_type { FrameDataType::Pixels => { self.pixel_converter.read_frame(&mut self.current_frame, &mut |out| self.decoder.decode_next_bytes(out))?; }, FrameDataType::Lzw { min_code_size } => { let mut vec = if matches!(self.current_frame.buffer, Cow::Owned(_)) { let mut vec = mem::replace(&mut self.current_frame.buffer, Cow::Borrowed(&[])).into_owned(); vec.clear(); vec } else { Vec::new() }; // Guesstimate 2bpp vec.try_reserve(usize::from(self.current_frame.width) * usize::from(self.current_frame.height) / 4) .map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?; self.copy_lzw_into_buffer(min_code_size, &mut vec)?; self.current_frame.buffer = Cow::Owned(vec); }, } Ok(Some(&self.current_frame)) } else { Ok(None) } } /// This is private for iterator's use fn take_current_frame(&mut self) -> Option> { if self.current_frame.buffer.is_empty() { return None; } Some(self.current_frame.take()) } /// 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> { self.pixel_converter.read_into_buffer(&mut self.current_frame, buf, &mut |out| self.decoder.decode_next_bytes(out)) } fn copy_lzw_into_buffer(&mut self, min_code_size: u8, buf: &mut Vec) -> Result<(), DecodingError> { // `write_lzw_pre_encoded_frame` smuggles `min_code_size` in the first byte. buf.push(min_code_size); loop { match self.decoder.decode_next(&mut OutputBuffer::Vec(buf))? { Some(Decoded::LzwDataCopied(_len)) => {}, Some(Decoded::DataEnd) => return Ok(()), _ => return Err(DecodingError::format("unexpected data")), } } } /// Reads data of the current frame into a pre-allocated buffer until the buffer has been /// filled completely. /// /// The buffer length must be an even number of pixels (multiple of 4 if decoding RGBA). /// /// `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, buf: &mut [u8]) -> Result { self.pixel_converter.fill_buffer(&mut self.current_frame, buf, &mut |out| self.decoder.decode_next_bytes(out)) } /// Output buffer size pub fn buffer_size(&self) -> usize { self.pixel_converter.buffer_size(&self.current_frame).unwrap() } /// Line length of the current frame pub fn line_length(&self) -> usize { self.pixel_converter.line_length(&self.current_frame) } /// Returns the color palette relevant for the frame that has been decoded #[inline] pub fn palette(&self) -> Result<&[u8], DecodingError> { Ok(match self.current_frame.palette { Some(ref table) => table, None => self.global_palette().ok_or(DecodingError::format( "no color table available for current frame", ))?, }) } /// The global color palette pub fn global_palette(&self) -> Option<&[u8]> { self.pixel_converter.global_palette() } /// Width of the image #[inline] pub fn width(&self) -> u16 { self.decoder.decoder.width() } /// Height of the image #[inline] pub fn height(&self) -> u16 { self.decoder.decoder.height() } /// Abort decoding and recover the `io::Read` instance pub fn into_inner(self) -> io::BufReader { self.decoder.into_inner() } /// Index of the background color in the global palette /// /// In practice this is not used, and the background is /// always transparent pub fn bg_color(&self) -> Option { self.bg_color.map(|v| v as usize) } /// Number of loop repetitions #[inline] pub fn repeat(&self) -> Repeat { self.repeat } } impl IntoIterator for Decoder { type Item = Result, DecodingError>; type IntoIter = DecoderIter; #[inline] fn into_iter(self) -> Self::IntoIter { DecoderIter { inner: self, ended: false, } } } /// Use `decoder.into_iter()` to iterate over the frames pub struct DecoderIter { inner: Decoder, ended: bool, } impl DecoderIter { /// Abort decoding and recover the `io::Read` instance /// /// Use `for frame in iter.by_ref()` to be able to call this afterwards. pub fn into_inner(self) -> io::BufReader { self.inner.into_inner() } } impl FusedIterator for DecoderIter {} impl Iterator for DecoderIter { type Item = Result, DecodingError>; fn next(&mut self) -> Option { if !self.ended { match self.inner.read_next_frame() { Ok(Some(_)) => self.inner.take_current_frame().map(Ok), Ok(None) => { self.ended = true; None }, Err(err) => { self.ended = true; Some(Err(err)) }, } } else { None } } } gif-0.13.1/src/traits.rs000064400000000000000000000021761046102023000131240ustar 00000000000000//! Traits used in this library use std::io; /// Writer extension to write little endian data pub trait WriteBytesExt { /// 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 WriteBytesExt for W { #[inline(always)] fn write_le(&mut self, n: u8) -> io::Result<()> { self.write_all(&[n]) } } impl WriteBytesExt for W { #[inline] fn write_le(&mut self, n: u16) -> io::Result<()> { self.write_all(&[n as u8, (n >> 8) as u8]) } } impl WriteBytesExt 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 WriteBytesExt 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) } }