From 72608c2c8ca7877e2c22cd8d2067d9cecb5602e8 Mon Sep 17 00:00:00 2001 From: Venomalia Date: Thu, 10 Feb 2022 15:48:29 +0100 Subject: [PATCH 1/2] functions for scaling textures --- .../Data/DynamicInputPack.cs | 19 ++++- .../Data/DynamicInputTexture.cs | 85 ++++++++++++++++++- 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/DolphinDynamicInputTexture/Data/DynamicInputPack.cs b/DolphinDynamicInputTexture/Data/DynamicInputPack.cs index b02a6e2..5e9c754 100644 --- a/DolphinDynamicInputTexture/Data/DynamicInputPack.cs +++ b/DolphinDynamicInputTexture/Data/DynamicInputPack.cs @@ -121,8 +121,8 @@ public string GameID /// export directory public void ExportToLocation(string location) { - WriteJson(Path.Combine(location, GeneratedJsonName + ".json")); WriteImages(location); + WriteJson(Path.Combine(location, GeneratedJsonName + ".json")); WriteGameID(location); } @@ -153,6 +153,7 @@ private void WriteImages(string location) { //exports the images for the output_textures WriteImage(location, texture); + foreach (HostDevice device in texture.HostDevices) { foreach (HostKey key in device.HostKeys) @@ -164,7 +165,7 @@ private void WriteImages(string location) } } - private void WriteImage(string location, Interfaces.IImage image) + private void WriteImage(string location, IImage image) { // Unlikely that we get here but skip textures that don't exist if (!File.Exists(image.TexturePath)) @@ -174,13 +175,25 @@ private void WriteImage(string location, Interfaces.IImage image) image.RelativeTexturePath = CheckRelativeTexturePath(image); string output_location = Path.Combine(location, image.RelativeTexturePath); + //create the directory when it does not exist. + Directory.CreateDirectory(Path.GetDirectoryName(output_location)); + + //Scaling of the texture if necessary + if (image is DynamicInputTexture texture) + { + if (texture.ExportImageScaling != 0 && texture.ExportImageScaling != texture.ImageWidthScaling) + { + texture.SetImageScaling(output_location, texture.ExportImageScaling); + return; + } + } + // Prevents the file from trying to overwrite itself. if (Path.GetFullPath(output_location) == Path.GetFullPath(image.TexturePath)) return; //write the image const bool overwrite = true; - Directory.CreateDirectory(Path.GetDirectoryName(output_location)); File.Copy(image.TexturePath, output_location, overwrite); } diff --git a/DolphinDynamicInputTexture/Data/DynamicInputTexture.cs b/DolphinDynamicInputTexture/Data/DynamicInputTexture.cs index 4e6aed0..7264481 100644 --- a/DolphinDynamicInputTexture/Data/DynamicInputTexture.cs +++ b/DolphinDynamicInputTexture/Data/DynamicInputTexture.cs @@ -1,5 +1,6 @@ using DolphinDynamicInputTexture.Interfaces; using Newtonsoft.Json; +using System; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Drawing; @@ -187,18 +188,98 @@ public double ImageHeightScaling } } + /// + /// The image size is set to a multiple of the original size when exporting. + /// 0 = Keep current size. + /// + public int ExportImageScaling + { + get => _export_image_scaling; + set + { + if (value < 0) throw new ArgumentException("cannot be less than zero", nameof(ExportImageScaling)); + + _export_image_scaling = (byte)value; + OnPropertyChanged(nameof(ExportImageScaling)); + } + } + private byte _export_image_scaling = 0; #endregion #region Update + /// + /// Scales the image in relation to the original size. + /// + /// Save location of the new file + /// Size in relation to the original size + public void SetImageScaling(string savepath, int Scaling) + { + if (Scaling <= 0) + { + throw new ArgumentException("Must be at least 1 or greater", nameof(Scaling)); + } + + SetImageScaling(savepath, (int)(HashProperties.ImageWidth * Scaling), (int)(HashProperties.ImageHeight * Scaling)); + } + + /// + /// Scales the image to an absulute size. + /// + /// Save location of the new file + /// width in pixel + /// height in pixel + public void SetImageScaling(string savepath, int width, int height) + { + using(Bitmap newImage = new Bitmap(width, height)) + { + using (Bitmap Image = new Bitmap(TexturePath)) + using (Graphics graphics = Graphics.FromImage(newImage)) + { + graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; + graphics.DrawImage(Image, 0, 0, newImage.Width, newImage.Height); + } + newImage.Save(savepath, System.Drawing.Imaging.ImageFormat.Png); + + // sets the new path and updates the regions to the new size. + TexturePath = savepath; + } + } + + /// + /// reads the image size and adjusts the regions if necessary. + /// private void UpdateImageWidthHeight() { if (File.Exists(_texture_path)) { using (var bmp = new Bitmap(_texture_path)) { - ImageHeight = bmp.Height; - ImageWidth = bmp.Width; + if (ImageHeight > 0 && ImageWidth > 0) + { + double width_scale = (double)bmp.Width / ImageWidth; + double height_scale = (double)bmp.Height / ImageHeight; + + //When scaling up, the scale must be set directly. + if (width_scale >= 1) ImageWidth = bmp.Width; + if (height_scale >= 1) ImageHeight = bmp.Height; + + foreach (InputRegion region in Regions) + { + region.RegionRect.X *= width_scale; + region.RegionRect.Width *= width_scale; + region.RegionRect.Y *= height_scale; + region.RegionRect.Height *= height_scale; + } + + ImageWidth = bmp.Width; + ImageHeight = bmp.Height; + } + else + { + ImageHeight = bmp.Height; + ImageWidth = bmp.Width; + } } } } From c244f03901fef49788760722864fbb888449e7d9 Mon Sep 17 00:00:00 2001 From: Venomalia Date: Fri, 11 Feb 2022 19:52:00 +0100 Subject: [PATCH 2/2] UI for scaling textures on export --- Controls/Metadata.xaml | 3 + ViewModels/Dialogs.cs | 3 + ViewModels/DynamicInputPackViewModel.cs | 102 ++++++++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/Controls/Metadata.xaml b/Controls/Metadata.xaml index df5d61b..45f1c7b 100644 --- a/Controls/Metadata.xaml +++ b/Controls/Metadata.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:DolphinDynamicInputTextureCreator.Controls" + xmlns:converters="clr-namespace:DolphinDynamicInputTextureCreator.ValueConverters" mc:Ignorable="d" Height="220" Width="320"> @@ -31,6 +32,8 @@ diff --git a/ViewModels/Dialogs.cs b/ViewModels/Dialogs.cs index b47fca6..abca180 100644 --- a/ViewModels/Dialogs.cs +++ b/ViewModels/Dialogs.cs @@ -124,7 +124,10 @@ public static bool DialogExportToLocation(in DynamicInputPackViewModel pack) var dialog = new System.Windows.Forms.FolderBrowserDialog(); if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { + pack.SetExportTextureScaling(); pack.ExportToLocation(dialog.SelectedPath); + // Updating the user interface in case the image file has changed. + pack.Textures.Select(pack.Textures.Selected); return true; } return false; diff --git a/ViewModels/DynamicInputPackViewModel.cs b/ViewModels/DynamicInputPackViewModel.cs index f5bd181..75f6b63 100644 --- a/ViewModels/DynamicInputPackViewModel.cs +++ b/ViewModels/DynamicInputPackViewModel.cs @@ -132,7 +132,45 @@ public bool ShouldGetHashFromTextureFilename } } + /// + /// List of possible texture scaling during export. + /// + public string[] ExportTextureScalingModes + { + get => _export_texture_scaling_modes ??= new string[] { "Deactivated", "Dynamic", "1x (Original Size)", "2x", "3x", "4x", "5x" , "6x", "7x", "8x" }; + } + private string[] _export_texture_scaling_modes; + + /// + /// The currently selected export texture scaling. + /// + public string SelectedExportTextureScaling + { + get => (string)ExportTextureScalingModes.GetValue(_selected_export_texture_scaling); + set + { + for (int i = 0; i < ExportTextureScalingModes.Length; i++) + { + if (value == ExportTextureScalingModes[i]) + { + _selected_export_texture_scaling = i; + OnPropertyChanged(nameof(SelectedExportTextureScaling)); + return; + } + } + _selected_export_texture_scaling = 0; + OnPropertyChanged(nameof(SelectedExportTextureScaling)); + } + } + internal int _selected_export_texture_scaling = 0; + + /// + /// Number of pixels that the smallest region should have at least after exporting, when the Dynamic mode is used. + /// + private int _targeted_regione_size = 96; + #endregion + #region Commands #region SelectedTexture @@ -331,5 +369,69 @@ public void SetInitialZoom(DynamicInputTexture Texture, double absolutescale = 6 #endregion + #region ExportScale + + /// + /// set the export scale for each texture. + /// + public void SetExportTextureScaling() + { + foreach (DynamicInputTexture texture in Textures) + { + switch (_selected_export_texture_scaling) + { + case 0: + //(Disabled) Leaves the textures unchanged. + texture.ExportImageScaling = 0; + break; + case 1: + //(Dynamic) Calculates a good display size. + SetExportTextureScaling_Dynamic(texture); + break; + default: + //(x1-8) Use the specified scaling. + texture.ExportImageScaling = _selected_export_texture_scaling - 1; + break; + } + } + + } + + /// + /// Calculates a good texture scaling based on region size. + /// + /// + private void SetExportTextureScaling_Dynamic(DynamicInputTexture texture) + { + // 0 if there are no regions. + if (texture.Regions.Count <= 0) + { + texture.ExportImageScaling = 0; + return; + } + + //calculate a scaling based on the regions. + int smallest_region = texture.ImageWidth; + foreach (InputRegion region in texture.Regions) + { + int regionsize = (int)(region.RegionRect.Height + region.RegionRect.Width) / 2; + if (smallest_region > regionsize) + { + smallest_region = regionsize; + } + } + smallest_region = (int)(smallest_region / ((texture.ImageHeightScaling + texture.ImageWidthScaling) / 2)); + int scaling = (int)Math.Round((double)_targeted_regione_size / smallest_region, 0, MidpointRounding.AwayFromZero); + + // unlikely however try to avoid too large textures. + int pixelsize = texture.HashProperties.ImageWidth > texture.HashProperties.ImageHeight ? texture.HashProperties.ImageWidth : texture.HashProperties.ImageHeight; + if (pixelsize * scaling > 4096) + { + scaling = (int)Math.Round((double)4096 / pixelsize, MidpointRounding.ToZero); + } + + texture.ExportImageScaling = (byte)scaling; + } + #endregion } }