tikz-gallery-generator

Custum build of stapix for tikz.pablopie.xyz

Commit
0bf8476078c1f52bce5bac346906c479085b6855
Parent
aa47386e22095f4c286014be29648d7893a0a707
Author
Pablo <pablo-pie@riseup.net>
Date

Reimplemented the functionality of the image crate

Diffstats

5 files changed, 431 insertions, 984 deletions

Status Name Changes Insertions Deletions
Modified .gitignore 2 files changed 2 0
Modified Cargo.lock 2 files changed 20 937
Modified Cargo.toml 2 files changed 4 2
Added src/image.rs 1 file changed 373 0
Modified src/main.rs 2 files changed 32 45
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,4 @@
 /target
 /examples/site
+.project.gf
+tags
diff --git a/Cargo.lock b/Cargo.lock
@@ -3,162 +3,6 @@
 version = 4
 
 [[package]]
-name = "adler2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-
-[[package]]
-name = "aligned"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
-dependencies = [
- "as-slice",
-]
-
-[[package]]
-name = "aligned-vec"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
-dependencies = [
- "equator",
-]
-
-[[package]]
-name = "anyhow"
-version = "1.0.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
-
-[[package]]
-name = "arbitrary"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
-
-[[package]]
-name = "arg_enum_proc_macro"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "arrayvec"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
-
-[[package]]
-name = "as-slice"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
-dependencies = [
- "stable_deref_trait",
-]
-
-[[package]]
-name = "autocfg"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
-
-[[package]]
-name = "av-scenechange"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
-dependencies = [
- "aligned",
- "anyhow",
- "arg_enum_proc_macro",
- "arrayvec",
- "log",
- "num-rational",
- "num-traits",
- "pastey",
- "rayon",
- "thiserror",
- "v_frame",
- "y4m",
-]
-
-[[package]]
-name = "av1-grain"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
-dependencies = [
- "anyhow",
- "arrayvec",
- "log",
- "nom",
- "num-rational",
- "v_frame",
-]
-
-[[package]]
-name = "avif-serialize"
-version = "0.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f"
-dependencies = [
- "arrayvec",
-]
-
-[[package]]
-name = "bit_field"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
-
-[[package]]
-name = "bitflags"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
-
-[[package]]
-name = "bitstream-io"
-version = "4.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757"
-dependencies = [
- "core2",
-]
-
-[[package]]
-name = "built"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
-
-[[package]]
-name = "bumpalo"
-version = "3.19.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
-
-[[package]]
-name = "bytemuck"
-version = "1.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
-
-[[package]]
-name = "byteorder-lite"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
-
-[[package]]
 name = "cc"
 version = "1.0.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -169,200 +13,18 @@ dependencies = [
 ]
 
 [[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "color_quant"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
-
-[[package]]
-name = "core2"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "crc32fast"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "crossbeam-deque"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
-dependencies = [
- "cfg-if",
- "crossbeam-epoch",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-epoch"
-version = "0.9.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
-dependencies = [
- "autocfg",
- "cfg-if",
- "crossbeam-utils",
- "memoffset",
- "scopeguard",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
-[[package]]
-name = "either"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
-
-[[package]]
-name = "equator"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
-dependencies = [
- "equator-macro",
-]
-
-[[package]]
-name = "equator-macro"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
 name = "equivalent"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
-name = "exr"
-version = "1.74.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
-dependencies = [
- "bit_field",
- "half",
- "lebe",
- "miniz_oxide",
- "rayon-core",
- "smallvec",
- "zune-inflate",
-]
-
-[[package]]
-name = "fax"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
-dependencies = [
- "fax_derive",
-]
-
-[[package]]
-name = "fax_derive"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "fdeflate"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
-dependencies = [
- "simd-adler32",
-]
-
-[[package]]
-name = "flate2"
-version = "1.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
-dependencies = [
- "crc32fast",
- "miniz_oxide",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi",
- "wasip2",
-]
-
-[[package]]
-name = "gif"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e"
-dependencies = [
- "color_quant",
- "weezl",
-]
-
-[[package]]
 name = "glob"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
-name = "half"
-version = "2.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
-dependencies = [
- "cfg-if",
- "crunchy",
- "zerocopy",
-]
-
-[[package]]
 name = "hashbrown"
 version = "0.14.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -375,46 +37,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
 
 [[package]]
-name = "image"
-version = "0.25.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
-dependencies = [
- "bytemuck",
- "byteorder-lite",
- "color_quant",
- "exr",
- "gif",
- "image-webp",
- "moxcms",
- "num-traits",
- "png",
- "qoi",
- "ravif",
- "rayon",
- "rgb",
- "tiff",
- "zune-core 0.5.0",
- "zune-jpeg 0.5.8",
-]
-
-[[package]]
-name = "image-webp"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
-dependencies = [
- "byteorder-lite",
- "quick-error",
-]
-
-[[package]]
-name = "imgref"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
-
-[[package]]
 name = "indexmap"
 version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -425,26 +47,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "interpolate_name"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "itertools"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
-dependencies = [
- "either",
-]
-
-[[package]]
 name = "itoa"
 version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -460,168 +62,20 @@ dependencies = [
 ]
 
 [[package]]
-name = "lebe"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
-
-[[package]]
 name = "libc"
 version = "0.2.180"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
 
 [[package]]
-name = "libfuzzer-sys"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
-dependencies = [
- "arbitrary",
- "cc",
-]
-
-[[package]]
 name = "libwebp-sys"
-version = "0.9.4"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0df0a0f9444d52aee6335cd724d21a2ee3285f646291799a72be518ec8ee3c"
+checksum = "39f019895d5064a40a6d1e8859d8093c3dfdaee8d4a15fa08c80405a856658f0"
 dependencies = [
  "cc",
  "glob",
-]
-
-[[package]]
-name = "log"
-version = "0.4.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
-
-[[package]]
-name = "loop9"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
-dependencies = [
- "imgref",
-]
-
-[[package]]
-name = "maybe-rayon"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
-dependencies = [
- "cfg-if",
- "rayon",
-]
-
-[[package]]
-name = "memchr"
-version = "2.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
-
-[[package]]
-name = "memoffset"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "miniz_oxide"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
-dependencies = [
- "adler2",
- "simd-adler32",
-]
-
-[[package]]
-name = "moxcms"
-version = "0.7.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
-dependencies = [
- "num-traits",
- "pxfm",
-]
-
-[[package]]
-name = "new_debug_unreachable"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
-
-[[package]]
-name = "nom"
-version = "8.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "noop_proc_macro"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
-
-[[package]]
-name = "num-bigint"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-derive"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "num-integer"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
-[[package]]
-name = "num-rational"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
-dependencies = [
- "num-bigint",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
+ "pkg-config",
 ]
 
 [[package]]
@@ -635,44 +89,10 @@ dependencies = [
 ]
 
 [[package]]
-name = "once_cell"
-version = "1.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
-
-[[package]]
-name = "paste"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
-
-[[package]]
-name = "pastey"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
-
-[[package]]
-name = "png"
-version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
-dependencies = [
- "bitflags",
- "crc32fast",
- "fdeflate",
- "flate2",
- "miniz_oxide",
-]
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.21"
+name = "pkg-config"
+version = "0.3.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
-dependencies = [
- "zerocopy",
-]
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
 
 [[package]]
 name = "proc-macro2"
@@ -684,49 +104,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "profiling"
-version = "1.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
-dependencies = [
- "profiling-procmacros",
-]
-
-[[package]]
-name = "profiling-procmacros"
-version = "1.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
-dependencies = [
- "quote",
- "syn",
-]
-
-[[package]]
-name = "pxfm"
-version = "0.1.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
-dependencies = [
- "num-traits",
-]
-
-[[package]]
-name = "qoi"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
-dependencies = [
- "bytemuck",
-]
-
-[[package]]
-name = "quick-error"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
-
-[[package]]
 name = "quote"
 version = "1.0.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -736,135 +113,12 @@ dependencies = [
 ]
 
 [[package]]
-name = "r-efi"
-version = "5.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-
-[[package]]
-name = "rand"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
-dependencies = [
- "rand_chacha",
- "rand_core",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
-dependencies = [
- "ppv-lite86",
- "rand_core",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
-dependencies = [
- "getrandom",
-]
-
-[[package]]
-name = "rav1e"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
-dependencies = [
- "aligned-vec",
- "arbitrary",
- "arg_enum_proc_macro",
- "arrayvec",
- "av-scenechange",
- "av1-grain",
- "bitstream-io",
- "built",
- "cfg-if",
- "interpolate_name",
- "itertools",
- "libc",
- "libfuzzer-sys",
- "log",
- "maybe-rayon",
- "new_debug_unreachable",
- "noop_proc_macro",
- "num-derive",
- "num-traits",
- "paste",
- "profiling",
- "rand",
- "rand_chacha",
- "simd_helpers",
- "thiserror",
- "v_frame",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "ravif"
-version = "0.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
-dependencies = [
- "avif-serialize",
- "imgref",
- "loop9",
- "quick-error",
- "rav1e",
- "rayon",
- "rgb",
-]
-
-[[package]]
-name = "rayon"
-version = "1.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
-dependencies = [
- "either",
- "rayon-core",
-]
-
-[[package]]
-name = "rayon-core"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
-dependencies = [
- "crossbeam-deque",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "rgb"
-version = "0.8.52"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
-
-[[package]]
-name = "rustversion"
-version = "1.0.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-
-[[package]]
 name = "ryu"
 version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
-[[package]]
 name = "serde"
 version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -904,27 +158,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
 
 [[package]]
-name = "simd_helpers"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
-dependencies = [
- "quote",
-]
-
-[[package]]
-name = "smallvec"
-version = "1.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
-
-[[package]]
-name = "stable_deref_trait"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
-
-[[package]]
 name = "syn"
 version = "2.0.114"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -936,26 +169,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "thiserror"
-version = "2.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "2.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
 name = "threadpool"
 version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -965,29 +178,17 @@ dependencies = [
 ]
 
 [[package]]
-name = "tiff"
-version = "0.10.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
-dependencies = [
- "fax",
- "flate2",
- "half",
- "quick-error",
- "weezl",
- "zune-jpeg 0.4.21",
-]
-
-[[package]]
 name = "tikz_gallery_generator"
 version = "1.0.0"
 dependencies = [
- "image",
+ "libwebp-sys",
  "num_cpus",
  "serde",
  "serde_yaml",
  "threadpool",
- "webp",
+ "zune-core",
+ "zune-jpeg",
+ "zune-png",
 ]
 
 [[package]]
@@ -1003,129 +204,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
 
 [[package]]
-name = "v_frame"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
-dependencies = [
- "aligned-vec",
- "num-traits",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "wasip2"
-version = "1.0.1+wasi-0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
-dependencies = [
- "wit-bindgen",
-]
-
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
-dependencies = [
- "cfg-if",
- "once_cell",
- "rustversion",
- "wasm-bindgen-macro",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
-dependencies = [
- "bumpalo",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "webp"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c071456adef4aca59bf6a583c46b90ff5eb0b4f758fc347cea81290288f37ce1"
-dependencies = [
- "image",
- "libwebp-sys",
-]
-
-[[package]]
-name = "weezl"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
-
-[[package]]
-name = "wit-bindgen"
-version = "0.46.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
-
-[[package]]
-name = "y4m"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
-
-[[package]]
-name = "zerocopy"
-version = "0.8.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "zune-core"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
-
-[[package]]
 name = "zune-core"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773"
+checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
 
 [[package]]
 name = "zune-inflate"
@@ -1138,18 +220,19 @@ dependencies = [
 
 [[package]]
 name = "zune-jpeg"
-version = "0.4.21"
+version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
+checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5"
 dependencies = [
- "zune-core 0.4.12",
+ "zune-core",
 ]
 
 [[package]]
-name = "zune-jpeg"
-version = "0.5.8"
+name = "zune-png"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5"
+checksum = "af6dc406edc296451b9597ee98643df8606396066d8f0864682c266eda16f310"
 dependencies = [
- "zune-core 0.5.0",
+ "zune-core",
+ "zune-inflate",
 ]
diff --git a/Cargo.toml b/Cargo.toml
@@ -9,8 +9,10 @@ license = "GPLv3"
 [dependencies]
 serde = { version = "1.0", features = ["derive"] }
 serde_yaml = "0.9"
-webp = "0.3"
-image = "0.25"
+libwebp-sys = "0.14"
+zune-core = "0.5"
+zune-jpeg = "0.5"
+zune-png = "0.5"
 threadpool = "1.8.1"
 num_cpus = "1.16.0"
 
diff --git /dev/null b/src/image.rs
@@ -0,0 +1,373 @@
+//! Efficient-ish routines for parsing JPEG/PNG and encoding WebP
+//!
+//! We avoid using the `image` crate because it is bloated, yet we do use some
+//! libraries:
+//!
+//! * `libwebp-sys` (we don't use `webp` because it requires `image`
+//! * `zune-jpeg`   (used internaly by `image`)
+//! * `zune-png`
+//!
+//! The `zune-` crates implement SIMD optimizations.
+//!
+//! Working with these crates directly also allows us the oportunity to
+//! streamline pixel format conversions. For example, when parsing JPEG we now
+//! feed the YUV pixel data directly to `libwebp` instead of first converting
+//! it to RGB and then converting it back to YUV (which is what `image` would
+//! have done).
+
+use std::{
+  slice,
+  ptr,
+  io::{self, Write, Read, BufReader},
+  path::Path,
+  fs::File,
+};
+use zune_core::{
+  colorspace::ColorSpace,
+  options::DecoderOptions,
+  bytestream::ZCursor,
+  result::DecodingResult,
+};
+use zune_jpeg::JpegDecoder;
+use zune_png::PngDecoder;
+use libwebp_sys::WebPEncodingError;
+
+use crate::create_file;
+
+#[derive(Clone, Debug)]
+pub struct Image {
+  pixels: PixelBuffer,
+  pub width:  u32,
+  pub height: u32,
+}
+
+#[derive(Clone, Debug)]
+enum PixelBuffer {
+  Yuv { y: Vec<u8>, u: Vec<u8>, v: Vec<u8>, uv_stride: u32, },
+  Grayscale { level: Vec<u8>, alpha: Option<Vec<u8>>, },
+  Rgb(Vec<u8>),
+  Rgba(Vec<u8>),
+}
+
+pub fn parse_jpeg(path: &Path) -> Result<Image, ()> {
+  let options = DecoderOptions::default()
+    .jpeg_set_out_colorspace(ColorSpace::YCbCr);
+
+  let f = BufReader::new(open_file(path).map_err(|_| ())?);
+  let mut decoder = JpegDecoder::new_with_options(f, options);
+
+  let pixels = match decoder.decode() {
+    Ok(pixels) => pixels,
+    Err(e)     => {
+      errorln!("Couldn't parse {path:?}: {e}");
+      return Err(());
+    }
+  };
+
+  let info = decoder.info().expect("should have already parsed JPEG header");
+
+  let options = decoder.options();
+
+  let width  = info.width  as u32;
+  let height = info.height as u32;
+  let uv_width  = width.div_ceil(2);
+  let uv_height = height.div_ceil(2);
+
+  assert_expected_pixels_len(
+    width as usize,
+    height as usize,
+    3,
+    pixels.len()
+  );
+  let y_len:  usize = (width * height) as _;
+  let uv_len: usize = (uv_width * uv_height) as _;
+
+  let colorspace = options.jpeg_get_out_colorspace();
+  debug_assert!(colorspace == ColorSpace::YCbCr,
+                "unexpected colorspace when parsing JPEG: {colorspace:?}");
+
+  let mut y = vec![0; y_len];
+  let mut u = vec![0; uv_len];
+  let mut v = vec![0; uv_len];
+
+  // TODO: [optmize]: can we optimize these loops?
+  for i in 0..y_len { y[i] = pixels[i*3]; }
+  for uv_x in 0..uv_width {
+    for uv_y in 0..uv_height {
+      let x = uv_x * 2;
+      let y = uv_y * 2;
+
+      let i: usize = (y * width + x) as _;
+      let j: usize = (uv_y * uv_width + uv_x) as _;
+
+      // NOTE: using an approximation of nearest neighbor for efficient
+      let u_val = pixels[i*3+1];
+      let v_val = pixels[i*3+2];
+      u[j] = u_val;
+      v[j] = v_val;
+    }
+  }
+
+  Ok(Image {
+    pixels: PixelBuffer::Yuv { y, u, v, uv_stride: uv_width, },
+    width,
+    height,
+  })
+}
+
+pub fn parse_png(path: &Path) -> Result<Image, ()> {
+  let options = DecoderOptions::default()
+    .png_set_strip_to_8bit(true);
+
+  let mut f = open_file(path).map_err(|_| ())?;
+  let size: usize = f.metadata()
+    .expect("OS should support file metadata")
+    .len() as _;
+  let mut f_contents = Vec::with_capacity(size);
+  f.read_to_end(&mut f_contents)
+    .map_err(|e| errorln!("Could not read {path:?}: {e}"))?;
+  let f_contents = ZCursor::new(f_contents);
+
+  let mut decoder = PngDecoder::new_with_options(f_contents, options);
+
+  let pixels = match decoder.decode() {
+    Ok(DecodingResult::U8(pixels)) => pixels,
+    Ok(DecodingResult::U16(_)) => {
+      unreachable!("PNG strip was set to 8 bits")
+    }
+    Ok(_) => {
+      unreachable!("PngDecoder::decode should always return DecodingResult::U8 or DecodingResult::U16")
+    }
+    Err(e) => {
+      errorln!("Couldn't parse {path:?}: {e}");
+      return Err(());
+    }
+  };
+
+  let (width, height) = decoder
+    .dimensions()
+    .expect("should already have decoded PNG headers");
+  let colorspace = decoder
+    .colorspace()
+    .expect("should already have decoded PNG headers");
+
+  match colorspace {
+    ColorSpace::RGB => {
+      assert_expected_pixels_len(width, height, 3, pixels.len());
+
+      Ok(Image {
+        pixels: PixelBuffer::Rgb(pixels),
+        width:  width  as u32,
+        height: height as u32,
+      })
+    }
+    ColorSpace::RGBA => {
+      assert_expected_pixels_len(width, height, 4, pixels.len());
+
+      Ok(Image {
+        pixels: PixelBuffer::Rgba(pixels),
+        width:  width  as u32,
+        height: height as u32,
+      })
+    }
+    ColorSpace::Luma => {
+      assert_expected_pixels_len(width, height, 1, pixels.len());
+
+      Ok(Image {
+        pixels: PixelBuffer::Grayscale { level: pixels, alpha: None, },
+        width:  width  as u32,
+        height: height as u32,
+      })
+    }
+    ColorSpace::LumaA => {
+      assert_expected_pixels_len(width, height, 2, pixels.len());
+
+      let len = pixels.len()/2;
+      let mut level = vec![0; len];
+      let mut alpha = vec![0; len];
+
+      #[allow(clippy::identity_op)]
+      for i in 0..len {
+        level[i] = pixels[i*2+0];
+        alpha[i] = pixels[i*2+1];
+      }
+
+      Ok(Image {
+        pixels: PixelBuffer::Grayscale { level, alpha: Some(alpha), },
+        width:  width  as u32,
+        height: height as u32,
+      })
+    }
+    colorspace => {
+      unreachable!("unexpected colorspace when parsing PNG: {colorspace:?}")
+    }
+  }
+}
+
+pub fn encode_webp(img: &Image, output_path: &Path) -> Result<(), ()> {
+  use std::mem::MaybeUninit;
+  use libwebp_sys::*;
+  const WEBP_IMAGE_QUALITY: f32 = 90.0;
+
+  unsafe {
+    // ======================================================================
+    let mut config = MaybeUninit::uninit();
+    let init = WebPInitConfig(config.as_mut_ptr());
+    let mut config: WebPConfig = config.assume_init();
+    debug_assert!(init, "libwebp version mismatch");
+
+    config.lossless          = 0; // using lossy compression
+    config.alpha_compression = 1; // using lossy compression
+    config.quality           = WEBP_IMAGE_QUALITY;
+
+    debug_assert!(WebPValidateConfig(&config) != 0,
+                  "WebP config should be valid");
+
+    // ======================================================================
+    // NOTE: it makes no sense to re-use ww because WebPMemoryWriterClear(ww)
+    //       literally frees ww.mem and sets it NULL
+    let mut ww = MaybeUninit::uninit();
+    WebPMemoryWriterInit(ww.as_mut_ptr());
+    let mut ww: WebPMemoryWriter = ww.assume_init();
+
+    // NOTE: we cannot re-use picture because WebPPictureAlloc,
+    //       WebPPictureImportRGB & WebPPictureImportRGBA all free the pixel
+    //       buffers before reallocating them
+    let mut picture = MaybeUninit::uninit();
+    let init = WebPPictureInit(picture.as_mut_ptr());
+    let mut picture: WebPPicture = picture.assume_init();
+    debug_assert!(init, "libwebp version mismatch");
+
+    // NOTE: the libwebp documentation recommends using YUV (which is the
+    //       default in WebPPictureInit) when doing lossy compression
+    // picture.use_argb = 0;
+    picture.width  = img.width  as i32;
+    picture.height = img.height as i32;
+    let import_status = match &img.pixels {
+      PixelBuffer::Yuv { y, u, v, uv_stride, } => {
+        // NOTE: setting picture.y, picture.u & picture.v manually to avoid
+        //       recopying the pixel buffers
+        //
+        //       we must indicate picture is a "view" (i.e. does not own the
+        //       pixel buffer memory) by setting picture.memory_ to NULL:
+        //       libwebp/src/enc/picture_rescale_enc.c:97
+        picture.memory_   = ptr::null_mut();
+        picture.y_stride  = img.width  as i32;
+        picture.uv_stride = *uv_stride as i32;
+        picture.y = y.as_ptr() as *mut _;
+        picture.u = u.as_ptr() as *mut _;
+        picture.v = v.as_ptr() as *mut _;
+
+        debug_assert!(WebPPictureIsView(&picture) != 0,
+                      "picture should have been configured as \"view\"");
+        true
+      }
+      PixelBuffer::Grayscale { level, alpha, } => {
+        if alpha.is_some() { picture.colorspace = WebPEncCSP::WEBP_YUV420A; }
+        let status = WebPPictureAlloc(&mut picture) != 0;
+        if status {
+          let uv_width:  usize = img.width.div_ceil(2)  as _;
+          let uv_height: usize = img.height.div_ceil(2) as _;
+          let uv_len = uv_width * uv_height;
+
+          ptr::copy_nonoverlapping(level.as_ptr(), picture.y, level.len());
+          ptr::write_bytes(picture.u, 128, uv_len);
+          ptr::write_bytes(picture.v, 128, uv_len);
+          if let Some(alpha) = alpha {
+            ptr::copy_nonoverlapping(alpha.as_ptr(), picture.a, alpha.len());
+          }
+        }
+
+        status
+      }
+      PixelBuffer::Rgb(pixels) => {
+        WebPPictureImportRGB(
+          &mut picture,
+          pixels.as_ptr(),
+          img.width as i32 * 3,
+        ) != 0
+      }
+      PixelBuffer::Rgba(pixels) => {
+        WebPPictureImportRGBA(
+          &mut picture,
+          pixels.as_ptr(),
+          img.width as i32 * 4,
+        ) != 0
+      }
+    };
+    if !import_status {
+      log_webp_memmory_error(picture.error_code, output_path);
+      return Err(());
+    }
+    picture.writer = Some(WebPMemoryWrite);
+    picture.custom_ptr = &mut ww as *mut WebPMemoryWriter as _;
+
+    // ======================================================================
+    let status = WebPEncode(&config, &mut picture) != 0;
+
+    let bytes = if status {
+      slice::from_raw_parts(ww.mem, ww.size)
+    } else {
+      log_webp_memmory_error(picture.error_code, output_path);
+      return Err(());
+    };
+
+    // ======================================================================
+    let mut thumb_file = create_file(output_path).map_err(|_| ())?;
+    if let Err(e) = thumb_file.write_all(bytes) {
+      errorln!("Couldn't write thumnail to file {output_path:?}: {e}");
+      return Err(());
+    }
+
+    WebPPictureFree(&mut picture);
+  }
+
+  Ok(())
+}
+
+#[inline]
+fn assert_expected_pixels_len(
+  width: usize,
+  height: usize,
+  bpp: usize,
+  pixels_len: usize,
+) {
+  let expected_pixels_len = width * height * bpp;
+  debug_assert!(
+    pixels_len == expected_pixels_len,
+    "unexpected pixel buffer length: expected {width} × {height} × {bpp} = {expected_pixels_len} but got {pixels_len}",
+  );
+}
+
+#[inline]
+fn log_webp_memmory_error(e: WebPEncodingError, output_path: &Path) {
+  match e {
+    WebPEncodingError::VP8_ENC_ERROR_OUT_OF_MEMORY => {
+      errorln!("Could not generate {output_path:?}: out of memmory");
+    }
+    WebPEncodingError::VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY => {
+      errorln!("Could not generate {output_path:?}: memory error while flushing bits");
+    }
+    WebPEncodingError::VP8_ENC_ERROR_PARTITION0_OVERFLOW => {
+      errorln!("Could not generate {output_path:?}: partition is bigger than 512k");
+    }
+    WebPEncodingError::VP8_ENC_ERROR_PARTITION_OVERFLOW => {
+      errorln!("Could not generate {output_path:?}: partition is bigger than 16M");
+    }
+    WebPEncodingError::VP8_ENC_ERROR_BAD_WRITE => {
+      errorln!("Could not generate {output_path:?}: error while flushing bytes");
+    }
+    WebPEncodingError::VP8_ENC_ERROR_FILE_TOO_BIG => {
+      errorln!("Could not generate {output_path:?}: file is bigger than 4G");
+    }
+    WebPEncodingError::VP8_ENC_ERROR_USER_ABORT => {
+      errorln!("Could not generate {output_path:?}: abort requested by the user");
+    }
+    e => unreachable!("unexpected WebPEncodingError variant: {e:?}"),
+  }
+}
+
+fn open_file(path: &Path) -> io::Result<File> {
+  File::open(path)
+    .map_err(|e| { errorln!("Could not parse {path:?}: {e}"); e })
+}
diff --git a/src/main.rs b/src/main.rs
@@ -1,4 +1,3 @@
-use image::{DynamicImage, ImageReader};
 use std::{
   cmp,
   env,
@@ -10,15 +9,16 @@ use std::{
   sync::mpsc,
   time::Instant,
 };
-use gallery_entry::{GalleryEntry, FileFormat, LicenseType};
 use threadpool::ThreadPool;
 
+use gallery_entry::{GalleryEntry, FileFormat, LicenseType};
 use escape::Escaped;
 
 #[macro_use]
 mod log;
-mod escape;
+mod image;
 mod gallery_entry;
+mod escape;
 
 /// A wrapper for displaying the path for the thumbnail of a given path
 pub struct ThumbPath<'a>(pub &'a GalleryEntry);
@@ -87,7 +87,7 @@ fn main() -> ExitCode {
         }
       }
     }
-    
+
     if !is_valid_arg {
       if arg.starts_with("-") {
         errorln!("Unknown option: {arg:?}");
@@ -434,6 +434,18 @@ fn write_license(f: &mut File) -> io::Result<()> {
 }
 
 fn render_thumbnail(pic: &GalleryEntry, thumb_path: &Path) -> Result<(), ()> {
+  const TARGET_HEIGHT: u32 = 500;
+
+  #[inline]
+  fn warn_img_size(src_width: u32, src_height: u32, src_path: &Path) {
+    let target_width = src_width * TARGET_HEIGHT / src_height;
+    let scale_x      = src_width / target_width;
+
+    if scale_x > 1 {
+      warnln!("Thumbnail for {src_path:?}? {src_width}×{src_height}: thumbnails are not downsampled");
+    }
+  }
+
   match pic.file_format {
     FileFormat::TeX => {
       // tikztosvg -o thumb_path
@@ -469,52 +481,27 @@ fn render_thumbnail(pic: &GalleryEntry, thumb_path: &Path) -> Result<(), ()> {
         );
         return Err(());
       }
-    },
+    }
     FileFormat::Svg => {
       let mut src_path = PathBuf::from(TARGET_PATH);
       src_path.push(IMAGES_PATH);
       src_path.push(&pic.file_name);
 
       copy(&src_path, thumb_path)?;
-    },
-    FileFormat::Jpeg | FileFormat::Png => {
-      /// Target height of the thumbnails
-      const THUMB_HEIGHT:       u32 = 500;
-      const WEBP_IMAGE_QUALITY: f32 = 90.0;
-
-      let mut thumb_file = create_file(thumb_path).map_err(|_| ())?;
-
-      let img_reader = ImageReader::open(&pic.path)
-        .map_err(|e| {
-          errorln!(
-            "Couldn't open file {path:?} to render thumbnail: {e}",
-            path = pic.file_name,
-          );
-        })?;
-
-      let img = img_reader
-        .decode()
-        .map_err(|e| {
-          errorln!(
-            "Faileded to decode image file {name:?}: {e}",
-            name = pic.file_name,
-          );
-        })?;
-
-      let h = THUMB_HEIGHT;
-      let w = (h * img.width()) / img.height();
-
-      // we should make sure that the image is in the RGBA8 format so that
-      // the webp crate can encode it
-      let img = DynamicImage::from(img.thumbnail(w, h).into_rgba8());
-      let mem = webp::Encoder::from_image(&img)
-        .expect("image should be in the RGBA8 format")
-        .encode(WEBP_IMAGE_QUALITY);
-
-      if let Err(e) = thumb_file.write_all(&mem) {
-        errorln!( "Couldn't write thumnail to file {thumb_path:?}: {e}");
-        return Err(());
-      }
+    }
+    FileFormat::Jpeg => {
+      // NOTE: we do not rescale the images since the scale-factor for the
+      //       actual images included in our gallery should always be 1
+      let img = image::parse_jpeg(&pic.path)?;
+      warn_img_size(img.width, img.height, &pic.path);
+      image::encode_webp(&img, thumb_path)?;
+    }
+    FileFormat::Png => {
+      // NOTE: we do not rescale the images since the scale-factor for the
+      //       actual images included in our gallery should always be 1
+      let img = image::parse_png(&pic.path)?;
+      warn_img_size(img.width, img.height, &pic.path);
+      image::encode_webp(&img, thumb_path)?;
     }
   }
 
@@ -534,7 +521,7 @@ fn needs_update(pic: &GalleryEntry, dst: &Path) -> bool {
 
 fn create_file(path: &Path) -> io::Result<File> {
   File::create(path)
-    .map_err(|e| { errorln!("Could not open file {path:?}: {e}"); e })
+    .map_err(|e| { errorln!("Could not open {path:?}: {e}"); e })
 }
 
 fn copy(from: &Path, to: &Path) -> Result<(), ()> {