diff --git a/src/Tools/tim_tool/src/gui/file_tab.rs b/src/Tools/tim_tool/src/gui/file_tab.rs index 4bf68e6f..d076e654 100644 --- a/src/Tools/tim_tool/src/gui/file_tab.rs +++ b/src/Tools/tim_tool/src/gui/file_tab.rs @@ -1,32 +1,78 @@ use crate::MainWindow; use super::{GUIElements, GUIElementsRef, MainWindowRef, display_error}; -use slint::Image; +use slint::{Image, SharedString}; +use std::rc::Rc; +use tim_tool::logic::tim::Encoding; use tool_helper::Error; pub struct FileTab { - main_window: MainWindowRef + main_window: MainWindowRef, + encoding_options: Rc> } impl FileTab { + fn update_encoding_options(&self, width: u32, height: u32, has_palette: bool) -> Result<&str, Error> { + self.encoding_options.clear(); + + if has_palette { + let mut selected_str = Encoding::EightBit.to_str(); + let has_4bit = width%4 == 0; + let has_8bit = width%2 == 0; + + if !has_4bit && !has_8bit { + return Err(Error::from_text(format!("Image width must be multiple of 2 and or 4 ({}/{})", width, height))); + } + + if has_4bit { + self.encoding_options.push(SharedString::from(format!("{} ({}/{})", Encoding::FourBit.to_str(), width/4, height))); + selected_str = Encoding::FourBit.to_str(); + } + + if has_8bit { + self.encoding_options.push(SharedString::from(format!("{} ({}/{})", Encoding::EightBit.to_str(), width/2, height))); + } + Ok(selected_str) + } + + else { + self.encoding_options.push(SharedString::from(format!("{} ({}/{})", Encoding::FullColor.to_str(), width, height))); + Ok(Encoding::FullColor.to_str()) + } + } + pub fn new(main_window: MainWindowRef) -> FileTab { - FileTab{main_window} + let encoding_options = Rc::new(slint::VecModel::from(Vec::::new())); + + main_window.borrow().set_file_tab_encoding_options(encoding_options.clone().into()); + FileTab{main_window, encoding_options} } pub fn clear_load(&self) { let main_window = self.main_window.borrow(); + self.encoding_options.clear(); main_window.set_file_tab_browse_path("".into()); main_window.set_file_tab_image_data(Image::default()); + main_window.set_file_tab_image_width(0); + main_window.set_file_tab_image_height(0); + main_window.set_file_tab_palette_data(Image::default()); + main_window.set_file_tab_palette_width(0); + main_window.set_file_tab_palette_height(0); main_window.set_file_tab_image_name("".into()); main_window.set_file_tab_enable(false); } - pub fn update_new_loaded_file(&self, file_name: Option, image: Image, palette: Option) { + pub fn update_new_loaded_file(&self, file_name: Option, image: Image, palette: Option) -> Result<(), Error> { + let has_palette = palette.is_some(); + self.update_palette(palette); let main_window = self.main_window.borrow(); + let image_size = image.size(); main_window.set_file_tab_image_data(image); + main_window.set_file_tab_image_width(image_size.width as i32); + main_window.set_file_tab_image_height(image_size.height as i32); if let Some(file_name) = file_name { main_window.set_file_tab_image_name(file_name.into()); } @@ -34,7 +80,10 @@ impl FileTab { else { main_window.set_file_tab_image_name("".into()); } + + main_window.set_file_tab_selected_encoding(SharedString::from(self.update_encoding_options(image_size.width, image_size.height, has_palette)?)); main_window.set_file_tab_enable(true); + Ok(()) } pub fn update_palette(&self, palette: Option) { @@ -60,6 +109,26 @@ impl FileTab { self.main_window.borrow().get_file_tab_image_name().to_string() } + pub fn get_encoding(&self) -> Result { + let selected_encoding = self.main_window.borrow().get_file_tab_selected_encoding(); + + if selected_encoding.starts_with(Encoding::FourBit.to_str()) { + Ok(Encoding::FourBit) + } + + else if selected_encoding.starts_with(Encoding::EightBit.to_str()) { + Ok(Encoding::EightBit) + } + + else if selected_encoding.starts_with(Encoding::FullColor.to_str()) { + Ok(Encoding::FullColor) + } + + else { + Err(Error::from_str("Failed to obtain encoding")) + } + } + pub fn on_browse_file(&self, gui_elements: GUIElementsRef, mut function: impl FnMut(&mut GUIElements, &MainWindow) -> Result<(), Error> + 'static) { let main_window_cloned = self.main_window.clone(); let gui_cloned = gui_elements.clone(); diff --git a/src/Tools/tim_tool/src/gui/main_tab.rs b/src/Tools/tim_tool/src/gui/main_tab.rs index 84db3017..4e2b5c32 100644 --- a/src/Tools/tim_tool/src/gui/main_tab.rs +++ b/src/Tools/tim_tool/src/gui/main_tab.rs @@ -1,7 +1,8 @@ use crate::{gui::{VRAM_HEIGHT, VRAM_WIDTH}, MainWindow, VRAMImage}; use super::{GUIElementsRef, MainWindowRef}; -use slint::Model; +use slint::{Model, SharedString}; +use tim_tool::logic::tim::Encoding; use std::{rc::Rc, sync::{Arc, Mutex}}; struct VRAM { @@ -26,13 +27,15 @@ impl MainTab { MainTab{main_window, vram: Arc::new(Mutex::new(VRAM{file_list: vram_file_list, image_list: vram_image_list}))} } - pub fn add_new_vram_file(&mut self, file_name: &String, image: slint::Image, image_palette: Option) { + pub fn add_new_vram_file(&mut self, file_name: &String, image: slint::Image, encoding: Encoding, image_palette: Option) { let vram_data = self.vram.lock().expect("VRAM already locked"); - let add_new_image = |file_name: &String, image: slint::Image, palette_count: i32, is_palette: bool| { - let vram_image = VRAMImage{ + let add_new_image = |file_name: &String, image: slint::Image, encoding: Encoding, palette_count: i32, is_palette: bool| { + let encoding_str = if is_palette {""} else {encoding.to_str()}; + let vram_image = VRAMImage{ img: image, x: 0, y: 0, + encoding_str: SharedString::from(encoding_str), palette_count, is_palette, }; @@ -41,10 +44,10 @@ impl MainTab { vram_data.image_list.push(vram_image); }; - add_new_image(file_name, image, if image_palette.is_some() {1} else {0}, false); + add_new_image(file_name, image, encoding, if image_palette.is_some() {1} else {0}, false); if let Some(image_palette) = image_palette { let file_name = " => ".to_owned() + file_name.as_str() + " (Palette)"; - add_new_image(&file_name, image_palette, 0, true); + add_new_image(&file_name, image_palette, encoding, 0, true); } } diff --git a/src/Tools/tim_tool/src/logic/mod.rs b/src/Tools/tim_tool/src/logic/mod.rs index fd5fc7b3..71fbe9fc 100644 --- a/src/Tools/tim_tool/src/logic/mod.rs +++ b/src/Tools/tim_tool/src/logic/mod.rs @@ -2,7 +2,7 @@ pub mod tim; use std::path::PathBuf; use slint::Image; -use tim::TIMInfo; +use tim::{Encoding, TIMInfo}; use tool_helper::Error; pub struct Logic { @@ -16,18 +16,18 @@ impl Logic { pub fn set_unadded_tim(&mut self, path: &PathBuf) -> Result<(Image, Option), Error> { let tim_info = TIMInfo::from_image(path)?; - let image = tim_info.get_slint_images(); + let image = tim_info.get_slint_images(Encoding::FullColor); self.unadded_tim = Some(tim_info); Ok(image) } - pub fn add_unadded_tim_as(&mut self, _name: &String) -> Result<(Image, Option), Error> { + pub fn add_unadded_tim_as(&mut self, _name: &String, encoding: Encoding) -> Result<(Image, Option), Error> { if let Some(unadded_tim) = &self.unadded_tim { - let image = unadded_tim.get_slint_images(); + let (image, palette) = unadded_tim.get_slint_images(encoding); self.unadded_tim = None; - Ok(image) + Ok((image, palette)) } else { diff --git a/src/Tools/tim_tool/src/logic/tim.rs b/src/Tools/tim_tool/src/logic/tim.rs index 15a1f6b0..6f176afc 100644 --- a/src/Tools/tim_tool/src/logic/tim.rs +++ b/src/Tools/tim_tool/src/logic/tim.rs @@ -2,6 +2,27 @@ use std::{fs::File, path::PathBuf}; use slint::{Rgba8Pixel, SharedPixelBuffer}; use tool_helper::Error; +#[derive(Clone, Copy)] +pub enum Encoding { + FourBit, + EightBit, + FullColor, +} + +impl Encoding { + const FOUR_BIT_NAME:&str = "4 bit"; + const EIGHT_BIT_NAME:&str = "8 bit"; + const FULL_COLOR_NAME:&str = "Full color"; + + pub const fn to_str(&self) -> &str { + match self { + Encoding::FourBit => Self::FOUR_BIT_NAME, + Encoding::EightBit => Self::EIGHT_BIT_NAME, + Encoding::FullColor => Self::FULL_COLOR_NAME, + } + } +} + pub struct TIMInfo { image_data: SharedPixelBuffer, palette: Option, @@ -86,8 +107,28 @@ impl TIMInfo { } } - pub fn get_slint_images(&self) -> (slint::Image, Option) { - (slint::Image::from_rgba8_premultiplied(self.image_data.clone()), if let Some(palette) = &self.palette { + pub fn get_slint_images(&self, encoding: Encoding) -> (slint::Image, Option) { + fn scaled_copy(src: &SharedPixelBuffer, factor: u32) -> SharedPixelBuffer { + let mut image_data = SharedPixelBuffer::::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 diff --git a/src/Tools/tim_tool/src/main.rs b/src/Tools/tim_tool/src/main.rs index d351734e..496e4767 100644 --- a/src/Tools/tim_tool/src/main.rs +++ b/src/Tools/tim_tool/src/main.rs @@ -72,7 +72,7 @@ fn setup_file_tab(gui_elements_ref: Rc>, logic_ref: Rc VRAM_WIDTH as u32 || img_size.height > VRAM_HEIGHT as u32 { return Err(Error::from_text(format!("Image size ({}; {}) is to big for VRAM ({}, {})", img_size.width, img_size.height, VRAM_WIDTH, VRAM_HEIGHT))); } - file_tab.update_new_loaded_file(file_name, image, palette); + return file_tab.update_new_loaded_file(file_name, image, palette); } Ok(()) @@ -84,9 +84,10 @@ fn setup_file_tab(gui_elements_ref: Rc>, logic_ref: Rc main_tab.move_vram_image; // Convert Image values + in-out property file_tab-encoding_options <=> file_tab.conv-encoding_options; in-out property file_tab-browse_path <=> file_tab.conv-image_path; in-out property file_tab-image_data <=> file_tab.conv-image_data; + in-out property file_tab-image_width <=> file_tab.conv-image_width; + in-out property file_tab-image_height <=> file_tab.conv-image_height; in-out property file_tab-palette_data <=> file_tab.conv-palette_data; in-out property file_tab-palette_width <=> file_tab.conv-palette_width; in-out property file_tab-palette_height <=> file_tab.conv-palette_height; in-out property file_tab-palette_visible <=> file_tab.conv-palette_enable; + in-out property file_tab-selected_encoding <=> file_tab.conv-selected_encoding; in-out property file_tab-image_name <=> file_tab.conv-image_name; in-out property file_tab-enable <=> file_tab.conv-enable_view; callback file_tab-update_palette_size <=> file_tab.conv-image_update_palette_size; diff --git a/src/Tools/tim_tool/ui/tab/file-tab.slint b/src/Tools/tim_tool/ui/tab/file-tab.slint index 958b7070..a9a9e078 100644 --- a/src/Tools/tim_tool/ui/tab/file-tab.slint +++ b/src/Tools/tim_tool/ui/tab/file-tab.slint @@ -1,4 +1,4 @@ -import { Button, TabWidget, LineEdit, GroupBox } from "std-widgets.slint"; +import { Button, TabWidget, LineEdit, GroupBox, ComboBox } from "std-widgets.slint"; export enum State { Project, @@ -12,14 +12,18 @@ component ProjectWidget inherits Rectangle { } component ConvertImageWidget inherits Rectangle { - in-out property image_path; - in-out property image_name; - in-out property image_data; - in-out property palette_data; - in-out property palette_width: 0; - in-out property palette_height: 0; - in-out property enable_view: false; - in-out property palette_visible: false; + in-out property <[string]> encoding_options: []; + in-out property image_path; + in-out property image_name; + in-out property image_data; + in-out property image-width; + in-out property image-height; + in-out property palette_data; + in-out property palette_width: 0; + in-out property palette_height: 0; + in-out property selected_encoding; + in-out property enable_view: false; + in-out property palette_visible: false; callback browse_clicked(); callback add_clicked(); @@ -59,18 +63,43 @@ component ConvertImageWidget inherits Rectangle { padding: 4px; HorizontalLayout { alignment: center; - Rectangle { - width: 256px + 2*4px; // < Because of border - height: 256px + 2*4px; // < Because of border - background: #404040; - border-color: #808080; - border-width: 4px; - Image { - width: 256px; - height: 256px; - source: root.image_data; - image-fit: contain; - image-rendering: pixelated; + VerticalLayout { + Rectangle { + width: 256px + 2*4px; // < Because of border + height: 256px + 2*4px; // < Because of border + background: #404040; + border-color: #808080; + border-width: 4px; + Image { + width: 256px; + height: 256px; + source: root.image_data; + image-fit: contain; + image-rendering: pixelated; + } + } + VerticalLayout { + alignment: center; + Text { + text: "Width: " + root.image-width; + } + } + VerticalLayout { + alignment: center; + Text { + text: "Height: " + root.image-height; + } + } + VerticalLayout { + alignment: center; + ComboBox { + model: root.encoding_options; + enabled: root.enable_view; + + selected(current-value) => { + root.selected_encoding = current-value; + } + } } } Rectangle { @@ -172,16 +201,18 @@ component ConvertImageWidget inherits Rectangle { } export component FileTab inherits Rectangle { - // TODO: Names are messed up here! - in-out property conv-image_path; - in-out property conv-image_name; - in-out property conv-image_data; - in-out property conv-palette_data; - in-out property conv-palette_width; - in-out property conv-palette_height; - in-out property conv-palette_enable; - in-out property conv-enable_view; - + in-out property <[string]> conv-encoding_options; + in-out property conv-image_path; + in-out property conv-image_name; + in-out property conv-image_data; + in-out property conv-image_width; + in-out property conv-image_height; + in-out property conv-palette_data; + in-out property conv-palette_width; + in-out property conv-palette_height; + in-out property conv-selected_encoding; + in-out property conv-palette_enable; + in-out property conv-enable_view; in-out property state; callback conv-image_update_palette_size(int, int); @@ -217,15 +248,20 @@ export component FileTab inherits Rectangle { alignment: start; if root.state == State.Project : ProjectWidget { } + if root.state == State.ConvertImage : ConvertImageWidget { - image_path <=> root.conv-image_path; - image_data <=> root.conv-image_data; - palette_data <=> root.conv-palette_data; - palette_width <=> root.conv-palette_width; - palette_height <=> root.conv-palette_height; - palette_visible <=> root.conv-palette_enable; - image_name <=> root.conv-image_name; - enable_view <=> root.conv-enable_view; + encoding_options <=> root.conv-encoding_options; + image_path <=> root.conv-image_path; + image_data <=> root.conv-image_data; + image-width <=> root.conv-image_width; + image-height <=> root.conv-image_height; + palette_data <=> root.conv-palette_data; + palette_width <=> root.conv-palette_width; + palette_height <=> root.conv-palette_height; + palette_visible <=> root.conv-palette_enable; + selected_encoding <=> root.conv-selected_encoding; + image_name <=> root.conv-image_name; + enable_view <=> root.conv-enable_view; update_palette_size(width, height) => { root.conv-image_update_palette_size(width, height); diff --git a/src/Tools/tim_tool/ui/tab/main-tab.slint b/src/Tools/tim_tool/ui/tab/main-tab.slint index aa2b4202..fbfcbe30 100644 --- a/src/Tools/tim_tool/ui/tab/main-tab.slint +++ b/src/Tools/tim_tool/ui/tab/main-tab.slint @@ -5,6 +5,7 @@ struct VRAMImage { img: image, x: int, y: int, + encoding_str: string, palette_count: int, is_palette: bool, } @@ -78,10 +79,12 @@ export component MainTab inherits Rectangle { } if event.kind == PointerEventKind.down { - cur_sel_x.text = parent.img_x; - cur_sel_y.text = parent.img_y; - cur_sel_img.source = parent.img; - cur_sel_img.visible = true; + cur_sel_x.text = parent.img_x; + cur_sel_y.text = parent.img_y; + cur_sel_img.source = parent.img; + encoding_text.encoding_str = vram-image.encoding_str; + cur_sel_img.visible = true; + vram_files_list.current-item = i; } } @@ -150,10 +153,11 @@ export component MainTab inherits Rectangle { model: root.vram_files; current-item-changed(current-item) => { - cur_sel_x.text = root.vram_images[current-item].x; - cur_sel_y.text = root.vram_images[current-item].y; - cur_sel_img.source = root.vram_images[current-item].img; - cur_sel_img.visible = true; + cur_sel_x.text = root.vram_images[current-item].x; + cur_sel_y.text = root.vram_images[current-item].y; + cur_sel_img.source = root.vram_images[current-item].img; + encoding_text.encoding_str = root.vram_images[current-item].encoding_str; + cur_sel_img.visible = true; } } HorizontalLayout { @@ -232,9 +236,9 @@ export component MainTab inherits Rectangle { } } } - ComboBox { - model: ["4-bit", "16-bit", "24-bit"]; - current-value: "4-bit"; + encoding_text := Text { + in-out property encoding_str; + text: "Encoding: " + encoding_str; } } }