diff --git a/UndertaleModLib/Util/GMImage.cs b/UndertaleModLib/Util/GMImage.cs
index eef1eb7a7..87bf450f9 100644
--- a/UndertaleModLib/Util/GMImage.cs
+++ b/UndertaleModLib/Util/GMImage.cs
@@ -1,8 +1,8 @@
-using ICSharpCode.SharpZipLib.BZip2;
-using ImageMagick;
-using System;
+using System;
using System.Buffers.Binary;
using System.IO;
+using ICSharpCode.SharpZipLib.BZip2;
+using ImageMagick;
namespace UndertaleModLib.Util;
@@ -34,7 +34,12 @@ public enum ImageFormat
///
/// BZip2 compression applied on top of GameMaker's custom variant of the QOI image file format.
///
- Bz2Qoi
+ Bz2Qoi,
+
+ ///
+ /// DDS file format.
+ ///
+ Dds,
}
///
@@ -77,6 +82,11 @@ public enum ImageFormat
///
private static ReadOnlySpan MagicBz2Footer => new byte[] { 0x17, 0x72, 0x45, 0x38, 0x50, 0x90 };
+ ///
+ /// DDS file format magic.
+ ///
+ private static ReadOnlySpan MagicDds => "DDS "u8;
+
///
/// Backing data for the image, whether compressed or not.
///
@@ -335,6 +345,61 @@ public static GMImage FromBinaryReader(IBinaryReader reader, long maxEndOfStream
return FromQoi(reader.ReadBytes(12 + (int)compressedLength));
}
+ // DDS
+ if (header.StartsWith(MagicDds))
+ {
+ // Size int skipped because 8 bytes were already read
+ uint flags = reader.ReadUInt32();
+ uint height = reader.ReadUInt32();
+
+ //uint width = reader.ReadUInt32();
+ reader.Position += 4;
+
+ uint pitchOrLinearSize = reader.ReadUInt32();
+
+ //uint depth = reader.ReadUInt32();
+ //uint mipMapCount = reader.ReadUInt32();
+ //byte[] reserved1 = reader.ReadBytes(4 * 11);
+ //uint pixelFormatSize = reader.ReadUInt32();
+ reader.Position += 56;
+
+ uint pixelFormatFlags = reader.ReadUInt32();
+ uint pixelFormatFourCC = reader.ReadUInt32();
+
+ // TODO: Check caps for DDSCAPS_COMPLEX (when there's extra data afterwards)
+
+ // Skip to end of header
+ reader.Position += 40;
+
+ // Check if DX10 header is present and skip it
+ // DDPF_FOURCC == 0x4
+ // DX10 == 0x30315844
+ if ((pixelFormatFlags & 0x4) != 0 && pixelFormatFourCC == 0x30315844)
+ reader.Position += 20;
+
+ // Check if that int is the size or pitch
+ // DDSD_LINEARSIZE == 0x80000
+ int size = (int)(reader.Position - startAddress);
+ if ((flags & 0x80000) != 0)
+ size += (int)pitchOrLinearSize;
+ else
+ size += (int)(pitchOrLinearSize * height);
+
+ // Read entire data
+ reader.Position = startAddress;
+ byte[] bytes = reader.ReadBytes(size);
+
+ // Check if rest of bytes are 0x00 padded
+ byte[] paddingBytes = reader.ReadBytes((int)(maxEndOfStreamPosition - reader.Position));
+ for (int i=0; i
+ /// Creates a of DDS format, wrapping around the provided byte array containing DDS data.
+ ///
+ /// Byte array of DDS data.
+ public static GMImage FromDds(byte[] data)
+ {
+ ArgumentNullException.ThrowIfNull(data);
+
+ ReadOnlySpan span = data.AsSpan();
+
+ // Get height and width
+ int height = (int)BinaryPrimitives.ReadUInt32LittleEndian(span[12..16]);
+ int width = (int)BinaryPrimitives.ReadUInt32LittleEndian(span[16..20]);
+
+ // Create wrapper image
+ return new GMImage(ImageFormat.Dds, width, height, data);
+ }
+
+ private static void AddMagickToPngSettings(MagickReadSettings settings)
+ {
+ settings.SetDefine(MagickFormat.Png32, "compression-level", 4);
+ settings.SetDefine(MagickFormat.Png32, "compression-filter", 5);
+ settings.SetDefine(MagickFormat.Png32, "compression-strategy", 2);
+ }
+
// Settings to be used for raw data, and when encoding a PNG
private MagickReadSettings GetMagickRawToPngSettings()
{
@@ -462,9 +552,18 @@ private MagickReadSettings GetMagickRawToPngSettings()
Format = MagickFormat.Bgra,
Compression = CompressionMethod.NoCompression
};
- settings.SetDefine(MagickFormat.Png32, "compression-level", 4);
- settings.SetDefine(MagickFormat.Png32, "compression-filter", 5);
- settings.SetDefine(MagickFormat.Png32, "compression-strategy", 2);
+ AddMagickToPngSettings(settings);
+ return settings;
+ }
+
+ // Settings to be used for decoding DDS, and when encoding a PNG
+ private MagickReadSettings GetMagickDdsToPngSettings()
+ {
+ var settings = new MagickReadSettings()
+ {
+ Format = MagickFormat.Dds,
+ };
+ AddMagickToPngSettings(settings);
return settings;
}
@@ -518,6 +617,15 @@ public void SavePng(Stream stream)
rawImage.SavePng(stream);
break;
}
+ case ImageFormat.Dds:
+ {
+ // Create image using ImageMagick, and save it as PNG format
+ using var image = new MagickImage(_data, GetMagickDdsToPngSettings());
+ image.Alpha(AlphaOption.Set);
+ image.Format = MagickFormat.Png32;
+ image.Write(stream);
+ break;
+ }
default:
throw new InvalidOperationException($"Unknown format {Format}");
}
@@ -536,6 +644,7 @@ public GMImage ConvertToFormat(ImageFormat format, MemoryStream sharedStream = n
ImageFormat.Png => ConvertToPng(),
ImageFormat.Qoi => ConvertToQoi(),
ImageFormat.Bz2Qoi => ConvertToBz2Qoi(sharedStream),
+ ImageFormat.Dds => ConvertToDds(),
_ => throw new ArgumentOutOfRangeException(nameof(format)),
};
}
@@ -553,6 +662,7 @@ public GMImage ConvertToRawBgra()
return this;
}
case ImageFormat.Png:
+ case ImageFormat.Dds:
{
// Convert image to raw byte array
var image = new MagickImage(_data);
@@ -632,6 +742,14 @@ public GMImage ConvertToPng()
// Convert raw image to PNG
return rawImage.ConvertToPng();
}
+ case ImageFormat.Dds:
+ {
+ // Create image using ImageMagick, and convert it to PNG format
+ using var image = new MagickImage(_data, GetMagickDdsToPngSettings());
+ image.Alpha(AlphaOption.Set);
+ image.Format = MagickFormat.Png32;
+ return new GMImage(ImageFormat.Png, Width, Height, image.ToByteArray());
+ }
}
throw new InvalidOperationException($"Unknown source format {Format}");
@@ -647,6 +765,7 @@ public GMImage ConvertToQoi()
case ImageFormat.RawBgra:
case ImageFormat.Png:
case ImageFormat.Bz2Qoi:
+ case ImageFormat.Dds:
{
// Encode image as QOI
return new GMImage(ImageFormat.Qoi, Width, Height, QoiConverter.GetArrayFromImage(this, false));
@@ -706,6 +825,7 @@ public GMImage ConvertToBz2Qoi(MemoryStream sharedStream = null)
{
case ImageFormat.RawBgra:
case ImageFormat.Png:
+ case ImageFormat.Dds:
{
// Encode image as QOI, first
byte[] data = QoiConverter.GetArrayFromImage(this, false);
@@ -726,6 +846,17 @@ public GMImage ConvertToBz2Qoi(MemoryStream sharedStream = null)
throw new InvalidOperationException($"Unknown source format {Format}");
}
+ ///
+ /// Same as .
+ ///
+ /// This is supposd to return the image converted to format, but that's not implemented yet.
+ ///
+ public GMImage ConvertToDds()
+ {
+ // TODO: Actually convert to DDS
+ return ConvertToPng();
+ }
+
///
/// Returns the raw BGRA32 pixel data of this image, which can be modified.
///
@@ -756,6 +887,7 @@ public void WriteToBinaryWriter(BinaryWriter writer, bool gm2022_5)
case ImageFormat.RawBgra:
case ImageFormat.Png:
case ImageFormat.Qoi:
+ case ImageFormat.Dds:
// Data is stored identically to file format, so write it verbatim
writer.Write(_data);
break;
@@ -843,6 +975,16 @@ public MagickImage GetMagickImage()
case ImageFormat.Bz2Qoi:
// Convert to raw data, then parse that
return ConvertToRawBgra().GetMagickImage();
+ case ImageFormat.Dds:
+ {
+ // Parse the DDS data
+ MagickReadSettings settings = new()
+ {
+ Format = MagickFormat.Dds
+ };
+ MagickImage image = new(_data, settings);
+ return image;
+ }
}
throw new InvalidOperationException($"Unknown format {Format}");
diff --git a/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml.cs
index 8950e0885..631e36e96 100644
--- a/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml.cs
+++ b/UndertaleModTool/Editors/UndertaleEmbeddedTextureEditor.xaml.cs
@@ -1,28 +1,20 @@
-using Microsoft.Win32;
-using System;
-using System.Collections.Generic;
+using System;
+using System.ComponentModel;
+using System.Globalization;
using System.IO;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
-using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-using System.Drawing;
+using System.Windows.Threading;
+using ImageMagick;
+using Microsoft.Win32;
using UndertaleModLib.Models;
using UndertaleModLib.Util;
-using System.Globalization;
-using UndertaleModLib;
using UndertaleModTool.Windows;
-using System.Windows.Threading;
-using ImageMagick;
-using System.ComponentModel;
namespace UndertaleModTool
{
@@ -252,9 +244,19 @@ 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");
}
+ var previousFormat = target.TextureData.Image.Format;
+
// Import image
target.TextureData.Image = image;
+ var currentFormat = target.TextureData.Image.Format;
+
+ // If texture was DDS, warn user that texture has been converted to PNG
+ if (previousFormat == GMImage.ImageFormat.Dds && currentFormat == GMImage.ImageFormat.Png)
+ {
+ mainWindow.ShowMessage($"{target} was converted into PNG format since we don't support converting images into DDS format. This might have performance issues in the game.");
+ }
+
// Update width/height properties in the UI
TexWidth.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
TexHeight.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
diff --git a/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs b/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs
index 1724def29..102598fa6 100644
--- a/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs
+++ b/UndertaleModTool/Editors/UndertaleTexturePageItemEditor.xaml.cs
@@ -1,15 +1,15 @@
-using Microsoft.Win32;
-using System;
+using System;
+using System.ComponentModel;
using System.Windows;
-using UndertaleModLib.Models;
-using UndertaleModLib.Util;
using System.Windows.Controls;
-using System.Windows.Media;
using System.Windows.Data;
-using UndertaleModTool.Windows;
-using ImageMagick;
+using System.Windows.Media;
using System.Windows.Media.Imaging;
-using System.ComponentModel;
+using ImageMagick;
+using Microsoft.Win32;
+using UndertaleModLib.Models;
+using UndertaleModLib.Util;
+using UndertaleModTool.Windows;
namespace UndertaleModTool
{
@@ -150,8 +150,19 @@ private void Import_Click(object sender, RoutedEventArgs e)
{
using MagickImage image = TextureWorker.ReadBGRAImageFromFile(dlg.FileName);
UndertaleTexturePageItem item = DataContext as UndertaleTexturePageItem;
+
+ var previousFormat = item.TexturePage.TextureData.Image.Format;
+
item.ReplaceTexture(image);
+ var currentFormat = item.TexturePage.TextureData.Image.Format;
+
+ // If texture was DDS, warn user that texture has been converted to PNG
+ if (previousFormat == GMImage.ImageFormat.Dds && currentFormat == GMImage.ImageFormat.Png)
+ {
+ mainWindow.ShowMessage($"{item.TexturePage} was converted into PNG format since we don't support converting images into DDS format. This might have performance issues in the game.");
+ }
+
// Refresh the image of "ItemDisplay"
if (ItemDisplay.FindName("RenderAreaBorder") is not Border border)
return;