From 1eeed73c5f64fc34d63f2f2673a89ca046b5c500 Mon Sep 17 00:00:00 2001 From: jaby Date: Sun, 29 Dec 2024 14:06:04 +0100 Subject: [PATCH] Support TIM conversion --- src/Tools/psxfileconv/Cargo.toml | 2 +- src/Tools/psxfileconv/src/images/args.rs | 33 +++++++++++++++++++ src/Tools/psxfileconv/src/images/mod.rs | 18 +++++----- .../psxfileconv/src/images/reduced_tim/mod.rs | 6 ++-- .../src/images/reduced_tim/types.rs | 9 +++-- src/Tools/psxfileconv/src/images/tim/mod.rs | 23 ++++++++++--- src/Tools/psxfileconv/src/images/tim/types.rs | 16 +++++---- src/Tools/psxfileconv/src/images/types.rs | 15 ++++++++- 8 files changed, 95 insertions(+), 27 deletions(-) diff --git a/src/Tools/psxfileconv/Cargo.toml b/src/Tools/psxfileconv/Cargo.toml index e7030c75..3b6a2079 100644 --- a/src/Tools/psxfileconv/Cargo.toml +++ b/src/Tools/psxfileconv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "psxfileconv" -version = "0.7.5" +version = "0.8.5" edition = "2021" [profile.release] diff --git a/src/Tools/psxfileconv/src/images/args.rs b/src/Tools/psxfileconv/src/images/args.rs index 0891885d..166b0c27 100644 --- a/src/Tools/psxfileconv/src/images/args.rs +++ b/src/Tools/psxfileconv/src/images/args.rs @@ -1,4 +1,5 @@ use clap::{Args, ValueEnum}; +use std::str::FromStr; #[derive(Args)] pub struct Arguments { @@ -15,6 +16,38 @@ pub struct Arguments { pub transparent_palette: bool } +#[derive(Clone)] +pub struct Point { + pub x: u16, + pub y: u16, +} + +impl Point { + pub const POINT_VALUE_NAME:&'static str = "{x,y}"; +} + +impl std::default::Default for Point { + fn default() -> Self { + Point{x: 0, y: 0} + } +} + +impl FromStr for Point { + type Err = String; + + fn from_str(s: &str) -> Result { + let values:Vec<&str> = s.split(&['{', ',', '}']).filter_map(|value| if value.is_empty() {None} else {Some(value)}).collect(); + + if values.len() != 2 { + return Err(format!("Two values expected for Point but found {}", values.len())); + } + + let x = values[0].parse().map_err(|e| format!("Failed converting 'x' for Point: {e}"))?; + let y = values[1].parse().map_err(|e| format!("Failed converting 'y' for Point: {e}"))?; + Ok(Point{x, y}) + } +} + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum ColorType{ Clut4, diff --git a/src/Tools/psxfileconv/src/images/mod.rs b/src/Tools/psxfileconv/src/images/mod.rs index 95ca5119..71b86bd2 100644 --- a/src/Tools/psxfileconv/src/images/mod.rs +++ b/src/Tools/psxfileconv/src/images/mod.rs @@ -5,10 +5,10 @@ pub mod reduced_tim; pub mod tim; pub mod types; -use args::{ColorType, ClutAlignment}; +use args::{ColorType, ClutAlignment, Point}; use color_clut::{IndexedImage, OutputType}; use color_full16::{RgbaImage, RgbImage}; -use types::{Color as PSXColor, HeaderEncoder, PSXImageConverter}; +use types::{Color as PSXColor, HeaderEncoder, PSXImageConverter, Rect}; use image::{DynamicImage, io::Reader as ImageReader}; use std::io::{Cursor, Write}; use tool_helper::{Error, Input}; @@ -36,7 +36,7 @@ fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_trans image } -fn encode(header_conv: &mut dyn HeaderEncoder, image: T, color_depth: ColorType, clut_align: ClutAlignment, output: &mut dyn Write) -> Result<(), Error> { +fn encode(header_conv: &mut dyn HeaderEncoder, image: T, tex_pos: Point, clut_pos: Point, color_depth: ColorType, clut_align: ClutAlignment, output: &mut dyn Write) -> Result<(), Error> { let (width, height) = { fn return_error(clut_type: u32, div: u32, width: u16, height: u16) -> Result<(u16, u16), Error> { return Err(Error::from_callback(|| {format!("CLUT {} images require a width divideable by {} (found width: {}/{}={}, height: {})", clut_type, div, width, div, (width as f32/div as f32), height)})); @@ -93,7 +93,7 @@ fn encode(header_conv: &mut dyn HeaderEncoder, image: T, c } }; - header_conv.encode_settings(color_depth, width, height, pal_width, pal_height)?; + header_conv.encode_settings(color_depth, Rect::new(tex_pos.x, tex_pos.y, width, height), Rect::new(clut_pos.x, clut_pos.y, pal_width, pal_height))?; header_conv.write_header(output)?; header_conv.write_clut_header(output)?; @@ -118,12 +118,12 @@ fn encode(header_conv: &mut dyn HeaderEncoder, image: T, c Ok(()) } -fn convert_full16(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write) -> Result<(), Error> { +fn convert_full16(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, tex_pos: Point) -> Result<(), Error> { match ImageReader::new(Cursor::new(tool_helper::input_to_vec(input)?)).with_guessed_format()?.decode() { Ok(image) => { match image { - DynamicImage::ImageRgb8(image) => encode(header_conv, RgbImage::new(image), ColorType::Full16, ClutAlignment::None, output), - DynamicImage::ImageRgba8(image) => encode(header_conv, RgbaImage::new(image), ColorType::Full16, ClutAlignment::None, output), + DynamicImage::ImageRgb8(image) => encode(header_conv, RgbImage::new(image), tex_pos, Point::default(), ColorType::Full16, ClutAlignment::None, output), + DynamicImage::ImageRgba8(image) => encode(header_conv, RgbaImage::new(image), tex_pos, Point::default(), ColorType::Full16, ClutAlignment::None, output), _ => Err(Error::from_str("Only RGB and RGBA images are supported for 16bit encoding")) } @@ -132,7 +132,7 @@ fn convert_full16(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mu } } -fn convert_palette_based(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, color_type: ColorType, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> Result<(), Error> { +fn convert_palette_based(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, tex_pos: Point, clut_pos: Point, color_type: ColorType, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> Result<(), Error> { match png::Decoder::new(input).read_info() { Ok(reader) => { let output_type = { @@ -143,7 +143,7 @@ fn convert_palette_based(header_conv: &mut dyn HeaderEncoder, input: Input, outp } }; - encode(header_conv, modify_palette(IndexedImage::new(reader, output_type)?, clut_align, semi_transparent, transparent_palette), color_type, clut_align, output) + encode(header_conv, modify_palette(IndexedImage::new(reader, output_type)?, clut_align, semi_transparent, transparent_palette), tex_pos, clut_pos, color_type, clut_align, output) }, Err(error) => Err(Error::from_error(error)) } diff --git a/src/Tools/psxfileconv/src/images/reduced_tim/mod.rs b/src/Tools/psxfileconv/src/images/reduced_tim/mod.rs index f1e08d38..6db242b1 100644 --- a/src/Tools/psxfileconv/src/images/reduced_tim/mod.rs +++ b/src/Tools/psxfileconv/src/images/reduced_tim/mod.rs @@ -1,6 +1,6 @@ pub mod types; -use super::args::ColorType; +use super::args::{ColorType, Point}; use std::io::Write; use types::Header; use tool_helper::{Error, Input}; @@ -10,7 +10,7 @@ pub type Arguments = super::args::Arguments; pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> { let mut header_conv = Header::default(); match args.color_depth { - ColorType::Full16 => super::convert_full16(&mut header_conv, input, output), - _ => super::convert_palette_based(&mut header_conv, input, output, args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette), + ColorType::Full16 => super::convert_full16(&mut header_conv, input, output, Point::default()), + _ => super::convert_palette_based(&mut header_conv, input, output, Point::default(), Point::default(), args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette), } } \ No newline at end of file diff --git a/src/Tools/psxfileconv/src/images/reduced_tim/types.rs b/src/Tools/psxfileconv/src/images/reduced_tim/types.rs index e1d58363..5c91093d 100644 --- a/src/Tools/psxfileconv/src/images/reduced_tim/types.rs +++ b/src/Tools/psxfileconv/src/images/reduced_tim/types.rs @@ -1,4 +1,4 @@ -use super::super::{args::ColorType, types::{HeaderEncoder, set_member_value}}; +use super::super::{args::ColorType, types::{HeaderEncoder, set_member_value, Rect}}; use std::io::Write; use tool_helper::{bits::BitRange, raw::RawConversion, Error}; @@ -21,7 +21,12 @@ impl Default for Header { } impl HeaderEncoder for Header { - fn encode_settings(&mut self, _color_type: ColorType, tex_width: u16, tex_height: u16, clut_width: u16, clut_height: u16) -> Result<(), Error> { + fn encode_settings(&mut self, _color_type: ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error> { + let clut_width = clut_rect.width; + let clut_height = clut_rect.height; + let tex_width = tex_rect.width; + let tex_height = tex_rect.height; + if tex_width & 1 == 1 || tex_height & 1 == 1 { Err(Error::from_text(format!("Image size (width: {}, height: {}) needs to be even", tex_width, tex_height))) } diff --git a/src/Tools/psxfileconv/src/images/tim/mod.rs b/src/Tools/psxfileconv/src/images/tim/mod.rs index f1e08d38..20a67f59 100644 --- a/src/Tools/psxfileconv/src/images/tim/mod.rs +++ b/src/Tools/psxfileconv/src/images/tim/mod.rs @@ -1,16 +1,29 @@ pub mod types; -use super::args::ColorType; +use super::args::{ColorType, Point}; +use clap::Args; use std::io::Write; use types::Header; use tool_helper::{Error, Input}; -pub type Arguments = super::args::Arguments; +#[derive(Args)] +pub struct Arguments { + #[clap(flatten)] + global: super::args::Arguments, + + #[clap(long, value_parser, value_name = Point::POINT_VALUE_NAME)] + clut_pos: Point, + + #[clap(long, value_parser, value_name = Point::POINT_VALUE_NAME)] + tex_pos: Point, +} pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> { + let global_args = args.global; let mut header_conv = Header::default(); - match args.color_depth { - ColorType::Full16 => super::convert_full16(&mut header_conv, input, output), - _ => super::convert_palette_based(&mut header_conv, input, output, args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette), + + match global_args.color_depth { + ColorType::Full16 => super::convert_full16(&mut header_conv, input, output, args.tex_pos), + _ => super::convert_palette_based(&mut header_conv, input, output, args.tex_pos, args.clut_pos, global_args.color_depth, global_args.clut_align, global_args.semi_transparent, global_args.transparent_palette), } } \ No newline at end of file diff --git a/src/Tools/psxfileconv/src/images/tim/types.rs b/src/Tools/psxfileconv/src/images/tim/types.rs index b786dd4c..fa4959c0 100644 --- a/src/Tools/psxfileconv/src/images/tim/types.rs +++ b/src/Tools/psxfileconv/src/images/tim/types.rs @@ -1,4 +1,4 @@ -use super::super::{args::ColorType, types::HeaderEncoder}; +use super::super::{args::ColorType, types::{HeaderEncoder, Rect}}; use std::io::Write; use tool_helper::{bits::{Bit, BitRange}, raw::RawConversion, Error}; @@ -26,16 +26,15 @@ impl Default for Header { } impl HeaderEncoder for Header { - fn encode_settings(&mut self, color_type: ColorType, tex_width: u16, tex_height: u16, clut_width: u16, clut_height: u16) -> Result<(), Error> { + fn encode_settings(&mut self, color_type: ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error> { self.flag = match color_type { ColorType::Clut4 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x0) | Self::FLAG_CF_BIT.as_value(true)) as u32, ColorType::Clut8 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x1) | Self::FLAG_CF_BIT.as_value(true)) as u32, ColorType::Full16 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x2) | Self::FLAG_CF_BIT.as_value(true)) as u32, }; - // TODO: Support tex & clut x/y - self.clut_block = DataBlock::new(0, 0, clut_width, clut_height); - self.pixel_block = DataBlock::new(0, 0, tex_width, tex_height); + self.clut_block = DataBlock::new(clut_rect); + self.pixel_block = DataBlock::new(tex_rect); Ok(()) } @@ -64,7 +63,12 @@ pub struct DataBlock { impl DataBlock { const RAW_HEADER_SIZE: usize = (4*std::mem::size_of::()) + std::mem::size_of::(); - pub fn new(x: u16, y: u16, w: u16, h: u16) -> DataBlock { + pub fn new(rect: Rect) -> DataBlock { + let x = rect.x; + let y = rect.y; + let w = rect.width; + let h = rect.height; + let bytes = ((w as usize*h as usize*std::mem::size_of::()) + Self::RAW_HEADER_SIZE) as u32; DataBlock{bytes, x, y, w, h} } diff --git a/src/Tools/psxfileconv/src/images/types.rs b/src/Tools/psxfileconv/src/images/types.rs index 5015e14b..bd70ecb3 100644 --- a/src/Tools/psxfileconv/src/images/types.rs +++ b/src/Tools/psxfileconv/src/images/types.rs @@ -123,8 +123,21 @@ impl RawConversion<2> for Color { } } +pub struct Rect { + pub x: u16, + pub y: u16, + pub width: u16, + pub height: u16, +} + +impl Rect { + pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect { + Rect{x, y, width, height} + } +} + pub trait HeaderEncoder { - fn encode_settings(&mut self, color_type: args::ColorType, tex_width: u16, tex_height: u16, clut_width: u16, clut_height: u16) -> Result<(), Error>; + fn encode_settings(&mut self, color_type: args::ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error>; fn write_header(&self, output: &mut dyn Write) -> Result; fn write_clut_header(&self, output: &mut dyn Write) -> Result; fn write_pixel_header(&self, output: &mut dyn Write) -> Result;