Shuffle around more functions
This commit is contained in:
parent
b718fa5c4b
commit
e7fd734e0c
|
@ -8,11 +8,11 @@ pub struct Arguments {
|
||||||
#[clap(value_enum, value_parser, default_value_t=ClutAlignment::None)]
|
#[clap(value_enum, value_parser, default_value_t=ClutAlignment::None)]
|
||||||
pub clut_align: ClutAlignment,
|
pub clut_align: ClutAlignment,
|
||||||
|
|
||||||
#[clap(long=SemiTransparent::NAME, default_value_t=false)]
|
#[clap(long="semi-trans", default_value_t=false)]
|
||||||
pub semi_transparent: SemiTransparent::Type,
|
pub semi_transparent: bool,
|
||||||
|
|
||||||
#[clap(long=TransparentPalette::NAME, default_value_t=false)]
|
#[clap(long="color-trans", default_value_t=false)]
|
||||||
pub transparent_palette: TransparentPalette::Type
|
pub transparent_palette: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||||
|
@ -27,16 +27,4 @@ pub enum ClutAlignment {
|
||||||
None,
|
None,
|
||||||
Linear,
|
Linear,
|
||||||
Block
|
Block
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub mod SemiTransparent {
|
|
||||||
pub type Type = bool;
|
|
||||||
pub const NAME:&'static str = "semi-trans";
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub mod TransparentPalette {
|
|
||||||
pub type Type = bool;
|
|
||||||
pub const NAME:&'static str = "";
|
|
||||||
}
|
|
|
@ -3,4 +3,147 @@ pub mod color_clut;
|
||||||
pub mod color_full16;
|
pub mod color_full16;
|
||||||
pub mod reduced_tim;
|
pub mod reduced_tim;
|
||||||
pub mod tim;
|
pub mod tim;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
|
use args::{ColorType, ClutAlignment};
|
||||||
|
use color_clut::{IndexedImage, OutputType};
|
||||||
|
use color_full16::{RgbaImage, RgbImage};
|
||||||
|
use types::{Color as PSXColor, PSXImageConverter};
|
||||||
|
use image::{DynamicImage, io::Reader as ImageReader};
|
||||||
|
use std::io::{Cursor, Write};
|
||||||
|
use tool_helper::{Error, Input};
|
||||||
|
use reduced_tim::types::Header;
|
||||||
|
|
||||||
|
fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> IndexedImage {
|
||||||
|
if semi_transparent {
|
||||||
|
for color in image.palette.iter_mut() {
|
||||||
|
*color = PSXColor::semi_transparent(color.get_red(), color.get_green(), color.get_blue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if transparent_palette {
|
||||||
|
if clut_align == ClutAlignment::Block {
|
||||||
|
for color in image.palette.iter_mut().step_by(16) {
|
||||||
|
*color = PSXColor::transparent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
if let Some(first_color) = image.palette.get_mut(0) {
|
||||||
|
*first_color = PSXColor::transparent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode<T: PSXImageConverter>(image: T, 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)}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = image.width();
|
||||||
|
let height = image.height();
|
||||||
|
match color_depth {
|
||||||
|
ColorType::Clut4 => {
|
||||||
|
if width & 3 == 0 {
|
||||||
|
Ok((width/4, height))
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return_error(4, 4, width, height)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ColorType::Clut8 => {
|
||||||
|
if width & 1 == 0 {
|
||||||
|
Ok((width/2, height))
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return_error(8, 2, width, height)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ColorType::Full16 => {
|
||||||
|
Ok((width, height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
let palette = image.get_palette();
|
||||||
|
let (pal_width, pal_height) = {
|
||||||
|
if let Some(palette) = &palette {
|
||||||
|
let pal_length_adjusted = {
|
||||||
|
let pal_length = palette.len();
|
||||||
|
if pal_length <= 16 {
|
||||||
|
16u16
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
256u16
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match clut_align {
|
||||||
|
ClutAlignment::None |
|
||||||
|
ClutAlignment::Linear => (pal_length_adjusted, 1u16),
|
||||||
|
ClutAlignment::Block => (16u16, pal_length_adjusted/16u16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
(0u16, 0u16)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let header = Header::encode(width, height, pal_width, pal_height).ok_or(Error::from_callback(|| {format!("Image size (width: {}, height: {}) needs to be even", width, height)}))?;
|
||||||
|
|
||||||
|
tool_helper::raw::write_raw(output, &header)?;
|
||||||
|
if let Some(palette) = palette {
|
||||||
|
let mut color_count = pal_width*pal_height;
|
||||||
|
for color in palette {
|
||||||
|
tool_helper::raw::write_raw(output, color)?;
|
||||||
|
color_count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while color_count > 0 {
|
||||||
|
tool_helper::raw::write_raw(output, &PSXColor::black())?;
|
||||||
|
color_count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for color in image {
|
||||||
|
tool_helper::raw::write_raw(output, &color)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_full16(input: Input, output: &mut dyn Write) -> 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(RgbImage::new(image), ColorType::Full16, ClutAlignment::None, output),
|
||||||
|
DynamicImage::ImageRgba8(image) => encode(RgbaImage::new(image), ColorType::Full16, ClutAlignment::None, output),
|
||||||
|
|
||||||
|
_ => Err(Error::from_str("Only RGB and RGBA images are supported for 16bit encoding"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => Err(Error::from_error(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_palette_based(input: Input, output: &mut dyn Write, 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 = {
|
||||||
|
match color_type {
|
||||||
|
ColorType::Clut4 => OutputType::FourBit,
|
||||||
|
ColorType::Clut8 => OutputType::EightBit,
|
||||||
|
_ => return Err(Error::from_str("ColorType not supported"))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
encode(modify_palette(IndexedImage::new(reader, output_type)?, clut_align, semi_transparent, transparent_palette), color_type, clut_align, output)
|
||||||
|
},
|
||||||
|
Err(error) => Err(Error::from_error(error))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,150 +1,14 @@
|
||||||
use super::args::{ColorType, ClutAlignment};
|
pub mod types;
|
||||||
use super::color_clut::{IndexedImage, OutputType};
|
|
||||||
use super::color_full16::{RgbaImage, RgbImage};
|
use super::args::ColorType;
|
||||||
use super::types::{Header, Color as PSXColor, PSXImageConverter};
|
use std::io::Write;
|
||||||
use image::{DynamicImage, io::Reader as ImageReader};
|
|
||||||
use std::io::{Cursor, Write};
|
|
||||||
use tool_helper::{Error, Input};
|
use tool_helper::{Error, Input};
|
||||||
|
|
||||||
pub type Arguments = super::args::Arguments;
|
pub type Arguments = super::args::Arguments;
|
||||||
|
|
||||||
fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> IndexedImage {
|
|
||||||
if semi_transparent {
|
|
||||||
for color in image.palette.iter_mut() {
|
|
||||||
*color = PSXColor::semi_transparent(color.get_red(), color.get_green(), color.get_blue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if transparent_palette {
|
|
||||||
if clut_align == ClutAlignment::Block {
|
|
||||||
for color in image.palette.iter_mut().step_by(16) {
|
|
||||||
*color = PSXColor::transparent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
if let Some(first_color) = image.palette.get_mut(0) {
|
|
||||||
*first_color = PSXColor::transparent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
image
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encode<T: PSXImageConverter>(image: T, 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)}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let width = image.width();
|
|
||||||
let height = image.height();
|
|
||||||
match color_depth {
|
|
||||||
ColorType::Clut4 => {
|
|
||||||
if width & 3 == 0 {
|
|
||||||
Ok((width/4, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
return_error(4, 4, width, height)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ColorType::Clut8 => {
|
|
||||||
if width & 1 == 0 {
|
|
||||||
Ok((width/2, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
return_error(8, 2, width, height)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ColorType::Full16 => {
|
|
||||||
Ok((width, height))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
let palette = image.get_palette();
|
|
||||||
let (pal_width, pal_height) = {
|
|
||||||
if let Some(palette) = &palette {
|
|
||||||
let pal_length_adjusted = {
|
|
||||||
let pal_length = palette.len();
|
|
||||||
if pal_length <= 16 {
|
|
||||||
16u16
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
256u16
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match clut_align {
|
|
||||||
ClutAlignment::None |
|
|
||||||
ClutAlignment::Linear => (pal_length_adjusted, 1u16),
|
|
||||||
ClutAlignment::Block => (16u16, pal_length_adjusted/16u16),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
(0u16, 0u16)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let header = Header::encode(width, height, pal_width, pal_height).ok_or(Error::from_callback(|| {format!("Image size (width: {}, height: {}) needs to be even", width, height)}))?;
|
|
||||||
|
|
||||||
tool_helper::raw::write_raw(output, &header)?;
|
|
||||||
if let Some(palette) = palette {
|
|
||||||
let mut color_count = pal_width*pal_height;
|
|
||||||
for color in palette {
|
|
||||||
tool_helper::raw::write_raw(output, color)?;
|
|
||||||
color_count -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while color_count > 0 {
|
|
||||||
tool_helper::raw::write_raw(output, &PSXColor::black())?;
|
|
||||||
color_count -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for color in image {
|
|
||||||
tool_helper::raw::write_raw(output, &color)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_full16(input: Input, output: &mut dyn Write) -> 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(RgbImage::new(image), ColorType::Full16, ClutAlignment::None, output),
|
|
||||||
DynamicImage::ImageRgba8(image) => encode(RgbaImage::new(image), ColorType::Full16, ClutAlignment::None, output),
|
|
||||||
|
|
||||||
_ => Err(Error::from_str("Only RGB and RGBA images are supported for 16bit encoding"))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(error) => Err(Error::from_error(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_palette_based(input: Input, output: &mut dyn Write, 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 = {
|
|
||||||
match color_type {
|
|
||||||
ColorType::Clut4 => OutputType::FourBit,
|
|
||||||
ColorType::Clut8 => OutputType::EightBit,
|
|
||||||
_ => return Err(Error::from_str("ColorType not supported"))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
encode(modify_palette(IndexedImage::new(reader, output_type)?, clut_align, semi_transparent, transparent_palette), color_type, clut_align, output)
|
|
||||||
},
|
|
||||||
Err(error) => Err(Error::from_error(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
||||||
match args.color_depth {
|
match args.color_depth {
|
||||||
ColorType::Full16 => convert_full16(input, output),
|
ColorType::Full16 => super::convert_full16(input, output),
|
||||||
_ => convert_palette_based(input, output, args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette),
|
_ => super::convert_palette_based(input, output, args.color_depth, args.clut_align, args.semi_transparent, args.transparent_palette),
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
use super::super::types::set_member_value;
|
||||||
|
use tool_helper::{bits::BitRange, raw::RawConversion};
|
||||||
|
|
||||||
|
#[repr(packed(1))]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Header {
|
||||||
|
value: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl Header {
|
||||||
|
const TEX_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(0, 8);
|
||||||
|
const TEX_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(9, 16);
|
||||||
|
const CLUT_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(17, 22);
|
||||||
|
const CLUT_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(23, 31);
|
||||||
|
|
||||||
|
pub fn encode(tex_width: u16, tex_height: u16, clut_width: u16, clut_height: u16) -> Option<Header> {
|
||||||
|
if tex_width & 1 == 1 || tex_height & 1 == 1 {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0,
|
||||||
|
tex_width, 1, u32),
|
||||||
|
tex_height, 1, u32),
|
||||||
|
clut_width, 4, u32),
|
||||||
|
clut_height, 0, u32);
|
||||||
|
|
||||||
|
Some(Header{value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawConversion<4> for Header {
|
||||||
|
fn convert_to_raw(&self) -> [u8; 4] {
|
||||||
|
self.value.to_le_bytes()
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,20 +6,16 @@ pub struct Color {
|
||||||
value: u16
|
value: u16
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed(1))]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct Header {
|
|
||||||
value: u32
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! set_member_value {
|
macro_rules! set_member_value {
|
||||||
($dst:expr, $color:ident, $shift:expr, $bits:ident) => {
|
($dst:expr, $color:ident, $shift:expr, $bits:ident) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
bits::[< set_value_ $bits >]($dst, ($color >> $shift) as $bits, &Self::[< $color:upper _BIT_RANGE >])
|
tool_helper::bits::[< set_value_ $bits >]($dst, ($color >> $shift) as $bits, &Self::[< $color:upper _BIT_RANGE >])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) use set_member_value;
|
||||||
|
|
||||||
macro_rules! make_member_getter_setter {
|
macro_rules! make_member_getter_setter {
|
||||||
($color:ident, $shift:expr, $bits:ident) => {
|
($color:ident, $shift:expr, $bits:ident) => {
|
||||||
paste::item! {
|
paste::item! {
|
||||||
|
@ -124,36 +120,6 @@ impl RawConversion<2> for Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl Header {
|
|
||||||
const TEX_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(0, 8);
|
|
||||||
const TEX_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(9, 16);
|
|
||||||
const CLUT_WIDTH_BIT_RANGE: BitRange = BitRange::from_to(17, 22);
|
|
||||||
const CLUT_HEIGHT_BIT_RANGE: BitRange = BitRange::from_to(23, 31);
|
|
||||||
|
|
||||||
pub fn encode(tex_width: u16, tex_height: u16, clut_width: u16, clut_height: u16) -> Option<Header> {
|
|
||||||
if tex_width & 1 == 1 || tex_height & 1 == 1 {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0,
|
|
||||||
tex_width, 1, u32),
|
|
||||||
tex_height, 1, u32),
|
|
||||||
clut_width, 4, u32),
|
|
||||||
clut_height, 0, u32);
|
|
||||||
|
|
||||||
Some(Header{value})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawConversion<4> for Header {
|
|
||||||
fn convert_to_raw(&self) -> [u8; 4] {
|
|
||||||
self.value.to_le_bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait PSXImageConverter: std::iter::Iterator<Item = Color> {
|
pub trait PSXImageConverter: std::iter::Iterator<Item = Color> {
|
||||||
fn width(&self) -> u16;
|
fn width(&self) -> u16;
|
||||||
fn height(&self) -> u16;
|
fn height(&self) -> u16;
|
||||||
|
|
Loading…
Reference in New Issue