use super::parser::PictParser;
use crate::common::error::{Error, Result};
use super::data::{get_bitmap_pixel, stretch_coordinates, unpack_bits};
use super::types::{PictBitmap, PictRect};
use image::{DynamicImage, ImageBuffer, ImageFormat, Rgba, RgbaImage};
use std::io::Cursor;
#[derive(Debug, Clone)]
pub struct PictToRasterOptions {
pub width: Option<u32>,
pub height: Option<u32>,
pub background_color: Rgba<u8>,
}
impl Default for PictToRasterOptions {
fn default() -> Self {
Self {
width: None,
height: None,
background_color: Rgba([255, 255, 255, 255]),
}
}
}
#[inline]
pub fn rect_to_native(rect: &mut super::types::PictRect) {
rect.top = i16::from_be(rect.top);
rect.left = i16::from_be(rect.left);
rect.bottom = i16::from_be(rect.bottom);
rect.right = i16::from_be(rect.right);
}
#[inline]
pub fn bitmap_to_native(bitmap: &mut super::types::PictBitmap) {
bitmap.row_bytes = i16::from_be(bitmap.row_bytes);
rect_to_native(&mut bitmap.bounds);
rect_to_native(&mut bitmap.src_rect);
rect_to_native(&mut bitmap.dst_rect);
bitmap.mode = i16::from_be(bitmap.mode);
}
#[inline]
pub fn region_to_native(region: &mut super::types::PictRegion) {
region.region_size = i16::from_be(region.region_size);
rect_to_native(&mut region.rect);
}
pub struct PictConverter {
parser: PictParser,
options: PictToRasterOptions,
}
impl PictConverter {
pub fn new(parser: PictParser, options: PictToRasterOptions) -> Self {
Self { parser, options }
}
fn calculate_dimensions(&self) -> (u32, u32) {
let src_width = self.parser.width().max(1) as u32;
let src_height = self.parser.height().max(1) as u32;
match (self.options.width, self.options.height) {
(Some(w), Some(h)) => (w, h),
(Some(w), None) => {
let aspect = src_height as f64 / src_width as f64;
let h = (w as f64 * aspect) as u32;
(w, h)
}
(None, Some(h)) => {
let aspect = src_width as f64 / src_height as f64;
let w = (h as f64 * aspect) as u32;
(w, h)
}
(None, None) => {
let max_dim = 4096;
if src_width > max_dim || src_height > max_dim {
let scale = (max_dim as f64) / src_width.max(src_height) as f64;
(
(src_width as f64 * scale) as u32,
(src_height as f64 * scale) as u32,
)
} else {
(src_width, src_height)
}
}
}
}
fn extract_embedded_bitmap(&self) -> Option<DynamicImage> {
for record in &self.parser.records {
match record.opcode {
0x009A | 0x009B => {
if let Some(img) = self.parse_direct_bits(&record.data) {
return Some(img);
}
}
0x8200 => {
if let Some(img) = self.parse_compressed_quicktime(&record.data) {
return Some(img);
}
}
_ => {}
}
}
None
}
fn parse_direct_bits(&self, data: &[u8]) -> Option<DynamicImage> {
if data.len() < std::mem::size_of::<PictBitmap>() {
return None;
}
let mut bitmap: PictBitmap = unsafe {
std::ptr::read_unaligned(data.as_ptr() as *const PictBitmap)
};
bitmap_to_native(&mut bitmap);
let width = (bitmap.bounds.right - bitmap.bounds.left) as u32;
let height = (bitmap.bounds.bottom - bitmap.bounds.top) as u32;
if width == 0 || height == 0 || width > 8192 || height > 8192 {
return None;
}
let _row_bytes = bitmap.row_bytes as usize;
let bitmap_data_start = std::mem::size_of::<PictBitmap>();
let bitmap_data_end = data.len();
if bitmap_data_start >= bitmap_data_end {
return None;
}
let compressed_data = &data[bitmap_data_start..];
let mut img = ImageBuffer::new(width, height);
let mut data_offset = 0;
let expected_row_size = (width as usize).div_ceil(8);
for y in 0..height as usize {
if data_offset >= compressed_data.len() {
break;
}
if data_offset + 1 >= compressed_data.len() {
break;
}
let byte_count = compressed_data[data_offset] as usize;
data_offset += 1;
let row_compressed_start = data_offset;
let row_compressed_end = std::cmp::min(data_offset + byte_count, compressed_data.len());
data_offset = row_compressed_end;
if byte_count == 0 {
continue;
}
let row_compressed = &compressed_data[row_compressed_start..row_compressed_end];
match unpack_bits(row_compressed, expected_row_size) {
Ok(unpacked_row) => {
self.render_bitmap_row(&unpacked_row, &bitmap, y as i32, &mut img);
}
Err(_) => {
continue;
}
}
}
Some(DynamicImage::ImageRgba8(img))
}
fn render_bitmap_row(
&self,
unpacked_row: &[u8],
bitmap: &PictBitmap,
y: i32,
img: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
) {
let width = (bitmap.bounds.right - bitmap.bounds.left) as u32;
let _height = (bitmap.bounds.bottom - bitmap.bounds.top) as u32;
let src_width = bitmap.src_rect.right - bitmap.src_rect.left;
let src_height = bitmap.src_rect.bottom - bitmap.src_rect.top;
let dst_width = bitmap.dst_rect.right - bitmap.dst_rect.left;
let dst_height = bitmap.dst_rect.bottom - bitmap.dst_rect.top;
let src_rect = PictRect {
left: bitmap.src_rect.left - bitmap.bounds.left,
top: bitmap.src_rect.top - bitmap.bounds.top,
right: bitmap.src_rect.left - bitmap.bounds.left + src_width,
bottom: bitmap.src_rect.top - bitmap.bounds.top + src_height,
};
let dst_rect = PictRect {
left: bitmap.dst_rect.left - self.parser.header.frame.1,
top: bitmap.dst_rect.top - self.parser.header.frame.0,
right: bitmap.dst_rect.left - self.parser.header.frame.1 + dst_width,
bottom: bitmap.dst_rect.top - self.parser.header.frame.0 + dst_height,
};
for x in 0..width as i32 {
let mut src_x = 0;
let mut src_y = 0;
stretch_coordinates(&dst_rect, &src_rect, x, y, &mut src_x, &mut src_y);
let color_u32 = get_bitmap_pixel(unpacked_row, &bitmap.bounds, src_x, src_y);
let color = Rgba([
((color_u32 >> 16) & 0xFF) as u8, ((color_u32 >> 8) & 0xFF) as u8, (color_u32 & 0xFF) as u8, ((color_u32 >> 24) & 0xFF) as u8, ]);
if x < img.width() as i32 && y < img.height() as i32 {
img.put_pixel(x as u32, y as u32, color);
}
}
}
fn parse_compressed_quicktime(&self, data: &[u8]) -> Option<DynamicImage> {
if data.len() > 2 {
for i in 0..data.len() - 2 {
if data[i] == 0xFF && data[i + 1] == 0xD8 {
if let Ok(img) = image::load_from_memory(&data[i..]) {
return Some(img);
}
}
}
}
None
}
fn create_placeholder(&self, width: u32, height: u32) -> RgbaImage {
let mut img = ImageBuffer::from_pixel(width, height, self.options.background_color);
let border_color = Rgba([128, 128, 128, 255]);
for x in 0..width {
if x < height {
img.put_pixel(x, 0, border_color);
img.put_pixel(x, height - 1, border_color);
}
}
for y in 0..height {
if y < width {
img.put_pixel(0, y, border_color);
img.put_pixel(width - 1, y, border_color);
}
}
let min_dim = width.min(height);
for i in 0..min_dim {
img.put_pixel(i, i, border_color);
if height > i {
img.put_pixel(i, height - 1 - i, border_color);
}
}
img
}
pub fn convert_to_image(&self) -> Result<DynamicImage> {
let (target_width, target_height) = self.calculate_dimensions();
if let Some(embedded) = self.extract_embedded_bitmap() {
if embedded.width() != target_width || embedded.height() != target_height {
return Ok(DynamicImage::ImageRgba8(image::imageops::resize(
&embedded,
target_width,
target_height,
image::imageops::FilterType::Lanczos3,
)));
}
return Ok(embedded);
}
let placeholder = self.create_placeholder(target_width, target_height);
Ok(DynamicImage::ImageRgba8(placeholder))
}
pub fn convert_to_format(&self, format: ImageFormat) -> Result<Vec<u8>> {
let image = self.convert_to_image()?;
let mut buffer = Cursor::new(Vec::new());
image
.write_to(&mut buffer, format)
.map_err(|e| Error::ParseError(format!("Failed to encode image: {}", e)))?;
Ok(buffer.into_inner())
}
pub fn convert_to_png(&self) -> Result<Vec<u8>> {
self.convert_to_format(ImageFormat::Png)
}
pub fn convert_to_jpeg(&self) -> Result<Vec<u8>> {
self.convert_to_format(ImageFormat::Jpeg)
}
pub fn convert_to_webp(&self) -> Result<Vec<u8>> {
self.convert_to_format(ImageFormat::WebP)
}
}