From 22ceca09b88d190931b8e99082b1963816919ed4 Mon Sep 17 00:00:00 2001 From: Miepee Date: Wed, 15 Nov 2023 18:58:08 +0100 Subject: [PATCH 01/22] Refactor from System.Drawing.Common to Skiasharp. --- .../Models/UndertaleEmbeddedTexture.cs | 14 ++- .../Models/UndertaleTexturePageItem.cs | 12 +- UndertaleModLib/UndertaleModLib.csproj | 4 +- UndertaleModLib/Util/QoiConverter.cs | 46 ++++---- UndertaleModLib/Util/TextureWorker.cs | 103 ++++++------------ 5 files changed, 72 insertions(+), 107 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs index 61b3599d3..786127b05 100644 --- a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs +++ b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs @@ -7,6 +7,8 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Text; +using SkiaSharp; using UndertaleModLib.Util; namespace UndertaleModLib.Models; @@ -408,7 +410,7 @@ public void Serialize(FileBinaryWriter writer, bool gm2022_3, bool gm2022_5) writer.Write(QOIAndBZip2Header); // Encode the PNG data back to QOI+BZip2 - using Bitmap bmp = TextureWorker.GetImageFromByteArray(TextureBlob); + using SKBitmap bmp = TextureWorker.GetImageFromByteArray(TextureBlob); writer.Write((short)bmp.Width); writer.Write((short)bmp.Height); byte[] qoiData = QoiConverter.GetArrayFromImage(bmp, gm2022_3 ? 0 : 4); @@ -423,7 +425,7 @@ public void Serialize(FileBinaryWriter writer, bool gm2022_3, bool gm2022_5) else { // Encode the PNG data back to QOI - using Bitmap bmp = TextureWorker.GetImageFromByteArray(TextureBlob); + using SKBitmap bmp = TextureWorker.GetImageFromByteArray(TextureBlob); writer.Write(QoiConverter.GetSpanFromImage(bmp, gm2022_3 ? 0 : 4)); } } @@ -464,9 +466,9 @@ public void Unserialize(IBinaryReader reader, bool gm2022_5) sharedStream.Seek(0, SeekOrigin.Begin); BZip2.Decompress(reader.Stream, sharedStream, false); ReadOnlySpan decompressed = sharedStream.GetBuffer().AsSpan()[..(int)sharedStream.Position]; - using Bitmap bmp = QoiConverter.GetImageFromSpan(decompressed); + using SKBitmap bmp = QoiConverter.GetImageFromSpan(decompressed); sharedStream.Seek(0, SeekOrigin.Begin); - bmp.Save(sharedStream, ImageFormat.Png); + bmp.Encode(sharedStream, SKEncodedImageFormat.Png, 100); TextureBlob = new byte[(int)sharedStream.Position]; sharedStream.Seek(0, SeekOrigin.Begin); sharedStream.Read(TextureBlob, 0, TextureBlob.Length); @@ -478,10 +480,10 @@ public void Unserialize(IBinaryReader reader, bool gm2022_5) FormatBZ2 = false; // Need to convert the QOI data to PNG for compatibility purposes (at least for now) - using Bitmap bmp = QoiConverter.GetImageFromStream(reader.Stream); + using SKBitmap bmp = QoiConverter.GetImageFromStream(reader.Stream); if (sharedStream.Length != 0) sharedStream.Seek(0, SeekOrigin.Begin); - bmp.Save(sharedStream, ImageFormat.Png); + bmp.Encode(sharedStream, SKEncodedImageFormat.Png, 100); TextureBlob = new byte[(int)sharedStream.Position]; sharedStream.Seek(0, SeekOrigin.Begin); sharedStream.Read(TextureBlob, 0, TextureBlob.Length); diff --git a/UndertaleModLib/Models/UndertaleTexturePageItem.cs b/UndertaleModLib/Models/UndertaleTexturePageItem.cs index 612041e1e..86bfe265a 100644 --- a/UndertaleModLib/Models/UndertaleTexturePageItem.cs +++ b/UndertaleModLib/Models/UndertaleTexturePageItem.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.Drawing; +using SkiaSharp; using UndertaleModLib.Util; namespace UndertaleModLib.Models; @@ -146,19 +147,18 @@ public void Dispose() /// /// The new image that shall be applied to this texture page item. /// Whether to dispose afterwards. - public void ReplaceTexture(Image replaceImage, bool disposeImage = true) + public void ReplaceTexture(SKBitmap replaceImage, bool disposeImage = true) { - Image finalImage = TextureWorker.ResizeImage(replaceImage, SourceWidth, SourceHeight); + SKBitmap finalImage = TextureWorker.ResizeImage(replaceImage, SourceWidth, SourceHeight); // Apply the image to the TexturePage. lock (TexturePage.TextureData) { TextureWorker worker = new TextureWorker(); - Bitmap embImage = worker.GetEmbeddedTexture(TexturePage); // Use SetPixel if needed. + SKBitmap embImage = worker.GetEmbeddedTexture(TexturePage); // Use SetPixel if needed. - Graphics g = Graphics.FromImage(embImage); - g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy; - g.DrawImage(finalImage, SourceX, SourceY); + SKCanvas g = new SKCanvas(embImage); + g.DrawBitmap(finalImage, SourceX, SourceY); g.Dispose(); TexturePage.TextureData.TextureBlob = TextureWorker.GetImageBytes(embImage); diff --git a/UndertaleModLib/UndertaleModLib.csproj b/UndertaleModLib/UndertaleModLib.csproj index d42677546..2a4116c9a 100644 --- a/UndertaleModLib/UndertaleModLib.csproj +++ b/UndertaleModLib/UndertaleModLib.csproj @@ -30,8 +30,8 @@ - - + + diff --git a/UndertaleModLib/Util/QoiConverter.cs b/UndertaleModLib/Util/QoiConverter.cs index 16dd55ab9..5ab238c1c 100644 --- a/UndertaleModLib/Util/QoiConverter.cs +++ b/UndertaleModLib/Util/QoiConverter.cs @@ -3,6 +3,7 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; +using SkiaSharp; namespace UndertaleModLib.Util { @@ -46,12 +47,12 @@ public static void InitSharedBuffer(int size) } /// - /// Creates a from a . + /// Creates a from a . /// /// The stream to create the PNG image from. /// The QOI image as a PNG. /// If there is an invalid QOIF magic header or there was an error with stride width. - public static Bitmap GetImageFromStream(Stream s) + public static SKBitmap GetImageFromStream(Stream s) { Span header = stackalloc byte[12]; s.Read(header); @@ -63,19 +64,19 @@ public static Bitmap GetImageFromStream(Stream s) } /// - /// Creates a from a of s. + /// Creates a from a of s. /// /// The of s to create the PNG image from. /// The QOI image as a PNG. /// If there is an invalid QOIF magic header or there was an error with stride width. - public static Bitmap GetImageFromSpan(ReadOnlySpan bytes) => GetImageFromSpan(bytes, out _); + public static SKBitmap GetImageFromSpan(ReadOnlySpan bytes) => GetImageFromSpan(bytes, out _); /// /// /// The total amount of data read from the . /// /// - public unsafe static Bitmap GetImageFromSpan(ReadOnlySpan bytes, out int length) + public unsafe static SKBitmap GetImageFromSpan(ReadOnlySpan bytes, out int length) { ReadOnlySpan header = bytes[..12]; if (header[0] != (byte)'f' || header[1] != (byte)'i' || header[2] != (byte)'o' || header[3] != (byte)'q') @@ -87,14 +88,12 @@ public unsafe static Bitmap GetImageFromSpan(ReadOnlySpan bytes, out int l ReadOnlySpan pixelData = bytes.Slice(12, length); - Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb); - bmp.SetResolution(96.0f, 96.0f); - - BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); - if (data.Stride != width * 4) + SKBitmap bmp = new SKBitmap(width, height); + + if (bmp.RowBytes != width * 4) throw new Exception("Need to reimplement QOI conversions to account for stride, apparently"); - byte* bmpPtr = (byte*)data.Scan0; + byte* bmpPtr = (byte*)bmp.GetPixels(); byte* bmpEnd = bmpPtr + (4 * width * height); int pos = 0; @@ -176,30 +175,28 @@ public unsafe static Bitmap GetImageFromSpan(ReadOnlySpan bytes, out int l *bmpPtr++ = r; *bmpPtr++ = a; } - - bmp.UnlockBits(data); - + length += header.Length; return bmp; } /// - /// Creates a QOI image as a byte array from a . + /// Creates a QOI image as a byte array from a . /// - /// The to create the QOI image from. + /// The to create the QOI image from. /// The amount of bytes of padding that should be used. /// A QOI Image as a byte array. /// If there was an error with stride width. - public static byte[] GetArrayFromImage(Bitmap bmp, int padding = 4) => GetSpanFromImage(bmp, padding).ToArray(); + public static byte[] GetArrayFromImage(SKBitmap bmp, int padding = 4) => GetSpanFromImage(bmp, padding).ToArray(); /// - /// Creates a QOI image as a from a . + /// Creates a QOI image as a from a . /// - /// The to create the QOI image from. + /// The to create the QOI image from. /// The amount of bytes of padding that should be used. /// A QOI Image as a byte array. /// If there was an error with stride width. - public static unsafe Span GetSpanFromImage(Bitmap bmp, int padding = 4) + public static unsafe Span GetSpanFromImage(SKBitmap bmp, int padding = 4) { if (!isBufferEmpty) Array.Clear(sharedBuffer); @@ -214,11 +211,10 @@ public static unsafe Span GetSpanFromImage(Bitmap bmp, int padding = 4) sharedBuffer[6] = (byte)(bmp.Height & 0xff); sharedBuffer[7] = (byte)((bmp.Height >> 8) & 0xff); - BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); - if (data.Stride != bmp.Width * 4) + if (bmp.RowBytes != bmp.Width * 4) throw new Exception("Need to reimplement QOI conversions to account for stride, apparently"); - byte* bmpPtr = (byte*)data.Scan0; + byte* bmpPtr = (byte*)bmp.GetPixels(); byte* bmpEnd = bmpPtr + (4 * bmp.Width * bmp.Height); int resPos = HeaderSize; @@ -310,9 +306,7 @@ public static unsafe Span GetSpanFromImage(Bitmap bmp, int padding = 4) vPrev = v; bmpPtr += 4; } - - bmp.UnlockBits(data); - + // Add padding resPos += padding; diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index aef3189bc..8ebd716b0 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -4,25 +4,25 @@ using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; +using SkiaSharp; using UndertaleModLib.Models; namespace UndertaleModLib.Util { public class TextureWorker { - private Dictionary embeddedDictionary = new Dictionary(); - private static readonly ImageConverter _imageConverter = new ImageConverter(); + private Dictionary embeddedDictionary = new Dictionary(); // Cleans up all the images when usage of this worker is finished. // Should be called when a TextureWorker will never be used again. public void Cleanup() { - foreach (Bitmap img in embeddedDictionary.Values) + foreach (SKBitmap img in embeddedDictionary.Values) img.Dispose(); embeddedDictionary.Clear(); } - public Bitmap GetEmbeddedTexture(UndertaleEmbeddedTexture embeddedTexture) + public SKBitmap GetEmbeddedTexture(UndertaleEmbeddedTexture embeddedTexture) { lock (embeddedDictionary) { @@ -37,23 +37,26 @@ public void ExportAsPNG(UndertaleTexturePageItem texPageItem, string FullPath, s SaveImageToFile(FullPath, GetTextureFor(texPageItem, imageName != null ? imageName : Path.GetFileNameWithoutExtension(FullPath), includePadding)); } - public Bitmap GetTextureFor(UndertaleTexturePageItem texPageItem, string imageName, bool includePadding = false) + public SKBitmap GetTextureFor(UndertaleTexturePageItem texPageItem, string imageName, bool includePadding = false) { int exportWidth = texPageItem.BoundingWidth; // sprite.Width int exportHeight = texPageItem.BoundingHeight; // sprite.Height - Bitmap embeddedImage = GetEmbeddedTexture(texPageItem.TexturePage); + SKBitmap embeddedImage = GetEmbeddedTexture(texPageItem.TexturePage); // Sanity checks. if (includePadding && ((texPageItem.TargetWidth > exportWidth) || (texPageItem.TargetHeight > exportHeight))) throw new InvalidDataException(imageName + "'s texture is larger than its bounding box!"); // Create a bitmap representing that part of the texture page. - Bitmap resultImage = null; + SKBitmap resultImage = new SKBitmap(); + lock (embeddedImage) { try { - resultImage = embeddedImage.Clone(new Rectangle(texPageItem.SourceX, texPageItem.SourceY, texPageItem.SourceWidth, texPageItem.SourceHeight), PixelFormat.DontCare); + var sourceRect = SKRectI.Create(texPageItem.SourceX, texPageItem.SourceY, texPageItem.SourceWidth, texPageItem.SourceHeight); + embeddedImage.ExtractSubset(resultImage, sourceRect); + resultImage = resultImage.Copy(); } catch (OutOfMemoryException) { @@ -66,71 +69,41 @@ public Bitmap GetTextureFor(UndertaleTexturePageItem texPageItem, string imageNa resultImage = ResizeImage(resultImage, texPageItem.TargetWidth, texPageItem.TargetHeight); // Put it in the final holder image. - Bitmap returnImage = resultImage; + SKBitmap returnImage = resultImage; if (includePadding) { - returnImage = new Bitmap(exportWidth, exportHeight); - Graphics g = Graphics.FromImage(returnImage); - g.DrawImage(resultImage, new Rectangle(texPageItem.TargetX, texPageItem.TargetY, resultImage.Width, resultImage.Height), new Rectangle(0, 0, resultImage.Width, resultImage.Height), GraphicsUnit.Pixel); + returnImage = new SKBitmap(exportWidth, exportHeight); + SKCanvas g = new SKCanvas(returnImage); + g.DrawBitmap(resultImage, SKRect.Create(0, 0, resultImage.Width, resultImage.Height), SKRect.Create(texPageItem.TargetX, texPageItem.TargetY, resultImage.Width, resultImage.Height)); g.Dispose(); } return returnImage; } - public static Bitmap ReadImageFromFile(string filePath) + public static SKBitmap ReadImageFromFile(string filePath) { return GetImageFromByteArray(File.ReadAllBytes(filePath)); } - // Grabbed from https://stackoverflow.com/questions/3801275/how-to-convert-image-to-byte-array/16576471#16576471 - public static Bitmap GetImageFromByteArray(byte[] byteArray) + public static SKBitmap GetImageFromByteArray(byte[] byteArray) { - Bitmap bm = (Bitmap)_imageConverter.ConvertFrom(byteArray); - - if (bm != null && (bm.HorizontalResolution != (int)bm.HorizontalResolution || - bm.VerticalResolution != (int)bm.VerticalResolution)) - { - // Correct a strange glitch that has been observed in the test program when converting - // from a PNG file image created by CopyImageToByteArray() - the dpi value "drifts" - // slightly away from the nominal integer value - bm.SetResolution((int)(bm.HorizontalResolution + 0.5f), - (int)(bm.VerticalResolution + 0.5f)); - } - + SKBitmap bm = SKBitmap.Decode(byteArray); return bm; } // This should perform a high quality resize. - // Grabbed from https://stackoverflow.com/questions/1922040/how-to-resize-an-image-c-sharp - public static Bitmap ResizeImage(Image image, int width, int height) + public static SKBitmap ResizeImage(SKBitmap image, int width, int height) { var destRect = new Rectangle(0, 0, width, height); - var destImage = new Bitmap(width, height); - - destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); - - using (var graphics = Graphics.FromImage(destImage)) - { - graphics.CompositingMode = CompositingMode.SourceCopy; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - - using (var wrapMode = new ImageAttributes()) - { - wrapMode.SetWrapMode(WrapMode.TileFlipXY); - graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode); - } - } - + var destImage = new SKBitmap(width, height); + image.ScalePixels(destImage, SKFilterQuality.High); return destImage; } public static byte[] ReadMaskData(string filePath) { - Bitmap image = ReadImageFromFile(filePath); + SKBitmap image = ReadImageFromFile(filePath); List bytes = new List(); int enableColor = Color.White.ToArgb(); @@ -143,7 +116,7 @@ public static byte[] ReadMaskData(string filePath) int pxEnd = Math.Min(pxStart + 8, (int) image.Width); for (int x = pxStart; x < pxEnd; x++) - if (image.GetPixel(x, y).ToArgb() == enableColor) // Don't use Color == OtherColor, it doesn't seem to give us the type of equals comparison we want here. + if ((uint)image.GetPixel(x, y) == enableColor) // Don't use Color == OtherColor, it doesn't seem to give us the type of equals comparison we want here. fullByte |= (byte)(0b1 << (7 - (x - pxStart))); bytes.Add(fullByte); @@ -156,23 +129,23 @@ public static byte[] ReadMaskData(string filePath) public static byte[] ReadTextureBlob(string filePath) { - Image.FromFile(filePath).Dispose(); // Make sure the file is valid image. + SKBitmap.Decode(filePath).Dispose(); // Make sure the file is valid image. return File.ReadAllBytes(filePath); } public static void SaveEmptyPNG(string FullPath, int width, int height) { - var blackImage = new Bitmap(width, height); + var blackImage = new SKBitmap(width, height); for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) - blackImage.SetPixel(x, y, Color.Black); + blackImage.SetPixel(x, y, SKColors.Black); SaveImageToFile(FullPath, blackImage); } - public static Bitmap GetCollisionMaskImage(UndertaleSprite sprite, UndertaleSprite.MaskEntry mask) + public static SKBitmap GetCollisionMaskImage(UndertaleSprite sprite, UndertaleSprite.MaskEntry mask) { byte[] maskData = mask.Data; - Bitmap bitmap = new Bitmap((int)sprite.Width, (int)sprite.Height, PixelFormat.Format32bppArgb); // Ugh. I want to use 1bpp, but for some BS reason C# doesn't allow SetPixel in that mode. + SKBitmap bitmap = new SKBitmap((int)sprite.Width, (int)sprite.Height); // Ugh. I want to use 1bpp, but for some BS reason C# doesn't allow SetPixel in that mode. for (int y = 0; y < sprite.Height; y++) { @@ -181,7 +154,7 @@ public static Bitmap GetCollisionMaskImage(UndertaleSprite sprite, UndertaleSpri { byte temp = maskData[rowStart + (x / 8)]; bool pixelBit = (temp & (0b1 << (7 - (x % 8)))) != 0b0; - bitmap.SetPixel(x, y, pixelBit ? Color.White : Color.Black); + bitmap.SetPixel(x, y, pixelBit ? SKColors.White : SKColors.Black); } } @@ -193,22 +166,18 @@ public static void ExportCollisionMaskPNG(UndertaleSprite sprite, UndertaleSprit SaveImageToFile(fullPath, GetCollisionMaskImage(sprite, mask)); } - public static byte[] GetImageBytes(Image image, bool disposeImage = true) + public static byte[] GetImageBytes(SKBitmap image, bool disposeImage = true) { - using (var ms = new MemoryStream()) - { - image.Save(ms, image.RawFormat); - byte[] result = ms.ToArray(); - if (disposeImage) - image.Dispose(); - return result; - } + byte[] result = image.Bytes; + if (disposeImage) + image.Dispose(); + return result; } - public static void SaveImageToFile(string FullPath, Image image, Boolean disposeImage = true) + public static void SaveImageToFile(string FullPath, SKBitmap image, Boolean disposeImage = true) { var stream = new FileStream(FullPath, FileMode.Create); - image.Save(stream, ImageFormat.Png); + image.Encode(stream, SKEncodedImageFormat.Png, 100); stream.Close(); if (disposeImage) image.Dispose(); From c26eb7501b48970f432d53e28f89559802f306e4 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Mon, 25 Dec 2023 02:02:44 +0300 Subject: [PATCH 02/22] Some UI fixes. --- .../Models/UndertaleTexturePageItem.cs | 2 +- UndertaleModLib/Util/TextureWorker.cs | 3 +- .../Converters/UndertaleCachedImageLoader.cs | 117 +++++----- .../Editors/UndertaleRoomEditor.xaml.cs | 14 +- .../UndertaleTexturePageItemEditor.xaml.cs | 201 +++++++++--------- 5 files changed, 174 insertions(+), 163 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleTexturePageItem.cs b/UndertaleModLib/Models/UndertaleTexturePageItem.cs index 86bfe265a..f04b11f07 100644 --- a/UndertaleModLib/Models/UndertaleTexturePageItem.cs +++ b/UndertaleModLib/Models/UndertaleTexturePageItem.cs @@ -157,7 +157,7 @@ public void ReplaceTexture(SKBitmap replaceImage, bool disposeImage = true) TextureWorker worker = new TextureWorker(); SKBitmap embImage = worker.GetEmbeddedTexture(TexturePage); // Use SetPixel if needed. - SKCanvas g = new SKCanvas(embImage); + SKCanvas g = new(embImage); g.DrawBitmap(finalImage, SourceX, SourceY); g.Dispose(); diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index 8ebd716b0..2b5b52847 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -168,7 +168,8 @@ public static void ExportCollisionMaskPNG(UndertaleSprite sprite, UndertaleSprit public static byte[] GetImageBytes(SKBitmap image, bool disposeImage = true) { - byte[] result = image.Bytes; + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + byte[] result = data.ToArray(); if (disposeImage) image.Dispose(); return result; diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index ceaf46054..3d015df1e 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; +using SkiaSharp; using System.Globalization; using System.IO; using System.Linq; @@ -21,9 +22,6 @@ namespace UndertaleModTool { - // TODO: "Bitmap" is Windows-only. - - #pragma warning disable CA1416 public class UndertaleCachedImageLoader : IValueConverter { [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] @@ -158,42 +156,48 @@ public static void Reset() currBufferSize = 1048576; } - public static Bitmap CreateSpriteBitmap(Rectangle rect, in UndertaleTexturePageItem texture, int diffW = 0, int diffH = 0, bool isTile = false) + public static SKBitmap CreateSpriteBitmap(Rectangle rect, in UndertaleTexturePageItem texture, int diffW = 0, int diffH = 0, bool isTile = false) { - using MemoryStream stream = new(texture.TexturePage.TextureData.TextureBlob); - Bitmap spriteBMP = new(rect.Width, rect.Height); + SKBitmap spriteBMP = new(rect.Width, rect.Height); rect.Width -= (diffW > 0) ? diffW : 0; rect.Height -= (diffH > 0) ? diffH : 0; int x = isTile ? texture.TargetX : 0; int y = isTile ? texture.TargetY : 0; - using (Graphics g = Graphics.FromImage(spriteBMP)) + using (SKCanvas g = new(spriteBMP)) { - using Image img = Image.FromStream(stream); // "ImageConverter.ConvertFrom()" does the same, except it doesn't explicitly dispose MemoryStream - g.DrawImage(img, new Rectangle(x, y, rect.Width, rect.Height), rect, GraphicsUnit.Pixel); + using SKBitmap img = SKBitmap.Decode(texture.TexturePage.TextureData.TextureBlob); + + var rectDest = SKRect.Create(x, y, rect.Width, rect.Height); + var rectSrc = SKRect.Create(rect.Left, rect.Top, rect.Width, rect.Height); + g.DrawBitmap(img, rectSrc, rectDest); } return spriteBMP; } private ImageSource CreateSpriteSource(in Rectangle rect, in UndertaleTexturePageItem texture, int diffW = 0, int diffH = 0, bool isTile = false) { - Bitmap spriteBMP = CreateSpriteBitmap(rect, in texture, diffW, diffH, isTile); + SKBitmap spriteBMP = CreateSpriteBitmap(rect, in texture, diffW, diffH, isTile); + var data = spriteBMP.Encode(SKEncodedImageFormat.Png, 100); + + BitmapImage spriteSrc = new(); + spriteSrc.BeginInit(); + spriteSrc.CacheOption = BitmapCacheOption.OnLoad; + spriteSrc.StreamSource = data.AsStream(); + spriteSrc.EndInit(); - IntPtr bmpPtr = spriteBMP.GetHbitmap(); - ImageSource spriteSrc = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bmpPtr, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); - DeleteObject(bmpPtr); spriteBMP.Dispose(); + data.Dispose(); spriteSrc.Freeze(); // allow UI thread access return spriteSrc; } - private void ProcessTileSet(string textureName, Bitmap bmp, List> tileRectList, int targetX, int targetY) + private void ProcessTileSet(string textureName, SKBitmap bmp, List> tileRectList, int targetX, int targetY) { - BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat); - int depth = Image.GetPixelFormatSize(data.PixelFormat) / 8; + int depth = bmp.BytesPerPixel; - int bufferLen = data.Stride * bmp.Height; + int bufferLen = bmp.RowBytes * bmp.Height; byte[] buffer; if (ReuseTileBuffer) { @@ -208,7 +212,7 @@ private void ProcessTileSet(string textureName, Bitmap bmp, List { @@ -228,7 +232,7 @@ private void ProcessTileSet(string textureName, Bitmap bmp, List data.Width || h > data.Height || x < 0 || y < 0 || x + w > data.Width || y + h > data.Height) + if (w > bmp.Width || h > bmp.Height || x < 0 || y < 0 || x + w > bmp.Width || y + h > bmp.Height) return; int bufferResLen = w * h * depth; @@ -240,23 +244,25 @@ private void ProcessTileSet(string textureName, Bitmap bmp, List.Shared.Return(bufferRes); - IntPtr bmpPtr = tileBMP.GetHbitmap(); - ImageSource spriteSrc = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bmpPtr, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); - DeleteObject(bmpPtr); + var data = tileBMP.Encode(SKEncodedImageFormat.Png, 100); + BitmapImage spriteSrc = new(); + spriteSrc.BeginInit(); + spriteSrc.CacheOption = BitmapCacheOption.OnLoad; + spriteSrc.StreamSource = data.AsStream(); + spriteSrc.EndInit(); tileBMP.Dispose(); + data.Dispose(); spriteSrc.Freeze(); // allow UI thread access @@ -264,7 +270,6 @@ private void ProcessTileSet(string textureName, Bitmap bmp, List, Bitmap> TileCache { get; set; } = new(); - private static readonly ConcurrentDictionary tilePageCache = new(); + public static ConcurrentDictionary, SKBitmap> TileCache { get; set; } = new(); + private static readonly ConcurrentDictionary tilePageCache = new(); public static void Reset() { - foreach (Bitmap bmp in TileCache.Values) + foreach (SKBitmap bmp in TileCache.Values) bmp.Dispose(); - foreach (Bitmap bmp in tilePageCache.Values) + foreach (SKBitmap bmp in tilePageCache.Values) bmp.Dispose(); TileCache.Clear(); @@ -374,7 +379,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur return null; } - Bitmap tilePageBMP; + SKBitmap tilePageBMP; if (tilePageCache.ContainsKey(texName)) { tilePageBMP = tilePageCache[texName]; @@ -389,18 +394,16 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur tilePageCache[texName] = tilePageBMP; } - BitmapData data = tilePageBMP.LockBits(new Rectangle(0, 0, tilePageBMP.Width, tilePageBMP.Height), ImageLockMode.ReadOnly, tilePageBMP.PixelFormat); - int depth = Image.GetPixelFormatSize(data.PixelFormat) / 8; - byte[] buffer = new byte[data.Stride * tilePageBMP.Height]; - Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); - tilePageBMP.UnlockBits(data); + int depth = tilePageBMP.BytesPerPixel; + + byte[] buffer = new byte[tilePageBMP.RowBytes * tilePageBMP.Height]; + Marshal.Copy(tilePageBMP.GetPixels(), buffer, 0, buffer.Length); int w = (int)tilesBG.GMS2TileWidth; int h = (int)tilesBG.GMS2TileHeight; int outX = (int)tilesBG.GMS2OutputBorderX; int outY = (int)tilesBG.GMS2OutputBorderY; int tileRows = (int)Math.Ceiling(tilesBG.GMS2TileCount / (double)tilesBG.GMS2TileColumns); - System.Drawing.Imaging.PixelFormat format = tilePageBMP.PixelFormat; bool outOfBounds = false; _ = Parallel.For(0, tileRows, (y) => @@ -411,7 +414,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur { int x1 = ((x + 1) * outX) + (x * (w + outX)); - if (x1 + w > data.Width || y1 + h > data.Height) + if (x1 + w > tilePageBMP.Width || y1 + h > tilePageBMP.Height) { outOfBounds = true; return; @@ -426,7 +429,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur { for (int j = 0; j < w * depth; j += depth) { - int origIndex = (y1 * data.Stride) + (i * data.Stride) + (x1 * depth) + j; + int origIndex = (y1 * tilePageBMP.RowBytes) + (i * tilePageBMP.RowBytes) + (x1 * depth) + j; int croppedIndex = (i * w * depth) + j; Buffer.BlockCopy(buffer, origIndex, bufferRes, croppedIndex, depth); @@ -434,10 +437,8 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur } } - Bitmap tileBMP = new(w, h); - BitmapData tileData = tileBMP.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, format); - Marshal.Copy(bufferRes, 0, tileData.Scan0, bufferResLen); - tileBMP.UnlockBits(tileData); + SKBitmap tileBMP = new(w, h); + Marshal.Copy(bufferRes, 0, tileBMP.GetPixels(), bufferResLen); ArrayPool.Shared.Return(bufferRes); TileCache.TryAdd(new(texName, (uint)((tilesBG.GMS2TileColumns * y) + x)), tileBMP); @@ -466,10 +467,10 @@ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in UndertaleBackground tilesBG, in int w, in int h) { - Bitmap layerBMP = new(w * (int)tilesData.TilesX, h * (int)tilesData.TilesY); + SKBitmap layerBMP = new(w * (int)tilesData.TilesX, h * (int)tilesData.TilesY); uint maxID = tilesData.Background.GMS2TileIds.Select(x => x.ID).Max(); - using Graphics g = Graphics.FromImage(layerBMP); + using SKCanvas g = new(layerBMP); for (int y = 0; y < tilesData.TilesY; y++) { for (int x = 0; x < tilesData.TilesX; x++) @@ -487,7 +488,7 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under continue; } - Bitmap resBMP = (Bitmap)TileCache[new(tilesBG.Texture.Name.Content, realID)].Clone(); + /*SKBitmap resBMP = SKBitmap.FromImage(TileCache[new(tilesBG.Texture.Name.Content, realID)]); switch (id >> 28) { @@ -516,24 +517,26 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under default: Debug.WriteLine("Tile of " + tilesData.ParentLayer.LayerName + " located at (" + x + ", " + y + ") has unknown flag."); break; - } - - g.DrawImageUnscaled(resBMP, x * w, y * h); + }*/ - resBMP.Dispose(); + g.DrawBitmap(TileCache[new(tilesBG.Texture.Name.Content, realID)], x * w, y * h); } else - g.DrawImageUnscaled(TileCache[new(tilesBG.Texture.Name.Content, id)], x * w, y * h); + g.DrawBitmap(TileCache[new(tilesBG.Texture.Name.Content, id)], x * w, y * h); } } - IntPtr bmpPtr = layerBMP.GetHbitmap(); - ImageSource spriteSrc = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bmpPtr, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); - DeleteObject(bmpPtr); + var data = layerBMP.Encode(SKEncodedImageFormat.Png, 100); + BitmapImage spriteSrc = new(); + spriteSrc.BeginInit(); + spriteSrc.CacheOption = BitmapCacheOption.OnLoad; + spriteSrc.StreamSource = data.AsStream(); + spriteSrc.EndInit(); layerBMP.Dispose(); + data.Dispose(); + // TODO: spriteSrc.Freeze() ? return spriteSrc; } } - #pragma warning restore CA1416 } diff --git a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs index 6a3cd5c53..b77b77438 100644 --- a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs @@ -1,4 +1,5 @@ using Microsoft.Win32; +using SkiaSharp; using System; using System.Buffers; using System.Collections; @@ -2534,9 +2535,16 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur { Tuple tileKey = new(tilesBG.Texture.Name.Content, id); - IntPtr bmpPtr = CachedTileDataLoader.TileCache[tileKey].GetHbitmap(); - ImageSource spriteSrc = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bmpPtr, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); - DeleteObject(bmpPtr); + SKBitmap bmp = CachedTileDataLoader.TileCache[tileKey]; + var data = bmp.Encode(SKEncodedImageFormat.Png, 100); + + BitmapImage spriteSrc = new(); + spriteSrc.BeginInit(); + spriteSrc.CacheOption = BitmapCacheOption.OnLoad; + spriteSrc.StreamSource = data.AsStream(); + spriteSrc.EndInit(); + data.Dispose(); + spriteSrc.Freeze(); // allow UI thread access TileCache.TryAdd(tileKey, spriteSrc); diff --git a/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs index 2a9404daa..c51fd5b4a 100644 --- a/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs @@ -1,101 +1,100 @@ -using Microsoft.Win32; -using System; -using System.Drawing; -using System.Windows; -using System.IO; -using UndertaleModLib.Models; -using UndertaleModLib.Util; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Data; -using UndertaleModTool.Windows; - -namespace UndertaleModTool -{ - /// - /// Logika interakcji dla klasy UndertaleTexturePageItemEditor.xaml - /// - public partial class UndertaleTexturePageItemEditor : DataUserControl - { - private static readonly MainWindow mainWindow = Application.Current.MainWindow as MainWindow; - - public UndertaleTexturePageItemEditor() - { - InitializeComponent(); - } - - private void Import_Click(object sender, RoutedEventArgs e) - { - OpenFileDialog dlg = new OpenFileDialog(); - - dlg.DefaultExt = ".png"; - dlg.Filter = "PNG files (.png)|*.png|All files|*"; - - if (!(dlg.ShowDialog() ?? false)) - return; - - try - { - Bitmap image = TextureWorker.ReadImageFromFile(dlg.FileName); - image.SetResolution(96.0F, 96.0F); - (this.DataContext as UndertaleTexturePageItem).ReplaceTexture(image); - - // Refresh the image of "ItemDisplay" - if (ItemDisplay.FindName("RenderAreaBorder") is not Border border) - return; - if (border.Background is not ImageBrush brush) - return; - BindingOperations.GetBindingExpression(brush, ImageBrush.ImageSourceProperty)?.UpdateTarget(); - } - catch (Exception ex) - { - mainWindow.ShowError(ex.Message, "Failed to import image"); - } - } - - private void Export_Click(object sender, RoutedEventArgs e) - { - SaveFileDialog dlg = new SaveFileDialog(); - - dlg.DefaultExt = ".png"; - dlg.Filter = "PNG files (.png)|*.png|All files|*"; - - if (dlg.ShowDialog() == true) - { - TextureWorker worker = new TextureWorker(); - try - { - worker.ExportAsPNG((UndertaleTexturePageItem)this.DataContext, dlg.FileName); - } - catch (Exception ex) - { - mainWindow.ShowError("Failed to export file: " + ex.Message, "Failed to export file"); - } - worker.Cleanup(); - } - } - - private void FindReferencesButton_Click(object sender, RoutedEventArgs e) - { - var obj = (sender as FrameworkElement)?.DataContext; - if (obj is not UndertaleTexturePageItem item) - return; - - FindReferencesTypesDialog dialog = null; - try - { - dialog = new(item, mainWindow.Data); - dialog.ShowDialog(); - } - catch (Exception ex) - { - mainWindow.ShowError("An error occured in the object references related window.\n" + - $"Please report this on GitHub.\n\n{ex}"); - } - finally - { - dialog?.Close(); - } - } - } -} +using Microsoft.Win32; +using System; +using System.Drawing; +using System.Windows; +using System.IO; +using UndertaleModLib.Models; +using UndertaleModLib.Util; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Data; +using UndertaleModTool.Windows; + +namespace UndertaleModTool +{ + /// + /// Logika interakcji dla klasy UndertaleTexturePageItemEditor.xaml + /// + public partial class UndertaleTexturePageItemEditor : DataUserControl + { + private static readonly MainWindow mainWindow = Application.Current.MainWindow as MainWindow; + + public UndertaleTexturePageItemEditor() + { + InitializeComponent(); + } + + private void Import_Click(object sender, RoutedEventArgs e) + { + OpenFileDialog dlg = new OpenFileDialog(); + + dlg.DefaultExt = ".png"; + dlg.Filter = "PNG files (.png)|*.png|All files|*"; + + if (!(dlg.ShowDialog() ?? false)) + return; + + try + { + SkiaSharp.SKBitmap image = TextureWorker.ReadImageFromFile(dlg.FileName); + (this.DataContext as UndertaleTexturePageItem).ReplaceTexture(image); + + // Refresh the image of "ItemDisplay" + if (ItemDisplay.FindName("RenderAreaBorder") is not Border border) + return; + if (border.Background is not ImageBrush brush) + return; + BindingOperations.GetBindingExpression(brush, ImageBrush.ImageSourceProperty)?.UpdateTarget(); + } + catch (Exception ex) + { + mainWindow.ShowError(ex.Message, "Failed to import image"); + } + } + + private void Export_Click(object sender, RoutedEventArgs e) + { + SaveFileDialog dlg = new SaveFileDialog(); + + dlg.DefaultExt = ".png"; + dlg.Filter = "PNG files (.png)|*.png|All files|*"; + + if (dlg.ShowDialog() == true) + { + TextureWorker worker = new TextureWorker(); + try + { + worker.ExportAsPNG((UndertaleTexturePageItem)this.DataContext, dlg.FileName); + } + catch (Exception ex) + { + mainWindow.ShowError("Failed to export file: " + ex.Message, "Failed to export file"); + } + worker.Cleanup(); + } + } + + private void FindReferencesButton_Click(object sender, RoutedEventArgs e) + { + var obj = (sender as FrameworkElement)?.DataContext; + if (obj is not UndertaleTexturePageItem item) + return; + + FindReferencesTypesDialog dialog = null; + try + { + dialog = new(item, mainWindow.Data); + dialog.ShowDialog(); + } + catch (Exception ex) + { + mainWindow.ShowError("An error occured in the object references related window.\n" + + $"Please report this on GitHub.\n\n{ex}"); + } + finally + { + dialog?.Close(); + } + } + } +} From 9e841c6b7622431daeb688d2aefabcfc50782289 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Mon, 25 Dec 2023 18:26:05 +0300 Subject: [PATCH 03/22] Implement GMS 2 tile flag rendering. --- .../Converters/UndertaleCachedImageLoader.cs | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index 3d015df1e..7c0c45d62 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -173,6 +173,7 @@ public static SKBitmap CreateSpriteBitmap(Rectangle rect, in UndertaleTexturePag var rectSrc = SKRect.Create(rect.Left, rect.Top, rect.Width, rect.Height); g.DrawBitmap(img, rectSrc, rectDest); } + spriteBMP.SetImmutable(); return spriteBMP; } @@ -440,6 +441,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur SKBitmap tileBMP = new(w, h); Marshal.Copy(bufferRes, 0, tileBMP.GetPixels(), bufferResLen); ArrayPool.Shared.Return(bufferRes); + tileBMP.SetImmutable(); TileCache.TryAdd(new(texName, (uint)((tilesBG.GMS2TileColumns * y) + x)), tileBMP); } @@ -467,10 +469,10 @@ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in UndertaleBackground tilesBG, in int w, in int h) { - SKBitmap layerBMP = new(w * (int)tilesData.TilesX, h * (int)tilesData.TilesY); + using SKBitmap layerBMP = new(w * (int)tilesData.TilesX, h * (int)tilesData.TilesY); + SKCanvas layerG = new(layerBMP); uint maxID = tilesData.Background.GMS2TileIds.Select(x => x.ID).Max(); - using SKCanvas g = new(layerBMP); for (int y = 0; y < tilesData.TilesY; y++) { for (int x = 0; x < tilesData.TilesX; x++) @@ -488,51 +490,60 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under continue; } - /*SKBitmap resBMP = SKBitmap.FromImage(TileCache[new(tilesBG.Texture.Name.Content, realID)]); + SKBitmap srcBMP = TileCache[new(tilesBG.Texture.Name.Content, realID)]; + using SKSurface tileSurf = SKSurface.Create(srcBMP.Info); switch (id >> 28) { case 1: - resBMP.RotateFlip(RotateFlipType.RotateNoneFlipX); + // Flip X + tileSurf.Canvas.Scale(-1, 1, srcBMP.Width / 2, 0); break; case 2: - resBMP.RotateFlip(RotateFlipType.RotateNoneFlipY); + // Flip Y + tileSurf.Canvas.Scale(1, -1, 0, srcBMP.Height / 2); break; case 3: - resBMP.RotateFlip(RotateFlipType.RotateNoneFlipXY); + // Flip X and Y + tileSurf.Canvas.Scale(-1, -1, srcBMP.Width / 2, srcBMP.Height / 2); break; case 4: - resBMP.RotateFlip(RotateFlipType.Rotate90FlipNone); + tileSurf.Canvas.RotateDegrees(90); break; case 5: - resBMP.RotateFlip(RotateFlipType.Rotate270FlipY); + tileSurf.Canvas.RotateDegrees(270); + tileSurf.Canvas.Scale(1, -1, 0, srcBMP.Height / 2); break; case 6: - resBMP.RotateFlip(RotateFlipType.Rotate90FlipY); + tileSurf.Canvas.RotateDegrees(90); + tileSurf.Canvas.Scale(1, -1, 0, srcBMP.Height / 2); break; case 7: - resBMP.RotateFlip(RotateFlipType.Rotate270FlipNone); + tileSurf.Canvas.RotateDegrees(270); break; default: Debug.WriteLine("Tile of " + tilesData.ParentLayer.LayerName + " located at (" + x + ", " + y + ") has unknown flag."); break; - }*/ + } - g.DrawBitmap(TileCache[new(tilesBG.Texture.Name.Content, realID)], x * w, y * h); + tileSurf.Canvas.DrawBitmap(srcBMP, 0, 0); + tileSurf.Draw(layerG, x * w, y * h, null); } else - g.DrawBitmap(TileCache[new(tilesBG.Texture.Name.Content, id)], x * w, y * h); + layerG.DrawBitmap(TileCache[new(tilesBG.Texture.Name.Content, id)], x * w, y * h); } } + layerG.Dispose(); var data = layerBMP.Encode(SKEncodedImageFormat.Png, 100); + BitmapImage spriteSrc = new(); spriteSrc.BeginInit(); spriteSrc.CacheOption = BitmapCacheOption.OnLoad; spriteSrc.StreamSource = data.AsStream(); spriteSrc.EndInit(); - layerBMP.Dispose(); + data.Dispose(); // TODO: spriteSrc.Freeze() ? From 9faf60b918cf4f58cfa7e6e8767eb6e88035db4e Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 00:40:50 +0300 Subject: [PATCH 04/22] A little optimization of `CreateLayerSource()` --- UndertaleModTool/Converters/UndertaleCachedImageLoader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index 7c0c45d62..39a5dc2fc 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -473,6 +473,8 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under SKCanvas layerG = new(layerBMP); uint maxID = tilesData.Background.GMS2TileIds.Select(x => x.ID).Max(); + using SKSurface tileSurf = SKSurface.Create(new SKImageInfo(w, h)); + for (int y = 0; y < tilesData.TilesY; y++) { for (int x = 0; x < tilesData.TilesX; x++) @@ -491,7 +493,7 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under } SKBitmap srcBMP = TileCache[new(tilesBG.Texture.Name.Content, realID)]; - using SKSurface tileSurf = SKSurface.Create(srcBMP.Info); + tileSurf.Canvas.ResetMatrix(); switch (id >> 28) { From ce38b10f0456798de27329e8035ae3059d931049 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 15:48:18 +0300 Subject: [PATCH 05/22] Remove redundant DLL import and uncomment one line. --- .../Converters/UndertaleCachedImageLoader.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index 39a5dc2fc..87f192d09 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -24,10 +24,6 @@ namespace UndertaleModTool { public class UndertaleCachedImageLoader : IValueConverter { - [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeleteObject([In] IntPtr hObject); - private static readonly ConcurrentDictionary imageCache = new(); private static readonly ConcurrentDictionary>, ImageSource> tileCache = new(); private static readonly MainWindow mainWindow = Application.Current.MainWindow as MainWindow; @@ -335,10 +331,6 @@ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, public class CachedTileDataLoader : IMultiValueConverter { - [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeleteObject([In] IntPtr hObject); - // Tile text. page, tile ID - tile pixel data public static ConcurrentDictionary, SKBitmap> TileCache { get; set; } = new(); private static readonly ConcurrentDictionary tilePageCache = new(); @@ -547,7 +539,7 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under spriteSrc.EndInit(); data.Dispose(); - // TODO: spriteSrc.Freeze() ? + spriteSrc.Freeze(); return spriteSrc; } From c55aeab0794e4a32610654cd2405d016c75134f4 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 15:56:49 +0300 Subject: [PATCH 06/22] Simplify disposal of the disposable objects. --- .../Converters/UndertaleCachedImageLoader.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index 87f192d09..aab89bc51 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -175,8 +175,8 @@ public static SKBitmap CreateSpriteBitmap(Rectangle rect, in UndertaleTexturePag } private ImageSource CreateSpriteSource(in Rectangle rect, in UndertaleTexturePageItem texture, int diffW = 0, int diffH = 0, bool isTile = false) { - SKBitmap spriteBMP = CreateSpriteBitmap(rect, in texture, diffW, diffH, isTile); - var data = spriteBMP.Encode(SKEncodedImageFormat.Png, 100); + using SKBitmap spriteBMP = CreateSpriteBitmap(rect, in texture, diffW, diffH, isTile); + using var data = spriteBMP.Encode(SKEncodedImageFormat.Png, 100); BitmapImage spriteSrc = new(); spriteSrc.BeginInit(); @@ -184,8 +184,6 @@ private ImageSource CreateSpriteSource(in Rectangle rect, in UndertaleTexturePag spriteSrc.StreamSource = data.AsStream(); spriteSrc.EndInit(); - spriteBMP.Dispose(); - data.Dispose(); spriteSrc.Freeze(); // allow UI thread access return spriteSrc; @@ -248,18 +246,16 @@ private void ProcessTileSet(string textureName, SKBitmap bmp, List.Shared.Return(bufferRes); - var data = tileBMP.Encode(SKEncodedImageFormat.Png, 100); + using var data = tileBMP.Encode(SKEncodedImageFormat.Png, 100); BitmapImage spriteSrc = new(); spriteSrc.BeginInit(); spriteSrc.CacheOption = BitmapCacheOption.OnLoad; spriteSrc.StreamSource = data.AsStream(); spriteSrc.EndInit(); - tileBMP.Dispose(); - data.Dispose(); spriteSrc.Freeze(); // allow UI thread access @@ -530,7 +526,7 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under } layerG.Dispose(); - var data = layerBMP.Encode(SKEncodedImageFormat.Png, 100); + using var data = layerBMP.Encode(SKEncodedImageFormat.Png, 100); BitmapImage spriteSrc = new(); spriteSrc.BeginInit(); @@ -538,7 +534,6 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under spriteSrc.StreamSource = data.AsStream(); spriteSrc.EndInit(); - data.Dispose(); spriteSrc.Freeze(); return spriteSrc; From 1067b9bbcd007000d80e96f488a9404a792745db Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 16:21:52 +0300 Subject: [PATCH 07/22] Minor code improvements for "TextureWorker.cs". --- UndertaleModLib/Util/TextureWorker.cs | 34 +++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index 2b5b52847..cd254f8ee 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -11,7 +11,7 @@ namespace UndertaleModLib.Util { public class TextureWorker { - private Dictionary embeddedDictionary = new Dictionary(); + private readonly Dictionary embeddedDictionary = new(); // Cleans up all the images when usage of this worker is finished. // Should be called when a TextureWorker will never be used again. @@ -32,16 +32,16 @@ public SKBitmap GetEmbeddedTexture(UndertaleEmbeddedTexture embeddedTexture) } } - public void ExportAsPNG(UndertaleTexturePageItem texPageItem, string FullPath, string imageName = null, bool includePadding = false) + public void ExportAsPNG(UndertaleTexturePageItem texPageItem, string fullPath, string imageName = null, bool includePadding = false) { - SaveImageToFile(FullPath, GetTextureFor(texPageItem, imageName != null ? imageName : Path.GetFileNameWithoutExtension(FullPath), includePadding)); + SaveImageToFile(fullPath, GetTextureFor(texPageItem, imageName ?? Path.GetFileNameWithoutExtension(fullPath), includePadding)); } public SKBitmap GetTextureFor(UndertaleTexturePageItem texPageItem, string imageName, bool includePadding = false) { int exportWidth = texPageItem.BoundingWidth; // sprite.Width int exportHeight = texPageItem.BoundingHeight; // sprite.Height - SKBitmap embeddedImage = GetEmbeddedTexture(texPageItem.TexturePage); + using SKBitmap embeddedImage = GetEmbeddedTexture(texPageItem.TexturePage); // Sanity checks. if (includePadding && ((texPageItem.TargetWidth > exportWidth) || (texPageItem.TargetHeight > exportHeight))) @@ -73,9 +73,9 @@ public SKBitmap GetTextureFor(UndertaleTexturePageItem texPageItem, string image if (includePadding) { returnImage = new SKBitmap(exportWidth, exportHeight); - SKCanvas g = new SKCanvas(returnImage); - g.DrawBitmap(resultImage, SKRect.Create(0, 0, resultImage.Width, resultImage.Height), SKRect.Create(texPageItem.TargetX, texPageItem.TargetY, resultImage.Width, resultImage.Height)); - g.Dispose(); + using SKCanvas g = new(returnImage); + g.DrawBitmap(resultImage, SKRect.Create(0, 0, resultImage.Width, resultImage.Height), + SKRect.Create(texPageItem.TargetX, texPageItem.TargetY, resultImage.Width, resultImage.Height)); } return returnImage; @@ -95,7 +95,6 @@ public static SKBitmap GetImageFromByteArray(byte[] byteArray) // This should perform a high quality resize. public static SKBitmap ResizeImage(SKBitmap image, int width, int height) { - var destRect = new Rectangle(0, 0, width, height); var destImage = new SKBitmap(width, height); image.ScalePixels(destImage, SKFilterQuality.High); return destImage; @@ -103,8 +102,8 @@ public static SKBitmap ResizeImage(SKBitmap image, int width, int height) public static byte[] ReadMaskData(string filePath) { - SKBitmap image = ReadImageFromFile(filePath); - List bytes = new List(); + using SKBitmap image = ReadImageFromFile(filePath); + List bytes = new(); int enableColor = Color.White.ToArgb(); for (int y = 0; y < image.Height; y++) @@ -112,8 +111,8 @@ public static byte[] ReadMaskData(string filePath) for (int xByte = 0; xByte < (image.Width + 7) / 8; xByte++) { byte fullByte = 0x00; - int pxStart = (xByte * 8); - int pxEnd = Math.Min(pxStart + 8, (int) image.Width); + int pxStart = xByte * 8; + int pxEnd = Math.Min(pxStart + 8, image.Width); for (int x = pxStart; x < pxEnd; x++) if ((uint)image.GetPixel(x, y) == enableColor) // Don't use Color == OtherColor, it doesn't seem to give us the type of equals comparison we want here. @@ -123,7 +122,6 @@ public static byte[] ReadMaskData(string filePath) } } - image.Dispose(); return bytes.ToArray(); } @@ -133,19 +131,19 @@ public static byte[] ReadTextureBlob(string filePath) return File.ReadAllBytes(filePath); } - public static void SaveEmptyPNG(string FullPath, int width, int height) + public static void SaveEmptyPNG(string fullPath, int width, int height) { var blackImage = new SKBitmap(width, height); for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) blackImage.SetPixel(x, y, SKColors.Black); - SaveImageToFile(FullPath, blackImage); + SaveImageToFile(fullPath, blackImage); } public static SKBitmap GetCollisionMaskImage(UndertaleSprite sprite, UndertaleSprite.MaskEntry mask) { byte[] maskData = mask.Data; - SKBitmap bitmap = new SKBitmap((int)sprite.Width, (int)sprite.Height); // Ugh. I want to use 1bpp, but for some BS reason C# doesn't allow SetPixel in that mode. + SKBitmap bitmap = new((int)sprite.Width, (int)sprite.Height); // Ugh. I want to use 1bpp, but for some BS reason C# doesn't allow SetPixel in that mode. for (int y = 0; y < sprite.Height; y++) { @@ -175,9 +173,9 @@ public static byte[] GetImageBytes(SKBitmap image, bool disposeImage = true) return result; } - public static void SaveImageToFile(string FullPath, SKBitmap image, Boolean disposeImage = true) + public static void SaveImageToFile(string fullPath, SKBitmap image, bool disposeImage = true) { - var stream = new FileStream(FullPath, FileMode.Create); + using var stream = new FileStream(fullPath, FileMode.Create); image.Encode(stream, SKEncodedImageFormat.Png, 100); stream.Close(); if (disposeImage) From 61b975b0b09a4d0e8cbf3118943c9899a234fe02 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 16:43:48 +0300 Subject: [PATCH 08/22] Improve performance of some methods in `TextureWorker`. --- UndertaleModLib/Util/TextureWorker.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index cd254f8ee..f5b507a8e 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -12,6 +12,16 @@ namespace UndertaleModLib.Util public class TextureWorker { private readonly Dictionary embeddedDictionary = new(); + private static readonly SKPaint paintBlack = new() + { + Color = SKColors.Black, + BlendMode = SKBlendMode.Src + }; + private static readonly SKPaint paintWhite = new() + { + Color = SKColors.White, + BlendMode = SKBlendMode.Src + }; // Cleans up all the images when usage of this worker is finished. // Should be called when a TextureWorker will never be used again. @@ -105,7 +115,6 @@ public static byte[] ReadMaskData(string filePath) using SKBitmap image = ReadImageFromFile(filePath); List bytes = new(); - int enableColor = Color.White.ToArgb(); for (int y = 0; y < image.Height; y++) { for (int xByte = 0; xByte < (image.Width + 7) / 8; xByte++) @@ -115,7 +124,7 @@ public static byte[] ReadMaskData(string filePath) int pxEnd = Math.Min(pxStart + 8, image.Width); for (int x = pxStart; x < pxEnd; x++) - if ((uint)image.GetPixel(x, y) == enableColor) // Don't use Color == OtherColor, it doesn't seem to give us the type of equals comparison we want here. + if (image.GetPixel(x, y) == SKColors.White) fullByte |= (byte)(0b1 << (7 - (x - pxStart))); bytes.Add(fullByte); @@ -134,16 +143,16 @@ public static byte[] ReadTextureBlob(string filePath) public static void SaveEmptyPNG(string fullPath, int width, int height) { var blackImage = new SKBitmap(width, height); - for (int x = 0; x < width; x++) - for (int y = 0; y < height; y++) - blackImage.SetPixel(x, y, SKColors.Black); + using SKCanvas g = new(blackImage); + g.Clear(SKColors.Black); SaveImageToFile(fullPath, blackImage); } public static SKBitmap GetCollisionMaskImage(UndertaleSprite sprite, UndertaleSprite.MaskEntry mask) { byte[] maskData = mask.Data; - SKBitmap bitmap = new((int)sprite.Width, (int)sprite.Height); // Ugh. I want to use 1bpp, but for some BS reason C# doesn't allow SetPixel in that mode. + SKBitmap bitmap = new((int)sprite.Width, (int)sprite.Height, SKColorType.Gray8, SKAlphaType.Premul); + using SKCanvas g = new(bitmap); for (int y = 0; y < sprite.Height; y++) { @@ -152,7 +161,7 @@ public static SKBitmap GetCollisionMaskImage(UndertaleSprite sprite, UndertaleSp { byte temp = maskData[rowStart + (x / 8)]; bool pixelBit = (temp & (0b1 << (7 - (x % 8)))) != 0b0; - bitmap.SetPixel(x, y, pixelBit ? SKColors.White : SKColors.Black); + g.DrawPoint(x, y, pixelBit ? paintWhite : paintBlack); } } From ca437af4e86af712004f936d4f0d7dbd194f92a4 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 16:52:34 +0300 Subject: [PATCH 09/22] Minor code improvements for "QoiConverter.cs". --- UndertaleModLib/Util/QoiConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UndertaleModLib/Util/QoiConverter.cs b/UndertaleModLib/Util/QoiConverter.cs index 5ab238c1c..7346c464e 100644 --- a/UndertaleModLib/Util/QoiConverter.cs +++ b/UndertaleModLib/Util/QoiConverter.cs @@ -66,7 +66,7 @@ public static SKBitmap GetImageFromStream(Stream s) /// /// Creates a from a of s. /// - /// The of s to create the PNG image from. + /// The of s to create the PNG image from. /// The QOI image as a PNG. /// If there is an invalid QOIF magic header or there was an error with stride width. public static SKBitmap GetImageFromSpan(ReadOnlySpan bytes) => GetImageFromSpan(bytes, out _); From c67710053ed65f0d5c79d72e558189f08fd1165e Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 17:12:29 +0300 Subject: [PATCH 10/22] Fix a crash on exporting all frames of a sprite, --- UndertaleModLib/Util/TextureWorker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index f5b507a8e..c28b7bc88 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -51,7 +51,7 @@ public SKBitmap GetTextureFor(UndertaleTexturePageItem texPageItem, string image { int exportWidth = texPageItem.BoundingWidth; // sprite.Width int exportHeight = texPageItem.BoundingHeight; // sprite.Height - using SKBitmap embeddedImage = GetEmbeddedTexture(texPageItem.TexturePage); + SKBitmap embeddedImage = GetEmbeddedTexture(texPageItem.TexturePage); // Sanity checks. if (includePadding && ((texPageItem.TargetWidth > exportWidth) || (texPageItem.TargetHeight > exportHeight))) From f74955a51dd350742f23516c30f80a0a19e98899 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 17:46:37 +0300 Subject: [PATCH 11/22] Remove all references of "System.Drawing" from the lib and GUI. --- .../Models/UndertaleEmbeddedTexture.cs | 2 -- UndertaleModLib/Models/UndertaleRoom.cs | 15 +++++++------ .../Models/UndertaleTexturePageItem.cs | 1 - UndertaleModLib/Util/QoiConverter.cs | 2 -- UndertaleModLib/Util/TextureWorker.cs | 3 --- .../Converters/UndertaleCachedImageLoader.cs | 16 +++++++------- .../UndertaleEmbeddedTextureEditor.xaml.cs | 21 +++++++------------ .../UndertaleTexturePageItemEditor.xaml.cs | 1 - 8 files changed, 21 insertions(+), 40 deletions(-) diff --git a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs index 786127b05..f61841193 100644 --- a/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs +++ b/UndertaleModLib/Models/UndertaleEmbeddedTexture.cs @@ -2,8 +2,6 @@ using System; using System.Buffers.Binary; using System.ComponentModel; -using System.Drawing; -using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.CompilerServices; diff --git a/UndertaleModLib/Models/UndertaleRoom.cs b/UndertaleModLib/Models/UndertaleRoom.cs index 3730d0a48..196536610 100644 --- a/UndertaleModLib/Models/UndertaleRoom.cs +++ b/UndertaleModLib/Models/UndertaleRoom.cs @@ -3,7 +3,6 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; -using System.Drawing; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -490,7 +489,7 @@ public void SetupRoom(bool calculateGridWidth = true, bool calculateGridHeight = // Automatically set the grid size to whatever most tiles are sized - Dictionary tileSizes = new(); + Dictionary<(int w, int h), uint> tileSizes = new(); IEnumerable tileList; if (Layers.Count > 0) @@ -502,9 +501,9 @@ public void SetupRoom(bool calculateGridWidth = true, bool calculateGridHeight = tileList = tileList.Concat(layer.AssetsData.LegacyTiles); else if (layer.LayerType == LayerType.Tiles && layer.TilesData.TileData.Length != 0) { - int w = (int) (Width / layer.TilesData.TilesX); - int h = (int) (Height / layer.TilesData.TilesY); - tileSizes[new(w, h)] = layer.TilesData.TilesX * layer.TilesData.TilesY; + int w = (int)(Width / layer.TilesData.TilesX); + int h = (int)(Height / layer.TilesData.TilesY); + tileSizes[(w, h)] = layer.TilesData.TilesX * layer.TilesData.TilesY; } } @@ -515,7 +514,7 @@ public void SetupRoom(bool calculateGridWidth = true, bool calculateGridHeight = // Loop through each tile and save how many times their sizes are used foreach (Tile tile in tileList) { - Point scale = new((int) tile.Width, (int) tile.Height); + (int w, int h) scale = ((int)tile.Width, (int)tile.Height); if (tileSizes.ContainsKey(scale)) tileSizes[scale]++; else @@ -535,9 +534,9 @@ public void SetupRoom(bool calculateGridWidth = true, bool calculateGridHeight = // If tiles exist at all, grab the most used tile size and use that as our grid size var largestKey = tileSizes.Aggregate((x, y) => x.Value > y.Value ? x : y).Key; if (calculateGridWidth) - GridWidth = largestKey.X; + GridWidth = largestKey.w; if (calculateGridHeight) - GridHeight = largestKey.Y; + GridHeight = largestKey.h; } /// diff --git a/UndertaleModLib/Models/UndertaleTexturePageItem.cs b/UndertaleModLib/Models/UndertaleTexturePageItem.cs index f04b11f07..7e5d5e408 100644 --- a/UndertaleModLib/Models/UndertaleTexturePageItem.cs +++ b/UndertaleModLib/Models/UndertaleTexturePageItem.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; -using System.Drawing; using SkiaSharp; using UndertaleModLib.Util; diff --git a/UndertaleModLib/Util/QoiConverter.cs b/UndertaleModLib/Util/QoiConverter.cs index 7346c464e..1d69fe835 100644 --- a/UndertaleModLib/Util/QoiConverter.cs +++ b/UndertaleModLib/Util/QoiConverter.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; using System.IO; using SkiaSharp; diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index c28b7bc88..1c1dc3d2f 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; using System.IO; using SkiaSharp; using UndertaleModLib.Models; diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index aab89bc51..149e23e7d 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -3,8 +3,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; -using System.Drawing.Imaging; using SkiaSharp; using System.Globalization; using System.IO; @@ -95,7 +93,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (tileRectList is not null) { - Rectangle rect = new(texture.SourceX, texture.SourceY, texture.SourceWidth, texture.SourceHeight); + Rect rect = new(texture.SourceX, texture.SourceY, texture.SourceWidth, texture.SourceHeight); ProcessTileSet(texName, CreateSpriteBitmap(rect, in texture), tileRectList, texture.TargetX, texture.TargetY); return null; @@ -110,7 +108,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (!imageCache.ContainsKey(texName) || !cacheEnabled) { - Rectangle rect; + Rect rect; // how many pixels are out of bounds of tile texture page int diffW = 0; @@ -152,9 +150,9 @@ public static void Reset() currBufferSize = 1048576; } - public static SKBitmap CreateSpriteBitmap(Rectangle rect, in UndertaleTexturePageItem texture, int diffW = 0, int diffH = 0, bool isTile = false) + public static SKBitmap CreateSpriteBitmap(Rect rect, in UndertaleTexturePageItem texture, int diffW = 0, int diffH = 0, bool isTile = false) { - SKBitmap spriteBMP = new(rect.Width, rect.Height); + SKBitmap spriteBMP = new((int)rect.Width, (int)rect.Height); rect.Width -= (diffW > 0) ? diffW : 0; rect.Height -= (diffH > 0) ? diffH : 0; @@ -165,15 +163,15 @@ public static SKBitmap CreateSpriteBitmap(Rectangle rect, in UndertaleTexturePag { using SKBitmap img = SKBitmap.Decode(texture.TexturePage.TextureData.TextureBlob); - var rectDest = SKRect.Create(x, y, rect.Width, rect.Height); - var rectSrc = SKRect.Create(rect.Left, rect.Top, rect.Width, rect.Height); + var rectDest = SKRect.Create(x, y, (float)rect.Width, (float)rect.Height); + var rectSrc = SKRect.Create((float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height); g.DrawBitmap(img, rectSrc, rectDest); } spriteBMP.SetImmutable(); return spriteBMP; } - private ImageSource CreateSpriteSource(in Rectangle rect, in UndertaleTexturePageItem texture, int diffW = 0, int diffH = 0, bool isTile = false) + private ImageSource CreateSpriteSource(in Rect rect, in UndertaleTexturePageItem texture, int diffW = 0, int diffH = 0, bool isTile = false) { using SKBitmap spriteBMP = CreateSpriteBitmap(rect, in texture, diffW, diffH, isTile); using var data = spriteBMP.Encode(SKEncodedImageFormat.Png, 100); diff --git a/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml.cs index c8ac26e3e..0749e542b 100644 --- a/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml.cs @@ -14,7 +14,7 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; -using System.Drawing; +using SkiaSharp; using UndertaleModLib.Models; using UndertaleModLib.Util; using System.Globalization; @@ -154,12 +154,7 @@ private void Import_Click(object sender, RoutedEventArgs e) { try { - Bitmap bmp; - using (var ms = new MemoryStream(TextureWorker.ReadTextureBlob(dlg.FileName))) - { - bmp = new Bitmap(ms); - } - bmp.SetResolution(96.0F, 96.0F); + using SKBitmap bmp = SKBitmap.Decode(TextureWorker.ReadTextureBlob(dlg.FileName)); var width = (uint)bmp.Width; var height = (uint)bmp.Height; @@ -169,14 +164,12 @@ private void Import_Click(object sender, RoutedEventArgs e) mainWindow.ShowWarning("WARNING: texture page dimensions are not powers of 2. Sprite blurring is very likely in game.", "Unexpected texture dimensions"); } - using (var stream = new MemoryStream()) - { - bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png); - target.TextureData.TextureBlob = stream.ToArray(); + using var stream = new MemoryStream(); + bmp.Encode(stream, SKEncodedImageFormat.Png, 100); + target.TextureData.TextureBlob = stream.ToArray(); - TexWidth.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget(); - TexHeight.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget(); - } + TexWidth.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget(); + TexHeight.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget(); } catch (Exception ex) { diff --git a/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs index c51fd5b4a..045497468 100644 --- a/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs @@ -1,6 +1,5 @@ using Microsoft.Win32; using System; -using System.Drawing; using System.Windows; using System.IO; using UndertaleModLib.Models; From a8331c79a79c77be645a8ce85d1f8fbf731cea6a Mon Sep 17 00:00:00 2001 From: VladiStep Date: Tue, 26 Dec 2023 23:04:20 +0300 Subject: [PATCH 12/22] Remove all references of "System.Drawing" from the scripts (WIP) --- UndertaleModLib/Util/TextureWorker.cs | 19 ++- .../UndertaleDialogSimulator.csx | 9 +- .../Community Scripts/ImportGMS2FontData.csx | 23 ++-- .../Community Scripts/ScaleAllTextures.csx | 26 +--- .../ApplyBasicGraphicsMod.csx | 8 +- .../Resource Repackers/ImportFontData.csx | 120 +++++++++++------- .../Resource Repackers/ImportGraphics.csx | 87 +++++++------ .../Resource Repackers/ImportMasks.csx | 15 +-- 8 files changed, 169 insertions(+), 138 deletions(-) diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index 1c1dc3d2f..cac3c1cff 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -99,11 +99,24 @@ public static SKBitmap GetImageFromByteArray(byte[] byteArray) return bm; } - // This should perform a high quality resize. - public static SKBitmap ResizeImage(SKBitmap image, int width, int height) + public static SKSizeI GetImageSizeFromFile(string filePath) + { + using SKCodec codec = SKCodec.Create(filePath); + + return codec?.Info.Size ?? default; + } + public static SKSizeI GetImageSizeFromByteArray(byte[] byteArray) + { + using MemoryStream stream = new(byteArray); + using SKCodec codec = SKCodec.Create(stream); + + return codec?.Info.Size ?? default; + } + + public static SKBitmap ResizeImage(SKBitmap image, int width, int height, bool useNearestNeighbor = false) { var destImage = new SKBitmap(width, height); - image.ScalePixels(destImage, SKFilterQuality.High); + image.ScalePixels(destImage, useNearestNeighbor ? SKFilterQuality.None : SKFilterQuality.High); return destImage; } diff --git a/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx b/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx index cdf8b698b..b03e186da 100644 --- a/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx +++ b/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx @@ -3,7 +3,6 @@ using System.IO; using System; -using System.Drawing; using System.Windows.Forms; using UndertaleModLib.Util; @@ -25,10 +24,10 @@ else if (GameName == "deltarune chapter 1&2") } if (Data.GeneralInfo.Name.Content == "NXTALE" || Data.GeneralInfo.Name.Content.StartsWith("UNDERTALE")) { - if (!ScriptQuestion("Would you like to apply this mod?")) - { - return; - } + if (!ScriptQuestion("Would you like to apply this mod?")) + { + return; + } } else if (Data.GeneralInfo.DisplayName.Content == "SURVEY_PROGRAM" || Data.GeneralInfo.DisplayName.Content == "DELTARUNE Chapter 1") { diff --git a/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx b/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx index c6bd9aba8..b6a07452e 100644 --- a/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx +++ b/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx @@ -2,7 +2,7 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -11,6 +11,7 @@ using UndertaleModLib; using UndertaleModLib.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using UndertaleModLib.Util; EnsureDataLoaded(); @@ -96,10 +97,10 @@ else if (attemptToFixFontNotAppearing) fontTexGroup.Fonts.Add(new UndertaleResourceById() { Resource = font }); } -// Prepare font texture -Bitmap textureBitmap = new Bitmap(fontTexturePath); -// Make the DPI exactly 96 for this bitmap -textureBitmap.SetResolution(96.0F, 96.0F); +// Get texture properties +var imgSize = TextureWorker.GetImageSizeFromFile(fontTexturePath); +ushort width = (ushort)imgSize.Width; +ushort height = (ushort)imgSize.Height; UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); // ??? Why? @@ -115,14 +116,14 @@ texturePageItem.Name = new UndertaleString("PageItem " + Data.TexturePageItems.C texturePageItem.TexturePage = texture; texturePageItem.SourceX = 0; texturePageItem.SourceY = 0; -texturePageItem.SourceWidth = (ushort)textureBitmap.Width; -texturePageItem.SourceHeight = (ushort)textureBitmap.Height; +texturePageItem.SourceWidth = width; +texturePageItem.SourceHeight = height; texturePageItem.TargetX = 0; texturePageItem.TargetY = 0; -texturePageItem.TargetWidth = (ushort)textureBitmap.Width; -texturePageItem.TargetHeight = (ushort)textureBitmap.Height; -texturePageItem.BoundingWidth = (ushort)textureBitmap.Width; -texturePageItem.BoundingHeight = (ushort)textureBitmap.Height; +texturePageItem.TargetWidth = width; +texturePageItem.TargetHeight = height; +texturePageItem.BoundingWidth = width; +texturePageItem.BoundingHeight = height; Data.TexturePageItems.Add(texturePageItem); font.DisplayName = Data.Strings.MakeString((string)fontData["fontName"]); diff --git a/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx b/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx index f1adc7fb8..6bb038723 100644 --- a/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx +++ b/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx @@ -1,8 +1,6 @@ using System; using System.IO; -using System.Drawing; -using System.Drawing.Imaging; -using System.Drawing.Drawing2D; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -21,9 +19,9 @@ if (!ScriptQuestion("Visual glitches are very likely to occur in game. Do you ac TextureWorker worker = new TextureWorker(); double scale = -1; -bool SelectScale = true; +bool selectScale = true; -if (SelectScale) +if (selectScale) { bool success = false; while (scale <= 0 || scale > 10) @@ -131,9 +129,8 @@ ChangeSelection(Data.Rooms.ByName("room_ruins1")); void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) { - Bitmap embImage = worker.GetEmbeddedTexture(tex); - embImage = ResizeBitmap(embImage, (int)(embImage.Width * scale), (int)(embImage.Height * scale)); - embImage.SetResolution(96.0F, 96.0F); + SKBitmap embImage = worker.GetEmbeddedTexture(tex); + embImage = worker.ResizeImage(embImage, (int)(embImage.Width * scale), (int)(embImage.Height * scale), true); try { var width = (uint)embImage.Width; @@ -144,7 +141,7 @@ void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) } using (var stream = new MemoryStream()) { - embImage.Save(stream, System.Drawing.Imaging.ImageFormat.Png); + embImage.Encode(stream, SKEncodedImageFormat.Png, 100); tex.TextureData.TextureBlob = stream.ToArray(); } } @@ -153,14 +150,3 @@ void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) //ScriptError("Failed to import file: " + ex.Message, "Failed to import file"); } } - -private Bitmap ResizeBitmap(Bitmap sourceBMP, int width, int height) -{ - Bitmap result = new Bitmap(width, height); - using (Graphics g = Graphics.FromImage(result)) - { - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; - g.DrawImage(sourceBMP, 0, 0, width, height); - } - return result; -} diff --git a/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx b/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx index 05018a98a..2cca662a7 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -105,12 +104,7 @@ await Task.Run(() => { { try { - Bitmap bmp; - using (var ms = new MemoryStream(TextureWorker.ReadTextureBlob(file))) - { - bmp = new Bitmap(ms); - } - bmp.SetResolution(96.0F, 96.0F); + SKBitmap bmp = SKBitmap.Decode(file); var width = (uint)bmp.Width; var height = (uint)bmp.Height; var CheckWidth = (uint)(sprite.Textures[frame].Texture.TargetWidth); diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx index f849db582..fc33373ce 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx @@ -3,7 +3,7 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -37,7 +37,6 @@ int atlasCount = 0; foreach (Atlas atlas in packer.Atlasses) { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - Bitmap atlasBitmap = new Bitmap(atlasName); UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); texture.Name = new UndertaleString("Texture " + ++lastTextPage); texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); @@ -86,8 +85,6 @@ foreach (Atlas atlas in packer.Atlasses) atlasCount++; } - - HideProgressBar(); ScriptMessage("Import Complete!"); @@ -176,10 +173,17 @@ public enum BestFitHeuristic Area, MaxOneAxis, } +public struct Rect +{ + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } +} public class Node { - public Rectangle Bounds; + public Rect Bounds; public TextureInfo Texture; public SplitType SplitType; } @@ -201,6 +205,26 @@ public class Packer public bool DebugMode; public BestFitHeuristic FitHeuristic; public List Atlasses; + public static readonly SKPaint paintGreen = new() + { + Color = SKColors.Green, + BlendMode = SKBlendMode.Src + }; + public static readonly SKPaint paintBlack = new() + { + Color = SKColors.Black, + BlendMode = SKBlendMode.Src + }; + public static readonly SKPaint paintWhite = new() + { + Color = SKColors.White, + BlendMode = SKBlendMode.Src + }; + public static readonly SKPaint paintDarkMagenta = new() + { + Color = SKColors.DarkMagenta, + BlendMode = SKBlendMode.Src + }; public Packer() { @@ -256,13 +280,10 @@ public class Packer { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); //1: Save images - Image img = CreateAtlasImage(atlas); - //DPI fix start - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - //DPI fix end - img2.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + using SKBitmap img = CreateAtlasImage(atlas); + using FileStream fs = new(atlasName, FileMode.Create, FileAccess.Write); + img.Encode(fs, SKEncodedImageFormat.Png, 100); + fs.Close(); //2: save description in file foreach (Node n in atlas.Nodes) { @@ -293,25 +314,27 @@ public class Packer FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); foreach (FileInfo fi in files) { - Image img = Image.FromFile(fi.FullName); - if (img != null) + var imgSize = TextureWorker.GetImageSizeFromFile(fi.FullName); + if (imgSize == default) + continue; + int width = imgSize.Width; + int height = imgSize.Height; + + if (width <= AtlasSize && height <= AtlasSize) { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); + TextureInfo ti = new TextureInfo(); - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; + ti.Source = fi.FullName; + ti.Width = width; + ti.Height = height; - SourceTextures.Add(ti); + SourceTextures.Add(ti); - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); } } } @@ -404,7 +427,8 @@ public class Packer _Atlas.Nodes = new List(); textures = _Textures.ToList(); Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.Bounds.Width = _Atlas.Width; + root.Bounds.Height = _Atlas.Height; root.SplitType = SplitType.Horizontal; freeList.Add(root); while (freeList.Count > 0 && textures.Count > 0) @@ -432,42 +456,46 @@ public class Packer return textures; } - private Image CreateAtlasImage(Atlas _Atlas) + private SKBitmap CreateAtlasImage(Atlas _Atlas) { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); + SKBitmap img = new(_Atlas.Width, _Atlas.Height); + using SKCanvas g = new(img); if (DebugMode) - { - g.FillRectangle(Brushes.Green, new Rectangle(0, 0, _Atlas.Width, _Atlas.Height)); - } + g.DrawRect(0, 0, _Atlas.Width, _Atlas.Height, paintGreen); + foreach (Node n in _Atlas.Nodes) { + SKRect rect = SKRect.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + if (n.Texture != null) { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); + using SKBitmap sourceImg = SKBitmap.Decode(n.Texture.Source); + g.DrawBitmap(sourceImg, rect); if (DebugMode) { string label = Path.GetFileNameWithoutExtension(n.Texture.Source); - SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); - RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); - g.FillRectangle(Brushes.Black, rectBounds); - g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); + SKRect labelBox = default; + paintWhite.MeasureText(label, ref labelBox); + SKRect rectBounds = SKRect.Create(n.Bounds.X, n.Bounds.Y, labelBox.Width, labelBox.Height); + g.DrawRect(rectBounds, paintBlack); + g.DrawText(label, rectBounds.Left, rectBounds.Top, paintWhite); } } else { - g.FillRectangle(Brushes.DarkMagenta, n.Bounds); + g.DrawRect(rect, paintDarkMagenta); if (DebugMode) { - string label = n.Bounds.Width.ToString() + "x" + n.Bounds.Height.ToString(); - SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); - RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); - g.FillRectangle(Brushes.Black, rectBounds); - g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); + string label = $"{n.Bounds.Width}x{n.Bounds.Height}"; + SKRect labelBox = default; + paintWhite.MeasureText(label, ref labelBox); + SKRect rectBounds = SKRect.Create(n.Bounds.X, n.Bounds.Y, labelBox.Width, labelBox.Height); + g.DrawRect(rectBounds, paintBlack); + g.DrawText(label, rectBounds.Left, rectBounds.Top, paintWhite); } } } + return img; } } diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx index f2fd5e374..7dc3e3229 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx @@ -3,7 +3,7 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -43,7 +43,7 @@ int atlasCount = 0; foreach (Atlas atlas in packer.Atlasses) { string atlasName = Path.Combine(packDir, String.Format(prefix + "{0:000}" + ".png", atlasCount)); - Bitmap atlasBitmap = new Bitmap(atlasName); + SKBitmap atlasBitmap = SKBitmap.Decode(atlasName); UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); texture.Name = new UndertaleString("Texture " + ++lastTextPage); texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); @@ -148,19 +148,23 @@ foreach (Atlas atlas in packer.Atlasses) newSprite.Textures.Add(null); } newSprite.CollisionMasks.Add(newSprite.NewMaskEntry()); - Rectangle bmpRect = new Rectangle(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); - System.Drawing.Imaging.PixelFormat format = atlasBitmap.PixelFormat; - Bitmap cloneBitmap = atlasBitmap.Clone(bmpRect, format); + + SKBitmap cloneBitmap = new(); + var bmpRect = SKRectI.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + atlasBitmap.ExtractSubset(cloneBitmap, bmpRect); + cloneBitmap = cloneBitmap.Copy(); + int width = ((n.Bounds.Width + 7) / 8) * 8; BitArray maskingBitArray = new BitArray(width * n.Bounds.Height); for (int y = 0; y < n.Bounds.Height; y++) { for (int x = 0; x < n.Bounds.Width; x++) { - Color pixelColor = cloneBitmap.GetPixel(x, y); - maskingBitArray[y * width + x] = (pixelColor.A > 0); + SKColor pixelColor = cloneBitmap.GetPixel(x, y); + maskingBitArray[y * width + x] = (pixelColor.Alpha > 0); } } + cloneBitmap.Dispose(); BitArray tempBitArray = new BitArray(width * n.Bounds.Height); for (int i = 0; i < maskingBitArray.Length; i += 8) { @@ -234,9 +238,16 @@ public enum BestFitHeuristic MaxOneAxis, } +public struct Rect +{ + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } +} public class Node { - public Rectangle Bounds; + public Rect Bounds; public TextureInfo Texture; public SplitType SplitType; } @@ -313,8 +324,10 @@ public class Packer { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); //1: Save images - Image img = CreateAtlasImage(atlas); - img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + using SKBitmap img = CreateAtlasImage(atlas); + using FileStream fs = new(atlasName, FileMode.Create, FileAccess.Write); + img.Encode(fs, SKEncodedImageFormat.Png, 100); + fs.Close(); //2: save description in file foreach (Node n in atlas.Nodes) { @@ -345,25 +358,27 @@ public class Packer FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); foreach (FileInfo fi in files) { - Image img = Image.FromFile(fi.FullName); - if (img != null) + var imgSize = TextureWorker.GetImageSizeFromFile(fi.FullName); + if (imgSize == default) + continue; + int width = imgSize.Width; + int height = imgSize.Height; + + if (width <= AtlasSize && height <= AtlasSize) { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); + TextureInfo ti = new TextureInfo(); - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; + ti.Source = fi.FullName; + ti.Width = width; + ti.Height = height; - SourceTextures.Add(ti); + SourceTextures.Add(ti); - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); } } } @@ -456,7 +471,8 @@ public class Packer _Atlas.Nodes = new List(); textures = _Textures.ToList(); Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.Bounds.Width = _Atlas.Width; + root.Bounds.Height = _Atlas.Height; root.SplitType = SplitType.Horizontal; freeList.Add(root); while (freeList.Count > 0 && textures.Count > 0) @@ -484,24 +500,21 @@ public class Packer return textures; } - private Image CreateAtlasImage(Atlas _Atlas) + private SKBitmap CreateAtlasImage(Atlas _Atlas) { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); + SKBitmap img = new(_Atlas.Width, _Atlas.Height); + using SKCanvas g = new(img); foreach (Node n in _Atlas.Nodes) { if (n.Texture != null) { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); + using SKBitmap sourceImg = SKBitmap.Decode(n.Texture.Source); + SKRect rect = SKRect.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + g.DrawBitmap(sourceImg, rect); } } - // DPI FIX START - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - return img2; - // DPI FIX END + + return img; } } diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx index a9ac426d8..193cd496f 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx @@ -4,7 +4,6 @@ using System; using System.IO; -using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -38,15 +37,13 @@ foreach (string file in dirFiles) { throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); } - if (Data.Sprites.ByName(spriteName) == null) // Reject non-existing sprites - { + var sprite = Data.Sprites.ByName(spriteName); + if (sprite is null) // Reject non-existing sprites throw new ScriptException(FileNameWithExtension + " could not be imported as the sprite " + spriteName + " does not exist."); - } - using (Image img = Image.FromFile(file)) - { - if ((Data.Sprites.ByName(spriteName).Width != (uint)img.Width) || (Data.Sprites.ByName(spriteName).Height != (uint)img.Height)) - throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + Data.Sprites.ByName(spriteName).Width.ToString() + " px, height: " + Data.Sprites.ByName(spriteName).Height.ToString() + " px."); - } + + var imgSize = TextureWorker.GetImageSizeFromFile(file); + if ((sprite.Width != (uint)imgSize.Width) || (sprite.Height != (uint)imgSize.Height)) + throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + sprite.Width.ToString() + " px, height: " + sprite.Height.ToString() + " px."); Int32 validFrameNumber = 0; try From 0c21988b146d0116cb755dfe10351afb24f77bb9 Mon Sep 17 00:00:00 2001 From: Vladislav Stepanov Date: Wed, 27 Dec 2023 15:31:14 +0300 Subject: [PATCH 13/22] Revert scripts changes --- UndertaleModLib/Util/TextureWorker.cs | 19 +-- .../UndertaleDialogSimulator.csx | 9 +- .../Community Scripts/ImportGMS2FontData.csx | 23 ++-- .../Community Scripts/ScaleAllTextures.csx | 26 +++- .../ApplyBasicGraphicsMod.csx | 8 +- .../Resource Repackers/ImportFontData.csx | 120 +++++++----------- .../Resource Repackers/ImportGraphics.csx | 87 ++++++------- .../Resource Repackers/ImportMasks.csx | 15 ++- 8 files changed, 138 insertions(+), 169 deletions(-) diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index cac3c1cff..1c1dc3d2f 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -99,24 +99,11 @@ public static SKBitmap GetImageFromByteArray(byte[] byteArray) return bm; } - public static SKSizeI GetImageSizeFromFile(string filePath) - { - using SKCodec codec = SKCodec.Create(filePath); - - return codec?.Info.Size ?? default; - } - public static SKSizeI GetImageSizeFromByteArray(byte[] byteArray) - { - using MemoryStream stream = new(byteArray); - using SKCodec codec = SKCodec.Create(stream); - - return codec?.Info.Size ?? default; - } - - public static SKBitmap ResizeImage(SKBitmap image, int width, int height, bool useNearestNeighbor = false) + // This should perform a high quality resize. + public static SKBitmap ResizeImage(SKBitmap image, int width, int height) { var destImage = new SKBitmap(width, height); - image.ScalePixels(destImage, useNearestNeighbor ? SKFilterQuality.None : SKFilterQuality.High); + image.ScalePixels(destImage, SKFilterQuality.High); return destImage; } diff --git a/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx b/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx index b03e186da..cdf8b698b 100644 --- a/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx +++ b/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx @@ -3,6 +3,7 @@ using System.IO; using System; +using System.Drawing; using System.Windows.Forms; using UndertaleModLib.Util; @@ -24,10 +25,10 @@ else if (GameName == "deltarune chapter 1&2") } if (Data.GeneralInfo.Name.Content == "NXTALE" || Data.GeneralInfo.Name.Content.StartsWith("UNDERTALE")) { - if (!ScriptQuestion("Would you like to apply this mod?")) - { - return; - } + if (!ScriptQuestion("Would you like to apply this mod?")) + { + return; + } } else if (Data.GeneralInfo.DisplayName.Content == "SURVEY_PROGRAM" || Data.GeneralInfo.DisplayName.Content == "DELTARUNE Chapter 1") { diff --git a/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx b/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx index b6a07452e..c6bd9aba8 100644 --- a/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx +++ b/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx @@ -2,7 +2,7 @@ using System; using System.IO; -using SkiaSharp; +using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -11,7 +11,6 @@ using UndertaleModLib; using UndertaleModLib.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using UndertaleModLib.Util; EnsureDataLoaded(); @@ -97,10 +96,10 @@ else if (attemptToFixFontNotAppearing) fontTexGroup.Fonts.Add(new UndertaleResourceById() { Resource = font }); } -// Get texture properties -var imgSize = TextureWorker.GetImageSizeFromFile(fontTexturePath); -ushort width = (ushort)imgSize.Width; -ushort height = (ushort)imgSize.Height; +// Prepare font texture +Bitmap textureBitmap = new Bitmap(fontTexturePath); +// Make the DPI exactly 96 for this bitmap +textureBitmap.SetResolution(96.0F, 96.0F); UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); // ??? Why? @@ -116,14 +115,14 @@ texturePageItem.Name = new UndertaleString("PageItem " + Data.TexturePageItems.C texturePageItem.TexturePage = texture; texturePageItem.SourceX = 0; texturePageItem.SourceY = 0; -texturePageItem.SourceWidth = width; -texturePageItem.SourceHeight = height; +texturePageItem.SourceWidth = (ushort)textureBitmap.Width; +texturePageItem.SourceHeight = (ushort)textureBitmap.Height; texturePageItem.TargetX = 0; texturePageItem.TargetY = 0; -texturePageItem.TargetWidth = width; -texturePageItem.TargetHeight = height; -texturePageItem.BoundingWidth = width; -texturePageItem.BoundingHeight = height; +texturePageItem.TargetWidth = (ushort)textureBitmap.Width; +texturePageItem.TargetHeight = (ushort)textureBitmap.Height; +texturePageItem.BoundingWidth = (ushort)textureBitmap.Width; +texturePageItem.BoundingHeight = (ushort)textureBitmap.Height; Data.TexturePageItems.Add(texturePageItem); font.DisplayName = Data.Strings.MakeString((string)fontData["fontName"]); diff --git a/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx b/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx index 6bb038723..f1adc7fb8 100644 --- a/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx +++ b/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx @@ -1,6 +1,8 @@ using System; using System.IO; -using SkiaSharp; +using System.Drawing; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -19,9 +21,9 @@ if (!ScriptQuestion("Visual glitches are very likely to occur in game. Do you ac TextureWorker worker = new TextureWorker(); double scale = -1; -bool selectScale = true; +bool SelectScale = true; -if (selectScale) +if (SelectScale) { bool success = false; while (scale <= 0 || scale > 10) @@ -129,8 +131,9 @@ ChangeSelection(Data.Rooms.ByName("room_ruins1")); void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) { - SKBitmap embImage = worker.GetEmbeddedTexture(tex); - embImage = worker.ResizeImage(embImage, (int)(embImage.Width * scale), (int)(embImage.Height * scale), true); + Bitmap embImage = worker.GetEmbeddedTexture(tex); + embImage = ResizeBitmap(embImage, (int)(embImage.Width * scale), (int)(embImage.Height * scale)); + embImage.SetResolution(96.0F, 96.0F); try { var width = (uint)embImage.Width; @@ -141,7 +144,7 @@ void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) } using (var stream = new MemoryStream()) { - embImage.Encode(stream, SKEncodedImageFormat.Png, 100); + embImage.Save(stream, System.Drawing.Imaging.ImageFormat.Png); tex.TextureData.TextureBlob = stream.ToArray(); } } @@ -150,3 +153,14 @@ void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) //ScriptError("Failed to import file: " + ex.Message, "Failed to import file"); } } + +private Bitmap ResizeBitmap(Bitmap sourceBMP, int width, int height) +{ + Bitmap result = new Bitmap(width, height); + using (Graphics g = Graphics.FromImage(result)) + { + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; + g.DrawImage(sourceBMP, 0, 0, width, height); + } + return result; +} diff --git a/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx b/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx index 2cca662a7..05018a98a 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -104,7 +105,12 @@ await Task.Run(() => { { try { - SKBitmap bmp = SKBitmap.Decode(file); + Bitmap bmp; + using (var ms = new MemoryStream(TextureWorker.ReadTextureBlob(file))) + { + bmp = new Bitmap(ms); + } + bmp.SetResolution(96.0F, 96.0F); var width = (uint)bmp.Width; var height = (uint)bmp.Height; var CheckWidth = (uint)(sprite.Textures[frame].Texture.TargetWidth); diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx index fc33373ce..f849db582 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx @@ -3,7 +3,7 @@ using System; using System.IO; -using SkiaSharp; +using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -37,6 +37,7 @@ int atlasCount = 0; foreach (Atlas atlas in packer.Atlasses) { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); + Bitmap atlasBitmap = new Bitmap(atlasName); UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); texture.Name = new UndertaleString("Texture " + ++lastTextPage); texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); @@ -85,6 +86,8 @@ foreach (Atlas atlas in packer.Atlasses) atlasCount++; } + + HideProgressBar(); ScriptMessage("Import Complete!"); @@ -173,17 +176,10 @@ public enum BestFitHeuristic Area, MaxOneAxis, } -public struct Rect -{ - public int X { get; set; } - public int Y { get; set; } - public int Width { get; set; } - public int Height { get; set; } -} public class Node { - public Rect Bounds; + public Rectangle Bounds; public TextureInfo Texture; public SplitType SplitType; } @@ -205,26 +201,6 @@ public class Packer public bool DebugMode; public BestFitHeuristic FitHeuristic; public List Atlasses; - public static readonly SKPaint paintGreen = new() - { - Color = SKColors.Green, - BlendMode = SKBlendMode.Src - }; - public static readonly SKPaint paintBlack = new() - { - Color = SKColors.Black, - BlendMode = SKBlendMode.Src - }; - public static readonly SKPaint paintWhite = new() - { - Color = SKColors.White, - BlendMode = SKBlendMode.Src - }; - public static readonly SKPaint paintDarkMagenta = new() - { - Color = SKColors.DarkMagenta, - BlendMode = SKBlendMode.Src - }; public Packer() { @@ -280,10 +256,13 @@ public class Packer { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); //1: Save images - using SKBitmap img = CreateAtlasImage(atlas); - using FileStream fs = new(atlasName, FileMode.Create, FileAccess.Write); - img.Encode(fs, SKEncodedImageFormat.Png, 100); - fs.Close(); + Image img = CreateAtlasImage(atlas); + //DPI fix start + Bitmap ResolutionFix = new Bitmap(img); + ResolutionFix.SetResolution(96.0F, 96.0F); + Image img2 = ResolutionFix; + //DPI fix end + img2.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); //2: save description in file foreach (Node n in atlas.Nodes) { @@ -314,27 +293,25 @@ public class Packer FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); foreach (FileInfo fi in files) { - var imgSize = TextureWorker.GetImageSizeFromFile(fi.FullName); - if (imgSize == default) - continue; - int width = imgSize.Width; - int height = imgSize.Height; - - if (width <= AtlasSize && height <= AtlasSize) + Image img = Image.FromFile(fi.FullName); + if (img != null) { - TextureInfo ti = new TextureInfo(); + if (img.Width <= AtlasSize && img.Height <= AtlasSize) + { + TextureInfo ti = new TextureInfo(); - ti.Source = fi.FullName; - ti.Width = width; - ti.Height = height; + ti.Source = fi.FullName; + ti.Width = img.Width; + ti.Height = img.Height; - SourceTextures.Add(ti); + SourceTextures.Add(ti); - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); + } } } } @@ -427,8 +404,7 @@ public class Packer _Atlas.Nodes = new List(); textures = _Textures.ToList(); Node root = new Node(); - root.Bounds.Width = _Atlas.Width; - root.Bounds.Height = _Atlas.Height; + root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); root.SplitType = SplitType.Horizontal; freeList.Add(root); while (freeList.Count > 0 && textures.Count > 0) @@ -456,46 +432,42 @@ public class Packer return textures; } - private SKBitmap CreateAtlasImage(Atlas _Atlas) + private Image CreateAtlasImage(Atlas _Atlas) { - SKBitmap img = new(_Atlas.Width, _Atlas.Height); - using SKCanvas g = new(img); + Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Graphics g = Graphics.FromImage(img); if (DebugMode) - g.DrawRect(0, 0, _Atlas.Width, _Atlas.Height, paintGreen); - + { + g.FillRectangle(Brushes.Green, new Rectangle(0, 0, _Atlas.Width, _Atlas.Height)); + } foreach (Node n in _Atlas.Nodes) { - SKRect rect = SKRect.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); - if (n.Texture != null) { - using SKBitmap sourceImg = SKBitmap.Decode(n.Texture.Source); - g.DrawBitmap(sourceImg, rect); + Image sourceImg = Image.FromFile(n.Texture.Source); + g.DrawImage(sourceImg, n.Bounds); if (DebugMode) { string label = Path.GetFileNameWithoutExtension(n.Texture.Source); - SKRect labelBox = default; - paintWhite.MeasureText(label, ref labelBox); - SKRect rectBounds = SKRect.Create(n.Bounds.X, n.Bounds.Y, labelBox.Width, labelBox.Height); - g.DrawRect(rectBounds, paintBlack); - g.DrawText(label, rectBounds.Left, rectBounds.Top, paintWhite); + SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); + RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); + g.FillRectangle(Brushes.Black, rectBounds); + g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); } } else { - g.DrawRect(rect, paintDarkMagenta); + g.FillRectangle(Brushes.DarkMagenta, n.Bounds); if (DebugMode) { - string label = $"{n.Bounds.Width}x{n.Bounds.Height}"; - SKRect labelBox = default; - paintWhite.MeasureText(label, ref labelBox); - SKRect rectBounds = SKRect.Create(n.Bounds.X, n.Bounds.Y, labelBox.Width, labelBox.Height); - g.DrawRect(rectBounds, paintBlack); - g.DrawText(label, rectBounds.Left, rectBounds.Top, paintWhite); + string label = n.Bounds.Width.ToString() + "x" + n.Bounds.Height.ToString(); + SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); + RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); + g.FillRectangle(Brushes.Black, rectBounds); + g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); } } } - return img; } } diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx index 7dc3e3229..f2fd5e374 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx @@ -3,7 +3,7 @@ using System; using System.IO; -using SkiaSharp; +using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -43,7 +43,7 @@ int atlasCount = 0; foreach (Atlas atlas in packer.Atlasses) { string atlasName = Path.Combine(packDir, String.Format(prefix + "{0:000}" + ".png", atlasCount)); - SKBitmap atlasBitmap = SKBitmap.Decode(atlasName); + Bitmap atlasBitmap = new Bitmap(atlasName); UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); texture.Name = new UndertaleString("Texture " + ++lastTextPage); texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); @@ -148,23 +148,19 @@ foreach (Atlas atlas in packer.Atlasses) newSprite.Textures.Add(null); } newSprite.CollisionMasks.Add(newSprite.NewMaskEntry()); - - SKBitmap cloneBitmap = new(); - var bmpRect = SKRectI.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); - atlasBitmap.ExtractSubset(cloneBitmap, bmpRect); - cloneBitmap = cloneBitmap.Copy(); - + Rectangle bmpRect = new Rectangle(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + System.Drawing.Imaging.PixelFormat format = atlasBitmap.PixelFormat; + Bitmap cloneBitmap = atlasBitmap.Clone(bmpRect, format); int width = ((n.Bounds.Width + 7) / 8) * 8; BitArray maskingBitArray = new BitArray(width * n.Bounds.Height); for (int y = 0; y < n.Bounds.Height; y++) { for (int x = 0; x < n.Bounds.Width; x++) { - SKColor pixelColor = cloneBitmap.GetPixel(x, y); - maskingBitArray[y * width + x] = (pixelColor.Alpha > 0); + Color pixelColor = cloneBitmap.GetPixel(x, y); + maskingBitArray[y * width + x] = (pixelColor.A > 0); } } - cloneBitmap.Dispose(); BitArray tempBitArray = new BitArray(width * n.Bounds.Height); for (int i = 0; i < maskingBitArray.Length; i += 8) { @@ -238,16 +234,9 @@ public enum BestFitHeuristic MaxOneAxis, } -public struct Rect -{ - public int X { get; set; } - public int Y { get; set; } - public int Width { get; set; } - public int Height { get; set; } -} public class Node { - public Rect Bounds; + public Rectangle Bounds; public TextureInfo Texture; public SplitType SplitType; } @@ -324,10 +313,8 @@ public class Packer { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); //1: Save images - using SKBitmap img = CreateAtlasImage(atlas); - using FileStream fs = new(atlasName, FileMode.Create, FileAccess.Write); - img.Encode(fs, SKEncodedImageFormat.Png, 100); - fs.Close(); + Image img = CreateAtlasImage(atlas); + img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); //2: save description in file foreach (Node n in atlas.Nodes) { @@ -358,27 +345,25 @@ public class Packer FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); foreach (FileInfo fi in files) { - var imgSize = TextureWorker.GetImageSizeFromFile(fi.FullName); - if (imgSize == default) - continue; - int width = imgSize.Width; - int height = imgSize.Height; - - if (width <= AtlasSize && height <= AtlasSize) + Image img = Image.FromFile(fi.FullName); + if (img != null) { - TextureInfo ti = new TextureInfo(); + if (img.Width <= AtlasSize && img.Height <= AtlasSize) + { + TextureInfo ti = new TextureInfo(); - ti.Source = fi.FullName; - ti.Width = width; - ti.Height = height; + ti.Source = fi.FullName; + ti.Width = img.Width; + ti.Height = img.Height; - SourceTextures.Add(ti); + SourceTextures.Add(ti); - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); + } } } } @@ -471,8 +456,7 @@ public class Packer _Atlas.Nodes = new List(); textures = _Textures.ToList(); Node root = new Node(); - root.Bounds.Width = _Atlas.Width; - root.Bounds.Height = _Atlas.Height; + root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); root.SplitType = SplitType.Horizontal; freeList.Add(root); while (freeList.Count > 0 && textures.Count > 0) @@ -500,21 +484,24 @@ public class Packer return textures; } - private SKBitmap CreateAtlasImage(Atlas _Atlas) + private Image CreateAtlasImage(Atlas _Atlas) { - SKBitmap img = new(_Atlas.Width, _Atlas.Height); - using SKCanvas g = new(img); + Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Graphics g = Graphics.FromImage(img); foreach (Node n in _Atlas.Nodes) { if (n.Texture != null) { - using SKBitmap sourceImg = SKBitmap.Decode(n.Texture.Source); - SKRect rect = SKRect.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); - g.DrawBitmap(sourceImg, rect); + Image sourceImg = Image.FromFile(n.Texture.Source); + g.DrawImage(sourceImg, n.Bounds); } } - - return img; + // DPI FIX START + Bitmap ResolutionFix = new Bitmap(img); + ResolutionFix.SetResolution(96.0F, 96.0F); + Image img2 = ResolutionFix; + return img2; + // DPI FIX END } } diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx index 193cd496f..a9ac426d8 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx @@ -4,6 +4,7 @@ using System; using System.IO; +using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -37,13 +38,15 @@ foreach (string file in dirFiles) { throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); } - var sprite = Data.Sprites.ByName(spriteName); - if (sprite is null) // Reject non-existing sprites + if (Data.Sprites.ByName(spriteName) == null) // Reject non-existing sprites + { throw new ScriptException(FileNameWithExtension + " could not be imported as the sprite " + spriteName + " does not exist."); - - var imgSize = TextureWorker.GetImageSizeFromFile(file); - if ((sprite.Width != (uint)imgSize.Width) || (sprite.Height != (uint)imgSize.Height)) - throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + sprite.Width.ToString() + " px, height: " + sprite.Height.ToString() + " px."); + } + using (Image img = Image.FromFile(file)) + { + if ((Data.Sprites.ByName(spriteName).Width != (uint)img.Width) || (Data.Sprites.ByName(spriteName).Height != (uint)img.Height)) + throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + Data.Sprites.ByName(spriteName).Width.ToString() + " px, height: " + Data.Sprites.ByName(spriteName).Height.ToString() + " px."); + } Int32 validFrameNumber = 0; try From 77d5725f535dda984dd6888cb94c6ef9f1998f64 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Wed, 27 Dec 2023 19:23:55 +0300 Subject: [PATCH 14/22] Add useful methods and one argument to `TextureWorker`. --- UndertaleModLib/Util/TextureWorker.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/UndertaleModLib/Util/TextureWorker.cs b/UndertaleModLib/Util/TextureWorker.cs index 1c1dc3d2f..6e4792a52 100644 --- a/UndertaleModLib/Util/TextureWorker.cs +++ b/UndertaleModLib/Util/TextureWorker.cs @@ -99,11 +99,24 @@ public static SKBitmap GetImageFromByteArray(byte[] byteArray) return bm; } - // This should perform a high quality resize. - public static SKBitmap ResizeImage(SKBitmap image, int width, int height) + public static SKSizeI GetImageSizeFromFile(string filePath) + { + using SKCodec codec = SKCodec.Create(filePath); + + return codec?.Info.Size ?? default; + } + public static SKSizeI GetImageSizeFromByteArray(byte[] byteArray) + { + using MemoryStream stream = new(byteArray); + using SKCodec codec = SKCodec.Create(stream); + + return codec?.Info.Size ?? default; + } + + public static SKBitmap ResizeImage(SKBitmap image, int width, int height, bool useNearestNeighbor = false) { var destImage = new SKBitmap(width, height); - image.ScalePixels(destImage, SKFilterQuality.High); + image.ScalePixels(destImage, useNearestNeighbor ? SKFilterQuality.None : SKFilterQuality.High); return destImage; } From 0ea44e8175c39b1cf65e9896baa6953c859eeef7 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Wed, 27 Dec 2023 19:38:35 +0300 Subject: [PATCH 15/22] Add missing comments for tile transformations. --- UndertaleModTool/Converters/UndertaleCachedImageLoader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index 149e23e7d..105dd8145 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -276,7 +276,7 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu // UndertaleCachedImageLoader wrappers public class CachedTileImageLoader : IMultiValueConverter { - private static UndertaleCachedImageLoader loader = new(); + private static readonly UndertaleCachedImageLoader loader = new(); public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values[0] is null) // tile @@ -496,17 +496,21 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under tileSurf.Canvas.Scale(-1, -1, srcBMP.Width / 2, srcBMP.Height / 2); break; case 4: + // Rotate 90 degrees clockwise tileSurf.Canvas.RotateDegrees(90); break; case 5: + // Rotate 270 degrees clockwise and flip Y tileSurf.Canvas.RotateDegrees(270); tileSurf.Canvas.Scale(1, -1, 0, srcBMP.Height / 2); break; case 6: + // Rotate 90 degrees clockwise and flip Y tileSurf.Canvas.RotateDegrees(90); tileSurf.Canvas.Scale(1, -1, 0, srcBMP.Height / 2); break; case 7: + // Rotate 270 degrees clockwise tileSurf.Canvas.RotateDegrees(270); break; From f693c8addb6ccd8110320003a10637ec45702e96 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Wed, 27 Dec 2023 19:40:21 +0300 Subject: [PATCH 16/22] Remove a redundant DLL import. --- UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs index b77b77438..2825042ca 100644 --- a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs @@ -2497,10 +2497,6 @@ public TileRectangle(ImageSource imageSrc, uint x, uint y, uint width, uint heig } public class TileRectanglesConverter : IMultiValueConverter { - [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeleteObject([In] IntPtr hObject); - public static ConcurrentDictionary, ImageSource> TileCache { get; set; } = new(); private static CachedTileDataLoader loader = new(); From f8433111051f06ae4b7c724d06d98615a06543ae Mon Sep 17 00:00:00 2001 From: VladiStep Date: Wed, 27 Dec 2023 19:44:32 +0300 Subject: [PATCH 17/22] Add reminder comments for tile transformation part. --- UndertaleModTool/Converters/UndertaleCachedImageLoader.cs | 1 + UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index 105dd8145..28db60cc1 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -481,6 +481,7 @@ public ImageSource CreateLayerSource(in Layer.LayerTilesData tilesData, in Under SKBitmap srcBMP = TileCache[new(tilesBG.Texture.Name.Content, realID)]; tileSurf.Canvas.ResetMatrix(); + // Don't forget to also modify `TileRectanglesConverter.Convert()` in "Editors/UndertaleRoomEditor.cs" switch (id >> 28) { case 1: diff --git a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs index 2825042ca..0ce55b0cb 100644 --- a/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs +++ b/UndertaleModTool/Editors/UndertaleRoomEditor.xaml.cs @@ -2498,7 +2498,7 @@ public TileRectangle(ImageSource imageSrc, uint x, uint y, uint width, uint heig public class TileRectanglesConverter : IMultiValueConverter { public static ConcurrentDictionary, ImageSource> TileCache { get; set; } = new(); - private static CachedTileDataLoader loader = new(); + private static readonly CachedTileDataLoader loader = new(); public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { @@ -2568,6 +2568,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur return; } + // Don't forget to also modify `CachedTileDataLoader.CreateLayerSource()` in "Converters/UndertaleCachedImageLoader.cs" switch (id >> 28) { case 1: From 98364cb88ed2af2f1bfbbdabc6613c0747fd9d5d Mon Sep 17 00:00:00 2001 From: VladiStep Date: Wed, 27 Dec 2023 19:56:59 +0300 Subject: [PATCH 18/22] Add a comment with a warning. --- UndertaleModTool/Converters/UndertaleCachedImageLoader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index 28db60cc1..6935ccca2 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -176,6 +176,7 @@ private ImageSource CreateSpriteSource(in Rect rect, in UndertaleTexturePageItem using SKBitmap spriteBMP = CreateSpriteBitmap(rect, in texture, diffW, diffH, isTile); using var data = spriteBMP.Encode(SKEncodedImageFormat.Png, 100); + // The `CacheOption` must be set after `BeginInit()`, otherwise it won't work. BitmapImage spriteSrc = new(); spriteSrc.BeginInit(); spriteSrc.CacheOption = BitmapCacheOption.OnLoad; @@ -295,7 +296,7 @@ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, } public class CachedImageLoaderWithIndex : IMultiValueConverter { - private static UndertaleCachedImageLoader loader = new(); + private static readonly UndertaleCachedImageLoader loader = new(); public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values.Any(x => x is null)) From a720cd712a1c8adb200daf8a27ad2c8a5f5f3a56 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Wed, 27 Dec 2023 20:05:29 +0300 Subject: [PATCH 19/22] Remove unneccesary `SKBitmap.SetImmutable()` call. --- UndertaleModTool/Converters/UndertaleCachedImageLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs index 6935ccca2..276aec7da 100644 --- a/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs +++ b/UndertaleModTool/Converters/UndertaleCachedImageLoader.cs @@ -167,7 +167,6 @@ public static SKBitmap CreateSpriteBitmap(Rect rect, in UndertaleTexturePageItem var rectSrc = SKRect.Create((float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height); g.DrawBitmap(img, rectSrc, rectDest); } - spriteBMP.SetImmutable(); return spriteBMP; } From a8337d0d28c79b47906f841903092166d45ad469 Mon Sep 17 00:00:00 2001 From: VladiStep Date: Wed, 27 Dec 2023 20:47:06 +0300 Subject: [PATCH 20/22] Revert the `QoiConverter.GetImageFromSpan()` comment change. --- UndertaleModLib/Util/QoiConverter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/UndertaleModLib/Util/QoiConverter.cs b/UndertaleModLib/Util/QoiConverter.cs index 1d69fe835..f288d2bd1 100644 --- a/UndertaleModLib/Util/QoiConverter.cs +++ b/UndertaleModLib/Util/QoiConverter.cs @@ -70,7 +70,6 @@ public static SKBitmap GetImageFromStream(Stream s) public static SKBitmap GetImageFromSpan(ReadOnlySpan bytes) => GetImageFromSpan(bytes, out _); /// - /// /// The total amount of data read from the . /// /// From 2ba06ebc069641204c0cd25ba93a181114911f5c Mon Sep 17 00:00:00 2001 From: Vladislav Stepanov Date: Sun, 2 Jun 2024 17:00:28 +0300 Subject: [PATCH 21/22] Replace "System.Drawing" with "SkiaSharp" in all scripts. (#24) * Restore WIP script changes * Finish removing/replacing of "System.Drawing" in all scripts (not tested) * Update MergeImages.csx * Update ImportMasks.csx --------- Co-authored-by: Miepee <38186597+Miepee@users.noreply.github.com> --- .../UndertaleDialogSimulator.csx | 9 +- .../Community Scripts/ImportGMS2FontData.csx | 23 ++-- .../Community Scripts/ScaleAllTextures.csx | 26 +--- .../ApplyBasicGraphicsMod.csx | 9 +- .../Resource Repackers/ImportFontData.csx | 120 +++++++++++------- .../Resource Repackers/ImportGraphics.csx | 85 +++++++------ .../Resource Repackers/ImportMasks.csx | 17 +-- .../Resource Repackers/NewTextureRepacker.csx | 37 ++---- .../ReduceEmbeddedTexturePages.csx | 71 ++++++----- .../Resource Unpackers/ExportAllTextures.csx | 1 - .../ExportAllTexturesGrouped.csx | 1 - .../ExportTextureGroups.csx | 1 - .../Resource Unpackers/MergeImages.csx | 51 +++----- .../ImportGraphics_Full_Repack.csx | 84 ++++++------ 14 files changed, 274 insertions(+), 261 deletions(-) diff --git a/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx b/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx index cdf8b698b..b03e186da 100644 --- a/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx +++ b/UndertaleModTool/Scripts/Builtin Scripts/UndertaleDialogSimulator.csx @@ -3,7 +3,6 @@ using System.IO; using System; -using System.Drawing; using System.Windows.Forms; using UndertaleModLib.Util; @@ -25,10 +24,10 @@ else if (GameName == "deltarune chapter 1&2") } if (Data.GeneralInfo.Name.Content == "NXTALE" || Data.GeneralInfo.Name.Content.StartsWith("UNDERTALE")) { - if (!ScriptQuestion("Would you like to apply this mod?")) - { - return; - } + if (!ScriptQuestion("Would you like to apply this mod?")) + { + return; + } } else if (Data.GeneralInfo.DisplayName.Content == "SURVEY_PROGRAM" || Data.GeneralInfo.DisplayName.Content == "DELTARUNE Chapter 1") { diff --git a/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx b/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx index c6bd9aba8..b6a07452e 100644 --- a/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx +++ b/UndertaleModTool/Scripts/Community Scripts/ImportGMS2FontData.csx @@ -2,7 +2,7 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -11,6 +11,7 @@ using UndertaleModLib; using UndertaleModLib.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using UndertaleModLib.Util; EnsureDataLoaded(); @@ -96,10 +97,10 @@ else if (attemptToFixFontNotAppearing) fontTexGroup.Fonts.Add(new UndertaleResourceById() { Resource = font }); } -// Prepare font texture -Bitmap textureBitmap = new Bitmap(fontTexturePath); -// Make the DPI exactly 96 for this bitmap -textureBitmap.SetResolution(96.0F, 96.0F); +// Get texture properties +var imgSize = TextureWorker.GetImageSizeFromFile(fontTexturePath); +ushort width = (ushort)imgSize.Width; +ushort height = (ushort)imgSize.Height; UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); // ??? Why? @@ -115,14 +116,14 @@ texturePageItem.Name = new UndertaleString("PageItem " + Data.TexturePageItems.C texturePageItem.TexturePage = texture; texturePageItem.SourceX = 0; texturePageItem.SourceY = 0; -texturePageItem.SourceWidth = (ushort)textureBitmap.Width; -texturePageItem.SourceHeight = (ushort)textureBitmap.Height; +texturePageItem.SourceWidth = width; +texturePageItem.SourceHeight = height; texturePageItem.TargetX = 0; texturePageItem.TargetY = 0; -texturePageItem.TargetWidth = (ushort)textureBitmap.Width; -texturePageItem.TargetHeight = (ushort)textureBitmap.Height; -texturePageItem.BoundingWidth = (ushort)textureBitmap.Width; -texturePageItem.BoundingHeight = (ushort)textureBitmap.Height; +texturePageItem.TargetWidth = width; +texturePageItem.TargetHeight = height; +texturePageItem.BoundingWidth = width; +texturePageItem.BoundingHeight = height; Data.TexturePageItems.Add(texturePageItem); font.DisplayName = Data.Strings.MakeString((string)fontData["fontName"]); diff --git a/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx b/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx index f1adc7fb8..8b290ea96 100644 --- a/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx +++ b/UndertaleModTool/Scripts/Community Scripts/ScaleAllTextures.csx @@ -1,8 +1,6 @@ using System; using System.IO; -using System.Drawing; -using System.Drawing.Imaging; -using System.Drawing.Drawing2D; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -21,9 +19,9 @@ if (!ScriptQuestion("Visual glitches are very likely to occur in game. Do you ac TextureWorker worker = new TextureWorker(); double scale = -1; -bool SelectScale = true; +bool selectScale = true; -if (SelectScale) +if (selectScale) { bool success = false; while (scale <= 0 || scale > 10) @@ -131,9 +129,8 @@ ChangeSelection(Data.Rooms.ByName("room_ruins1")); void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) { - Bitmap embImage = worker.GetEmbeddedTexture(tex); - embImage = ResizeBitmap(embImage, (int)(embImage.Width * scale), (int)(embImage.Height * scale)); - embImage.SetResolution(96.0F, 96.0F); + SKBitmap embImage = worker.GetEmbeddedTexture(tex); + embImage = TextureWorker.ResizeImage(embImage, (int)(embImage.Width * scale), (int)(embImage.Height * scale), useNearestNeighbor: true); try { var width = (uint)embImage.Width; @@ -144,7 +141,7 @@ void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) } using (var stream = new MemoryStream()) { - embImage.Save(stream, System.Drawing.Imaging.ImageFormat.Png); + embImage.Encode(stream, SKEncodedImageFormat.Png, 100); tex.TextureData.TextureBlob = stream.ToArray(); } } @@ -153,14 +150,3 @@ void ScaleEmbeddedTexture(UndertaleEmbeddedTexture tex) //ScriptError("Failed to import file: " + ex.Message, "Failed to import file"); } } - -private Bitmap ResizeBitmap(Bitmap sourceBMP, int width, int height) -{ - Bitmap result = new Bitmap(width, height); - using (Graphics g = Graphics.FromImage(result)) - { - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; - g.DrawImage(sourceBMP, 0, 0, width, height); - } - return result; -} diff --git a/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx b/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx index 05018a98a..311713f18 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ApplyBasicGraphicsMod.csx @@ -1,6 +1,6 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -105,12 +105,7 @@ await Task.Run(() => { { try { - Bitmap bmp; - using (var ms = new MemoryStream(TextureWorker.ReadTextureBlob(file))) - { - bmp = new Bitmap(ms); - } - bmp.SetResolution(96.0F, 96.0F); + SKBitmap bmp = SKBitmap.Decode(file); var width = (uint)bmp.Width; var height = (uint)bmp.Height; var CheckWidth = (uint)(sprite.Textures[frame].Texture.TargetWidth); diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx index f849db582..882e2d44e 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportFontData.csx @@ -3,7 +3,7 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -37,7 +37,6 @@ int atlasCount = 0; foreach (Atlas atlas in packer.Atlasses) { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - Bitmap atlasBitmap = new Bitmap(atlasName); UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); texture.Name = new UndertaleString("Texture " + ++lastTextPage); texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); @@ -86,8 +85,6 @@ foreach (Atlas atlas in packer.Atlasses) atlasCount++; } - - HideProgressBar(); ScriptMessage("Import Complete!"); @@ -176,10 +173,17 @@ public enum BestFitHeuristic Area, MaxOneAxis, } +public struct Rect +{ + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } +} public class Node { - public Rectangle Bounds; + public Rect Bounds; public TextureInfo Texture; public SplitType SplitType; } @@ -201,6 +205,26 @@ public class Packer public bool DebugMode; public BestFitHeuristic FitHeuristic; public List Atlasses; + public static readonly SKPaint paintGreen = new() + { + Color = SKColors.Green, + BlendMode = SKBlendMode.Src + }; + public static readonly SKPaint paintBlack = new() + { + Color = SKColors.Black, + BlendMode = SKBlendMode.Src + }; + public static readonly SKPaint paintWhite = new() + { + Color = SKColors.White, + BlendMode = SKBlendMode.Src + }; + public static readonly SKPaint paintDarkMagenta = new() + { + Color = SKColors.DarkMagenta, + BlendMode = SKBlendMode.Src + }; public Packer() { @@ -256,13 +280,8 @@ public class Packer { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); //1: Save images - Image img = CreateAtlasImage(atlas); - //DPI fix start - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - //DPI fix end - img2.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + using SKBitmap img = CreateAtlasImage(atlas); + TextureWorker.SaveImageToFile(atlasName, img); //2: save description in file foreach (Node n in atlas.Nodes) { @@ -293,25 +312,27 @@ public class Packer FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); foreach (FileInfo fi in files) { - Image img = Image.FromFile(fi.FullName); - if (img != null) + var imgSize = TextureWorker.GetImageSizeFromFile(fi.FullName); + if (imgSize == default) + continue; + int width = imgSize.Width; + int height = imgSize.Height; + + if (width <= AtlasSize && height <= AtlasSize) { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); + TextureInfo ti = new TextureInfo(); - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; + ti.Source = fi.FullName; + ti.Width = width; + ti.Height = height; - SourceTextures.Add(ti); + SourceTextures.Add(ti); - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); } } } @@ -404,7 +425,8 @@ public class Packer _Atlas.Nodes = new List(); textures = _Textures.ToList(); Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.Bounds.Width = _Atlas.Width; + root.Bounds.Height = _Atlas.Height; root.SplitType = SplitType.Horizontal; freeList.Add(root); while (freeList.Count > 0 && textures.Count > 0) @@ -432,42 +454,48 @@ public class Packer return textures; } - private Image CreateAtlasImage(Atlas _Atlas) + private SKBitmap CreateAtlasImage(Atlas _Atlas) { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); + SKBitmap img = new(_Atlas.Width, _Atlas.Height); + using SKCanvas g = new(img); if (DebugMode) - { - g.FillRectangle(Brushes.Green, new Rectangle(0, 0, _Atlas.Width, _Atlas.Height)); - } + g.DrawRect(0, 0, _Atlas.Width, _Atlas.Height, paintGreen); + foreach (Node n in _Atlas.Nodes) { + SKRect rect = SKRect.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + if (n.Texture != null) { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); + using SKBitmap sourceImg = SKBitmap.Decode(n.Texture.Source); + g.DrawBitmap(sourceImg, rect); if (DebugMode) { string label = Path.GetFileNameWithoutExtension(n.Texture.Source); - SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); - RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); - g.FillRectangle(Brushes.Black, rectBounds); - g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); + SKRect labelBox = default; + paintWhite.MeasureText(label, ref labelBox); + SKRect rectBounds = SKRect.Create(n.Bounds.X, n.Bounds.Y, labelBox.Width, labelBox.Height); + float yOff = paintWhite.FontMetrics.Ascent - paintWhite.FontMetrics.Descent - 1; // I am not sure if it's the correct way, but it works + g.DrawRect(rectBounds, paintBlack); + g.DrawText(label, rectBounds.Left, rectBounds.Top - yOff, paintWhite); } } else { - g.FillRectangle(Brushes.DarkMagenta, n.Bounds); + g.DrawRect(rect, paintDarkMagenta); if (DebugMode) { - string label = n.Bounds.Width.ToString() + "x" + n.Bounds.Height.ToString(); - SizeF labelBox = g.MeasureString(label, SystemFonts.MenuFont, new SizeF(n.Bounds.Size)); - RectangleF rectBounds = new Rectangle(n.Bounds.Location, new Size((int)labelBox.Width, (int)labelBox.Height)); - g.FillRectangle(Brushes.Black, rectBounds); - g.DrawString(label, SystemFonts.MenuFont, Brushes.White, rectBounds); + string label = $"{n.Bounds.Width}x{n.Bounds.Height}"; + SKRect labelBox = default; + paintWhite.MeasureText(label, ref labelBox); + SKRect rectBounds = SKRect.Create(n.Bounds.X, n.Bounds.Y, labelBox.Width, labelBox.Height); + float yOff = paintWhite.FontMetrics.Ascent - paintWhite.FontMetrics.Descent - 1; + g.DrawRect(rectBounds, paintBlack); + g.DrawText(label, rectBounds.Left, rectBounds.Top - yOff, paintWhite); } } } + return img; } } diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx index f2fd5e374..679ff2879 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportGraphics.csx @@ -3,7 +3,7 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -43,7 +43,7 @@ int atlasCount = 0; foreach (Atlas atlas in packer.Atlasses) { string atlasName = Path.Combine(packDir, String.Format(prefix + "{0:000}" + ".png", atlasCount)); - Bitmap atlasBitmap = new Bitmap(atlasName); + SKBitmap atlasBitmap = SKBitmap.Decode(atlasName); UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); texture.Name = new UndertaleString("Texture " + ++lastTextPage); texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); @@ -148,19 +148,23 @@ foreach (Atlas atlas in packer.Atlasses) newSprite.Textures.Add(null); } newSprite.CollisionMasks.Add(newSprite.NewMaskEntry()); - Rectangle bmpRect = new Rectangle(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); - System.Drawing.Imaging.PixelFormat format = atlasBitmap.PixelFormat; - Bitmap cloneBitmap = atlasBitmap.Clone(bmpRect, format); + + SKBitmap cloneBitmap = new(); + var bmpRect = SKRectI.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + atlasBitmap.ExtractSubset(cloneBitmap, bmpRect); + cloneBitmap = cloneBitmap.Copy(); + int width = ((n.Bounds.Width + 7) / 8) * 8; BitArray maskingBitArray = new BitArray(width * n.Bounds.Height); for (int y = 0; y < n.Bounds.Height; y++) { for (int x = 0; x < n.Bounds.Width; x++) { - Color pixelColor = cloneBitmap.GetPixel(x, y); - maskingBitArray[y * width + x] = (pixelColor.A > 0); + SKColor pixelColor = cloneBitmap.GetPixel(x, y); + maskingBitArray[y * width + x] = (pixelColor.Alpha > 0); } } + cloneBitmap.Dispose(); BitArray tempBitArray = new BitArray(width * n.Bounds.Height); for (int i = 0; i < maskingBitArray.Length; i += 8) { @@ -234,9 +238,16 @@ public enum BestFitHeuristic MaxOneAxis, } +public struct Rect +{ + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } +} public class Node { - public Rectangle Bounds; + public Rect Bounds; public TextureInfo Texture; public SplitType SplitType; } @@ -313,8 +324,8 @@ public class Packer { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); //1: Save images - Image img = CreateAtlasImage(atlas); - img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + using SKBitmap img = CreateAtlasImage(atlas); + TextureWorker.SaveImageToFile(atlasName, img); //2: save description in file foreach (Node n in atlas.Nodes) { @@ -345,25 +356,27 @@ public class Packer FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); foreach (FileInfo fi in files) { - Image img = Image.FromFile(fi.FullName); - if (img != null) + var imgSize = TextureWorker.GetImageSizeFromFile(fi.FullName); + if (imgSize == default) + continue; + int width = imgSize.Width; + int height = imgSize.Height; + + if (width <= AtlasSize && height <= AtlasSize) { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); + TextureInfo ti = new TextureInfo(); - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; + ti.Source = fi.FullName; + ti.Width = width; + ti.Height = height; - SourceTextures.Add(ti); + SourceTextures.Add(ti); - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); } } } @@ -456,7 +469,8 @@ public class Packer _Atlas.Nodes = new List(); textures = _Textures.ToList(); Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.Bounds.Width = _Atlas.Width; + root.Bounds.Height = _Atlas.Height; root.SplitType = SplitType.Horizontal; freeList.Add(root); while (freeList.Count > 0 && textures.Count > 0) @@ -484,24 +498,21 @@ public class Packer return textures; } - private Image CreateAtlasImage(Atlas _Atlas) + private SKBitmap CreateAtlasImage(Atlas _Atlas) { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); + SKBitmap img = new(_Atlas.Width, _Atlas.Height); + using SKCanvas g = new(img); foreach (Node n in _Atlas.Nodes) { if (n.Texture != null) { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); + using SKBitmap sourceImg = SKBitmap.Decode(n.Texture.Source); + SKRect rect = SKRect.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + g.DrawBitmap(sourceImg, rect); } } - // DPI FIX START - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - return img2; - // DPI FIX END + + return img; } } diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx index a9ac426d8..ac26d56b4 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportMasks.csx @@ -4,7 +4,6 @@ using System; using System.IO; -using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -38,15 +37,13 @@ foreach (string file in dirFiles) { throw new ScriptException("Getting the sprite name of " + FileNameWithExtension + " failed."); } - if (Data.Sprites.ByName(spriteName) == null) // Reject non-existing sprites - { - throw new ScriptException(FileNameWithExtension + " could not be imported as the sprite " + spriteName + " does not exist."); - } - using (Image img = Image.FromFile(file)) - { - if ((Data.Sprites.ByName(spriteName).Width != (uint)img.Width) || (Data.Sprites.ByName(spriteName).Height != (uint)img.Height)) - throw new ScriptException(FileNameWithExtension + " is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: " + Data.Sprites.ByName(spriteName).Width.ToString() + " px, height: " + Data.Sprites.ByName(spriteName).Height.ToString() + " px."); - } + var sprite = Data.Sprites.ByName(spriteName); + if (sprite is null) // Reject non-existing sprites + throw new ScriptException($"{FileNameWithExtension} could not be imported as the sprite {spriteName} does not exist."); + + var imgSize = TextureWorker.GetImageSizeFromFile(file); + if ((sprite.Width != (uint)imgSize.Width) || (sprite.Height != (uint)imgSize.Height)) + throw new ScriptException($"{FileNameWithExtension} is not the proper size to be imported! Please correct this before importing! The proper dimensions are width: {sprite.Width} px, height: {sprite.Height} px."); Int32 validFrameNumber = 0; try diff --git a/UndertaleModTool/Scripts/Resource Repackers/NewTextureRepacker.csx b/UndertaleModTool/Scripts/Resource Repackers/NewTextureRepacker.csx index ed88ba3c5..9d98ce05e 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/NewTextureRepacker.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/NewTextureRepacker.csx @@ -26,7 +26,7 @@ using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; using System.Collections.Concurrent; -using System.Drawing; +using SkiaSharp; using UndertaleModLib.Scripting; using UndertaleModLib.Util; using UndertaleModLib.Models; @@ -440,12 +440,10 @@ await Task.Run(() => UndertaleEmbeddedTexture tex = new UndertaleEmbeddedTexture(); tex.Name = new UndertaleString("Texture " + ++lastTextPage); Data.EmbeddedTextures.Add(tex); - Bitmap img = new Bitmap(atlas.Width, atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - // DPI fix - img.SetResolution(96.0F, 96.0F); + using SKBitmap img = new(atlas.Width, atlas.Height); + using SKCanvas g = new(img); - Graphics g = Graphics.FromImage(img); tex.Scaled = group.First().Scaled; // Make sure the original pane "Scaled" value is mantained. // Dump debug info regarding splits @@ -457,8 +455,8 @@ await Task.Run(() => { f.WriteLine($"tex: {texPageItems.IndexOf(item)}: {item.NewRect.X}, {item.NewRect.Y}, {item.NewRect.Width}, {item.NewRect.Height}"); - using (Bitmap source = new Bitmap(item.Filename)) - g.DrawImage(source, item.NewRect.X, item.NewRect.Y); + using SKBitmap source = TextureWorker.ReadImageFromFile(item.Filename); + g.DrawBitmap(source, item.NewRect.X, item.NewRect.Y); item.Item.TexturePage = tex; item.Item.SourceX = (ushort)item.NewRect.X; @@ -470,10 +468,8 @@ await Task.Run(() => // Save atlas into a file and load it back into string atlasFile = Path.Combine(packagerDirectory, $"atlas_{atlasName}.png"); - img.Save(atlasFile, System.Drawing.Imaging.ImageFormat.Png); + TextureWorker.SaveImageToFile(atlasFile, img); tex.TextureData.TextureBlob = File.ReadAllBytes(atlasFile); - - img.Dispose(); } else { @@ -493,21 +489,16 @@ await Task.Run(() => int potw = NearestPowerOf2((uint)item.OriginalRect.Width), poth = NearestPowerOf2((uint)item.OriginalRect.Height); - Bitmap img = new Bitmap(potw, poth, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - - // DPI fix - img.SetResolution(96.0F, 96.0F); - - Graphics g = Graphics.FromImage(img); - - // Load texture - using (Bitmap source = new Bitmap(item.Filename)) - g.DrawImage(source, 0, 0); + // Load texture and draw in on a new one with POT size + using SKBitmap img = new(potw, poth); + using (SKCanvas g = new(img)) + { + using SKBitmap source = SKBitmap.Decode(item.Filename); + g.DrawBitmap(source, 0, 0); + } itemFile = Path.Combine(packagerDirectory, $"pot_{texPageItems.IndexOf(item)}.png"); - img.Save(itemFile, System.Drawing.Imaging.ImageFormat.Png); - - img.Dispose(); + TextureWorker.SaveImageToFile(itemFile, img); } tex.TextureData.TextureBlob = File.ReadAllBytes(itemFile); diff --git a/UndertaleModTool/Scripts/Resource Repackers/ReduceEmbeddedTexturePages.csx b/UndertaleModTool/Scripts/Resource Repackers/ReduceEmbeddedTexturePages.csx index 4ecd1ba8f..df29c43ba 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ReduceEmbeddedTexturePages.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ReduceEmbeddedTexturePages.csx @@ -4,7 +4,7 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -128,7 +128,6 @@ int atlasCount = 0; foreach (Atlas atlas in packer.Atlasses) { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); - Bitmap atlasBitmap = new Bitmap(atlasName); UndertaleEmbeddedTexture texture = new UndertaleEmbeddedTexture(); texture.Name = new UndertaleString("Texture " + ++lastTextPage); texture.TextureData.TextureBlob = File.ReadAllBytes(atlasName); @@ -273,9 +272,17 @@ public enum BestFitHeuristic MaxOneAxis, } +public struct Rect +{ + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } +} + public class Node { - public Rectangle Bounds; + public Rect Bounds; public TextureInfo Texture; public SplitType SplitType; } @@ -352,8 +359,8 @@ public class Packer { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); //1: Save images - Image img = CreateAtlasImage(atlas); - img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + SKBitmap img = CreateAtlasImage(atlas); + TextureWorker.SaveImageToFile(atlasName, img); //2: save description in file foreach (Node n in atlas.Nodes) { @@ -384,25 +391,27 @@ public class Packer FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); foreach (FileInfo fi in files) { - Image img = Image.FromFile(fi.FullName); - if (img != null) + var imgSize = TextureWorker.GetImageSizeFromFile(fi.FullName); + if (imgSize == default) + continue; + int width = imgSize.Width; + int height = imgSize.Height; + + if (width <= AtlasSize && height <= AtlasSize) { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); + TextureInfo ti = new TextureInfo(); - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; + ti.Source = fi.FullName; + ti.Width = width; + ti.Height = height; - SourceTextures.Add(ti); + SourceTextures.Add(ti); - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); } } } @@ -495,7 +504,8 @@ public class Packer _Atlas.Nodes = new List(); textures = _Textures.ToList(); Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.Bounds.Width = _Atlas.Width; + root.Bounds.Height = _Atlas.Height; root.SplitType = SplitType.Horizontal; freeList.Add(root); while (freeList.Count > 0 && textures.Count > 0) @@ -523,23 +533,20 @@ public class Packer return textures; } - private Image CreateAtlasImage(Atlas _Atlas) + private SKBitmap CreateAtlasImage(Atlas _Atlas) { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); + SKBitmap img = new(_Atlas.Width, _Atlas.Height); + using SKCanvas g = new(img); foreach (Node n in _Atlas.Nodes) { if (n.Texture != null) { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); + using SKBitmap sourceImg = SKBitmap.Decode(n.Texture.Source); + SKRect rect = SKRect.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + g.DrawBitmap(sourceImg, rect); } } - // DPI FIX START - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - return img2; - // DPI FIX END + + return img; } } \ No newline at end of file diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTextures.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTextures.csx index 88ff02a36..cc7094863 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTextures.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTextures.csx @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTexturesGrouped.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTexturesGrouped.csx index 47b399b59..eab3a8511 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTexturesGrouped.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportAllTexturesGrouped.csx @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportTextureGroups.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportTextureGroups.csx index d07be5afe..17814b9d5 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportTextureGroups.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportTextureGroups.csx @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/UndertaleModTool/Scripts/Resource Unpackers/MergeImages.csx b/UndertaleModTool/Scripts/Resource Unpackers/MergeImages.csx index 66982b35b..e71d94811 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/MergeImages.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/MergeImages.csx @@ -1,8 +1,6 @@ using System; using System.IO; -using System.Drawing; -using System.Drawing.Imaging; -using System.Drawing.Drawing2D; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -10,19 +8,16 @@ using System.Text; using UndertaleModLib.Util; string importFolderA = PromptChooseDirectory(); -if (importFolderA == null) { +if (importFolderA is null) throw new ScriptException("The import folder was not set."); -} string importFolderB = PromptChooseDirectory(); -if (importFolderB == null) { +if (importFolderB is null) throw new ScriptException("The import folder was not set."); -} string exportFolder = PromptChooseDirectory(); -if (exportFolder == null) { +if (exportFolder is null) throw new ScriptException("The export folder was not set."); -} string searchPattern = "*.png"; @@ -30,31 +25,25 @@ DirectoryInfo textureDirectoryA = new DirectoryInfo(importFolderA); DirectoryInfo textureDirectoryB = new DirectoryInfo(importFolderB); FileInfo[] filesA = textureDirectoryA.GetFiles(searchPattern, SearchOption.AllDirectories); -foreach (FileInfo fileA in filesA) { +foreach (FileInfo fileA in filesA) +{ FileInfo[] fileMatch = textureDirectoryB.GetFiles(fileA.Name); - if (fileMatch == null) { + if (fileMatch is null) continue; - } FileInfo fileB = fileMatch[0]; - Bitmap bitmapA = new Bitmap(System.IO.Path.Combine(importFolderA, fileA.Name)); - Bitmap bitmapB = new Bitmap(System.IO.Path.Combine(importFolderB, fileA.Name)); - int width = bitmapA.Size.Width + bitmapB.Size.Width; - int height = bitmapA.Size.Width; - if (bitmapB.Size.Width > height) { - height = bitmapB.Size.Width; + using SKBitmap bitmapA = SKBitmap.Decode(Path.Combine(importFolderA, fileA.Name)); + using SKBitmap bitmapB = SKBitmap.Decode(Path.Combine(importFolderB, fileA.Name)); + int width = bitmapA.Width + bitmapB.Width; + int height = bitmapA.Height; + if (bitmapB.Width > height) + height = bitmapB.Width; + + using SKBitmap outputBitmap = new(width, height); + using (SKCanvas g = new(outputBitmap)) + { + g.DrawBitmap(bitmapA, 0, 0); + g.DrawBitmap(bitmapB, bitmapA.Width, 0); } - Bitmap outputBitmap = new Bitmap(width, height); - - outputBitmap = SuperimposeOntoBitmap(bitmapA, outputBitmap, 0); - outputBitmap = SuperimposeOntoBitmap(bitmapB, outputBitmap, bitmapA.Size.Width); - - outputBitmap.Save(System.IO.Path.Combine(exportFolder, fileA.Name), ImageFormat.Png); -} - -Bitmap SuperimposeOntoBitmap(Bitmap bitmapToAdd, Bitmap baseBitmap, int x) { - Graphics g = Graphics.FromImage(baseBitmap); - g.CompositingMode = CompositingMode.SourceOver; - g.DrawImage(bitmapToAdd, new System.Drawing.Point(x, 0)); - return baseBitmap; + TextureWorker.SaveImageToFile(Path.Combine(exportFolder, fileA.Name), outputBitmap); } diff --git a/UndertaleModTool/Scripts/Technical Scripts/ImportGraphics_Full_Repack.csx b/UndertaleModTool/Scripts/Technical Scripts/ImportGraphics_Full_Repack.csx index 7d038bea6..bae30e190 100644 --- a/UndertaleModTool/Scripts/Technical Scripts/ImportGraphics_Full_Repack.csx +++ b/UndertaleModTool/Scripts/Technical Scripts/ImportGraphics_Full_Repack.csx @@ -4,7 +4,7 @@ using System; using System.IO; -using System.Drawing; +using SkiaSharp; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -338,19 +338,23 @@ foreach (Atlas atlas in packer.Atlasses) newSprite.Textures.Add(null); } newSprite.CollisionMasks.Add(newSprite.NewMaskEntry()); - Rectangle bmpRect = new Rectangle(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); - System.Drawing.Imaging.PixelFormat format = atlasBitmap.PixelFormat; - Bitmap cloneBitmap = atlasBitmap.Clone(bmpRect, format); + + SKBitmap cloneBitmap = new(); + var bmpRect = SKRectI.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + atlasBitmap.ExtractSubset(cloneBitmap, bmpRect); + cloneBitmap = cloneBitmap.Copy(); + int width = ((n.Bounds.Width + 7) / 8) * 8; BitArray maskingBitArray = new BitArray(width * n.Bounds.Height); for (int y = 0; y < n.Bounds.Height; y++) { for (int x = 0; x < n.Bounds.Width; x++) { - Color pixelColor = cloneBitmap.GetPixel(x, y); - maskingBitArray[y * width + x] = (pixelColor.A > 0); + SKColor pixelColor = cloneBitmap.GetPixel(x, y); + maskingBitArray[y * width + x] = (pixelColor.Alpha > 0); } } + cloneBitmap.Dispose(); BitArray tempBitArray = new BitArray(width * n.Bounds.Height); for (int i = 0; i < maskingBitArray.Length; i += 8) { @@ -428,9 +432,17 @@ public enum BestFitHeuristic MaxOneAxis, } +public struct Rect +{ + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } +} + public class Node { - public Rectangle Bounds; + public Rect Bounds; public TextureInfo Texture; public SplitType SplitType; } @@ -507,8 +519,8 @@ public class Packer { string atlasName = String.Format(prefix + "{0:000}" + ".png", atlasCount); //1: Save images - Image img = CreateAtlasImage(atlas); - img.Save(atlasName, System.Drawing.Imaging.ImageFormat.Png); + using SKBitmap img = CreateAtlasImage(atlas); + TextureWorker.SaveImageToFile(atlasName, img); //2: save description in file foreach (Node n in atlas.Nodes) { @@ -539,25 +551,27 @@ public class Packer FileInfo[] files = di.GetFiles(_Wildcard, SearchOption.AllDirectories); foreach (FileInfo fi in files) { - Image img = Image.FromFile(fi.FullName); - if (img != null) + var imgSize = TextureWorker.GetImageSizeFromFile(fi.FullName); + if (imgSize == default) + continue; + int width = imgSize.Width; + int height = imgSize.Height; + + if (width <= AtlasSize && height <= AtlasSize) { - if (img.Width <= AtlasSize && img.Height <= AtlasSize) - { - TextureInfo ti = new TextureInfo(); + TextureInfo ti = new TextureInfo(); - ti.Source = fi.FullName; - ti.Width = img.Width; - ti.Height = img.Height; + ti.Source = fi.FullName; + ti.Width = width; + ti.Height = height; - SourceTextures.Add(ti); + SourceTextures.Add(ti); - Log.WriteLine("Added " + fi.FullName); - } - else - { - Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); - } + Log.WriteLine("Added " + fi.FullName); + } + else + { + Error.WriteLine(fi.FullName + " is too large to fix in the atlas. Skipping!"); } } } @@ -650,7 +664,8 @@ public class Packer _Atlas.Nodes = new List(); textures = _Textures.ToList(); Node root = new Node(); - root.Bounds.Size = new Size(_Atlas.Width, _Atlas.Height); + root.Bounds.Width = _Atlas.Width; + root.Bounds.Height = _Atlas.Height; root.SplitType = SplitType.Horizontal; freeList.Add(root); while (freeList.Count > 0 && textures.Count > 0) @@ -678,23 +693,20 @@ public class Packer return textures; } - private Image CreateAtlasImage(Atlas _Atlas) + private SKBitmap CreateAtlasImage(Atlas _Atlas) { - Image img = new Bitmap(_Atlas.Width, _Atlas.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - Graphics g = Graphics.FromImage(img); + SKBitmap img = new(_Atlas.Width, _Atlas.Height); + using SKCanvas g = new(img); foreach (Node n in _Atlas.Nodes) { if (n.Texture != null) { - Image sourceImg = Image.FromFile(n.Texture.Source); - g.DrawImage(sourceImg, n.Bounds); + using SKBitmap sourceImg = SKBitmap.Decode(n.Texture.Source); + SKRect rect = SKRect.Create(n.Bounds.X, n.Bounds.Y, n.Bounds.Width, n.Bounds.Height); + g.DrawBitmap(sourceImg, rect); } } - // DPI FIX START - Bitmap ResolutionFix = new Bitmap(img); - ResolutionFix.SetResolution(96.0F, 96.0F); - Image img2 = ResolutionFix; - return img2; - // DPI FIX END + + return img; } } \ No newline at end of file From 7309147c83c65bf7b2ea912ef98b23859f612e5f Mon Sep 17 00:00:00 2001 From: Miepee Date: Sun, 2 Jun 2024 16:18:55 +0200 Subject: [PATCH 22/22] add todos to scripts which are windows only --- UndertaleModTool/MainWindow.xaml.cs | 2 +- UndertaleModTool/Scripts/Community Scripts/FontEditor.csx | 1 + .../Scripts/Resource Repackers/ImportAllTilesets.csx | 1 + UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index fe91d0cbe..d93593b24 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -540,7 +540,7 @@ public static void SetDarkMode(bool enable, bool isStartup = false) { foreach (var pair in appDarkStyle) resources[pair.Key] = pair.Value; - + Windows.TextInput.BGColor = System.Drawing.Color.FromArgb(darkColor.R, darkColor.G, darkColor.B); diff --git a/UndertaleModTool/Scripts/Community Scripts/FontEditor.csx b/UndertaleModTool/Scripts/Community Scripts/FontEditor.csx index 871d9a4e1..707e2458c 100644 --- a/UndertaleModTool/Scripts/Community Scripts/FontEditor.csx +++ b/UndertaleModTool/Scripts/Community Scripts/FontEditor.csx @@ -1,4 +1,5 @@ //by porog +// TODO: this heavily uses windows stuff, should be made cross platform using System.Drawing; using System.Drawing.Imaging; diff --git a/UndertaleModTool/Scripts/Resource Repackers/ImportAllTilesets.csx b/UndertaleModTool/Scripts/Resource Repackers/ImportAllTilesets.csx index 4d214de22..a5f2375b2 100644 --- a/UndertaleModTool/Scripts/Resource Repackers/ImportAllTilesets.csx +++ b/UndertaleModTool/Scripts/Resource Repackers/ImportAllTilesets.csx @@ -1,5 +1,6 @@ // Adapted from original script by Grossley +// TODO: remove system.drawing from here, used for bitmaps using System.Text; using System; using System.IO; diff --git a/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx b/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx index dfc52b999..c0f723ff7 100644 --- a/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx +++ b/UndertaleModTool/Scripts/Resource Unpackers/ExportFontData.csx @@ -1,4 +1,5 @@ // Made by mono21400 +// TODO: this heavily uses windows stuff, should be made cross platform using System.Text; using System;