Skip to content

Commit

Permalink
Add initial DDS support (#706)
Browse files Browse the repository at this point in the history
Resolves #705.
  • Loading branch information
lahm86 authored Jun 12, 2024
1 parent 7b9b65d commit 9051247
Show file tree
Hide file tree
Showing 18 changed files with 182 additions and 27 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies, but these are listed as follows for reference.
### NuGet packages
* https://github.com/icsharpcode/SharpZipLib
* https://github.com/JamesNK/Newtonsoft.Json
* https://github.com/Nominom/BCnEncoder.NET

### Additional dependencies
* https://github.com/LostArtefacts/TRGameflowEditor/releases/latest
Expand Down
69 changes: 65 additions & 4 deletions TRImageControl/TRImage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using System.Drawing;
using BCnEncoder.Decoder;
using BCnEncoder.Shared;
using BCnEncoder.Shared.ImageFiles;
using Microsoft.Toolkit.HighPerformance;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
Expand Down Expand Up @@ -94,15 +98,25 @@ public TRImage(Size size, uint[] pixels)
public TRImage(string filePath)
{
using FileStream fs = File.OpenRead(filePath);
using Bitmap bmp = new(Image.FromStream(fs));
ReadBitmap(bmp);
if (IsDDS(filePath))
{
ReadDDS(fs);
}
else
{
using Bitmap bmp = new(Image.FromStream(fs));
ReadBitmap(bmp);
}
}

public TRImage(Bitmap bmp)
{
ReadBitmap(bmp);
}

private static bool IsDDS(string filePath)
=> string.Equals(Path.GetExtension(filePath), ".DDS", StringComparison.InvariantCultureIgnoreCase);

private void ReadBitmap(Bitmap bmp)
{
Size = bmp.Size;
Expand Down Expand Up @@ -134,6 +148,26 @@ private void ReadBitmap(Bitmap bmp)
bmp.UnlockBits(bd);
}

private void ReadDDS(Stream stream)
{
BcDecoder decoder = new();
Memory2D<ColorRgba32> pixels = decoder.Decode2D(stream);
Span2D<ColorRgba32> span = pixels.Span;

Size = new(pixels.Width, pixels.Height);
Pixels = new uint[Size.Width * Size.Height];

for (int y = 0; y < Height; y++)
{
Span<ColorRgba32> row = span.GetRowSpan(y);
for (int x = 0; x < Width; x++)
{
Color c = Color.FromArgb(row[x].a, row[x].r, row[x].g, row[x].b);
this[x, y] = (uint)c.ToArgb();
}
}
}

public byte[] ToRGB(List<TRColour> palette)
{
byte[] data = new byte[Pixels.Length];
Expand Down Expand Up @@ -207,7 +241,16 @@ public Bitmap ToBitmap()
}

public void Save(string fileName)
=> Save(fileName, ImageFormat.Png);
{
if (IsDDS(fileName))
{
WriteDDS(fileName);
}
else
{
Save(fileName, ImageFormat.Png);
}
}

public void Save(string fileName, ImageFormat format)
{
Expand All @@ -218,6 +261,24 @@ public void Save(string fileName, ImageFormat format)
public void Save(Stream stream, ImageFormat format)
=> ToBitmap().Save(stream, format);

private void WriteDDS(string fileName)
{
ColorRgba32[] colours = new ColorRgba32[Width * Height];
Read((c, x, y) => colours[y * Width + x] = new(c.R, c.G, c.B, c.A));

BCnEncoder.Encoder.BcEncoder encoder = new();
encoder.OutputOptions.GenerateMipMaps = true;
encoder.OutputOptions.MaxMipMapLevel = 6;
encoder.OutputOptions.FileFormat = OutputFileFormat.Dds;
encoder.OutputOptions.Format = CompressionFormat.Bc7;

Memory2D<ColorRgba32> pixels = colours.AsMemory().AsMemory2D(Height, Width);
DdsFile file = encoder.EncodeToDds(pixels);

using FileStream fs = File.OpenWrite(fileName);
file.Write(fs);
}

public Color GetPixel(int x, int y)
=> Color.FromArgb((int)this[x, y]);

Expand Down
1 change: 1 addition & 0 deletions TRImageControl/TRImageControl.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ProjectReference Include="..\TRLevelControl\TRLevelControl.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BCnEncoder.Net" Version="2.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Drawing.Common" Version="8.0.4" />
</ItemGroup>
Expand Down
24 changes: 24 additions & 0 deletions TRImageControlTests/ImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,28 @@ public void TestBlending()
Assert.AreEqual(120, blend.B);
Assert.AreEqual(193, blend.A);
}

[TestMethod]
public void TestDDS()
{
TRImage image = new(512, 256);
image.Fill(Color.Red);
image.Save("test.dds");

// Verify it's not PNG
File.Move("test.dds", "test.png", true);
try
{
image = new("test.png");
Assert.Fail();
}
catch { }

// Basic check that it's readable
File.Move("test.png", "test.dds", true);
image = new("test.dds");

Assert.AreEqual(512, image.Width);
Assert.AreEqual(256, image.Height);
}
}
8 changes: 4 additions & 4 deletions TRRandomizerCore/Editors/TR1ClassicEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ namespace TRRandomizerCore.Editors;

public class TR1ClassicEditor : TR1LevelEditor, ISettingsProvider
{
private static readonly Point _regularBadgePos = new(706, 537);
private static readonly Point _goldBadgePos = new(498, 445);
private static readonly Point _regularBadgePos = new(702, 538);
private static readonly Point _goldBadgePos = new(498, 444);

public RandomizerSettings Settings { get; private set; }

Expand Down Expand Up @@ -354,7 +354,7 @@ private void AmendTitleAndCredits(AbstractTRScriptEditor scriptEditor, TRSaveMon
{
string editedTitle = Path.Combine(GetWriteBasePath(), "title.png");
TRImage bg = new(backupTitle);
TRImage badge = new(@"Resources\Shared\Graphics\goldbadge-small.png");
TRImage badge = new(@"Resources\Shared\Graphics\tr1badge-small.png");
bg.Import(badge, scriptEditor.GameMode == GameMode.Gold ? _goldBadgePos : _regularBadgePos, true);

if (scriptEditor.GameMode == GameMode.Combined)
Expand All @@ -375,7 +375,7 @@ private void AmendTitleAndCredits(AbstractTRScriptEditor scriptEditor, TRSaveMon
string creditPath = @"data\trrando.png";

TRImage bg = new(1920, 1080);
TRImage badge = new(@"Resources\Shared\Graphics\goldbadge-large.png");
TRImage badge = new(@"Resources\Shared\Graphics\tr1badge-large.png");
bg.Fill(Color.Black);
bg.Import(badge, new(960 - badge.Width / 2, 540 - badge.Height / 2), true);
bg.Save(creditFile);
Expand Down
5 changes: 5 additions & 0 deletions TRRandomizerCore/Editors/TR1RemasteredEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ protected override void SaveImpl(AbstractTRScriptEditor scriptEditor, TRSaveMoni
}.Randomize(Settings.AudioSeed);
}

Task titleTask = Task.Run(() => TRRTitleEditor.Stamp(scriptEditor.Script as TRRScript, _io));

if (!monitor.IsCancelled && Settings.RandomizeTextures)
{
monitor.FireSaveStateBeginning(TRSaveCategory.Custom, "Randomizing textures");
Expand All @@ -240,5 +242,8 @@ protected override void SaveImpl(AbstractTRScriptEditor scriptEditor, TRSaveMoni
Settings = Settings,
}.Randomize(Settings.TextureSeed);
}

monitor.FireSaveStateBeginning(TRSaveCategory.Custom, "Finalizing tasks - please wait");
titleTask.Wait();
}
}
5 changes: 5 additions & 0 deletions TRRandomizerCore/Editors/TR2RemasteredEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ protected override void SaveImpl(AbstractTRScriptEditor scriptEditor, TRSaveMoni
}.Randomize(Settings.AudioSeed);
}

Task titleTask = Task.Run(() => TRRTitleEditor.Stamp(scriptEditor.Script as TRRScript, _io));

if (!monitor.IsCancelled && Settings.RandomizeTextures)
{
monitor.FireSaveStateBeginning(TRSaveCategory.Custom, "Randomizing textures");
Expand All @@ -220,5 +222,8 @@ protected override void SaveImpl(AbstractTRScriptEditor scriptEditor, TRSaveMoni
Settings = Settings,
}.Randomize(Settings.TextureSeed);
}

monitor.FireSaveStateBeginning(TRSaveCategory.Custom, "Finalizing tasks - please wait");
titleTask.Wait();
}
}
5 changes: 5 additions & 0 deletions TRRandomizerCore/Editors/TR3RemasteredEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ protected override void SaveImpl(AbstractTRScriptEditor scriptEditor, TRSaveMoni
}.Randomize(Settings.AudioSeed);
}

Task titleTask = Task.Run(() => TRRTitleEditor.Stamp(scriptEditor.Script as TRRScript, _io));

if (!monitor.IsCancelled && Settings.RandomizeTextures)
{
monitor.FireSaveStateBeginning(TRSaveCategory.Custom, "Randomizing textures");
Expand All @@ -243,5 +245,8 @@ protected override void SaveImpl(AbstractTRScriptEditor scriptEditor, TRSaveMoni
Settings = Settings,
}.Randomize(Settings.TextureSeed);
}

monitor.FireSaveStateBeginning(TRSaveCategory.Custom, "Finalizing tasks - please wait");
titleTask.Wait();
}
}
49 changes: 49 additions & 0 deletions TRRandomizerCore/Editors/TRRTitleEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Drawing;
using System.Text.RegularExpressions;
using TRGE.Core;
using TRImageControl;

namespace TRRandomizerCore.Editors;

public class TRRTitleEditor
{
private static readonly Regex _titleRegex = new(@"TITLE_[A-Z]{2}\.DDS", RegexOptions.IgnoreCase);

private static readonly Dictionary<TRVersion, Point> _badgePositions = new()
{
[TRVersion.TR1] = new(645, 588),
[TRVersion.TR2] = new(625, 665),
[TRVersion.TR3] = new(262, 266),
};

public static void Stamp(TRRScript script, TRDirectoryIOArgs io)
{
IEnumerable<string> titleFiles = script.GetAdditionalBackupFiles()
.Select(f => Path.GetFileName(f))
.Where(f => _titleRegex.IsMatch(f));
if (!titleFiles.Any())
{
return;
}

string sharedFolder = Path.GetFullPath(Path.Combine(io.BackupDirectory.FullName, @"..\..\TRR"));
Directory.CreateDirectory(sharedFolder);

TRImage badge = null;
foreach (string titleFile in titleFiles)
{
string cachedFile = Path.Combine(sharedFolder,
$"{Path.GetFileNameWithoutExtension(titleFile)}_{script.Edition.Version}{Path.GetExtension(titleFile)}");
if (!File.Exists(cachedFile))
{
// This is slow with debugger attached and intensive in release, so we only want to do it once per game.
TRImage titleImage = new(Path.Combine(io.BackupDirectory.FullName, titleFile));
badge ??= new($@"Resources\Shared\Graphics\{script.Edition.Version}badge-small.png");
titleImage.Import(badge, _badgePositions[script.Edition.Version], true);
titleImage.Save(cachedFile);
}

File.Copy(cachedFile, Path.Combine(io.WIPOutputDirectory.FullName, titleFile), true);
}
}
}
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 20 additions & 1 deletion TRRandomizerView/Windows/AboutWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
WindowStartupLocation="CenterOwner"
ShowInTaskbar="False"
Loaded="Window_Loaded"
Title="{Binding AboutTitle}" Height="250" Width="550">
SizeToContent="Height"
Title="{Binding AboutTitle}" Width="550">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
Expand Down Expand Up @@ -81,6 +82,24 @@
by James Newton-King
</TextBlock>

<TextBlock>
Zlib support via
<Hyperlink ToolTip="https://github.com/icsharpcode/SharpZipLib"
NavigateUri="https://github.com/icsharpcode/SharpZipLib"
RequestNavigate="Hyperlink_RequestNavigate">
SharpZipLib
</Hyperlink> by ICSharpCode
</TextBlock>

<TextBlock>
DDS support via
<Hyperlink ToolTip="https://github.com/Nominom/BCnEncoder.NET"
NavigateUri="https://github.com/Nominom/BCnEncoder.NET"
RequestNavigate="Hyperlink_RequestNavigate">
BCnEncoder.NET
</Hyperlink> by Nominom
</TextBlock>

<TextBlock Margin="0,20,0,0">
Icon made by
<Hyperlink ToolTip="https://www.flaticon.com/authors/kiranshastry"
Expand Down
3 changes: 0 additions & 3 deletions TextureExport/TextureExport.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Pfim" Version="0.11.2" />
</ItemGroup>
</Project>
18 changes: 3 additions & 15 deletions TextureExport/Types/TRRExporter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using Newtonsoft.Json;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using TRImageControl;
using TRLevelControl.Helpers;
using TRLevelControl.Model;
using TRRandomizerCore.Textures;
Expand All @@ -17,18 +15,8 @@ public static void Export(string ddsFolder, TRGameVersion version)

foreach (string ddsFile in Directory.GetFiles(ddsFolder))
{
using Pfim.IImage image = Pfim.Pfimage.FromFile(ddsFile);
GCHandle handle = GCHandle.Alloc(image.Data, GCHandleType.Pinned);
try
{
IntPtr data = Marshal.UnsafeAddrOfPinnedArrayElement(image.Data, 0);
Bitmap bitmap = new(image.Width, image.Height, image.Stride, PixelFormat.Format32bppArgb, data);
bitmap.Save(Path.Combine(dir, Path.ChangeExtension(Path.GetFileName(ddsFile), ".png")), ImageFormat.Png);
}
finally
{
handle.Free();
}
TRImage image = new(ddsFile);
image.Save(Path.Combine(dir, Path.ChangeExtension(Path.GetFileName(ddsFile), ".png")));
}
}

Expand Down

0 comments on commit 9051247

Please sign in to comment.