diff --git a/Cargo.lock b/Cargo.lock index ba317b4..3876864 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.7" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -158,18 +158,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.17" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80932e03c33999b9235edb8655bc9df3204adc9887c2f95b50cb1deb9fd54253" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.17" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c0db58c659eef1c73e444d298c27322a1b52f6927d2ad470c0c0f96fa7b8fa" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -289,18 +289,18 @@ dependencies = [ [[package]] name = "gif" -version = "0.13.0-beta.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87170da9d9cad96ecabfe781c1a4be8c6e44c0a055b04b3764b13434f56c865b" +checksum = "130a857b01e74bf2ca95a20baa639bf6c823b5ce40aa7532e48264298b255c54" dependencies = [ "weezl", ] [[package]] name = "gif-dispose" -version = "5.0.0-beta.1" +version = "5.0.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea1b872e439de2650f74102dfcd67f15af97df9f1b196a5205695575a36ecc8" +checksum = "b0d20a3802e15ff705c260e39152ff1987145a1c5ae016bc3d510abceb45b9ed" dependencies = [ "gif", "imgref", @@ -309,7 +309,7 @@ dependencies = [ [[package]] name = "gifski" -version = "1.14.0" +version = "1.14.1" dependencies = [ "clap", "crossbeam-channel", @@ -348,15 +348,15 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "imagequant" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d51957ac48371e8e2eaaeb4eba56150ff2109c1c8c200002afb7dd6e2d260f" +checksum = "85a7f142d232ccbdc00cbef49d17f45639aeb07d9bfe28e17c21dea3efac64e5" dependencies = [ "arrayvec", "once_cell", @@ -367,9 +367,9 @@ dependencies = [ [[package]] name = "imgref" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90d944e334f00f4449c9640b440a171f816be0152305c12ef90424fc35fd035c" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] name = "lazy_static" @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "lodepng" -version = "3.9.3" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f494e828d9924c447ae3755f4f7398a2bda231eb004ad4396830d1f106e11d12" +checksum = "a42d298694b14401847de29abd44adf278b42e989e516deac7b72018400002d8" dependencies = [ "crc32fast", "fallible_collections", @@ -502,15 +502,15 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -532,9 +532,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", @@ -542,9 +542,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -552,9 +552,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -564,9 +564,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" dependencies = [ "aho-corasick", "memchr", @@ -581,9 +581,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "resize" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6a672c8cfa0b43a11c629acdbbe4e552a92566c835932cd1b2456e57232de3" +checksum = "c3e29f584c07a8396c5e2eee0bd8d7aec5c8d9e0a3c2333806fd2ec1d2a5b080" dependencies = [ "rayon", "rgb", @@ -606,9 +606,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "shlex" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "strsim" @@ -674,9 +674,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "weezl" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wild" diff --git a/Cargo.toml b/Cargo.toml index a32a971..4744b60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "AGPL-3.0-or-later" name = "gifski" readme = "README.md" repository = "https://github.com/ImageOptim/gifski" -version = "1.14.0" +version = "1.14.1" autobins = false edition = "2021" rust-version = "1.65" @@ -22,9 +22,9 @@ required-features = ["binary"] [dependencies] clap = { version = "4.3.24", features = ["cargo"], optional = true } -gif = { version = "0.13.0-beta.1", default-features = false, features = ["std", "raii_no_panic"] } -gif-dispose = "5.0.0-beta.1" imgref = "1.10.0" +gif = { version = "0.13", default-features = false, features = ["std", "raii_no_panic"] } +gif-dispose = "5.0.0-beta.2" imagequant = "4.3.0" lodepng = { version = "3.10.1", optional = true } pbr = { version = "1.1.1", optional = true } @@ -48,7 +48,7 @@ default-features = false features = ["codec", "format", "filter", "software-resampling", "software-scaling"] [dev-dependencies] -lodepng = "3.9.3" +lodepng = "3.10.1" [features] # `cargo build` will skip the binaries with missing `required-features` diff --git a/src/bin/gif.rs b/src/bin/gif.rs index 7df6c3f..39316d9 100644 --- a/src/bin/gif.rs +++ b/src/bin/gif.rs @@ -38,7 +38,7 @@ impl Source for GifDecoder { let mut delay_ts = 0; while let Some(frame) = self.decoder.read_next_frame()? { self.screen.blit_frame(frame)?; - let pixels = self.screen.pixels.clone(); + let pixels = self.screen.pixels_rgba().map_buf(|b| b.to_owned()); let presentation_timestamp = f64::from(delay_ts) * (f64::from(self.speed) / 100.); c.add_frame_rgba(idx, pixels, presentation_timestamp)?; idx += 1; diff --git a/src/encoderust.rs b/src/encoderust.rs index 3356ff2..49fb2f8 100644 --- a/src/encoderust.rs +++ b/src/encoderust.rs @@ -6,6 +6,7 @@ use rgb::ComponentBytes; use rgb::RGB8; use std::cell::Cell; use std::io::Write; +use std::iter::repeat; use std::rc::Rc; #[cfg(feature = "gifsicle")] @@ -53,11 +54,13 @@ impl RustEncoder { let (buffer, width, height) = image.into_contiguous_buf(); - let mut pal_rgb = Vec::with_capacity(3 * pal.len()); - for p in &pal { - pal_rgb.extend_from_slice([p.rgb()].as_bytes()); + let mut pal_rgb = pal.as_bytes().to_vec(); + // Palette should be power-of-two sized + if pal.len() != 256 { + let needed_size = 3 * pal.len().max(2).next_power_of_two(); + pal_rgb.extend(repeat([115,107,105,46,103,105,102]).flat_map(|x| x).take(needed_size - pal_rgb.len())); + debug_assert_eq!(needed_size, pal_rgb.len()); } - let mut frame = gif::Frame { delay: 1, // TBD dispose, diff --git a/src/lib.rs b/src/lib.rs index 0929b92..8aaf8b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,7 +144,7 @@ struct GIFFrame { left: u16, top: u16, image: ImgVec, - pal: Vec, + pal: Vec, dispose: DisposalMethod, transparent_index: Option, } @@ -424,7 +424,8 @@ impl Writer { }; liq.set_quality(0, quality)?; if self.settings.s.quality < 50 { - liq.set_max_colors(u32::from(self.settings.s.quality * 2).max(16).next_power_of_two())?; + let min_colors = 5 + self.fixed_colors.len() as u32; + liq.set_max_colors(u32::from(self.settings.s.quality * 2).max(min_colors).next_power_of_two().min(256))?; } let (buf, width, height) = image.into_contiguous_buf(); let mut img = liq.new_image(buf, width, height, 0.)?; @@ -759,23 +760,24 @@ impl Writer { fn remap_frames(&self, mut inputs: OrdQueueIter, write_queue: Sender) -> CatResult<()> { let mut frame_index = 0; let first_frame = inputs.next().ok_or(Error::NoFrames)?; - let mut screen = gif_dispose::Screen::new(first_frame.liq_image.width(), first_frame.liq_image.height(), RGBA8::new(0, 0, 0, 0), None); + let mut screen = gif_dispose::Screen::new(first_frame.liq_image.width(), first_frame.liq_image.height(), None); let mut next_frame = Some(first_frame); while let Some(RemapMessage {ordinal_frame_number, end_pts, dispose, liq, remap, liq_image, out_buf, has_next_frame}) = next_frame { - let screen_width = screen.pixels.width() as u16; - let screen_height = screen.pixels.height() as u16; - let mut screen_after_dispose = screen.dispose(); + let pixels = screen.pixels_rgba(); + let screen_width = pixels.width() as u16; + let screen_height = pixels.height() as u16; + let mut screen_after_dispose = screen.dispose_only(); - let (mut image8, mut image8_pal) = { - let bg = if frame_index != 0 { Some(screen_after_dispose.pixels()) } else { None }; + let (mut image8, image8_pal) = { + let bg = if frame_index != 0 { Some(screen_after_dispose.pixels_rgba()) } else { None }; self.remap(liq, remap, liq_image, bg, out_buf)? }; - let transparent_index = transparent_index_from_palette(&mut image8_pal, image8.as_mut()); + let (image8_pal, transparent_index) = transparent_index_from_palette(image8_pal, image8.as_mut()); let (left, top) = if frame_index != 0 && has_next_frame { - let (left, top, new_width, new_height) = trim_image(image8.as_ref(), &image8_pal, transparent_index, dispose, screen_after_dispose.pixels()) + let (left, top, new_width, new_height) = trim_image(image8.as_ref(), &image8_pal, transparent_index, dispose, screen_after_dispose.pixels_rgba()) .unwrap_or((0, 0, 1, 1)); if new_width != image8.width() || new_height != image8.height() { let new_buf = image8.sub_image(left.into(), top.into(), new_width, new_height).to_contiguous_buf().0.into_owned(); @@ -811,12 +813,12 @@ impl Writer { } } -fn transparent_index_from_palette(image8_pal: &mut [RGBA], mut image8: ImgRefMut) -> Option { +fn transparent_index_from_palette(mut image8_pal: Vec, mut image8: ImgRefMut) -> (Vec, Option) { // Palette may have multiple transparent indices :( let mut transparent_index = None; for (i, p) in image8_pal.iter_mut().enumerate() { if p.a <= 128 { - p.a = 0; + *p = RGBA8::new(71,80,76,0); let new_index = i as u8; if let Some(old_index) = transparent_index { image8.pixels_mut().filter(|px| **px == new_index).for_each(|px| *px = old_index); @@ -831,7 +833,7 @@ fn transparent_index_from_palette(image8_pal: &mut [RGBA], mut image8: ImgRe Some(idx as u8) == transparent_index || color.a > 128 || !image8.pixels().any(|px| px == idx as u8) })); - transparent_index + (image8_pal.into_iter().map(|r| r.rgb()).collect(), transparent_index) } /// When one thread unexpectedly fails, all other threads fail with Aborted, but that Aborted isn't the relevant cause @@ -847,7 +849,7 @@ fn combine_res(res1: Result<(), Error>, res2: Result<(), Error>) -> Result<(), E } } -fn trim_image(mut image_trimmed: ImgRef, image8_pal: &[RGBA8], transparent_index: Option, dispose: DisposalMethod, mut screen: ImgRef) -> Option<(u16, u16, usize, usize)> { +fn trim_image(mut image_trimmed: ImgRef, image8_pal: &[RGB8], transparent_index: Option, dispose: DisposalMethod, mut screen: ImgRef) -> Option<(u16, u16, usize, usize)> { let is_matching_pixel = move |px: u8, bg: RGBA8| -> bool { if Some(px) == transparent_index { if dispose == DisposalMethod::Keep { @@ -858,7 +860,7 @@ fn trim_image(mut image_trimmed: ImgRef, image8_pal: &[RGBA8], transparent_i bg.a == 0 } } else { - image8_pal.get(px as usize).copied().unwrap_or_default() == bg + image8_pal.get(px as usize).map(|px| px.alpha(255)).unwrap_or_default() == bg } }; diff --git a/tests/tests.rs b/tests/tests.rs index 99e8f70..92ff71f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -95,7 +95,7 @@ fn for_each_frame(mut gif_data: &[u8], mut cb: impl FnMut(&gif::Frame, ImgRef