Support TIM conversion
This commit is contained in:
parent
18b74814c4
commit
7cd008f3f4
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "psxfileconv"
|
name = "psxfileconv"
|
||||||
version = "0.7.5"
|
version = "0.8.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use clap::{Args, ValueEnum};
|
use clap::{Args, ValueEnum};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct Arguments {
|
pub struct Arguments {
|
||||||
|
@ -15,6 +16,38 @@ pub struct Arguments {
|
||||||
pub transparent_palette: bool
|
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<Self, Self::Err> {
|
||||||
|
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)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||||
pub enum ColorType{
|
pub enum ColorType{
|
||||||
Clut4,
|
Clut4,
|
||||||
|
|
|
@ -5,10 +5,10 @@ pub mod reduced_tim;
|
||||||
pub mod tim;
|
pub mod tim;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use args::{ColorType, ClutAlignment};
|
use args::{ColorType, ClutAlignment, Point};
|
||||||
use color_clut::{IndexedImage, OutputType};
|
use color_clut::{IndexedImage, OutputType};
|
||||||
use color_full16::{RgbaImage, RgbImage};
|
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 image::{DynamicImage, io::Reader as ImageReader};
|
||||||
use std::io::{Cursor, Write};
|
use std::io::{Cursor, Write};
|
||||||
use tool_helper::{Error, Input};
|
use tool_helper::{Error, Input};
|
||||||
|
@ -36,7 +36,7 @@ fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_trans
|
||||||
image
|
image
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode<T: PSXImageConverter>(header_conv: &mut dyn HeaderEncoder, image: T, color_depth: ColorType, clut_align: ClutAlignment, output: &mut dyn Write) -> Result<(), Error> {
|
fn encode<T: PSXImageConverter>(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) = {
|
let (width, height) = {
|
||||||
fn return_error(clut_type: u32, div: u32, width: u16, height: u16) -> Result<(u16, u16), Error> {
|
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)}));
|
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<T: PSXImageConverter>(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_header(output)?;
|
||||||
header_conv.write_clut_header(output)?;
|
header_conv.write_clut_header(output)?;
|
||||||
|
@ -118,12 +118,12 @@ fn encode<T: PSXImageConverter>(header_conv: &mut dyn HeaderEncoder, image: T, c
|
||||||
Ok(())
|
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() {
|
match ImageReader::new(Cursor::new(tool_helper::input_to_vec(input)?)).with_guessed_format()?.decode() {
|
||||||
Ok(image) => {
|
Ok(image) => {
|
||||||
match image {
|
match image {
|
||||||
DynamicImage::ImageRgb8(image) => encode(header_conv, RgbImage::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), 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"))
|
_ => 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() {
|
match png::Decoder::new(input).read_info() {
|
||||||
Ok(reader) => {
|
Ok(reader) => {
|
||||||
let output_type = {
|
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))
|
Err(error) => Err(Error::from_error(error))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use super::args::ColorType;
|
use super::args::{ColorType, Point};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use types::Header;
|
use types::Header;
|
||||||
use tool_helper::{Error, Input};
|
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> {
|
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||||
let mut header_conv = Header::default();
|
let mut header_conv = Header::default();
|
||||||
match args.color_depth {
|
match args.color_depth {
|
||||||
ColorType::Full16 => super::convert_full16(&mut header_conv, input, output),
|
ColorType::Full16 => super::convert_full16(&mut header_conv, input, output, Point::default()),
|
||||||
_ => super::convert_palette_based(&mut header_conv, input, output, args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette),
|
_ => 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),
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 std::io::Write;
|
||||||
use tool_helper::{bits::BitRange, raw::RawConversion, Error};
|
use tool_helper::{bits::BitRange, raw::RawConversion, Error};
|
||||||
|
|
||||||
|
@ -21,7 +21,12 @@ impl Default for Header {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeaderEncoder 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 {
|
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)))
|
Err(Error::from_text(format!("Image size (width: {}, height: {}) needs to be even", tex_width, tex_height)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,29 @@
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use super::args::ColorType;
|
use super::args::{ColorType, Point};
|
||||||
|
use clap::Args;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use types::Header;
|
use types::Header;
|
||||||
use tool_helper::{Error, Input};
|
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> {
|
pub fn convert(args: Arguments, input: Input, output: &mut dyn Write) -> Result<(), Error> {
|
||||||
|
let global_args = args.global;
|
||||||
let mut header_conv = Header::default();
|
let mut header_conv = Header::default();
|
||||||
match args.color_depth {
|
|
||||||
ColorType::Full16 => super::convert_full16(&mut header_conv, input, output),
|
match global_args.color_depth {
|
||||||
_ => 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, 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),
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use super::super::{args::ColorType, types::HeaderEncoder};
|
use super::super::{args::ColorType, types::{HeaderEncoder, Rect}};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tool_helper::{bits::{Bit, BitRange}, raw::RawConversion, Error};
|
use tool_helper::{bits::{Bit, BitRange}, raw::RawConversion, Error};
|
||||||
|
|
||||||
|
@ -26,16 +26,15 @@ impl Default for Header {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeaderEncoder 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 {
|
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::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::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,
|
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(clut_rect);
|
||||||
self.clut_block = DataBlock::new(0, 0, clut_width, clut_height);
|
self.pixel_block = DataBlock::new(tex_rect);
|
||||||
self.pixel_block = DataBlock::new(0, 0, tex_width, tex_height);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +63,12 @@ pub struct DataBlock {
|
||||||
impl DataBlock {
|
impl DataBlock {
|
||||||
const RAW_HEADER_SIZE: usize = (4*std::mem::size_of::<u16>()) + std::mem::size_of::<u32>();
|
const RAW_HEADER_SIZE: usize = (4*std::mem::size_of::<u16>()) + std::mem::size_of::<u32>();
|
||||||
|
|
||||||
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::<u16>()) + Self::RAW_HEADER_SIZE) as u32;
|
let bytes = ((w as usize*h as usize*std::mem::size_of::<u16>()) + Self::RAW_HEADER_SIZE) as u32;
|
||||||
DataBlock{bytes, x, y, w, h}
|
DataBlock{bytes, x, y, w, h}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
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<usize, Error>;
|
fn write_header(&self, output: &mut dyn Write) -> Result<usize, Error>;
|
||||||
fn write_clut_header(&self, output: &mut dyn Write) -> Result<usize, Error>;
|
fn write_clut_header(&self, output: &mut dyn Write) -> Result<usize, Error>;
|
||||||
fn write_pixel_header(&self, output: &mut dyn Write) -> Result<usize, Error>;
|
fn write_pixel_header(&self, output: &mut dyn Write) -> Result<usize, Error>;
|
||||||
|
|
Loading…
Reference in New Issue