diff --git a/OtterGui b/OtterGui index 863d08bd..c8394607 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 863d08bd83381bb7fe4a8d5c514f0ba55379336f +Subproject commit c8394607addd29cb7f8ae3257f635a4486c40a63 diff --git a/Penumbra/Import/Textures/CombinedTexture.Manipulation.cs b/Penumbra/Import/Textures/CombinedTexture.Manipulation.cs index a32b9578..f8608071 100644 --- a/Penumbra/Import/Textures/CombinedTexture.Manipulation.cs +++ b/Penumbra/Import/Textures/CombinedTexture.Manipulation.cs @@ -6,23 +6,110 @@ using ImGuiNET; using OtterGui.Raii; using OtterGui; using SixLabors.ImageSharp.PixelFormats; +using Dalamud.Interface; +using Penumbra.UI; namespace Penumbra.Import.Textures; public partial class CombinedTexture { + private enum CombineOp + { + LeftCopy = -3, + RightCopy = -2, + Invalid = -1, + Over = 0, + Under = 1, + LeftMultiply = 2, + RightMultiply = 3, + CopyChannels = 4, + } + + [Flags] + private enum Channels + { + Red = 1, + Green = 2, + Blue = 4, + Alpha = 8, + } + private Matrix4x4 _multiplierLeft = Matrix4x4.Identity; + private Vector4 _constantLeft = Vector4.Zero; private Matrix4x4 _multiplierRight = Matrix4x4.Identity; - private bool _invertLeft = false; - private bool _invertRight = false; + private Vector4 _constantRight = Vector4.Zero; private int _offsetX = 0; - private int _offsetY = 0; + private int _offsetY = 0; + private CombineOp _combineOp = CombineOp.Over; + private Channels _copyChannels = Channels.Red | Channels.Green | Channels.Blue | Channels.Alpha; + + private static readonly IReadOnlyList CombineOpLabels = new string[] + { + "Overlay over Input", + "Input over Overlay", + "Ignore Overlay", + "Replace Input", + "Copy Channels", + }; + + private static readonly IReadOnlyList CombineOpTooltips = new string[] + { + "Standard composition.\nApply the overlay over the input.", + "Standard composition, reversed.\nApply the input over the overlay.", + "Use only the input, and ignore the overlay.", + "Completely replace the input with the overlay.", + "Replace some input channels with those from the overlay.\nUseful for Multi maps.", + }; + + private const float OneThird = 1.0f / 3.0f; + private const float RWeight = 0.2126f; + private const float GWeight = 0.7152f; + private const float BWeight = 0.0722f; + + private static readonly IReadOnlyList<(string Label, Matrix4x4 Multiplier, Vector4 Constant)> PredefinedColorTransforms = new (string, Matrix4x4, Vector4)[] + { + ("No Transform (Identity)", Matrix4x4.Identity, Vector4.Zero), + ("Grayscale (Average)", new Matrix4x4(OneThird, OneThird, OneThird, 0.0f, OneThird, OneThird, OneThird, 0.0f, OneThird, OneThird, OneThird, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), Vector4.Zero), + ("Grayscale (Weighted)", new Matrix4x4(RWeight, RWeight, RWeight, 0.0f, GWeight, GWeight, GWeight, 0.0f, BWeight, BWeight, BWeight, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), Vector4.Zero), + ("Grayscale (Average) to Alpha", new Matrix4x4(OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, OneThird, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.Zero), + ("Grayscale (Weighted) to Alpha", new Matrix4x4(RWeight, RWeight, RWeight, RWeight, GWeight, GWeight, GWeight, GWeight, BWeight, BWeight, BWeight, BWeight, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.Zero), + ("Extract Red", new Matrix4x4(1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.UnitW), + ("Extract Green", new Matrix4x4(0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.UnitW), + ("Extract Blue", new Matrix4x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), Vector4.UnitW), + ("Extract Alpha", new Matrix4x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f), Vector4.UnitW), + }; + + private CombineOp GetActualCombineOp() + { + var combineOp = (_left.IsLoaded, _right.IsLoaded) switch + { + (true, true) => _combineOp, + (true, false) => CombineOp.LeftMultiply, + (false, true) => CombineOp.RightMultiply, + (false, false) => CombineOp.Invalid, + }; + + if (combineOp == CombineOp.CopyChannels) + { + if (_copyChannels == 0) + combineOp = CombineOp.LeftMultiply; + else if (_copyChannels == (Channels.Red | Channels.Green | Channels.Blue | Channels.Alpha)) + combineOp = CombineOp.RightMultiply; + } + + return combineOp switch + { + CombineOp.LeftMultiply => (_multiplierLeft.IsIdentity && _constantLeft == Vector4.Zero) ? CombineOp.LeftCopy : CombineOp.LeftMultiply, + CombineOp.RightMultiply => (_multiplierRight.IsIdentity && _constantRight == Vector4.Zero) ? CombineOp.RightCopy : CombineOp.RightMultiply, + _ => combineOp, + }; + } private Vector4 DataLeft( int offset ) - => CappedVector( _left.RgbaPixels, offset, _multiplierLeft, _invertLeft ); + => CappedVector( _left.RgbaPixels, offset, _multiplierLeft, _constantLeft ); private Vector4 DataRight( int offset ) - => CappedVector( _right.RgbaPixels, offset, _multiplierRight, _invertRight ); + => CappedVector( _right.RgbaPixels, offset, _multiplierRight, _constantRight ); private Vector4 DataRight( int x, int y ) { @@ -34,7 +121,7 @@ public partial class CombinedTexture } var offset = ( y * _right.TextureWrap!.Width + x ) * 4; - return CappedVector( _right.RgbaPixels, offset, _multiplierRight, _invertRight ); + return CappedVector( _right.RgbaPixels, offset, _multiplierRight, _constantRight ); } private void AddPixelsMultiplied( int y, ParallelLoopState _ ) @@ -55,6 +142,43 @@ public partial class CombinedTexture } } + private void ReverseAddPixelsMultiplied( int y, ParallelLoopState _ ) + { + for( var x = 0; x < _left.TextureWrap!.Width; ++x ) + { + var offset = ( _left.TextureWrap!.Width * y + x ) * 4; + var left = DataLeft( offset ); + var right = DataRight( x, y ); + var alpha = left.W + right.W * ( 1 - left.W ); + var rgba = alpha == 0 + ? new Rgba32() + : new Rgba32( ( ( left * left.W + right * right.W * ( 1 - left.W ) ) / alpha ) with { W = alpha } ); + _centerStorage.RgbaPixels[ offset ] = rgba.R; + _centerStorage.RgbaPixels[ offset + 1 ] = rgba.G; + _centerStorage.RgbaPixels[ offset + 2 ] = rgba.B; + _centerStorage.RgbaPixels[ offset + 3 ] = rgba.A; + } + } + + private void ChannelMergePixelsMultiplied( int y, ParallelLoopState _ ) + { + var channels = _copyChannels; + for( var x = 0; x < _left.TextureWrap!.Width; ++x ) + { + var offset = ( _left.TextureWrap!.Width * y + x ) * 4; + var left = DataLeft( offset ); + var right = DataRight( x, y ); + var rgba = new Rgba32( ( channels & Channels.Red ) != 0 ? right.X : left.X, + ( channels & Channels.Green ) != 0 ? right.Y : left.Y, + ( channels & Channels.Blue ) != 0 ? right.Z : left.Z, + ( channels & Channels.Alpha ) != 0 ? right.W : left.W ); + _centerStorage.RgbaPixels[ offset ] = rgba.R; + _centerStorage.RgbaPixels[ offset + 1 ] = rgba.G; + _centerStorage.RgbaPixels[ offset + 2 ] = rgba.B; + _centerStorage.RgbaPixels[ offset + 3 ] = rgba.A; + } + } + private void MultiplyPixelsLeft( int y, ParallelLoopState _ ) { for( var x = 0; x < _left.TextureWrap!.Width; ++x ) @@ -74,8 +198,8 @@ public partial class CombinedTexture for( var x = 0; x < _right.TextureWrap!.Width; ++x ) { var offset = ( _right.TextureWrap!.Width * y + x ) * 4; - var left = DataRight( offset ); - var rgba = new Rgba32( left ); + var right = DataRight( offset ); + var rgba = new Rgba32( right ); _centerStorage.RgbaPixels[ offset ] = rgba.R; _centerStorage.RgbaPixels[ offset + 1 ] = rgba.G; _centerStorage.RgbaPixels[ offset + 2 ] = rgba.B; @@ -86,23 +210,25 @@ public partial class CombinedTexture private (int Width, int Height) CombineImage() { - var (width, height) = _left.IsLoaded + var combineOp = GetActualCombineOp(); + var (width, height) = combineOp is not CombineOp.Invalid or CombineOp.RightCopy or CombineOp.RightMultiply ? ( _left.TextureWrap!.Width, _left.TextureWrap!.Height ) : ( _right.TextureWrap!.Width, _right.TextureWrap!.Height ); _centerStorage.RgbaPixels = new byte[width * height * 4]; _centerStorage.Type = TextureType.Bitmap; - if( _left.IsLoaded ) + Parallel.For( 0, height, combineOp switch { - Parallel.For( 0, height, _right.IsLoaded ? AddPixelsMultiplied : MultiplyPixelsLeft ); - } - else - { - Parallel.For( 0, height, MultiplyPixelsRight ); - } + CombineOp.Over => AddPixelsMultiplied, + CombineOp.Under => ReverseAddPixelsMultiplied, + CombineOp.LeftMultiply => MultiplyPixelsLeft, + CombineOp.RightMultiply => MultiplyPixelsRight, + CombineOp.CopyChannels => ChannelMergePixelsMultiplied, + _ => throw new InvalidOperationException( $"Cannot combine images with operation {combineOp}" ), + } ); return ( width, height ); } - private static Vector4 CappedVector( IReadOnlyList< byte > bytes, int offset, Matrix4x4 transform, bool invert ) + private static Vector4 CappedVector( IReadOnlyList< byte > bytes, int offset, Matrix4x4 transform, Vector4 constant ) { if( bytes.Count == 0 ) { @@ -110,11 +236,7 @@ public partial class CombinedTexture } var rgba = new Rgba32( bytes[ offset ], bytes[ offset + 1 ], bytes[ offset + 2 ], bytes[ offset + 3 ] ); - var transformed = Vector4.Transform( rgba.ToVector4(), transform ); - if( invert ) - { - transformed = new Vector4( 1 - transformed.X, 1 - transformed.Y, 1 - transformed.Z, transformed.W ); - } + var transformed = Vector4.Transform( rgba.ToVector4(), transform ) + constant; transformed.X = Math.Clamp( transformed.X, 0, 1 ); transformed.Y = Math.Clamp( transformed.Y, 0, 1 ); @@ -138,8 +260,8 @@ public partial class CombinedTexture public void DrawMatrixInputLeft( float width ) { - var ret = DrawMatrixInput( ref _multiplierLeft, width ); - ret |= ImGui.Checkbox( "Invert Colors##Left", ref _invertLeft ); + var ret = DrawMatrixInput( ref _multiplierLeft, ref _constantLeft, width ); + ret |= DrawMatrixTools( ref _multiplierLeft, ref _constantLeft ); if( ret ) { Update(); @@ -148,23 +270,56 @@ public partial class CombinedTexture public void DrawMatrixInputRight( float width ) { - var ret = DrawMatrixInput( ref _multiplierRight, width ); - ret |= ImGui.Checkbox( "Invert Colors##Right", ref _invertRight ); - ImGui.SameLine(); - ImGui.SetNextItemWidth( 75 ); + var ret = DrawMatrixInput( ref _multiplierRight, ref _constantRight, width ); + ret |= DrawMatrixTools( ref _multiplierRight, ref _constantRight ); + ImGui.SetNextItemWidth( 75.0f * UiHelpers.Scale ); ImGui.DragInt( "##XOffset", ref _offsetX, 0.5f ); ret |= ImGui.IsItemDeactivatedAfterEdit(); ImGui.SameLine(); - ImGui.SetNextItemWidth( 75 ); + ImGui.SetNextItemWidth( 75.0f * UiHelpers.Scale ); ImGui.DragInt( "Offsets##YOffset", ref _offsetY, 0.5f ); ret |= ImGui.IsItemDeactivatedAfterEdit(); + ImGui.SetNextItemWidth( 200.0f * UiHelpers.Scale ); + using( var c = ImRaii.Combo( "Combine Operation", CombineOpLabels[ (int)_combineOp ] ) ) + { + if( c ) + { + foreach( var op in Enum.GetValues() ) + { + if ( (int)op < 0 ) // Negative codes are for internal use only. + continue; + + if( ImGui.Selectable( CombineOpLabels[ (int)op ], op == _combineOp ) ) + { + _combineOp = op; + ret = true; + } + + ImGuiUtil.SelectableHelpMarker( CombineOpTooltips[ (int)op ] ); + } + } + } + using( var dis = ImRaii.Disabled( _combineOp != CombineOp.CopyChannels )) + { + ImGui.TextUnformatted( "Copy" ); + foreach( var channel in Enum.GetValues() ) + { + ImGui.SameLine(); + var copy = ( _copyChannels & channel ) != 0; + if( ImGui.Checkbox( channel.ToString(), ref copy ) ) + { + _copyChannels = copy ? ( _copyChannels | channel ) : ( _copyChannels & ~channel ); + ret = true; + } + } + } if( ret ) { Update(); } } - private static bool DrawMatrixInput( ref Matrix4x4 multiplier, float width ) + private static bool DrawMatrixInput( ref Matrix4x4 multiplier, ref Vector4 constant, float width ) { using var table = ImRaii.Table( string.Empty, 5, ImGuiTableFlags.BordersInner | ImGuiTableFlags.SizingFixedFit ); if( !table ) @@ -217,6 +372,110 @@ public partial class CombinedTexture changes |= DragFloat( "##AB", inputWidth, ref multiplier.M43 ); changes |= DragFloat( "##AA", inputWidth, ref multiplier.M44 ); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.Text( "1 " ); + changes |= DragFloat( "##1R", inputWidth, ref constant.X ); + changes |= DragFloat( "##1G", inputWidth, ref constant.Y ); + changes |= DragFloat( "##1B", inputWidth, ref constant.Z ); + changes |= DragFloat( "##1A", inputWidth, ref constant.W ); + return changes; } + + private static bool DrawMatrixTools( ref Matrix4x4 multiplier, ref Vector4 constant ) + { + var changes = false; + + using( var combo = ImRaii.Combo( "Presets", string.Empty, ImGuiComboFlags.NoPreview ) ) + { + if( combo ) + { + foreach( var (label, preMultiplier, preConstant) in PredefinedColorTransforms ) + { + if( ImGui.Selectable( label, multiplier == preMultiplier && constant == preConstant ) ) + { + multiplier = preMultiplier; + constant = preConstant; + changes = true; + } + } + } + } + + ImGui.SameLine(); + ImGui.Dummy( ImGuiHelpers.ScaledVector2( 20, 0 ) ); + ImGui.SameLine(); + ImGui.TextUnformatted( "Invert" ); + ImGui.SameLine(); + if( ImGui.Button( "Colors" ) ) + { + InvertRed( ref multiplier, ref constant ); + InvertGreen( ref multiplier, ref constant ); + InvertBlue( ref multiplier, ref constant ); + changes = true; + } + ImGui.SameLine(); + if( ImGui.Button( "R" ) ) + { + InvertRed( ref multiplier, ref constant ); + changes = true; + } + ImGui.SameLine(); + if( ImGui.Button( "G" ) ) + { + InvertGreen( ref multiplier, ref constant ); + changes = true; + } + ImGui.SameLine(); + if( ImGui.Button( "B" ) ) + { + InvertBlue( ref multiplier, ref constant ); + changes = true; + } + ImGui.SameLine(); + if( ImGui.Button( "A" ) ) + { + InvertAlpha( ref multiplier, ref constant ); + changes = true; + } + + return changes; + } + + private static void InvertRed( ref Matrix4x4 multiplier, ref Vector4 constant ) + { + multiplier.M11 = -multiplier.M11; + multiplier.M21 = -multiplier.M21; + multiplier.M31 = -multiplier.M31; + multiplier.M41 = -multiplier.M41; + constant.X = 1.0f - constant.X; + } + + private static void InvertGreen( ref Matrix4x4 multiplier, ref Vector4 constant ) + { + multiplier.M12 = -multiplier.M12; + multiplier.M22 = -multiplier.M22; + multiplier.M32 = -multiplier.M32; + multiplier.M42 = -multiplier.M42; + constant.Y = 1.0f - constant.Y; + } + + private static void InvertBlue( ref Matrix4x4 multiplier, ref Vector4 constant ) + { + multiplier.M13 = -multiplier.M13; + multiplier.M23 = -multiplier.M23; + multiplier.M33 = -multiplier.M33; + multiplier.M43 = -multiplier.M43; + constant.Z = 1.0f - constant.Z; + } + + private static void InvertAlpha( ref Matrix4x4 multiplier, ref Vector4 constant ) + { + multiplier.M14 = -multiplier.M14; + multiplier.M24 = -multiplier.M24; + multiplier.M34 = -multiplier.M34; + multiplier.M44 = -multiplier.M44; + constant.W = 1.0f - constant.W; + } } \ No newline at end of file diff --git a/Penumbra/Import/Textures/CombinedTexture.cs b/Penumbra/Import/Textures/CombinedTexture.cs index c26cb900..14a8a41c 100644 --- a/Penumbra/Import/Textures/CombinedTexture.cs +++ b/Penumbra/Import/Textures/CombinedTexture.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Numerics; using System.Threading.Tasks; @@ -68,6 +69,38 @@ public partial class CombinedTexture : IDisposable _current.TextureWrap!.Height); } + public void SaveAs(TextureType? texType, TextureManager textures, string path, TextureSaveType type, bool mipMaps) + { + TextureType finalTexType; + if (texType.HasValue) + finalTexType = texType.Value; + else + { + finalTexType = Path.GetExtension(path).ToLowerInvariant() switch + { + ".tex" => TextureType.Tex, + ".dds" => TextureType.Dds, + ".png" => TextureType.Png, + _ => TextureType.Unknown, + }; + } + + switch (finalTexType) + { + case TextureType.Tex: + SaveAsTex(textures, path, type, mipMaps); + break; + case TextureType.Dds: + SaveAsDds(textures, path, type, mipMaps); + break; + case TextureType.Png: + SaveAsPng(textures, path); + break; + default: + throw new ArgumentException($"Cannot save texture as TextureType {finalTexType} with extension {Path.GetExtension(path).ToLowerInvariant()}"); + } + } + public void SaveAsTex(TextureManager textures, string path, TextureSaveType type, bool mipMaps) => SaveAs(textures, path, type, mipMaps, true); @@ -97,36 +130,22 @@ public partial class CombinedTexture : IDisposable public void Update() { Clean(); - if (_left.IsLoaded) + switch (GetActualCombineOp()) { - if (_right.IsLoaded) - { - _current = _centerStorage; - _mode = Mode.Custom; - } - else if (!_invertLeft && _multiplierLeft.IsIdentity) - { + case CombineOp.Invalid: + break; + case CombineOp.LeftCopy: _mode = Mode.LeftCopy; _current = _left; - } - else - { - _current = _centerStorage; - _mode = Mode.Custom; - } - } - else if (_right.IsLoaded) - { - if (!_invertRight && _multiplierRight.IsIdentity) - { - _current = _right; + break; + case CombineOp.RightCopy: _mode = Mode.RightCopy; - } - else - { - _current = _centerStorage; + _current = _right; + break; + default: _mode = Mode.Custom; - } + _current = _centerStorage; + break; } } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index 4d36ff8a..4cf3731d 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -90,7 +90,7 @@ public partial class ModEditWindow if (ImGui.Selectable(newText, idx == _currentSaveAs)) _currentSaveAs = idx; - ImGuiUtil.HoverTooltip(newDesc); + ImGuiUtil.SelectableHelpMarker(newDesc); } } @@ -114,73 +114,65 @@ public partial class ModEditWindow SaveAsCombo(); ImGui.SameLine(); MipMapInput(); - if (ImGui.Button("Save as TEX", -Vector2.UnitX)) + + var canSaveInPlace = Path.IsPathRooted(_left.Path) && _left.Type is TextureType.Tex or TextureType.Dds or TextureType.Png; + + var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + if (ImGuiUtil.DrawDisabledButton("Save in place", buttonSize2, + "This saves the texture in place. This is not revertible.", + !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) { - var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path); - _fileDialog.OpenSavePicker("Save Texture as TEX...", ".tex", fileName, ".tex", (a, b) => - { - if (a) - _center.SaveAsTex(_textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); - }, _mod!.ModPath.FullName, _forceTextureStartPath); - _forceTextureStartPath = false; + _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); + AddReloadTask(_left.Path, false); } - if (ImGui.Button("Save as DDS", -Vector2.UnitX)) + ImGui.SameLine(); + if (ImGui.Button("Save as TEX, DDS or PNG", buttonSize2)) { - var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); - _fileDialog.OpenSavePicker("Save Texture as DDS...", ".dds", fileName, ".dds", (a, b) => + 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, ".tex", (a, b) => { if (a) - _center.SaveAsDds(_textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); + { + _center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); + if (b == _left.Path) + AddReloadTask(_left.Path, false); + else if (b == _right.Path) + AddReloadTask(_right.Path, true); + } }, _mod!.ModPath.FullName, _forceTextureStartPath); _forceTextureStartPath = false; } ImGui.NewLine(); - if (ImGui.Button("Save as PNG", -Vector2.UnitX)) + 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)) { - var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path); - _fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => - { - if (a) - _center.SaveAsPng(_textures, b); - }, _mod!.ModPath.FullName, _forceTextureStartPath); - _forceTextureStartPath = false; + _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); + AddReloadTask(_left.Path, false); } - if (_left.Type is TextureType.Tex && _center.IsLeftCopy) + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton("Convert to BC3", buttonSize3, + "This converts the texture to BC3 format in place. This is not revertible.", + !canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB)) { - var buttonSize = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X * 2) / 3, 0); - if (ImGuiUtil.DrawDisabledButton("Convert to BC7", buttonSize, - "This converts the texture to BC7 format in place. This is not revertible.", - _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) - { - _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); - AddReloadTask(_left.Path); - } - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton("Convert to BC3", buttonSize, - "This converts the texture to BC3 format in place. This is not revertible.", - _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB)) - { - _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1); - AddReloadTask(_left.Path); - } - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton("Convert to RGBA", buttonSize, - "This converts the texture to RGBA format in place. This is not revertible.", - _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB)) - { - _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1); - AddReloadTask(_left.Path); - } + _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1); + AddReloadTask(_left.Path, false); } - else + + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton("Convert to RGBA", buttonSize3, + "This converts the texture to RGBA format in place. This is not revertible.", + !canConvertInPlace || _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB)) { - ImGui.NewLine(); + _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1); + AddReloadTask(_left.Path, false); } } @@ -212,17 +204,19 @@ public partial class ModEditWindow _center.Draw(_textures, imageSize); } - private void AddReloadTask(string path) + private void AddReloadTask(string path, bool right) { _center.SaveTask.ContinueWith(t => { if (!t.IsCompletedSuccessfully) return; - if (_left.Path != path) + var tex = right ? _right : _left; + + if (tex.Path != path) return; - _dalamud.Framework.RunOnFrameworkThread(() => _left.Reload(_textures)); + _dalamud.Framework.RunOnFrameworkThread(() => tex.Reload(_textures)); }); }