Skip to content

Commit ec6e322

Browse files
feat(png): add automatic gamma detection from PNG metadata for color-accurate quantization (#152)
* Initial plan * Initial analysis of PNG quantization color distortion issue Co-authored-by: Brooooooklyn <[email protected]> * fix(png): correct gamma handling in PNG quantization to prevent color distortion Co-authored-by: Brooooooklyn <[email protected]> * feat(png): add configurable gamma parameter for non-sRGB images in quantization - Add optional gamma parameter to PngQuantOptions - Default to 0.45 (sRGB) but allow override for linear RGB (1.0) and other color spaces - Added comprehensive documentation with common gamma values - Addresses concern about non-sRGB source images Co-authored-by: Brooooooklyn <[email protected]> * feat(png): add automatic gamma detection from PNG metadata in quantization Co-authored-by: Brooooooklyn <[email protected]> * feat(png): remove manual gamma override, use only automatic detection Removed the gamma field from PngQuantOptions struct per feedback from @Brooooooklyn. Now only uses detect_gamma_from_png_info() for automatic gamma detection from PNG metadata (sRGB chunk, gAMA chunk, or sRGB fallback). Co-authored-by: Brooooooklyn <[email protected]> * remove package-lock.json and add to gitignore Co-authored-by: Brooooooklyn <[email protected]> * revert accidental yarn.lock format change and generated files Co-authored-by: Brooooooklyn <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Brooooooklyn <[email protected]>
1 parent ab7bff6 commit ec6e322

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ nasa-small.*
2424
output-overlay-png.png
2525
output-debian.jpeg
2626
*.wasm
27-
packages/binding/npm
27+
packages/binding/npm
28+
package-lock.json

packages/binding/src/png.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ fn png_quantize_inner(input: &[u8], options: &PngQuantOptions) -> Result<Vec<u8>
255255
if decoded_buf.len() < (width * height * 4) as usize {
256256
return Ok(input.to_vec());
257257
}
258+
259+
// Configure gamma for quantization - this affects color accuracy during palette generation
260+
// Automatically detect gamma from PNG metadata (gAMA chunk, sRGB chunk, or sRGB fallback)
261+
let gamma = detect_gamma_from_png_info(reader.info());
262+
258263
let mut liq = imagequant::new();
259264
liq
260265
.set_speed(options.speed.unwrap_or(5) as i32)
@@ -266,7 +271,12 @@ fn png_quantize_inner(input: &[u8], options: &PngQuantOptions) -> Result<Vec<u8>
266271
)
267272
.map_err(|err| Error::new(Status::GenericFailure, format!("{err}")))?;
268273
let mut img = liq
269-
.new_image(decoded_buf.as_rgba(), width as usize, height as usize, 0.0)
274+
.new_image(
275+
decoded_buf.as_rgba(),
276+
width as usize,
277+
height as usize,
278+
gamma
279+
)
270280
.map_err(|err| Error::new(Status::GenericFailure, format!("Create image failed {err}")))?;
271281
let mut quantization_result = liq
272282
.quantize(&mut img)
@@ -295,6 +305,33 @@ fn png_quantize_inner(input: &[u8], options: &PngQuantOptions) -> Result<Vec<u8>
295305
Ok(output)
296306
}
297307

308+
/// Detects the appropriate gamma value for quantization from PNG metadata
309+
///
310+
/// Priority order:
311+
/// 1. If sRGB chunk is present, use sRGB gamma (reciprocal ~0.45)
312+
/// 2. If gAMA chunk is present, use reciprocal of gamma value
313+
/// 3. Default fallback to 0.45 (sRGB)
314+
fn detect_gamma_from_png_info(info: &png::Info) -> f64 {
315+
// sRGB color space implies gamma ~2.2, reciprocal ~0.45
316+
if info.srgb.is_some() {
317+
return 0.45;
318+
}
319+
320+
// Check for gAMA chunk or source_gamma (same information, different representation)
321+
if let Some(gamma) = info.source_gamma {
322+
let gamma_value = gamma.into_value() as f64;
323+
return 1.0 / gamma_value;
324+
}
325+
326+
if let Some(gama) = info.gama_chunk {
327+
let gamma_value = gama.into_value() as f64;
328+
return 1.0 / gamma_value;
329+
}
330+
331+
// Default fallback to sRGB
332+
0.45
333+
}
334+
298335
pub struct PngQuantTask {
299336
input: Uint8Array,
300337
options: PngQuantOptions,

0 commit comments

Comments
 (0)