# HG changeset patch # User Link Mauve # Date 1768684945 -3600 # Node ID 11bc22bad1bfed29784a905a71837978584bca54 # Parent a29122662cde809f603b276273a84257a9694f9e python: Replace the image crate with png We weren’t using any of its features anyway, so the png crate is exactly what we need, without the many heavy dependencies of image. https://github.com/image-rs/image-png/pull/670 will eventually make it even faster to build. diff --git a/python/Cargo.toml b/python/Cargo.toml --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -16,9 +16,9 @@ touhou-formats = "*" touhou-utils = { version = "*", path = "../utils" } pyo3 = "0.27" -image = { version = "0.25", default-features = false, features = ["png"] } glob = "0.3.3" kira = "0.11.0" +png = "0.18.0" [features] default = [] diff --git a/python/src/lib.rs b/python/src/lib.rs --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,4 +1,4 @@ -use image::{DynamicImage, GenericImageView, ImageFormat}; +use png::{BitDepth, ColorType, Transformations}; use pyo3::exceptions::{PyIOError, PyKeyError}; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyTuple}; @@ -215,15 +215,15 @@ fn get_image(&self, py: Python, name: &str) -> PyResult> { let vec = self.get_file_internal(name)?; - let inner = Image::from_rgb_png(&vec); - Py::new(py, Image { inner }) + let image = Image::from_rgb_png(&vec); + Py::new(py, image) } fn get_image_with_alpha(&self, py: Python, name: &str, alpha: &str) -> PyResult> { let rgb = self.get_file_internal(name)?; let alpha = self.get_file_internal(alpha)?; - let inner = Image::from_rgb_and_alpha_png(&rgb, &alpha); - Py::new(py, Image { inner }) + let image = Image::from_rgb_and_alpha_png(&rgb, &alpha); + Py::new(py, image) } } @@ -238,43 +238,68 @@ #[pyclass(module = "libtouhou")] #[derive(Debug)] struct Image { - inner: DynamicImage, + width: u32, + height: u32, + data: Vec, } #[pymethods] impl Image { #[getter] fn dimensions(&self) -> (u32, u32) { - self.inner.dimensions() + (self.width, self.height) } #[getter] fn pixels(&self) -> &[u8] { - self.inner.as_bytes() + &self.data } } impl Image { - fn from_rgb_png(rgb: &[u8]) -> DynamicImage { - let rgb = image::load_from_memory_with_format(rgb, ImageFormat::Png).unwrap(); - rgb.into_rgba8().into() + fn load_png(data: &[u8], add_alpha: bool) -> (u32, u32, ColorType, Vec) { + let cursor = std::io::Cursor::new(data); + let mut decoder = png::Decoder::new(cursor); + // Request either rgb8 or rgba8 data. + decoder.set_transformations(if add_alpha { + Transformations::ALPHA + } else { + Transformations::EXPAND + }); + let mut reader = decoder.read_info().unwrap(); + let mut buf = vec![0; reader.output_buffer_size().unwrap()]; + let info = reader.next_frame(&mut buf).unwrap(); + assert_eq!(buf.capacity(), info.buffer_size()); + assert_eq!(info.bit_depth, BitDepth::Eight); + (info.width, info.height, info.color_type, buf) } - fn from_rgb_and_alpha_png(rgb: &[u8], alpha: &[u8]) -> DynamicImage { - let rgb = image::load_from_memory_with_format(rgb, ImageFormat::Png).unwrap(); - let alpha = image::load_from_memory_with_format(alpha, ImageFormat::Png).unwrap(); + fn from_rgb_png(rgb: &[u8]) -> Self { + let (width, height, color_type, data) = Self::load_png(rgb, true); + assert_eq!(color_type, ColorType::Rgba); + Self { width, height, data } + } - let (width, height) = rgb.dimensions(); - let DynamicImage::ImageLuma8(alpha) = alpha.grayscale() else { - panic!("Alpha couldn’t be converted to grayscale!"); - }; - let pixels = rgb - .pixels() - .zip(alpha.pixels()) - .map(|((_x, _y, rgb), luma)| [rgb[0], rgb[1], rgb[2], luma[0]]) - .flatten() - .collect::>(); - image::RgbaImage::from_vec(width, height, pixels).unwrap().into() + fn from_rgb_and_alpha_png(rgb: &[u8], alpha: &[u8]) -> Self { + let (width, height, color_type, data) = Self::load_png(rgb, false); + if color_type == ColorType::Rgba { + // TODO: Check which should be used, the alpha channel in the primary PNG, or the alpha + // mask in the secondary PNG, in case both are present (such as for Patchouli’s face). + return Self { width, height, data }; + } + + let (alpha_width, alpha_height, color_type, alpha) = Self::load_png(alpha, false); + assert_eq!(color_type, ColorType::Rgb); + assert_eq!(width, alpha_width); + assert_eq!(height, alpha_height); + + let data = data + .as_chunks::<3>().0 + .into_iter() + .zip(alpha.as_chunks::<3>().0) + .flat_map(|([r, g, b], [luma, _, _])| [*r, *g, *b, *luma]) + .collect(); + Self { width, height, data } } }