blob: 0dfa81d16f52df5c1555691b858e7ab47b82f3b6 [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 = "png_codec::rust_png")]
mod ffi {
extern "Rust" {
fn decode_png(
src: &[u8],
reverse_byte_order: bool,
width: &mut i32,
height: &mut i32,
) -> Vec<u8>;
fn encode_bgr(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec<u8>;
fn encode_rgba(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec<u8>;
fn encode_bgra(
input: &[u8],
width: i32,
height: i32,
row_byte_width: usize,
discard_transparency: bool,
) -> Vec<u8>;
fn encode_gray(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec<u8>;
}
}
fn decode_png(
src: &[u8],
reverse_byte_order: bool,
width_out: &mut i32,
height_out: &mut i32,
) -> Vec<u8> {
*width_out = 0;
*height_out = 0;
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 Vec::new(),
};
let buf_size = match reader.output_buffer_size() {
Some(s) => s,
None => return Vec::new(),
};
let mut buf = vec![0; buf_size];
let output_info = match reader.next_frame(&mut buf) {
Ok(info) => info,
Err(_) => return Vec::new(),
};
let width = output_info.width as usize;
let height = output_info.height as usize;
if width == 0 || height == 0 {
return Vec::new();
}
let line_size = output_info.line_size;
if line_size < width {
return Vec::new();
}
let bytes_per_pixel = line_size / width;
if bytes_per_pixel == 0 || bytes_per_pixel > 4 {
return Vec::new();
}
if let Some(total_src_bytes) = line_size.checked_mul(height) {
if total_src_bytes > buf.len() {
return Vec::new();
}
} else {
return Vec::new();
}
let out_size = match width.checked_mul(height).and_then(|wh| wh.checked_mul(4)) {
Some(s) => s,
None => return Vec::new(),
};
let mut out = vec![0u8; out_size];
// 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 = y * width * 4;
let dst_slice = &mut out[dst_start..dst_start + width * 4];
for x in 0..width {
let (r, g, b, a) = match bytes_per_pixel {
1 => {
let gray = gamma_lut[src_row[x] as usize];
(gray, gray, gray, 255)
}
2 => {
let gray = gamma_lut[src_row[x * 2] as usize];
let alpha = src_row[x * 2 + 1];
(gray, gray, gray, alpha)
}
3 => {
let red = gamma_lut[src_row[x * 3] as usize];
let green = gamma_lut[src_row[x * 3 + 1] as usize];
let blue = gamma_lut[src_row[x * 3 + 2] as usize];
(red, green, blue, 255)
}
4 => {
let red = gamma_lut[src_row[x * 4] as usize];
let green = gamma_lut[src_row[x * 4 + 1] as usize];
let blue = gamma_lut[src_row[x * 4 + 2] as usize];
let alpha = src_row[x * 4 + 3];
(red, green, blue, alpha)
}
_ => return Vec::new(),
};
if reverse_byte_order {
dst_slice[x * 4] = b;
dst_slice[x * 4 + 1] = g;
dst_slice[x * 4 + 2] = r;
dst_slice[x * 4 + 3] = a;
} else {
dst_slice[x * 4] = r;
dst_slice[x * 4 + 1] = g;
dst_slice[x * 4 + 2] = b;
dst_slice[x * 4 + 3] = a;
}
}
}
*width_out = width as i32;
*height_out = height as i32;
out
}
fn write_png(data: &[u8], width: u32, height: u32, color_type: png::ColorType) -> Vec<u8> {
let mut out = Vec::new();
{
let mut encoder = png::Encoder::new(&mut out, width, height);
encoder.set_color(color_type);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = match encoder.write_header() {
Ok(w) => w,
Err(_) => return Vec::new(),
};
if writer.write_image_data(data).is_err() {
return Vec::new();
}
}
out
}
fn encode_bgr(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec<u8> {
if width <= 0 || height <= 0 {
return Vec::new();
}
let w = width as usize;
let h = height as usize;
if row_byte_width < w * 3 {
return Vec::new();
}
if let Some(total_input) = row_byte_width.checked_mul(h) {
if total_input > input.len() {
return Vec::new();
}
} else {
return Vec::new();
}
let mut rgb = vec![0u8; w * h * 3];
for y in 0..h {
let src_row = &input[y * row_byte_width..y * row_byte_width + w * 3];
let dst_row = &mut rgb[y * w * 3..(y + 1) * w * 3];
for x in 0..w {
dst_row[x * 3] = src_row[x * 3 + 2]; // R
dst_row[x * 3 + 1] = src_row[x * 3 + 1]; // G
dst_row[x * 3 + 2] = src_row[x * 3]; // B
}
}
write_png(&rgb, width as u32, height as u32, png::ColorType::Rgb)
}
fn encode_rgba(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec<u8> {
if width <= 0 || height <= 0 {
return Vec::new();
}
let w = width as usize;
let h = height as usize;
if row_byte_width < w * 4 {
return Vec::new();
}
if let Some(total_input) = row_byte_width.checked_mul(h) {
if total_input > input.len() {
return Vec::new();
}
} else {
return Vec::new();
}
if row_byte_width == w * 4 {
return write_png(&input[..w * h * 4], width as u32, height as u32, png::ColorType::Rgba);
}
let mut rgba = vec![0u8; w * h * 4];
for y in 0..h {
let src_row = &input[y * row_byte_width..y * row_byte_width + w * 4];
let dst_row = &mut rgba[y * w * 4..(y + 1) * w * 4];
dst_row.copy_from_slice(src_row);
}
write_png(&rgba, width as u32, height as u32, png::ColorType::Rgba)
}
fn encode_bgra(
input: &[u8],
width: i32,
height: i32,
row_byte_width: usize,
discard_transparency: bool,
) -> Vec<u8> {
if width <= 0 || height <= 0 {
return Vec::new();
}
let w = width as usize;
let h = height as usize;
if row_byte_width < w * 4 {
return Vec::new();
}
if let Some(total_input) = row_byte_width.checked_mul(h) {
if total_input > input.len() {
return Vec::new();
}
} else {
return Vec::new();
}
if discard_transparency {
let mut rgb = vec![0u8; w * h * 3];
for y in 0..h {
let src_row = &input[y * row_byte_width..y * row_byte_width + w * 4];
let dst_row = &mut rgb[y * w * 3..(y + 1) * w * 3];
for x in 0..w {
dst_row[x * 3] = src_row[x * 4 + 2]; // R
dst_row[x * 3 + 1] = src_row[x * 4 + 1]; // G
dst_row[x * 3 + 2] = src_row[x * 4]; // B
}
}
write_png(&rgb, width as u32, height as u32, png::ColorType::Rgb)
} else {
let mut rgba = vec![0u8; w * h * 4];
for y in 0..h {
let src_row = &input[y * row_byte_width..y * row_byte_width + w * 4];
let dst_row = &mut rgba[y * w * 4..(y + 1) * w * 4];
for x in 0..w {
dst_row[x * 4] = src_row[x * 4 + 2]; // R
dst_row[x * 4 + 1] = src_row[x * 4 + 1]; // G
dst_row[x * 4 + 2] = src_row[x * 4]; // B
dst_row[x * 4 + 3] = src_row[x * 4 + 3]; // A
}
}
write_png(&rgba, width as u32, height as u32, png::ColorType::Rgba)
}
}
fn encode_gray(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec<u8> {
if width <= 0 || height <= 0 {
return Vec::new();
}
let w = width as usize;
let h = height as usize;
if row_byte_width < w {
return Vec::new();
}
if let Some(total_input) = row_byte_width.checked_mul(h) {
if total_input > input.len() {
return Vec::new();
}
} else {
return Vec::new();
}
if row_byte_width == w {
return write_png(&input[..w * h], width as u32, height as u32, png::ColorType::Grayscale);
}
let mut gray = vec![0u8; w * h];
for y in 0..h {
let src_row = &input[y * row_byte_width..y * row_byte_width + w];
let dst_row = &mut gray[y * w..(y + 1) * w];
dst_row.copy_from_slice(src_row);
}
write_png(&gray, width as u32, height as u32, png::ColorType::Grayscale)
}