ravif-0.13.0/.cargo_vcs_info.json0000644000000001431046102023000122440ustar { "git": { "sha1": "760314e5100e259468324a777d2dda84961b87ce" }, "path_in_vcs": "ravif" }ravif-0.13.0/Cargo.lock0000644000000463001046102023000102240ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[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.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[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-parse" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f85ce2a7cd14ac0a30dc29a115de22466aeb8a029410f9f1e4f283443c959d1" dependencies = [ "arrayvec", "bitreader", "byteorder", "fallible_collections", "leb128", "log", ] [[package]] name = "avif-serialize" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" dependencies = [ "arrayvec", ] [[package]] name = "bitreader" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "886559b1e163d56c765bc3a985febb4eee8009f625244511d8ee3c432e08c066" dependencies = [ "cfg-if", ] [[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 = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "core2" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" dependencies = [ "memchr", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 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.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[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 = "fallible_collections" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b3e85d14d419ba3e1db925519461c0d17a49bdd2d67ea6316fa965ca7acdf74" [[package]] name = "find-msvc-tools" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[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 = "imgref" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[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 = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom", "libc", ] [[package]] name = "leb128" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[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 = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[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 = "nasm-rs" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34f676553b60ccbb76f41f9ae8f2428dac3f259ff8f1c2468a174778d06a1af9" dependencies = [ "jobserver", "log", ] [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "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.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "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", ] [[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 = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[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 = "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" checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] [[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", "cc", "cfg-if", "interpolate_name", "itertools", "libc", "libfuzzer-sys", "log", "maybe-rayon", "nasm-rs", "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.13.0" dependencies = [ "avif-parse", "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 = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ "quote", ] [[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" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[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.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[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", ] ravif-0.13.0/Cargo.toml0000644000000037471046102023000102570ustar # 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 = "2024" rust-version = "1.85" name = "ravif" version = "0.13.0" authors = ["Kornel Lesiński "] build = false include = [ "README.md", "LICENSE", "Cargo.toml", "/src/*.rs", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "rav1e-based pure Rust library for encoding images in AVIF format (powers the `cavif` tool)" homepage = "https://lib.rs/crates/ravif" readme = "README.md" keywords = [ "avif", "convert", "av1", "rav1f", "cav1f", ] categories = [ "multimedia::images", "multimedia::encoding", ] license = "BSD-3-Clause" repository = "https://github.com/kornelski/cavif-rs" [package.metadata.release] tag = false [features] asm = ["rav1e/asm"] default = [ "asm", "threading", ] threading = [ "dep:rayon", "rav1e/threading", ] [lib] name = "ravif" path = "src/lib.rs" [dependencies.avif-serialize] version = "0.8.6" [dependencies.imgref] version = "1.12.0" [dependencies.loop9] version = "0.1.5" [dependencies.quick-error] version = "2.0.1" [dependencies.rav1e] version = "0.8.1" default-features = false [dependencies.rayon] version = "1.11.0" optional = true [dependencies.rgb] version = "0.8.52" default-features = false [dev-dependencies.avif-parse] version = "1.4.0" [target.'cfg(target = "wasm32-unknown-unknown")'.dependencies.rav1e] version = "0.8" features = ["wasm"] default-features = false [profile.dev.package."*"] opt-level = 2 debug = 0 [profile.release] lto = true ravif-0.13.0/Cargo.toml.orig000064400000000000000000000023161046102023000137050ustar 00000000000000[package] name = "ravif" description = "rav1e-based pure Rust library for encoding images in AVIF format (powers the `cavif` tool)" version = "0.13.0" authors = ["Kornel Lesiński "] edition = "2024" license = "BSD-3-Clause" readme = "README.md" keywords = ["avif", "convert", "av1", "rav1f", "cav1f"] categories = ["multimedia::images", "multimedia::encoding"] homepage = "https://lib.rs/crates/ravif" repository = "https://github.com/kornelski/cavif-rs" include = ["README.md", "LICENSE", "Cargo.toml", "/src/*.rs"] rust-version = "1.85" [dependencies] avif-serialize = "0.8.6" imgref = "1.12.0" rav1e = { version = "0.8.1", default-features = false } rayon = { version = "1.11.0", optional = true } rgb = { version = "0.8.52", default-features = false } loop9 = "0.1.5" quick-error = "2.0.1" [target.'cfg(target = "wasm32-unknown-unknown")'.dependencies] rav1e = { version = "0.8", default-features = false, features = ["wasm"] } [features] default = ["asm", "threading"] asm = ["rav1e/asm"] threading = ["dep:rayon", "rav1e/threading"] [profile.release] lto = true [profile.dev.package."*"] debug = false opt-level = 2 [dev-dependencies] avif-parse = "1.4.0" [package.metadata.release] tag = false ravif-0.13.0/LICENSE000064400000000000000000000027521046102023000120270ustar 00000000000000BSD 3-Clause License Copyright (c) 2020, Kornel All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ravif-0.13.0/README.md000064400000000000000000000007321046102023000122750ustar 00000000000000# `ravif` — Pure Rust library for AVIF image encoding Encoder for AVIF images. Based on [`rav1e`](https://lib.rs/crates/rav1e) and [`avif-serialize`](https://lib.rs/crates/avif-serialize). The API is just a single `encode_rgba()` function call that spits an AVIF image. This library powers the [`cavif`](https://lib.rs/crates/cavif) encoder. It has an encoding configuration specifically tuned for still images, and gives better quality/performance than stock `rav1e`. ravif-0.13.0/src/av1encoder.rs000064400000000000000000000737531046102023000142170ustar 00000000000000#![allow(deprecated)] use std::borrow::Cow; use crate::dirtyalpha::blurred_dirty_alpha; use crate::error::Error; #[cfg(not(feature = "threading"))] use crate::rayoff as rayon; use imgref::{Img, ImgVec}; use rav1e::prelude::*; use rgb::{RGB8, RGBA8}; /// For [`Encoder::with_internal_color_model`] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum ColorModel { /// Standard color model for photographic content. Usually the best choice. /// This library always uses full-resolution color (4:4:4). /// This library will automatically choose between BT.601 or BT.709. YCbCr, /// RGB channels are encoded without color space transformation. /// Usually results in larger file sizes, and is less compatible than `YCbCr`. /// Use only if the content really makes use of RGB, e.g. anaglyph images or RGB subpixel anti-aliasing. RGB, } /// Handling of color channels in transparent images. For [`Encoder::with_alpha_color_mode`] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum AlphaColorMode { /// Use unassociated alpha channel and leave color channels unchanged, even if there's redundant color data in transparent areas. UnassociatedDirty, /// Use unassociated alpha channel, but set color channels of transparent areas to a solid color to eliminate invisible data and improve compression. UnassociatedClean, /// Store color channels of transparent images in premultiplied form. /// This requires support for premultiplied alpha in AVIF decoders. /// /// It may reduce file sizes due to clearing of fully-transparent pixels, but /// may also increase file sizes due to creation of new edges in the color channels. /// /// Note that this is only internal detail for the AVIF file. /// It does not change meaning of `RGBA` in this library — it's always unassociated. Premultiplied, } #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] pub enum BitDepth { Eight, Ten, /// Same as `Ten` #[default] Auto, } /// The newly-created image file + extra info FYI #[non_exhaustive] #[derive(Clone)] pub struct EncodedImage { /// AVIF (HEIF+AV1) encoded image data pub avif_file: Vec, /// FYI: number of bytes of AV1 payload used for the color pub color_byte_size: usize, /// FYI: number of bytes of AV1 payload used for the alpha channel pub alpha_byte_size: usize, } /// Encoder config builder /// /// The lifetime is relevant only for [`Encoder::with_exif()`]. Use `Encoder<'static>` if Rust complains. #[derive(Debug, Clone)] pub struct Encoder<'exif_slice> { /// 0-255 scale quantizer: u8, /// 0-255 scale alpha_quantizer: u8, /// rav1e preset 1 (slow) 10 (fast but crappy) speed: u8, /// True if RGBA input has already been premultiplied. It inserts appropriate metadata. premultiplied_alpha: bool, /// Which pixel format to use in AVIF file. RGB tends to give larger files. color_model: ColorModel, /// How many threads should be used (0 = match core count), None - use global rayon thread pool threads: Option, /// [`AlphaColorMode`] alpha_color_mode: AlphaColorMode, /// 8 or 10 output_depth: BitDepth, /// Dropped into MPEG infe BOX exif: Option>, } /// Builder methods impl<'exif_slice> Encoder<'exif_slice> { /// Start here #[must_use] pub fn new() -> Self { Self { quantizer: quality_to_quantizer(80.), alpha_quantizer: quality_to_quantizer(80.), speed: 5, output_depth: BitDepth::default(), premultiplied_alpha: false, color_model: ColorModel::YCbCr, threads: None, exif: None, alpha_color_mode: AlphaColorMode::UnassociatedClean, } } /// Quality `1..=100`. Panics if out of range. #[inline(always)] #[track_caller] #[must_use] pub fn with_quality(mut self, quality: f32) -> Self { assert!(quality >= 1. && quality <= 100.); self.quantizer = quality_to_quantizer(quality); self } #[doc(hidden)] #[deprecated(note = "Renamed to with_bit_depth")] #[must_use] pub fn with_depth(self, depth: Option) -> Self { self.with_bit_depth(depth.map(|d| if d >= 10 { BitDepth::Ten } else { BitDepth::Eight }).unwrap_or(BitDepth::Auto)) } /// Internal precision to use in the encoded AV1 data, for both color and alpha. 10-bit depth works best, even for 8-bit inputs/outputs. /// /// Use 8-bit depth only as a workaround for decoders that need it. /// /// This setting does not affect pixel inputs for this library. #[inline(always)] #[must_use] pub fn with_bit_depth(mut self, depth: BitDepth) -> Self { self.output_depth = depth; self } /// Quality for the alpha channel only. `1..=100`. Panics if out of range. #[inline(always)] #[track_caller] #[must_use] pub fn with_alpha_quality(mut self, quality: f32) -> Self { assert!(quality >= 1. && quality <= 100.); self.alpha_quantizer = quality_to_quantizer(quality); self } /// * 1 = very very slow, but max compression. /// * 10 = quick, but larger file sizes and lower quality. /// /// Panics if outside `1..=10`. #[inline(always)] #[track_caller] #[must_use] pub fn with_speed(mut self, speed: u8) -> Self { assert!(speed >= 1 && speed <= 10); self.speed = speed; self } /// Changes how color channels are stored in the image. The default is YCbCr. /// /// Note that this is only internal detail for the AVIF file, and doesn't /// change color model of inputs to encode functions. #[inline(always)] #[must_use] pub fn with_internal_color_model(mut self, color_model: ColorModel) -> Self { self.color_model = color_model; self } #[doc(hidden)] #[deprecated = "Renamed to `with_internal_color_model()`"] #[must_use] pub fn with_internal_color_space(self, color_model: ColorModel) -> Self { self.with_internal_color_model(color_model) } /// Configures `rayon` thread pool size. /// The default `None` is to use all threads in the default `rayon` thread pool. #[inline(always)] #[track_caller] #[must_use] pub fn with_num_threads(mut self, num_threads: Option) -> Self { assert!(num_threads.is_none_or(|n| n > 0)); self.threads = num_threads; self } /// Configure handling of color channels in transparent images /// /// Note that this doesn't affect input format for this library, /// which for RGBA is always uncorrelated alpha. #[inline(always)] #[must_use] pub fn with_alpha_color_mode(mut self, mode: AlphaColorMode) -> Self { self.alpha_color_mode = mode; self.premultiplied_alpha = mode == AlphaColorMode::Premultiplied; self } /// Embedded into AVIF file as-is /// /// The data can be `Vec`, or `&[u8]` if the encoder instance doesn't leave its scope. pub fn with_exif(mut self, exif_data: impl Into>) -> Self { self.exif = Some(exif_data.into()); self } } /// Once done with config, call one of the `encode_*` functions impl Encoder<'_> { /// Make a new AVIF image from RGBA pixels (non-premultiplied, alpha last) /// /// Make the `Img` for the `buffer` like this: /// /// ```rust,ignore /// Img::new(&pixels_rgba[..], width, height) /// ``` /// /// If you have pixels as `u8` slice, then use the `rgb` crate, and do: /// /// ```rust,ignore /// use rgb::ComponentSlice; /// let pixels_rgba = pixels_u8.as_rgba(); /// ``` /// /// If all pixels are opaque, the alpha channel will be left out automatically. /// /// This function takes 8-bit inputs, but will generate an AVIF file using 10-bit depth. /// /// returns AVIF file with info about sizes about AV1 payload. pub fn encode_rgba(&self, in_buffer: Img<&[rgb::RGBA]>) -> Result { let new_alpha = self.convert_alpha_8bit(in_buffer); let buffer = new_alpha.as_ref().map(|b| b.as_ref()).unwrap_or(in_buffer); let use_alpha = buffer.pixels().any(|px| px.a != 255); if !use_alpha { return self.encode_rgb_internal_from_8bit(buffer.width(), buffer.height(), buffer.pixels().map(|px| px.rgb())); } let width = buffer.width(); let height = buffer.height(); let matrix_coefficients = match self.color_model { ColorModel::YCbCr => MatrixCoefficients::BT601, ColorModel::RGB => MatrixCoefficients::Identity, }; match self.output_depth { BitDepth::Eight => { let planes = buffer.pixels().map(|px| match self.color_model { ColorModel::YCbCr => rgb_to_8_bit_ycbcr(px.rgb(), BT601).into(), ColorModel::RGB => rgb_to_8_bit_gbr(px.rgb()).into(), }); let alpha = buffer.pixels().map(|px| px.a); self.encode_raw_planes_8_bit(width, height, planes, Some(alpha), PixelRange::Full, matrix_coefficients) }, BitDepth::Ten | BitDepth::Auto => { let planes = buffer.pixels().map(|px| match self.color_model { ColorModel::YCbCr => rgb_to_10_bit_ycbcr(px.rgb(), BT601).into(), ColorModel::RGB => rgb_to_10_bit_gbr(px.rgb()).into(), }); let alpha = buffer.pixels().map(|px| to_ten(px.a)); self.encode_raw_planes_10_bit(width, height, planes, Some(alpha), PixelRange::Full, matrix_coefficients) }, } } fn convert_alpha_8bit(&self, in_buffer: Img<&[RGBA8]>) -> Option> { match self.alpha_color_mode { AlphaColorMode::UnassociatedDirty => None, AlphaColorMode::UnassociatedClean => blurred_dirty_alpha(in_buffer), AlphaColorMode::Premultiplied => { let prem = in_buffer.pixels() .map(|px| { if px.a == 0 || px.a == 255 { RGBA8::default() } else { RGBA8::new( (u16::from(px.r) * 255 / u16::from(px.a)) as u8, (u16::from(px.g) * 255 / u16::from(px.a)) as u8, (u16::from(px.b) * 255 / u16::from(px.a)) as u8, px.a, ) } }) .collect(); Some(ImgVec::new(prem, in_buffer.width(), in_buffer.height())) }, } } /// Make a new AVIF image from RGB pixels /// /// Make the `Img` for the `buffer` like this: /// /// ```rust,ignore /// Img::new(&pixels_rgb[..], width, height) /// ``` /// /// If you have pixels as `u8` slice, then first do: /// /// ```rust,ignore /// use rgb::ComponentSlice; /// let pixels_rgb = pixels_u8.as_rgb(); /// ``` /// /// returns AVIF file, size of color metadata #[inline] pub fn encode_rgb(&self, buffer: Img<&[RGB8]>) -> Result { self.encode_rgb_internal_from_8bit(buffer.width(), buffer.height(), buffer.pixels()) } fn encode_rgb_internal_from_8bit(&self, width: usize, height: usize, pixels: impl Iterator + Send + Sync) -> Result { let matrix_coefficients = match self.color_model { ColorModel::YCbCr => MatrixCoefficients::BT601, ColorModel::RGB => MatrixCoefficients::Identity, }; match self.output_depth { BitDepth::Eight => { let planes = pixels.map(|px| { let (y, u, v) = match self.color_model { ColorModel::YCbCr => rgb_to_8_bit_ycbcr(px, BT601), ColorModel::RGB => rgb_to_8_bit_gbr(px), }; [y, u, v] }); self.encode_raw_planes_8_bit(width, height, planes, None::<[_; 0]>, PixelRange::Full, matrix_coefficients) }, BitDepth::Ten | BitDepth::Auto => { let planes = pixels.map(|px| { let (y, u, v) = match self.color_model { ColorModel::YCbCr => rgb_to_10_bit_ycbcr(px, BT601), ColorModel::RGB => rgb_to_10_bit_gbr(px), }; [y, u, v] }); self.encode_raw_planes_10_bit(width, height, planes, None::<[_; 0]>, PixelRange::Full, matrix_coefficients) }, } } /// Encodes AVIF from 3 planar channels that are in the color space described by `matrix_coefficients`, /// with sRGB transfer characteristics and color primaries. /// /// Alpha always uses full range. Chroma subsampling is not supported, and it's a bad idea for AVIF anyway. /// If there's no alpha, use `None::<[_; 0]>`. /// /// `color_pixel_range` should be `PixelRange::Full` to avoid worsening already small 8-bit dynamic range. /// Support for limited range may be removed in the future. /// /// If `AlphaColorMode::Premultiplied` has been set, the alpha pixels must be premultiplied. /// `AlphaColorMode::UnassociatedClean` has no effect in this function, and is equivalent to `AlphaColorMode::UnassociatedDirty`. /// /// returns AVIF file, size of color metadata, size of alpha metadata overhead #[inline] pub fn encode_raw_planes_8_bit( &self, width: usize, height: usize, planes: impl IntoIterator + Send, alpha: Option + Send>, color_pixel_range: PixelRange, matrix_coefficients: MatrixCoefficients, ) -> Result { self.encode_raw_planes_internal(width, height, planes, alpha, color_pixel_range, matrix_coefficients, 8) } /// Encodes AVIF from 3 planar channels that are in the color space described by `matrix_coefficients`, /// with sRGB transfer characteristics and color primaries. /// /// The pixels are 10-bit (values `0.=1023`) in host's native endian. /// /// Alpha always uses full range. Chroma subsampling is not supported, and it's a bad idea for AVIF anyway. /// If there's no alpha, use `None::<[_; 0]>`. /// /// `color_pixel_range` should be `PixelRange::Full`. Support for limited range may be removed in the future. /// /// If `AlphaColorMode::Premultiplied` has been set, the alpha pixels must be premultiplied. /// `AlphaColorMode::UnassociatedClean` has no effect in this function, and is equivalent to `AlphaColorMode::UnassociatedDirty`. /// /// returns AVIF file, size of color metadata, size of alpha metadata overhead #[inline] pub fn encode_raw_planes_10_bit( &self, width: usize, height: usize, planes: impl IntoIterator + Send, alpha: Option + Send>, color_pixel_range: PixelRange, matrix_coefficients: MatrixCoefficients, ) -> Result { self.encode_raw_planes_internal(width, height, planes, alpha, color_pixel_range, matrix_coefficients, 10) } #[inline(never)] fn encode_raw_planes_internal( &self, width: usize, height: usize, planes: impl IntoIterator + Send, alpha: Option + Send>, color_pixel_range: PixelRange, matrix_coefficients: MatrixCoefficients, input_pixels_bit_depth: u8, ) -> Result { let color_description = Some(ColorDescription { transfer_characteristics: TransferCharacteristics::SRGB, color_primaries: ColorPrimaries::BT709, // sRGB-compatible matrix_coefficients, }); let threads = self.threads.map(|threads| { if threads > 0 { threads } else { rayon::current_num_threads() } }); let encode_color = move || { encode_to_av1::

( &Av1EncodeConfig { width, height, bit_depth: input_pixels_bit_depth.into(), quantizer: self.quantizer.into(), speed: SpeedTweaks::from_my_preset(self.speed, self.quantizer), threads, pixel_range: color_pixel_range, chroma_sampling: ChromaSampling::Cs444, color_description, }, move |frame| init_frame_3(width, height, planes, frame), ) }; let encode_alpha = move || { alpha.map(|alpha| { encode_to_av1::

( &Av1EncodeConfig { width, height, bit_depth: input_pixels_bit_depth.into(), quantizer: self.alpha_quantizer.into(), speed: SpeedTweaks::from_my_preset(self.speed, self.alpha_quantizer), threads, pixel_range: PixelRange::Full, chroma_sampling: ChromaSampling::Cs400, color_description: None, }, |frame| init_frame_1(width, height, alpha, frame), ) }) }; #[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))] let (color, alpha) = (encode_color(), encode_alpha()); #[cfg(not(all(target_arch = "wasm32", not(target_feature = "atomics"))))] let (color, alpha) = rayon::join(encode_color, encode_alpha); let (color, alpha) = (color?, alpha.transpose()?); let mut serializer_config = avif_serialize::Aviffy::new(); serializer_config .matrix_coefficients(match matrix_coefficients { MatrixCoefficients::Identity => avif_serialize::constants::MatrixCoefficients::Rgb, MatrixCoefficients::BT709 => avif_serialize::constants::MatrixCoefficients::Bt709, MatrixCoefficients::Unspecified => avif_serialize::constants::MatrixCoefficients::Unspecified, MatrixCoefficients::BT601 => avif_serialize::constants::MatrixCoefficients::Bt601, MatrixCoefficients::YCgCo => avif_serialize::constants::MatrixCoefficients::Ycgco, MatrixCoefficients::BT2020NCL => avif_serialize::constants::MatrixCoefficients::Bt2020Ncl, MatrixCoefficients::BT2020CL => avif_serialize::constants::MatrixCoefficients::Bt2020Cl, _ => return Err(Error::Unsupported("matrix coefficients")), }) .premultiplied_alpha(self.premultiplied_alpha); if let Some(exif) = &self.exif { serializer_config.set_exif(exif.to_vec()); } let avif_file = serializer_config.to_vec(&color, alpha.as_deref(), width as u32, height as u32, input_pixels_bit_depth); let color_byte_size = color.len(); let alpha_byte_size = alpha.as_ref().map_or(0, |a| a.len()); Ok(EncodedImage { avif_file, color_byte_size, alpha_byte_size, }) } } /// Native endian #[inline(always)] fn to_ten(x: u8) -> u16 { (u16::from(x) << 2) | (u16::from(x) >> 6) } /// Native endian #[inline(always)] fn rgb_to_10_bit_gbr(px: rgb::RGB) -> (u16, u16, u16) { (to_ten(px.g), to_ten(px.b), to_ten(px.r)) } #[inline(always)] fn rgb_to_8_bit_gbr(px: rgb::RGB) -> (u8, u8, u8) { (px.g, px.b, px.r) } // const REC709: [f32; 3] = [0.2126, 0.7152, 0.0722]; const BT601: [f32; 3] = [0.2990, 0.5870, 0.1140]; #[inline(always)] fn rgb_to_ycbcr(px: rgb::RGB, depth: u8, matrix: [f32; 3]) -> (f32, f32, f32) { let max_value = ((1 << depth) - 1) as f32; let scale = max_value / 255.; let shift = (max_value * 0.5).round(); let y = (scale * matrix[2]).mul_add(f32::from(px.b), (scale * matrix[0]).mul_add(f32::from(px.r), scale * matrix[1] * f32::from(px.g))); let cb = f32::from(px.b).mul_add(scale, -y).mul_add(0.5 / (1. - matrix[2]), shift); let cr = f32::from(px.r).mul_add(scale, -y).mul_add(0.5 / (1. - matrix[0]), shift); (y.round(), cb.round(), cr.round()) } #[inline(always)] fn rgb_to_10_bit_ycbcr(px: rgb::RGB, matrix: [f32; 3]) -> (u16, u16, u16) { let (y, u, v) = rgb_to_ycbcr(px, 10, matrix); (y as u16, u as u16, v as u16) } #[inline(always)] fn rgb_to_8_bit_ycbcr(px: rgb::RGB, matrix: [f32; 3]) -> (u8, u8, u8) { let (y, u, v) = rgb_to_ycbcr(px, 8, matrix); (y as u8, u as u8, v as u8) } fn quality_to_quantizer(quality: f32) -> u8 { let q = quality / 100.; let x = if q >= 0.82 { (1. - q) * 2.6 } else if q > 0.25 { q.mul_add(-0.5, 1. - 0.125) } else { 1. - q }; (x * 255.).round() as u8 } #[derive(Debug, Copy, Clone)] struct SpeedTweaks { pub speed_preset: u8, pub fast_deblock: Option, pub reduced_tx_set: Option, pub tx_domain_distortion: Option, pub tx_domain_rate: Option, pub encode_bottomup: Option, pub rdo_tx_decision: Option, pub cdef: Option, /// loop restoration filter pub lrf: Option, pub sgr_complexity_full: Option, pub use_satd_subpel: Option, pub inter_tx_split: Option, pub fine_directional_intra: Option, pub complex_prediction_modes: Option, pub partition_range: Option<(u8, u8)>, pub min_tile_size: u16, } impl SpeedTweaks { pub fn from_my_preset(speed: u8, quantizer: u8) -> Self { let low_quality = quantizer < quality_to_quantizer(55.); let high_quality = quantizer > quality_to_quantizer(80.); let max_block_size = if high_quality { 16 } else { 64 }; Self { speed_preset: speed, partition_range: Some(match speed { 0 => (4, 64.min(max_block_size)), 1 if low_quality => (4, 64.min(max_block_size)), 2 if low_quality => (4, 32.min(max_block_size)), 1..=4 => (4, 16), 5..=8 => (8, 16), _ => (16, 16), }), complex_prediction_modes: Some(speed <= 1), // 2x-3x slower, 2% better sgr_complexity_full: Some(speed <= 2), // 15% slower, barely improves anything -/+1% encode_bottomup: Some(speed <= 2), // may be costly (+60%), may even backfire // big blocks disabled at 3 // these two are together? rdo_tx_decision: Some(speed <= 4 && !high_quality), // it tends to blur subtle textures reduced_tx_set: Some(speed == 4 || speed >= 9), // It interacts with tx_domain_distortion too? // 4px blocks disabled at 5 fine_directional_intra: Some(speed <= 6), fast_deblock: Some(speed >= 7 && !high_quality), // mixed bag? // 8px blocks disabled at 8 lrf: Some(low_quality && speed <= 8), // hardly any help for hi-q images. recovers some q at low quality cdef: Some(low_quality && speed <= 9), // hardly any help for hi-q images. recovers some q at low quality inter_tx_split: Some(speed >= 9), // mixed bag even when it works, and it backfires if not used together with reduced_tx_set tx_domain_rate: Some(speed >= 10), // 20% faster, but also 10% larger files! tx_domain_distortion: None, // very mixed bag, sometimes helps speed sometimes it doesn't use_satd_subpel: Some(false), // doesn't make sense min_tile_size: match speed { 0 => 4096, 1 => 2048, 2 => 1024, 3 => 512, 4 => 256, _ => 128, } * if high_quality { 2 } else { 1 }, } } pub(crate) fn speed_settings(&self) -> SpeedSettings { let mut speed_settings = SpeedSettings::from_preset(self.speed_preset); speed_settings.multiref = false; speed_settings.rdo_lookahead_frames = 1; speed_settings.scene_detection_mode = SceneDetectionSpeed::None; speed_settings.motion.include_near_mvs = false; if let Some(v) = self.fast_deblock { speed_settings.fast_deblock = v; } if let Some(v) = self.reduced_tx_set { speed_settings.transform.reduced_tx_set = v; } if let Some(v) = self.tx_domain_distortion { speed_settings.transform.tx_domain_distortion = v; } if let Some(v) = self.tx_domain_rate { speed_settings.transform.tx_domain_rate = v; } if let Some(v) = self.encode_bottomup { speed_settings.partition.encode_bottomup = v; } if let Some(v) = self.rdo_tx_decision { speed_settings.transform.rdo_tx_decision = v; } if let Some(v) = self.cdef { speed_settings.cdef = v; } if let Some(v) = self.lrf { speed_settings.lrf = v; } if let Some(v) = self.inter_tx_split { speed_settings.transform.enable_inter_tx_split = v; } if let Some(v) = self.sgr_complexity_full { speed_settings.sgr_complexity = if v { SGRComplexityLevel::Full } else { SGRComplexityLevel::Reduced } } if let Some(v) = self.use_satd_subpel { speed_settings.motion.use_satd_subpel = v; } if let Some(v) = self.fine_directional_intra { speed_settings.prediction.fine_directional_intra = v; } if let Some(v) = self.complex_prediction_modes { speed_settings.prediction.prediction_modes = if v { PredictionModesSetting::ComplexAll } else { PredictionModesSetting::Simple} } if let Some((min, max)) = self.partition_range { debug_assert!(min <= max); fn sz(s: u8) -> BlockSize { match s { 4 => BlockSize::BLOCK_4X4, 8 => BlockSize::BLOCK_8X8, 16 => BlockSize::BLOCK_16X16, 32 => BlockSize::BLOCK_32X32, 64 => BlockSize::BLOCK_64X64, 128 => BlockSize::BLOCK_128X128, _ => panic!("bad size {s}"), } } speed_settings.partition.partition_range = PartitionRange::new(sz(min), sz(max)); } speed_settings } } struct Av1EncodeConfig { pub width: usize, pub height: usize, pub bit_depth: usize, pub quantizer: usize, pub speed: SpeedTweaks, /// 0 means num_cpus pub threads: Option, pub pixel_range: PixelRange, pub chroma_sampling: ChromaSampling, pub color_description: Option, } fn rav1e_config(p: &Av1EncodeConfig) -> Config { // AV1 needs all the CPU power you can give it, // except when it'd create inefficiently tiny tiles let tiles = { let threads = p.threads.unwrap_or_else(rayon::current_num_threads); threads.min((p.width * p.height) / (p.speed.min_tile_size as usize).pow(2)) }; let speed_settings = p.speed.speed_settings(); let cfg = Config::new() .with_encoder_config(EncoderConfig { width: p.width, height: p.height, time_base: Rational::new(1, 1), sample_aspect_ratio: Rational::new(1, 1), bit_depth: p.bit_depth, chroma_sampling: p.chroma_sampling, chroma_sample_position: ChromaSamplePosition::Unknown, pixel_range: p.pixel_range, color_description: p.color_description, mastering_display: None, content_light: None, enable_timing_info: false, still_picture: true, error_resilient: false, switch_frame_interval: 0, min_key_frame_interval: 0, max_key_frame_interval: 0, reservoir_frame_delay: None, low_latency: false, quantizer: p.quantizer, min_quantizer: p.quantizer as _, bitrate: 0, tune: Tune::Psychovisual, tile_cols: 0, tile_rows: 0, tiles, film_grain_params: None, level_idx: None, speed_settings, }); if let Some(threads) = p.threads { cfg.with_threads(threads) } else { cfg } } fn init_frame_3( width: usize, height: usize, planes: impl IntoIterator + Send, frame: &mut Frame

, ) -> Result<(), Error> { let mut f = frame.planes.iter_mut(); let mut planes = planes.into_iter(); // it doesn't seem to be necessary to fill padding area let mut y = f.next().unwrap().mut_slice(Default::default()); let mut u = f.next().unwrap().mut_slice(Default::default()); let mut v = f.next().unwrap().mut_slice(Default::default()); for ((y, u), v) in y.rows_iter_mut().zip(u.rows_iter_mut()).zip(v.rows_iter_mut()).take(height) { let y = &mut y[..width]; let u = &mut u[..width]; let v = &mut v[..width]; for ((y, u), v) in y.iter_mut().zip(u).zip(v) { let px = planes.next().ok_or(Error::TooFewPixels)?; *y = px[0]; *u = px[1]; *v = px[2]; } } Ok(()) } fn init_frame_1(width: usize, height: usize, planes: impl IntoIterator + Send, frame: &mut Frame

) -> Result<(), Error> { let mut y = frame.planes[0].mut_slice(Default::default()); let mut planes = planes.into_iter(); for y in y.rows_iter_mut().take(height) { let y = &mut y[..width]; for y in y.iter_mut() { *y = planes.next().ok_or(Error::TooFewPixels)?; } } Ok(()) } #[inline(never)] fn encode_to_av1(p: &Av1EncodeConfig, init: impl FnOnce(&mut Frame

) -> Result<(), Error>) -> Result, Error> { let mut ctx: Context

= rav1e_config(p).new_context()?; let mut frame = ctx.new_frame(); init(&mut frame)?; ctx.send_frame(frame)?; ctx.flush(); let mut out = Vec::new(); loop { match ctx.receive_packet() { Ok(mut packet) => match packet.frame_type { FrameType::KEY => { out.append(&mut packet.data); }, _ => continue, }, Err(EncoderStatus::Encoded | EncoderStatus::LimitReached) => break, Err(err) => Err(err)?, } } Ok(out) } ravif-0.13.0/src/dirtyalpha.rs000064400000000000000000000121701046102023000143130ustar 00000000000000use imgref::{Img, ImgRef}; use rgb::{ComponentMap, RGB, RGBA8}; #[inline] fn weighed_pixel(px: RGBA8) -> (u16, RGB) { if px.a == 0 { return (0, RGB::new(0, 0, 0)); } let weight = 256 - u16::from(px.a); (weight, RGB::new( u32::from(px.r) * u32::from(weight), u32::from(px.g) * u32::from(weight), u32::from(px.b) * u32::from(weight))) } /// Clear/change RGB components of fully-transparent RGBA pixels to make them cheaper to encode with AV1 pub(crate) fn blurred_dirty_alpha(img: ImgRef) -> Option>> { // get dominant visible transparent color (excluding opaque pixels) let mut sum = RGB::new(0, 0, 0); let mut weights = 0; // Only consider colors around transparent images // (e.g. solid semitransparent area doesn't need to contribute) loop9::loop9_img(img, |_, _, top, mid, bot| { if mid.curr.a == 255 || mid.curr.a == 0 { return; } if chain(&top, &mid, &bot).any(|px| px.a == 0) { let (w, px) = weighed_pixel(mid.curr); weights += u64::from(w); sum += px.map(u64::from); } }); if weights == 0 { return None; // opaque image } let neutral_alpha = RGBA8::new((sum.r / weights) as u8, (sum.g / weights) as u8, (sum.b / weights) as u8, 0); let img2 = bleed_opaque_color(img, neutral_alpha); Some(blur_transparent_pixels(img2.as_ref())) } /// copy color from opaque pixels to transparent pixels /// (so that when edges get crushed by compression, the distortion will be away from visible edge) fn bleed_opaque_color(img: ImgRef, bg: RGBA8) -> Img> { let mut out = Vec::with_capacity(img.width() * img.height()); loop9::loop9_img(img, |_, _, top, mid, bot| { out.push(if mid.curr.a == 255 { mid.curr } else { let (weights, sum) = chain(&top, &mid, &bot) .map(|c| weighed_pixel(*c)) .fold((0u32, RGB::new(0, 0, 0)), |mut sum, item| { sum.0 += u32::from(item.0); sum.1 += item.1; sum }); if weights == 0 { bg } else { let mut avg = sum.map(|c| (c / weights) as u8); if mid.curr.a == 0 { avg.with_alpha(0) } else { // also change non-transparent colors, but only within range where // rounding caused by premultiplied alpha would land on the same color avg.r = clamp(avg.r, premultiplied_minmax(mid.curr.r, mid.curr.a)); avg.g = clamp(avg.g, premultiplied_minmax(mid.curr.g, mid.curr.a)); avg.b = clamp(avg.b, premultiplied_minmax(mid.curr.b, mid.curr.a)); avg.with_alpha(mid.curr.a) } } }); }); Img::new(out, img.width(), img.height()) } /// ensure there are no sharp edges created by the cleared alpha fn blur_transparent_pixels(img: ImgRef) -> Img> { let mut out = Vec::with_capacity(img.width() * img.height()); loop9::loop9_img(img, |_, _, top, mid, bot| { out.push(if mid.curr.a == 255 { mid.curr } else { let sum: RGB = chain(&top, &mid, &bot).map(|px| px.rgb().map(u16::from)).sum(); let mut avg = sum.map(|c| (c / 9) as u8); if mid.curr.a == 0 { avg.with_alpha(0) } else { // also change non-transparent colors, but only within range where // rounding caused by premultiplied alpha would land on the same color avg.r = clamp(avg.r, premultiplied_minmax(mid.curr.r, mid.curr.a)); avg.g = clamp(avg.g, premultiplied_minmax(mid.curr.g, mid.curr.a)); avg.b = clamp(avg.b, premultiplied_minmax(mid.curr.b, mid.curr.a)); avg.with_alpha(mid.curr.a) } }); }); Img::new(out, img.width(), img.height()) } #[inline(always)] fn chain<'a, T>(top: &'a loop9::Triple, mid: &'a loop9::Triple, bot: &'a loop9::Triple) -> impl Iterator + 'a { top.iter().chain(mid.iter()).chain(bot.iter()) } #[inline] fn clamp(px: u8, (min, max): (u8, u8)) -> u8 { px.max(min).min(max) } /// safe range to change px color given its alpha /// (mostly-transparent colors tolerate more variation) #[inline] fn premultiplied_minmax(px: u8, alpha: u8) -> (u8, u8) { let alpha = u16::from(alpha); let rounded = u16::from(px) * alpha / 255 * 255; // leave some spare room for rounding let low = ((rounded + 16) / alpha) as u8; let hi = ((rounded + 239) / alpha) as u8; (low.min(px), hi.max(px)) } #[test] fn preminmax() { assert_eq!((100, 100), premultiplied_minmax(100, 255)); assert_eq!((78, 100), premultiplied_minmax(100, 10)); assert_eq!(100 * 10 / 255, 78 * 10 / 255); assert_eq!(100 * 10 / 255, 100 * 10 / 255); assert_eq!((8, 119), premultiplied_minmax(100, 2)); assert_eq!((16, 239), premultiplied_minmax(100, 1)); assert_eq!((15, 255), premultiplied_minmax(255, 1)); } ravif-0.13.0/src/error.rs000064400000000000000000000013561046102023000133070ustar 00000000000000use quick_error::quick_error; #[derive(Debug)] #[doc(hidden)] pub struct EncodingErrorDetail; // maybe later quick_error! { /// Failures enum #[derive(Debug)] #[non_exhaustive] pub enum Error { /// Slices given to `encode_raw_planes` must be `width * height` large. TooFewPixels { display("Provided buffer is smaller than width * height") } Unsupported(msg: &'static str) { display("Not supported: {}", msg) } EncodingError(e: EncodingErrorDetail) { display("Encoding error reported by rav1e") from(_e: rav1e::InvalidConfig) -> (EncodingErrorDetail) from(_e: rav1e::EncoderStatus) -> (EncodingErrorDetail) } } } ravif-0.13.0/src/lib.rs000064400000000000000000000123171046102023000127230ustar 00000000000000//! ```rust //! use ravif::*; //! # fn doit(pixels: &[RGBA8], width: usize, height: usize) -> Result<(), Error> { //! let res = Encoder::new() //! .with_quality(70.) //! .with_speed(4) //! .encode_rgba(Img::new(pixels, width, height))?; //! std::fs::write("hello.avif", res.avif_file); //! # Ok(()) } mod av1encoder; mod error; pub use av1encoder::ColorModel; pub use error::Error; #[doc(hidden)] #[deprecated = "Renamed to `ColorModel`"] pub type ColorSpace = ColorModel; pub use av1encoder::{AlphaColorMode, BitDepth, EncodedImage, Encoder}; #[doc(inline)] pub use rav1e::prelude::{MatrixCoefficients, PixelRange}; mod dirtyalpha; #[doc(no_inline)] pub use imgref::Img; #[doc(no_inline)] pub use rgb::{RGB8, RGBA8}; #[cfg(not(feature = "threading"))] mod rayoff { pub fn current_num_threads() -> usize { std::thread::available_parallelism().map(|v| v.get()).unwrap_or(1) } pub fn join(a: impl FnOnce() -> A, b: impl FnOnce() -> B) -> (A, B) { (a(), b()) } } #[test] fn encode8_with_alpha() { let img = imgref::ImgVec::new((0..200).flat_map(|y| (0..256).map(move |x| { RGBA8::new(x as u8, y as u8, 255, (x + y) as u8) })).collect(), 256, 200); let enc = Encoder::new() .with_quality(22.0) .with_bit_depth(BitDepth::Eight) .with_speed(1) .with_alpha_quality(22.0) .with_alpha_color_mode(AlphaColorMode::UnassociatedDirty) .with_num_threads(Some(2)); let EncodedImage { avif_file, color_byte_size, alpha_byte_size , .. } = enc.encode_rgba(img.as_ref()).unwrap(); assert!(color_byte_size > 50 && color_byte_size < 1000); assert!(alpha_byte_size > 50 && alpha_byte_size < 1000); // the image must have alpha let parsed = avif_parse::read_avif(&mut avif_file.as_slice()).unwrap(); assert!(parsed.alpha_item.is_some()); assert!(parsed.primary_item.len() > 100); assert!(parsed.primary_item.len() < 1000); let md = parsed.primary_item_metadata().unwrap(); assert_eq!(md.max_frame_width.get(), 256); assert_eq!(md.max_frame_height.get(), 200); assert_eq!(md.bit_depth, 8); } #[test] fn encode8_opaque() { let img = imgref::ImgVec::new((0..101).flat_map(|y| (0..129).map(move |x| { RGBA8::new(255, 100 + x as u8, y as u8, 255) })).collect(), 129, 101); let enc = Encoder::new() .with_quality(33.0) .with_speed(10) .with_alpha_quality(33.0) .with_bit_depth(BitDepth::Auto) .with_alpha_color_mode(AlphaColorMode::UnassociatedDirty) .with_num_threads(Some(1)); let EncodedImage { avif_file, color_byte_size, alpha_byte_size , .. } = enc.encode_rgba(img.as_ref()).unwrap(); assert_eq!(0, alpha_byte_size); // the image must not have alpha let tmp_path = format!("/tmp/ravif-encode-test-failure-{color_byte_size}.avif"); if color_byte_size <= 150 || color_byte_size >= 500 { std::fs::write(&tmp_path, &avif_file).expect(&tmp_path); } assert!(color_byte_size > 150 && color_byte_size < 500, "size = {color_byte_size}; expected ~= 215; see {tmp_path}"); let parsed1 = avif_parse::read_avif(&mut avif_file.as_slice()).unwrap(); assert_eq!(None, parsed1.alpha_item); let md = parsed1.primary_item_metadata().unwrap(); assert_eq!(md.max_frame_width.get(), 129); assert_eq!(md.max_frame_height.get(), 101); assert!(md.still_picture); assert_eq!(md.bit_depth, 10); let img = img.map_buf(|b| b.into_iter().map(|px| px.rgb()).collect::>()); let enc = Encoder::new() .with_quality(33.0) .with_speed(10) .with_bit_depth(BitDepth::Ten) .with_alpha_quality(33.0) .with_alpha_color_mode(AlphaColorMode::UnassociatedDirty) .with_num_threads(Some(1)); let EncodedImage { avif_file, color_byte_size, alpha_byte_size , .. } = enc.encode_rgb(img.as_ref()).unwrap(); assert_eq!(0, alpha_byte_size); // the image must not have alpha assert!(color_byte_size > 50 && color_byte_size < 1000); let parsed2 = avif_parse::read_avif(&mut avif_file.as_slice()).unwrap(); assert_eq!(parsed1.alpha_item, parsed2.alpha_item); assert_eq!(parsed1.primary_item, parsed2.primary_item); // both are the same pixels } #[test] fn encode8_cleans_alpha() { let img = imgref::ImgVec::new((0..200).flat_map(|y| (0..256).map(move |x| { RGBA8::new((((x/ 5 + y ) & 0xF) << 4) as u8, (7 * x + y / 2) as u8, ((x * y) & 0x3) as u8, ((x + y) as u8 & 0x7F).saturating_sub(100)) })).collect(), 256, 200); let enc = Encoder::new() .with_quality(66.0) .with_speed(6) .with_alpha_quality(88.0) .with_alpha_color_mode(AlphaColorMode::UnassociatedDirty) .with_num_threads(Some(1)); let dirty = enc .encode_rgba(img.as_ref()) .unwrap(); let clean = enc .with_alpha_color_mode(AlphaColorMode::UnassociatedClean) .encode_rgba(img.as_ref()) .unwrap(); assert_eq!(clean.alpha_byte_size, dirty.alpha_byte_size); // same alpha on both assert!(clean.alpha_byte_size > 200 && clean.alpha_byte_size < 1000); assert!(clean.color_byte_size > 2000 && clean.color_byte_size < 6000); assert!(clean.color_byte_size < dirty.color_byte_size / 2); // significant reduction in color data }