blob: ef8853a2a8e8bfe64a16721533e101e530bb5830 [file]
// Copyright 2026 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::io::Cursor;
#[cxx::bridge(namespace = "fxcodec::rust_png")]
mod ffi {
pub struct PngHeaderInfo {
pub width: u32,
pub height: u32,
}
extern "Rust" {
fn read_png_info(src: &[u8], info: &mut PngHeaderInfo) -> bool;
fn decode_png_to_buf(src: &[u8], dst: &mut [u8], dst_pitch: usize) -> bool;
}
}
fn read_png_info(src: &[u8], info: &mut ffi::PngHeaderInfo) -> bool {
if src.len() >= 24
&& &src[0..8] == b"\x89PNG\r\n\x1a\n"
&& &src[12..16] == b"IHDR"
&& let (Ok(w), Ok(h)) = (src[16..20].try_into(), src[20..24].try_into())
{
let width = u32::from_be_bytes(w);
let height = u32::from_be_bytes(h);
if width > 0 && height > 0 {
info.width = width;
info.height = height;
return true;
}
}
let cursor = Cursor::new(src);
let decoder = png::Decoder::new(cursor);
let reader = match decoder.read_info() {
Ok(r) => r,
Err(_) => return false,
};
let header = reader.info();
if header.width == 0 || header.height == 0 {
return false;
}
info.width = header.width;
info.height = header.height;
true
}
fn decode_png_to_buf(src: &[u8], dst: &mut [u8], dst_pitch: usize) -> bool {
let cursor = Cursor::new(src);
let mut decoder = png::Decoder::new(cursor);
decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16);
let mut reader = match decoder.read_info() {
Ok(r) => r,
Err(_) => return false,
};
let buf_size = match reader.output_buffer_size() {
Some(s) => s,
None => return false,
};
let mut buf = vec![0; buf_size];
let output_info = match reader.next_frame(&mut buf) {
Ok(info) => info,
Err(_) => return false,
};
let width = output_info.width as usize;
let height = output_info.height as usize;
if width == 0 || height == 0 {
return false;
}
let line_size = output_info.line_size;
if line_size < width {
return false;
}
let bytes_per_pixel = line_size / width;
if bytes_per_pixel == 0 || bytes_per_pixel > 4 {
return false;
}
if let Some(total_src_bytes) = line_size.checked_mul(height) {
if total_src_bytes > buf.len() {
return false;
}
} else {
return false;
}
let row_dst_bytes = match width.checked_mul(4) {
Some(b) => b,
None => return false,
};
// Standard default file gamma for sRGB (1.0 / 2.2).
const DEFAULT_FILE_GAMMA: f64 = 0.45455;
let file_gamma: f64 = if reader.info().srgb.is_some() {
DEFAULT_FILE_GAMMA
} else if let Some(gama) = reader.info().gama_chunk {
let val = gama.into_value() as f64;
if val > 0.0 {
val
} else {
DEFAULT_FILE_GAMMA
}
} else {
DEFAULT_FILE_GAMMA
};
const DISPLAY_GAMMA: f64 = 2.2;
let mut gamma_lut = [0u8; 256];
let exponent = 1.0 / (file_gamma * DISPLAY_GAMMA);
if (exponent - 1.0).abs() < 1e-4 {
for (i, item) in gamma_lut.iter_mut().enumerate() {
*item = i as u8;
}
} else {
for (i, item) in gamma_lut.iter_mut().enumerate() {
let v = (i as f64) / 255.0;
*item = (v.powf(exponent) * 255.0).round().clamp(0.0, 255.0) as u8;
}
}
for y in 0..height {
let src_start = y * line_size;
let src_row = &buf[src_start..src_start + line_size];
let dst_start = match y.checked_mul(dst_pitch) {
Some(s) => s,
None => return false,
};
let dst_end = match dst_start.checked_add(row_dst_bytes) {
Some(e) => e,
None => return false,
};
if dst_end > dst.len() {
return false;
}
let dst_slice = &mut dst[dst_start..dst_end];
if bytes_per_pixel == 1 {
for x in 0..width {
let g = gamma_lut[src_row[x] as usize];
dst_slice[x * 4] = g;
dst_slice[x * 4 + 1] = g;
dst_slice[x * 4 + 2] = g;
dst_slice[x * 4 + 3] = 255;
}
} else if bytes_per_pixel == 2 {
for x in 0..width {
let g = gamma_lut[src_row[x * 2] as usize];
let a = src_row[x * 2 + 1];
dst_slice[x * 4] = g;
dst_slice[x * 4 + 1] = g;
dst_slice[x * 4 + 2] = g;
dst_slice[x * 4 + 3] = a;
}
} else if bytes_per_pixel == 3 {
for x in 0..width {
let r = gamma_lut[src_row[x * 3] as usize];
let g = gamma_lut[src_row[x * 3 + 1] as usize];
let b = gamma_lut[src_row[x * 3 + 2] as usize];
dst_slice[x * 4] = b;
dst_slice[x * 4 + 1] = g;
dst_slice[x * 4 + 2] = r;
dst_slice[x * 4 + 3] = 255;
}
} else if bytes_per_pixel == 4 {
for x in 0..width {
let r = gamma_lut[src_row[x * 4] as usize];
let g = gamma_lut[src_row[x * 4 + 1] as usize];
let b = gamma_lut[src_row[x * 4 + 2] as usize];
let a = src_row[x * 4 + 3];
dst_slice[x * 4] = b;
dst_slice[x * 4 + 1] = g;
dst_slice[x * 4 + 2] = r;
dst_slice[x * 4 + 3] = a;
}
} else {
return false;
}
}
true
}