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 <3468483+Brooooooklyn@users.noreply.github.com>

* fix(png): correct gamma handling in PNG quantization to prevent color distortion

Co-authored-by: Brooooooklyn <3468483+Brooooooklyn@users.noreply.github.com>

* 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 <3468483+Brooooooklyn@users.noreply.github.com>

* feat(png): add automatic gamma detection from PNG metadata in quantization

Co-authored-by: Brooooooklyn <3468483+Brooooooklyn@users.noreply.github.com>

* 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 <3468483+Brooooooklyn@users.noreply.github.com>

* remove package-lock.json and add to gitignore

Co-authored-by: Brooooooklyn <3468483+Brooooooklyn@users.noreply.github.com>

* revert accidental yarn.lock format change and generated files

Co-authored-by: Brooooooklyn <3468483+Brooooooklyn@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Brooooooklyn <3468483+Brooooooklyn@users.noreply.github.com>
This commit is contained in:
Copilot 2025-09-04 10:00:26 +08:00 committed by GitHub
parent ab7bff6929
commit ec6e322148
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 2 deletions

3
.gitignore vendored
View File

@ -24,4 +24,5 @@ nasa-small.*
output-overlay-png.png
output-debian.jpeg
*.wasm
packages/binding/npm
packages/binding/npm
package-lock.json

View File

@ -255,6 +255,11 @@ fn png_quantize_inner(input: &[u8], options: &PngQuantOptions) -> Result<Vec<u8>
if decoded_buf.len() < (width * height * 4) as usize {
return Ok(input.to_vec());
}
// Configure gamma for quantization - this affects color accuracy during palette generation
// Automatically detect gamma from PNG metadata (gAMA chunk, sRGB chunk, or sRGB fallback)
let gamma = detect_gamma_from_png_info(reader.info());
let mut liq = imagequant::new();
liq
.set_speed(options.speed.unwrap_or(5) as i32)
@ -266,7 +271,12 @@ fn png_quantize_inner(input: &[u8], options: &PngQuantOptions) -> Result<Vec<u8>
)
.map_err(|err| Error::new(Status::GenericFailure, format!("{err}")))?;
let mut img = liq
.new_image(decoded_buf.as_rgba(), width as usize, height as usize, 0.0)
.new_image(
decoded_buf.as_rgba(),
width as usize,
height as usize,
gamma
)
.map_err(|err| Error::new(Status::GenericFailure, format!("Create image failed {err}")))?;
let mut quantization_result = liq
.quantize(&mut img)
@ -295,6 +305,33 @@ fn png_quantize_inner(input: &[u8], options: &PngQuantOptions) -> Result<Vec<u8>
Ok(output)
}
/// Detects the appropriate gamma value for quantization from PNG metadata
///
/// Priority order:
/// 1. If sRGB chunk is present, use sRGB gamma (reciprocal ~0.45)
/// 2. If gAMA chunk is present, use reciprocal of gamma value
/// 3. Default fallback to 0.45 (sRGB)
fn detect_gamma_from_png_info(info: &png::Info) -> f64 {
// sRGB color space implies gamma ~2.2, reciprocal ~0.45
if info.srgb.is_some() {
return 0.45;
}
// Check for gAMA chunk or source_gamma (same information, different representation)
if let Some(gamma) = info.source_gamma {
let gamma_value = gamma.into_value() as f64;
return 1.0 / gamma_value;
}
if let Some(gama) = info.gama_chunk {
let gamma_value = gama.into_value() as f64;
return 1.0 / gamma_value;
}
// Default fallback to sRGB
0.45
}
pub struct PngQuantTask {
input: Uint8Array,
options: PngQuantOptions,