From f6787f832f024dff79860536d9a4c8c0b53fcf72 Mon Sep 17 00:00:00 2001 From: William Calliari Date: Sun, 23 Aug 2026 13:14:05 +0200 Subject: [PATCH] fix: pass the number of channels from the source to the encoding --- cells/cell-image-proto/src/lib.rs | 1 + cells/cell-image/src/main.rs | 18 ++++++++-------- cells/cell-jxl-proto/src/lib.rs | 1 + cells/cell-jxl/src/main.rs | 6 +++--- cells/cell-webp-proto/src/lib.rs | 1 + cells/cell-webp/src/main.rs | 10 ++++++--- crates/dodeca/src/cells.rs | 26 ++++++++++++++++++++--- crates/dodeca/src/image.rs | 34 ++++++++++++++++++++++++------- 8 files changed, 73 insertions(+), 24 deletions(-) diff --git a/cells/cell-image-proto/src/lib.rs b/cells/cell-image-proto/src/lib.rs index e3b92d26c..6beb029aa 100644 --- a/cells/cell-image-proto/src/lib.rs +++ b/cells/cell-image-proto/src/lib.rs @@ -41,6 +41,7 @@ pub struct ThumbhashInput { pub pixels: Vec, pub width: u32, pub height: u32, + pub channels: u8, } /// Image processor interface. diff --git a/cells/cell-image/src/main.rs b/cells/cell-image/src/main.rs index 0e6ef07c1..061a8e303 100644 --- a/cells/cell-image/src/main.rs +++ b/cells/cell-image/src/main.rs @@ -58,14 +58,16 @@ impl ImageProcessor for ImageProcessorImpl { } async fn generate_thumbhash_data_url(&self, input: ThumbhashInput) -> ImageResult { - let img = match pixels_to_dynamic_image(&input.pixels, input.width, input.height, 4) { - Some(img) => img, - None => { - return ImageResult::Error { - message: "Invalid pixel data".to_string(), - }; - } - }; + let img = + match pixels_to_dynamic_image(&input.pixels, input.width, input.height, input.channels) + { + Some(img) => img, + None => { + return ImageResult::Error { + message: "Invalid pixel data".to_string(), + }; + } + }; // Thumbhash works best with small images, resize if needed let thumb_img = if input.width > 100 || input.height > 100 { diff --git a/cells/cell-jxl-proto/src/lib.rs b/cells/cell-jxl-proto/src/lib.rs index a944faf8c..5069400f6 100644 --- a/cells/cell-jxl-proto/src/lib.rs +++ b/cells/cell-jxl-proto/src/lib.rs @@ -11,6 +11,7 @@ pub struct JXLEncodeInput { pub width: u32, pub height: u32, pub quality: u8, + pub channels: u8, } /// Result of JXL processing operations diff --git a/cells/cell-jxl/src/main.rs b/cells/cell-jxl/src/main.rs index a6670d8a8..793020bb4 100644 --- a/cells/cell-jxl/src/main.rs +++ b/cells/cell-jxl/src/main.rs @@ -40,11 +40,11 @@ impl JXLProcessor for JXLProcessorImpl { } async fn encode_jxl(&self, input: JXLEncodeInput) -> JXLResult { - if input.pixels.len() != (input.width * input.height * 4) as usize { + if input.pixels.len() != (input.width * input.height * input.channels as u32) as usize { return JXLResult::Error { message: format!( "Expected {} bytes for {}x{} RGBA, got {}", - input.width * input.height * 4, + input.width * input.height * input.channels as u32, input.width, input.height, input.pixels.len() @@ -71,7 +71,7 @@ impl JXLProcessor for JXLProcessorImpl { }; encoder.has_alpha = true; - let frame = EncoderFrame::new(&input.pixels).num_channels(4); + let frame = EncoderFrame::new(&input.pixels).num_channels(input.channels as u32); let result = match encoder.encode_frame::<_, u8>(&frame, input.width, input.height) { Ok(r) => r, Err(e) => { diff --git a/cells/cell-webp-proto/src/lib.rs b/cells/cell-webp-proto/src/lib.rs index b6a100d89..0c3977975 100644 --- a/cells/cell-webp-proto/src/lib.rs +++ b/cells/cell-webp-proto/src/lib.rs @@ -11,6 +11,7 @@ pub struct WebPEncodeInput { pub width: u32, pub height: u32, pub quality: u8, + pub channels: u8, } /// Result of WebP processing operations diff --git a/cells/cell-webp/src/main.rs b/cells/cell-webp/src/main.rs index 75e940d76..060818502 100644 --- a/cells/cell-webp/src/main.rs +++ b/cells/cell-webp/src/main.rs @@ -29,11 +29,11 @@ impl WebPProcessor for WebPProcessorImpl { } async fn encode_webp(&self, input: WebPEncodeInput) -> WebPResult { - if input.pixels.len() != (input.width * input.height * 4) as usize { + if input.pixels.len() != (input.width * input.height * input.channels as u32) as usize { return WebPResult::Error { message: format!( "Expected {} bytes for {}x{} RGBA, got {}", - input.width * input.height * 4, + input.width * input.height * input.channels as u32, input.width, input.height, input.pixels.len() @@ -41,7 +41,11 @@ impl WebPProcessor for WebPProcessorImpl { }; } - let encoder = webp::Encoder::from_rgba(&input.pixels, input.width, input.height); + let encoder = if input.channels == 4 { + webp::Encoder::from_rgba(&input.pixels, input.width, input.height) + } else { + webp::Encoder::from_rgb(&input.pixels, input.width, input.height) + }; let webp = encoder.encode(input.quality as f32); WebPResult::EncodeSuccess { diff --git a/crates/dodeca/src/cells.rs b/crates/dodeca/src/cells.rs index f0da26067..ab659000d 100644 --- a/crates/dodeca/src/cells.rs +++ b/crates/dodeca/src/cells.rs @@ -585,11 +585,17 @@ pub async fn resize_image( } } -pub async fn generate_thumbhash(pixels: &[u8], width: u32, height: u32) -> Option { +pub async fn generate_thumbhash( + pixels: &[u8], + width: u32, + height: u32, + channels: u8, +) -> Option { let input = ThumbhashInput { pixels: pixels.to_vec(), width, height, + channels, }; match ddc_cell_image::ImageProcessorImpl .generate_thumbhash_data_url(input) @@ -604,12 +610,19 @@ pub async fn generate_thumbhash(pixels: &[u8], width: u32, height: u32) -> Optio } } -pub async fn encode_webp(pixels: &[u8], width: u32, height: u32, quality: u8) -> Option> { +pub async fn encode_webp( + pixels: &[u8], + width: u32, + height: u32, + quality: u8, + channels: u8, +) -> Option> { let input = WebPEncodeInput { pixels: pixels.to_vec(), width, height, quality, + channels, }; match ddc_cell_webp::WebPProcessorImpl.encode_webp(input).await { WebPResult::EncodeSuccess { data } => Some(data), @@ -621,12 +634,19 @@ pub async fn encode_webp(pixels: &[u8], width: u32, height: u32, quality: u8) -> } } -pub async fn encode_jxl(pixels: &[u8], width: u32, height: u32, quality: u8) -> Option> { +pub async fn encode_jxl( + pixels: &[u8], + width: u32, + height: u32, + quality: u8, + channels: u8, +) -> Option> { let input = JXLEncodeInput { pixels: pixels.to_vec(), width, height, quality, + channels, }; match ddc_cell_jxl::JXLProcessorImpl.encode_jxl(input).await { JXLResult::EncodeSuccess { data } => Some(data), diff --git a/crates/dodeca/src/image.rs b/crates/dodeca/src/image.rs index 3066611eb..52b65cf5b 100644 --- a/crates/dodeca/src/image.rs +++ b/crates/dodeca/src/image.rs @@ -128,18 +128,24 @@ async fn resize_image(decoded: &DecodedImage, target_width: u32) -> Option Option { - cells::generate_thumbhash(&decoded.pixels, decoded.width, decoded.height).await + cells::generate_thumbhash( + &decoded.pixels, + decoded.width, + decoded.height, + decoded.channels, + ) + .await } /// Encode pixels to WebP format. -async fn encode_webp(pixels: &[u8], width: u32, height: u32) -> Option> { - cells::encode_webp(pixels, width, height, 82).await +async fn encode_webp(pixels: &[u8], width: u32, height: u32, channels: u8) -> Option> { + cells::encode_webp(pixels, width, height, 82, channels).await } /// Encode pixels to JPEG-XL format. -async fn encode_jxl(pixels: &[u8], width: u32, height: u32) -> Option> { +async fn encode_jxl(pixels: &[u8], width: u32, height: u32, channels: u8) -> Option> { // Quality 80 maps to distance ~3 in the cell (high quality) - cells::encode_jxl(pixels, width, height, 80).await + cells::encode_jxl(pixels, width, height, 80, channels).await } /// Image metadata without the processed bytes @@ -219,7 +225,14 @@ pub async fn process_image(data: &[u8], input_format: InputFormat) -> Option Option