-
Notifications
You must be signed in to change notification settings - Fork 239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for games that use DDS textures #1929
Open
luizzeroxis
wants to merge
2
commits into
UnderminersTeam:master
Choose a base branch
from
luizzeroxis:dds-support
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+184
−29
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | |
/// <summary> | ||
/// BZip2 compression applied on top of GameMaker's custom variant of the QOI image file format. | ||
/// </summary> | ||
Bz2Qoi | ||
Bz2Qoi, | ||
|
||
/// <summary> | ||
/// DDS file format. | ||
/// </summary> | ||
Dds, | ||
} | ||
|
||
/// <summary> | ||
|
@@ -77,6 +82,11 @@ public enum ImageFormat | |
/// </summary> | ||
private static ReadOnlySpan<byte> MagicBz2Footer => new byte[] { 0x17, 0x72, 0x45, 0x38, 0x50, 0x90 }; | ||
|
||
/// <summary> | ||
/// DDS file format magic. | ||
/// </summary> | ||
private static ReadOnlySpan<byte> MagicDds => "DDS "u8; | ||
|
||
/// <summary> | ||
/// Backing data for the image, whether compressed or not. | ||
/// </summary> | ||
|
@@ -335,6 +345,14 @@ public static GMImage FromBinaryReader(IBinaryReader reader, long maxEndOfStream | |
return FromQoi(reader.ReadBytes(12 + (int)compressedLength)); | ||
} | ||
|
||
// DDS | ||
if (header.StartsWith(MagicDds)) | ||
{ | ||
// Read entire image | ||
reader.Position = startAddress; | ||
return FromDds(reader.ReadBytes((int)(maxEndOfStreamPosition - startAddress))); | ||
} | ||
|
||
throw new IOException("Failed to recognize any known image header"); | ||
} | ||
|
||
|
@@ -452,6 +470,31 @@ public static GMImage FromQoi(byte[] data) | |
return new GMImage(ImageFormat.Qoi, width, height, data); | ||
} | ||
|
||
/// <summary> | ||
/// Creates a <see cref="GMImage"/> of DDS format, wrapping around the provided byte array containing DDS data. | ||
/// </summary> | ||
/// <param name="data">Byte array of DDS data.</param> | ||
public static GMImage FromDds(byte[] data) | ||
{ | ||
ArgumentNullException.ThrowIfNull(data); | ||
|
||
ReadOnlySpan<byte> 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 void AddMagickToPngSettings(MagickReadSettings settings) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsure if this is going to stick around in the long run, but this can probably be marked There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally |
||
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 +505,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 +570,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 +597,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 +615,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 +695,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 +718,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 +778,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 +799,17 @@ public GMImage ConvertToBz2Qoi(MemoryStream sharedStream = null) | |
throw new InvalidOperationException($"Unknown source format {Format}"); | ||
} | ||
|
||
/// <summary> | ||
/// Same as <see cref="ConvertToPng"/>. | ||
/// </summary> | ||
/// <remarks>This is supposd to return the image converted to <see cref="ImageFormat.Dds"/> format, but that's not implemented yet.</remarks> | ||
/// <returns></returns> | ||
public GMImage ConvertToDds() | ||
{ | ||
// TODO: Actually convert to DDS | ||
return ConvertToPng(); | ||
} | ||
|
||
/// <summary> | ||
/// Returns the raw BGRA32 pixel data of this image, which can be modified. | ||
/// </summary> | ||
|
@@ -756,6 +840,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 +928,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}"); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If possible, we should be finding the length of the image data using the image contents, rather than using
maxEndOfStreamPosition
, which can result in unnecessary padding when re-saving with modifications.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't think about that! Sadly there's no easy way to find the length in this format. I changed it so it can get the size of file but only without the extra bdata2 field, because getting that is even harder.