diff --git a/Penumbra.Api b/Penumbra.Api index 552246e5..a38e9bcf 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 552246e595ffab2aaba2c75f578d564f8938fc9a +Subproject commit a38e9bcfb80c456102945bbb4c59f5621cae0442 diff --git a/Penumbra/Api/Api/EditingApi.cs b/Penumbra/Api/Api/EditingApi.cs index 93345053..e50b7a1b 100644 --- a/Penumbra/Api/Api/EditingApi.cs +++ b/Penumbra/Api/Api/EditingApi.cs @@ -10,6 +10,7 @@ public class EditingApi(TextureManager textureManager) : IPenumbraApiEditing, IA => textureType switch { TextureType.Png => textureManager.SavePng(inputFile, outputFile), + TextureType.Targa => textureManager.SaveTga(inputFile, outputFile), TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, inputFile, outputFile), TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, inputFile, outputFile), TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, inputFile, outputFile), @@ -26,6 +27,7 @@ public class EditingApi(TextureManager textureManager) : IPenumbraApiEditing, IA => textureType switch { TextureType.Png => textureManager.SavePng(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), + TextureType.Targa => textureManager.SaveTga(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), diff --git a/Penumbra/Import/Textures/CombinedTexture.cs b/Penumbra/Import/Textures/CombinedTexture.cs index 98b87ac3..c1a22088 100644 --- a/Penumbra/Import/Textures/CombinedTexture.cs +++ b/Penumbra/Import/Textures/CombinedTexture.cs @@ -55,6 +55,14 @@ public partial class CombinedTexture : IDisposable SaveTask = textures.SavePng(_current.BaseImage, path, _current.RgbaPixels, _current.TextureWrap!.Width, _current.TextureWrap!.Height); } + public void SaveAsTarga(TextureManager textures, string path) + { + if (!IsLoaded || _current == null) + return; + + SaveTask = textures.SaveTga(_current.BaseImage, path, _current.RgbaPixels, _current.TextureWrap!.Width, _current.TextureWrap!.Height); + } + private void SaveAs(TextureManager textures, string path, TextureSaveType type, bool mipMaps, bool writeTex) { if (!IsLoaded || _current == null) @@ -72,6 +80,7 @@ public partial class CombinedTexture : IDisposable ".tex" => TextureType.Tex, ".dds" => TextureType.Dds, ".png" => TextureType.Png, + ".tga" => TextureType.Targa, _ => TextureType.Unknown, }; @@ -85,6 +94,9 @@ public partial class CombinedTexture : IDisposable break; case TextureType.Png: SaveAsPng(textures, path); + break; + case TextureType.Targa: + SaveAsTarga(textures, path); break; default: throw new ArgumentException( diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 996b5dbf..7118f8af 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -8,6 +8,7 @@ using OtterGui.Tasks; using OtterTex; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; using Image = SixLabors.ImageSharp.Image; @@ -33,10 +34,17 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur } public Task SavePng(string input, string output) - => Enqueue(new SavePngAction(this, input, output)); + => Enqueue(new SaveImageSharpAction(this, input, output, TextureType.Png)); public Task SavePng(BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) - => Enqueue(new SavePngAction(this, image, path, rgba, width, height)); + => Enqueue(new SaveImageSharpAction(this, image, path, TextureType.Png, rgba, width, height)); + + public Task SaveTga(string input, string output) + => Enqueue(new SaveImageSharpAction(this, input, output, TextureType.Targa)); + + public Task SaveTga(BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) + => Enqueue(new SaveImageSharpAction(this, image, path, TextureType.Targa, rgba, width, height)); + public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, string output) => Enqueue(new SaveAsAction(this, type, mipMaps, asTex, input, output)); @@ -66,44 +74,65 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur return t; } - private class SavePngAction : IAction + private class SaveImageSharpAction : IAction { private readonly TextureManager _textures; private readonly string _outputPath; private readonly ImageInputData _input; + private readonly TextureType _type; - public SavePngAction(TextureManager textures, string input, string output) + public SaveImageSharpAction(TextureManager textures, string input, string output, TextureType type) { _textures = textures; _input = new ImageInputData(input); _outputPath = output; + _type = type; + if (_type.ReduceToBehaviour() is not TextureType.Png) + throw new ArgumentOutOfRangeException(nameof(type), type, $"Can not save as {type} with ImageSharp."); } - public SavePngAction(TextureManager textures, BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) + public SaveImageSharpAction(TextureManager textures, BaseImage image, string path, TextureType type, byte[]? rgba = null, int width = 0, + int height = 0) { _textures = textures; _input = new ImageInputData(image, rgba, width, height); _outputPath = path; + _type = type; + if (_type.ReduceToBehaviour() is not TextureType.Png) + throw new ArgumentOutOfRangeException(nameof(type), type, $"Can not save as {type} with ImageSharp."); } public void Execute(CancellationToken cancel) { - _textures._logger.Information($"[{nameof(TextureManager)}] Saving {_input} as .png to {_outputPath}..."); + _textures._logger.Information($"[{nameof(TextureManager)}] Saving {_input} as {_type} to {_outputPath}..."); var (image, rgba, width, height) = _input.GetData(_textures); cancel.ThrowIfCancellationRequested(); - Image? png = null; + Image? data = null; if (image.Type is TextureType.Unknown) { if (rgba != null && width > 0 && height > 0) - png = ConvertToPng(rgba, width, height).AsPng!; + data = ConvertToPng(rgba, width, height).AsPng!; } else { - png = ConvertToPng(image, cancel, rgba).AsPng!; + data = ConvertToPng(image, cancel, rgba).AsPng!; } cancel.ThrowIfCancellationRequested(); - png?.SaveAsync(_outputPath, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }, cancel).Wait(cancel); + switch (_type) + { + case TextureType.Png: + data?.SaveAsync(_outputPath, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }, cancel) + .Wait(cancel); + return; + case TextureType.Targa: + data?.SaveAsync(_outputPath, new TgaEncoder() + { + Compression = TgaCompression.None, + BitsPerPixel = TgaBitsPerPixel.Pixel32, + }, cancel).Wait(cancel); + return; + } } public override string ToString() @@ -111,7 +140,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur public bool Equals(IAction? other) { - if (other is not SavePngAction rhs) + if (other is not SaveImageSharpAction rhs) return false; return string.Equals(_outputPath, rhs._outputPath, StringComparison.OrdinalIgnoreCase) && _input.Equals(rhs._input); @@ -168,9 +197,8 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur var imageTypeBehaviour = image.Type.ReduceToBehaviour(); var dds = _type switch { - CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Png => ConvertToRgbaDds(image, _mipMaps, - cancel, rgba, - width, height), + CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Png => ConvertToRgbaDds(image, _mipMaps, cancel, + rgba, width, height), CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Dds => AddMipMaps(image.AsDds!, _mipMaps), CombinedTexture.TextureSaveType.Bitmap => ConvertToRgbaDds(image, _mipMaps, cancel, rgba, width, height), CombinedTexture.TextureSaveType.BC3 => ConvertToCompressedDds(image, _mipMaps, false, cancel, rgba, width, height), diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index 67a27a0b..c08e8a8e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -85,7 +85,7 @@ public partial class ModEditWindow ImGuiUtil.SelectableHelpMarker(newDesc); } - } + } private void RedrawOnSaveBox() { @@ -128,7 +128,8 @@ public partial class ModEditWindow ? "This saves the texture in place. This is not revertible." : $"This saves the texture in place. This is not revertible. Hold {_config.DeleteModModifier} to save."; - var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var buttonSize3 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X * 2) / 3, 0); if (ImGuiUtil.DrawDisabledButton("Save in place", buttonSize2, tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) { @@ -141,17 +142,18 @@ public partial class ModEditWindow if (ImGui.Button("Save as TEX", buttonSize2)) OpenSaveAsDialog(".tex"); - if (ImGui.Button("Export as PNG", buttonSize2)) + if (ImGui.Button("Export as TGA", buttonSize3)) + OpenSaveAsDialog(".tga"); + ImGui.SameLine(); + if (ImGui.Button("Export as PNG", buttonSize3)) OpenSaveAsDialog(".png"); ImGui.SameLine(); - if (ImGui.Button("Export as DDS", buttonSize2)) + if (ImGui.Button("Export as DDS", buttonSize3)) OpenSaveAsDialog(".dds"); - ImGui.NewLine(); var canConvertInPlace = canSaveInPlace && _left.Type is TextureType.Tex && _center.IsLeftCopy; - var buttonSize3 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X * 2) / 3, 0); if (ImGuiUtil.DrawDisabledButton("Convert to BC7", buttonSize3, "This converts the texture to BC7 format in place. This is not revertible.", !canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) @@ -226,7 +228,8 @@ public partial class ModEditWindow private void OpenSaveAsDialog(string defaultExtension) { var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path); - _fileDialog.OpenSavePicker("Save Texture as TEX, DDS or PNG...", "Textures{.png,.dds,.tex},.tex,.dds,.png", fileName, defaultExtension, + _fileDialog.OpenSavePicker("Save Texture as TEX, DDS, PNG or TGA...", "Textures{.png,.dds,.tex,.tga},.tex,.dds,.png,.tga", fileName, + defaultExtension, (a, b) => { if (a)