170 lines
6.5 KiB
Rust
170 lines
6.5 KiB
Rust
pub mod types;
|
|
|
|
use pathdiff::diff_paths;
|
|
use std::{fs::File, path::PathBuf};
|
|
use slint::{Rgba8Pixel, SharedPixelBuffer};
|
|
use tool_helper::Error;
|
|
use types::Encoding;
|
|
|
|
#[derive(Clone)]
|
|
pub struct TIMInfo {
|
|
path: PathBuf,
|
|
image_data: SharedPixelBuffer<Rgba8Pixel>,
|
|
palette: Option<PaletteInfo>,
|
|
}
|
|
|
|
impl TIMInfo {
|
|
pub fn from_image(path: &PathBuf) -> Result<TIMInfo, Error> {
|
|
fn make_color(iter: &mut dyn std::iter::Iterator<Item = &u8>, load_alpha: bool) -> Option<Rgba8Pixel> {
|
|
Some(Rgba8Pixel::new(
|
|
*iter.next()?, *iter.next()?, *iter.next()?,
|
|
if load_alpha {*iter.next()?} else {0xFF}))
|
|
}
|
|
|
|
let mut reader = png::Decoder::new(File::open(path).map_err(|error| Error::from_text(format!("Failed loading \"{}\" with {}", path.to_string_lossy().as_ref(), error)))?).read_info().or_else(|error| {Err(Error::from_error(error))})?;
|
|
let info = reader.info().clone();
|
|
|
|
let mut buffer = vec![0; reader.output_buffer_size()];
|
|
let frame_info = reader.next_frame(&mut buffer).or_else(|error| {Err(Error::from_error(error))})?;
|
|
let bytes_per_pixel = info.bytes_per_pixel();
|
|
let bit_depth = frame_info.bit_depth;
|
|
let mut image_data = SharedPixelBuffer::new(frame_info.width, frame_info.height);
|
|
let mut dst_pixels = image_data.make_mut_slice();
|
|
|
|
if info.color_type == png::ColorType::Indexed {
|
|
let palette = info.palette.ok_or(Error::from_str("Found indexed PNG without palette"))?;
|
|
let mut palette_colors = Vec::new();
|
|
let mut iter = palette.iter();
|
|
|
|
while let Some(color) = make_color(&mut iter, false) {
|
|
palette_colors.push(color);
|
|
}
|
|
|
|
for byte in buffer.into_iter() {
|
|
match bit_depth {
|
|
png::BitDepth::Four => {
|
|
dst_pixels[0] = palette_colors[(byte >> 4) as usize];
|
|
dst_pixels[1] = palette_colors[(byte & 0xF) as usize];
|
|
dst_pixels = &mut dst_pixels[2..];
|
|
},
|
|
png::BitDepth::Eight => {
|
|
dst_pixels[0] = palette_colors[byte as usize];
|
|
dst_pixels = &mut dst_pixels[1..];
|
|
},
|
|
_ => {return Err(Error::from_str("Only 4 and 8bit color depth are supported for indexed color images"));}
|
|
}
|
|
}
|
|
Ok(TIMInfo{path: path.clone(), image_data, palette: Some(PaletteInfo::new(palette_colors))})
|
|
}
|
|
|
|
else {
|
|
if bytes_per_pixel != 3 && bytes_per_pixel != 4 {
|
|
return Err(Error::from_text(format!("Image has {} bytes per pixel, but only 3 and 4 bytes per pixel are supported", bytes_per_pixel)));
|
|
}
|
|
|
|
let mut byte_iter = buffer.iter();
|
|
while let Some(color) = make_color(&mut byte_iter, bytes_per_pixel == 4) {
|
|
match bit_depth {
|
|
png::BitDepth::Eight => {
|
|
dst_pixels[0] = color;
|
|
dst_pixels = &mut dst_pixels[1..];
|
|
}
|
|
_ => {return Err(Error::from_str("Only 8bit color depth are supported for direct color images"));}
|
|
}
|
|
}
|
|
Ok(TIMInfo{path: path.clone(), image_data, palette: None})
|
|
}
|
|
}
|
|
|
|
pub fn change_palette_size(&mut self, width: u16, height: u16) -> Result<Option<slint::Image>, Error> {
|
|
if let Some(palette) = &mut self.palette {
|
|
if width == 0 || height == 0 {
|
|
return Err(Error::from_text(format!("{}px x {}px is not a valid size for palette", width, height)));
|
|
}
|
|
|
|
palette.width = width;
|
|
palette.height = height;
|
|
Ok(Some(palette.get_image()))
|
|
}
|
|
|
|
else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub fn get_slint_images(&self, encoding: Encoding) -> (slint::Image, Option<slint::Image>) {
|
|
fn scaled_copy(src: &SharedPixelBuffer<Rgba8Pixel>, factor: u32) -> SharedPixelBuffer<Rgba8Pixel> {
|
|
let mut image_data = SharedPixelBuffer::<Rgba8Pixel>::new(src.width()/factor, src.height());
|
|
let mut dst_pixel = image_data.make_mut_slice();
|
|
let src_pixel = src.as_slice();
|
|
|
|
for (idx, pixel) in src_pixel.into_iter().enumerate() {
|
|
if idx%factor as usize == 0 {
|
|
dst_pixel[0] = *pixel;
|
|
dst_pixel = &mut dst_pixel[1..];
|
|
}
|
|
}
|
|
image_data
|
|
}
|
|
|
|
let image_data = match encoding {
|
|
Encoding::FourBit => scaled_copy(&self.image_data, 4),
|
|
Encoding::EightBit => scaled_copy(&self.image_data, 2),
|
|
Encoding::FullColor => self.image_data.clone()
|
|
};
|
|
|
|
(slint::Image::from_rgba8_premultiplied(image_data), if let Some(palette) = &self.palette {
|
|
Some(palette.get_image())
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
|
|
pub fn get_palette_size(&self) -> Option<(u16, u16)> {
|
|
if let Some(palette_info) = &self.palette {
|
|
Some((palette_info.width, palette_info.height))
|
|
}
|
|
|
|
else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn get_path(&self) -> std::path::PathBuf {
|
|
self.path.clone()
|
|
}
|
|
|
|
pub fn get_path_rel_to(&self, mut reference_path: std::path::PathBuf) -> Result<std::path::PathBuf, Error> {
|
|
let file_path = self.path.clone();
|
|
|
|
reference_path.pop();
|
|
diff_paths(&file_path, &reference_path).ok_or_else(|| {
|
|
Error::from_text(format!("Can not create relative path from {} to {}", file_path.to_string_lossy().as_ref(), reference_path.to_string_lossy().as_ref()))
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct PaletteInfo {
|
|
data: Vec<Rgba8Pixel>,
|
|
width: u16,
|
|
height: u16,
|
|
}
|
|
|
|
impl PaletteInfo {
|
|
pub fn new(data: Vec<Rgba8Pixel>) -> PaletteInfo {
|
|
let width = if data.len() <= 16 {16} else {256};
|
|
PaletteInfo{data, width, height: 1}
|
|
}
|
|
|
|
pub fn get_image(&self) -> slint::Image {
|
|
let mut image_data = SharedPixelBuffer::new(self.width as u32, self.height as u32);
|
|
let dst_pixels = image_data.make_mut_slice();
|
|
|
|
for (idx, byte) in dst_pixels.iter_mut().enumerate() {
|
|
*byte = if idx < self.data.len() {self.data[idx]} else {Rgba8Pixel::new(0, 0, 0, 0xFF)};
|
|
}
|
|
|
|
slint::Image::from_rgba8_premultiplied(image_data.clone())
|
|
}
|
|
} |