From 8fdd4884e40faad2568c65d80eea96d8bfbaa57f Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Mon, 26 Jun 2023 08:16:43 -0700 Subject: [PATCH] Experimental automatic bound calculation after removal of bad layers --- support/src/layout.rs | 1 + support/src/main.rs | 6 +- support/src/render.rs | 220 +++++++++++++++++++++++++++--------------- 3 files changed, 148 insertions(+), 79 deletions(-) diff --git a/support/src/layout.rs b/support/src/layout.rs index 5d98f2f..44249d2 100644 --- a/support/src/layout.rs +++ b/support/src/layout.rs @@ -172,6 +172,7 @@ fn select_view(views: &mut HashMap) -> Option { "handheld layout", "external layout", "screen focus", + "unit only", ]; for name in desired_names { diff --git a/support/src/main.rs b/support/src/main.rs index 7f72f6f..9046f2c 100644 --- a/support/src/main.rs +++ b/support/src/main.rs @@ -29,7 +29,7 @@ enum FilterArg { Specific { name: String }, /// Match the games that use a particular CPU CPU { name: CPUType }, - /// Match the specific CPU types supported by the core currently. These are the SM510 and SM5a CPUs + /// Match the specific CPU types supported by the core currently. These are the SM510 (inc. Tiger) and SM5a CPUs Supported, /// All game types specified in the manifest.json All, @@ -185,7 +185,9 @@ fn main() { None } } - Some(FilterArg::Supported) => filter_platforms(vec![CPUType::SM510, CPUType::SM5a]), + Some(FilterArg::Supported) => { + filter_platforms(vec![CPUType::SM510, CPUType::SM510Tiger, CPUType::SM5a]) + } Some(FilterArg::CPU { name }) => filter_platforms(vec![name.clone()]), Some(FilterArg::All) | None => Some(manifest.iter().map(|(n, p)| (n.clone(), p)).collect()), }; diff --git a/support/src/render.rs b/support/src/render.rs index de7bbb1..8fefcf8 100644 --- a/support/src/render.rs +++ b/support/src/render.rs @@ -28,9 +28,16 @@ pub fn render( let mut elements: Vec<&Element> = vec![]; let mut screens: Vec<&Screen> = vec![]; + let mut filtered_items: Vec<&ViewElement> = vec![]; + + // Keep track of which refs have already been added to the image, as most layouts contain multiple duplicates + let mut already_applied_refs: HashSet<&String> = HashSet::<&String>::new(); + + // Prefilter all items for item in &layout.items { match item { ViewElement::Bounds(bounds) => { + // Filter out if view_bounds.is_some() { return Err(format!( "View {} in {platform_name} has multiple bounds. Skipping", @@ -39,54 +46,116 @@ pub fn render( } view_bounds = Some(bounds.to_xy()); } - ViewElement::Element(element) => elements.push(element), - ViewElement::Screen(screen) => screens.push(screen), + ViewElement::Element(element) => { + if already_applied_refs.contains(&element.ref_name) { + continue; + } + + already_applied_refs.insert(&element.ref_name); + + match element.ref_name.to_lowercase().as_str() { + "dust" | "bubbles" | "unit" | "backdrop" => { + // Ignore these features + println!("Ignoring element by name {}", element.ref_name); + continue; + } + value => { + // if value.starts_with("fix") || value.starts_with("gradient") { + if value.starts_with("gradient") { + println!("Ignoring element by name {}", element.ref_name); + continue; + } + } + } + + filtered_items.push(item); + elements.push(element); + } + ViewElement::Screen(screen) => { + filtered_items.push(item); + screens.push(screen); + } } } - let view_bounds = if let Some(view_bounds) = view_bounds { - view_bounds - } else { - // Calculate actual bounds - let mut min_x: Option = None; - let mut min_y: Option = None; - let mut max_width = 0; - let mut max_height = 0; + // Calculate actual bounds + let mut min_x: Option = None; + let mut min_y: Option = None; + let mut max_width = 0; + let mut max_height = 0; + let mut max_common_x: Option = None; + let mut max_common_y: Option = None; - for bounds in elements - .iter() - .map(|e| e.bounds.to_xy()) - .chain(screens.iter().map(|s| s.bounds.to_xy())) - { - if let Some(inner_min_x) = min_x { - if inner_min_x > bounds.x { - min_x = Some(bounds.x); - } - } - - if let Some(inner_min_y) = min_y { - if inner_min_y > bounds.y { - min_y = Some(bounds.y); - } - } - - if bounds.width + bounds.x > max_width { - max_width = bounds.width + bounds.x; - } - - if bounds.height + bounds.y > max_height { - max_height = bounds.height + bounds.y; + for bounds in elements + .iter() + .map(|e| e.bounds.to_xy()) + .chain(screens.iter().map(|s| s.bounds.to_xy())) + { + if let Some(inner_min_x) = min_x { + if inner_min_x > bounds.x { + min_x = Some(bounds.x); } } - Bounds { - x: min_x.map_or(0, |x| x), - y: min_y.map_or(0, |y| y), - width: max_width, - height: max_height, + if let Some(inner_min_y) = min_y { + if inner_min_y > bounds.y { + min_y = Some(bounds.y); + } } + + if bounds.width + bounds.x > max_width { + max_width = bounds.width + bounds.x; + } + + if bounds.height + bounds.y > max_height { + max_height = bounds.height + bounds.y; + } + + if let Some(common_x) = max_common_x { + if common_x > bounds.x { + // Shorten max common + max_common_x = Some(bounds.x); + } + } else { + // Set first value + max_common_x = Some(bounds.x); + } + + if let Some(common_y) = max_common_y { + if common_y > bounds.y { + // Shorten max common + max_common_y = Some(bounds.y); + } + } else { + // Set first value + max_common_y = Some(bounds.y); + } + + println!("{bounds:?}"); + } + + let max_common_x = max_common_x.map_or(0, |x| x); + let max_common_y = max_common_y.map_or(0, |y| y); + + let calculated_bounds = Bounds { + x: (min_x.map_or(0, |x| x) - max_common_x).max(0), + y: (min_y.map_or(0, |y| y) - max_common_y).max(0), + width: max_width - max_common_x, + height: max_height - max_common_y, }; + println!("{max_common_x} {max_common_y} {calculated_bounds:?} {view_bounds:?}"); + + let view_bounds = calculated_bounds; + + // if let Some(set_bounds) = view_bounds { + // if (set_bounds.width - calculated_bounds.width).abs() > calculated_bounds.width * 0.3 + // || (set_bounds.height - calculated_bounds.height).abs() > calculated_bounds.height * 0.3 + // { + // println!("Listed bounds differ from actual bounds by > 30%. Using actual bounds") + // } + // } + let x_ratio = WIDTH as f32 / view_bounds.width as f32; let y_ratio = HEIGHT as f32 / view_bounds.height as f32; @@ -105,8 +174,7 @@ pub fn render( (0, (HEIGHT as i32 - scaled_height.round() as i32) / 2) }; - // Keep track of which refs have already been added to the image, as most layouts contain multiple duplicates - let mut already_applied_refs = HashSet::<&String>::new(); + println!("{x_ratio} {y_ratio} {x_offset}, {y_offset}"); // Keep track of the set of pixels that make up each screen let mut pixels_to_mask_id: Vec> = vec![None; WIDTH * HEIGHT]; @@ -116,30 +184,9 @@ pub fn render( // We currently ignore offsetting by X/Y at the parent view, so the child positions are subtracted // from the parent's offset - for item in &layout.items { + for item in &filtered_items { match item { ViewElement::Element(element) => { - if already_applied_refs.contains(&element.ref_name) { - continue; - } - - already_applied_refs.insert(&element.ref_name); - - match element.ref_name.to_lowercase().as_str() { - "dust" | "bubbles" => { - // Ignore these features - println!("Ignoring element by name {}", element.ref_name); - continue; - } - value => { - // if value.starts_with("fix") || value.starts_with("gradient") { - if value.starts_with("gradient") { - println!("Ignoring element by name {}", element.ref_name); - continue; - } - } - } - let file_path = asset_dir .join("foo") .with_file_name(format!("{}.png", element.ref_name)); @@ -158,15 +205,25 @@ pub fn render( ) .expect("Could not convert image data"); - let dimensions = ImageDimensions::new( - &view_bounds, - &element.bounds.to_xy(), - ratio, - x_offset, - y_offset, + let element_bounds = element.bounds.to_xy(); + + let element_bounds = Bounds { + x: (element_bounds.x - max_common_x).max(0), + y: (element_bounds.y - max_common_y).max(0), + width: element_bounds.width, + height: element_bounds.height, + }; + + let dimensions = + ImageDimensions::new(&view_bounds, &element_bounds, ratio, x_offset, y_offset); + + println!( + "{element_bounds:?} {} {}, {dimensions:?}", + image.width(), + image.height() ); - let image = DynamicImage::ImageRgba8(image).resize_exact( + let image: DynamicImage = DynamicImage::ImageRgba8(image).resize_exact( dimensions.width, dimensions.height, FilterType::CatmullRom, @@ -227,6 +284,13 @@ pub fn render( let bounds = screen.bounds.to_xy(); + let bounds = Bounds { + x: (bounds.x - max_common_x).max(0), + y: (bounds.y - max_common_y).max(0), + width: bounds.width, + height: bounds.height, + }; + let dimensions = ImageDimensions::new(&view_bounds, &bounds, ratio, x_offset, y_offset); @@ -270,6 +334,8 @@ pub fn render( None, ); + println!("{} {}", output_mask.width(), output_mask.height()); + if debug { let debug_path = asset_dir.join(format!("{platform_name}.png")); let debug_background_path = asset_dir.join(format!("{platform_name}_background.png")); @@ -322,13 +388,13 @@ fn alpha_blend_colors( let foreground_alpha = foreground.alpha() as f32 / 255.0; - PremultipliedColorU8::from_rgba( - combine_values(foreground.red(), background.red(), foreground_alpha), - combine_values(foreground.green(), background.green(), foreground_alpha), - combine_values(foreground.blue(), background.blue(), foreground_alpha), - combine_values(foreground.alpha(), background.alpha(), foreground_alpha), - ) - .unwrap() + let red = combine_values(foreground.red(), background.red(), foreground_alpha); + let green = combine_values(foreground.green(), background.green(), foreground_alpha); + let blue = combine_values(foreground.blue(), background.blue(), foreground_alpha); + let alpha = combine_values(foreground.alpha(), background.alpha(), foreground_alpha); + + PremultipliedColorU8::from_rgba(red, green, blue, red.max(green).max(blue).max(alpha)) + .expect("Could not convert alpha blend color") } fn screen_filename(index: usize, platform_name: &str, platform: &PresetDefinition) -> String { @@ -353,7 +419,7 @@ fn screen_filename(index: usize, platform_name: &str, platform: &PresetDefinitio format!("{platform_name}{suffix}.svg") } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ImageDimensions { pub x: i32, pub y: i32,