use std::path::Path;
use ag_psd::{get_composite_image_data, read_psd};
use ag_psd::psd::ReadOptions;
use image::{DynamicImage, RgbaImage};
const COMPOSITE_MEMORY_LIMIT: usize = 256 * 1024 * 1024;
const MAX_COMPOSITE_DIMENSION: u32 = 12_000;
const MAX_COMPOSITE_FILE_BYTES: u64 = 500 * 1024 * 1024;
pub fn extract_psd_composite(path: &Path, max_dim: u32) -> Option<DynamicImage> {
let meta = std::fs::metadata(path).ok()?;
if meta.len() > MAX_COMPOSITE_FILE_BYTES {
log::debug!(
"PSD composite 跳过:文件过大 {} bytes: {:?}",
meta.len(),
path
);
return None;
}
let bytes = std::fs::read(path).ok()?;
let opts = ReadOptions {
skip_layer_image_data: Some(true),
skip_thumbnail: Some(true),
total_memory_limit: Some(COMPOSITE_MEMORY_LIMIT),
..Default::default()
};
let psd = read_psd(&bytes, &opts).ok()?;
let doc_w = psd.width as u32;
let doc_h = psd.height as u32;
if doc_w == 0 || doc_h == 0 {
return None;
}
if doc_w > MAX_COMPOSITE_DIMENSION || doc_h > MAX_COMPOSITE_DIMENSION {
log::debug!(
"PSD composite 跳过:文档尺寸过大 {}x{}: {:?}",
doc_w,
doc_h,
path
);
return None;
}
if let Some(canvas) = &psd.canvas {
return pixel_data_to_image(canvas).map(|img| scale_to_max_dim(img, max_dim));
}
if let Some(image_data) = &psd.image_data {
return pixel_data_to_image(image_data).map(|img| scale_to_max_dim(img, max_dim));
}
let pixels = get_composite_image_data(&psd).ok()??;
pixel_data_to_image(&pixels).map(|img| scale_to_max_dim(img, max_dim))
}
fn pixel_data_to_image(pixels: &ag_psd::psd::PixelData) -> Option<DynamicImage> {
let expected = (pixels.width as usize)
.checked_mul(pixels.height as usize)?
.checked_mul(4)?;
if pixels.data.len() < expected {
return None;
}
RgbaImage::from_raw(pixels.width, pixels.height, pixels.data.clone())
.map(DynamicImage::ImageRgba8)
}
fn scale_to_max_dim(img: DynamicImage, max_dim: u32) -> DynamicImage {
if img.width() > max_dim || img.height() > max_dim {
img.thumbnail(max_dim, max_dim)
} else {
img
}
}