Add a few texture manipulation tools.

This commit is contained in:
Exter-N 2023-08-25 06:28:09 +02:00
parent 3530e139d1
commit 42b874413d
4 changed files with 381 additions and 109 deletions

@ -1 +1 @@
Subproject commit 863d08bd83381bb7fe4a8d5c514f0ba55379336f Subproject commit c8394607addd29cb7f8ae3257f635a4486c40a63

View file

@ -6,23 +6,110 @@ using ImGuiNET;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui; using OtterGui;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Dalamud.Interface;
using Penumbra.UI;
namespace Penumbra.Import.Textures; namespace Penumbra.Import.Textures;
public partial class CombinedTexture 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 Matrix4x4 _multiplierLeft = Matrix4x4.Identity;
private Vector4 _constantLeft = Vector4.Zero;
private Matrix4x4 _multiplierRight = Matrix4x4.Identity; private Matrix4x4 _multiplierRight = Matrix4x4.Identity;
private bool _invertLeft = false; private Vector4 _constantRight = Vector4.Zero;
private bool _invertRight = false;
private int _offsetX = 0; 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<string> CombineOpLabels = new string[]
{
"Overlay over Input",
"Input over Overlay",
"Ignore Overlay",
"Replace Input",
"Copy Channels",
};
private static readonly IReadOnlyList<string> 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 ) private Vector4 DataLeft( int offset )
=> CappedVector( _left.RgbaPixels, offset, _multiplierLeft, _invertLeft ); => CappedVector( _left.RgbaPixels, offset, _multiplierLeft, _constantLeft );
private Vector4 DataRight( int offset ) private Vector4 DataRight( int offset )
=> CappedVector( _right.RgbaPixels, offset, _multiplierRight, _invertRight ); => CappedVector( _right.RgbaPixels, offset, _multiplierRight, _constantRight );
private Vector4 DataRight( int x, int y ) private Vector4 DataRight( int x, int y )
{ {
@ -34,7 +121,7 @@ public partial class CombinedTexture
} }
var offset = ( y * _right.TextureWrap!.Width + x ) * 4; 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 _ ) 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 _ ) private void MultiplyPixelsLeft( int y, ParallelLoopState _ )
{ {
for( var x = 0; x < _left.TextureWrap!.Width; ++x ) 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 ) for( var x = 0; x < _right.TextureWrap!.Width; ++x )
{ {
var offset = ( _right.TextureWrap!.Width * y + x ) * 4; var offset = ( _right.TextureWrap!.Width * y + x ) * 4;
var left = DataRight( offset ); var right = DataRight( offset );
var rgba = new Rgba32( left ); var rgba = new Rgba32( right );
_centerStorage.RgbaPixels[ offset ] = rgba.R; _centerStorage.RgbaPixels[ offset ] = rgba.R;
_centerStorage.RgbaPixels[ offset + 1 ] = rgba.G; _centerStorage.RgbaPixels[ offset + 1 ] = rgba.G;
_centerStorage.RgbaPixels[ offset + 2 ] = rgba.B; _centerStorage.RgbaPixels[ offset + 2 ] = rgba.B;
@ -86,23 +210,25 @@ public partial class CombinedTexture
private (int Width, int Height) CombineImage() 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 ) ? ( _left.TextureWrap!.Width, _left.TextureWrap!.Height )
: ( _right.TextureWrap!.Width, _right.TextureWrap!.Height ); : ( _right.TextureWrap!.Width, _right.TextureWrap!.Height );
_centerStorage.RgbaPixels = new byte[width * height * 4]; _centerStorage.RgbaPixels = new byte[width * height * 4];
_centerStorage.Type = TextureType.Bitmap; _centerStorage.Type = TextureType.Bitmap;
if( _left.IsLoaded ) Parallel.For( 0, height, combineOp switch
{ {
Parallel.For( 0, height, _right.IsLoaded ? AddPixelsMultiplied : MultiplyPixelsLeft ); CombineOp.Over => AddPixelsMultiplied,
} CombineOp.Under => ReverseAddPixelsMultiplied,
else CombineOp.LeftMultiply => MultiplyPixelsLeft,
{ CombineOp.RightMultiply => MultiplyPixelsRight,
Parallel.For( 0, height, MultiplyPixelsRight ); CombineOp.CopyChannels => ChannelMergePixelsMultiplied,
} _ => throw new InvalidOperationException( $"Cannot combine images with operation {combineOp}" ),
} );
return ( width, height ); 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 ) 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 rgba = new Rgba32( bytes[ offset ], bytes[ offset + 1 ], bytes[ offset + 2 ], bytes[ offset + 3 ] );
var transformed = Vector4.Transform( rgba.ToVector4(), transform ); var transformed = Vector4.Transform( rgba.ToVector4(), transform ) + constant;
if( invert )
{
transformed = new Vector4( 1 - transformed.X, 1 - transformed.Y, 1 - transformed.Z, transformed.W );
}
transformed.X = Math.Clamp( transformed.X, 0, 1 ); transformed.X = Math.Clamp( transformed.X, 0, 1 );
transformed.Y = Math.Clamp( transformed.Y, 0, 1 ); transformed.Y = Math.Clamp( transformed.Y, 0, 1 );
@ -138,8 +260,8 @@ public partial class CombinedTexture
public void DrawMatrixInputLeft( float width ) public void DrawMatrixInputLeft( float width )
{ {
var ret = DrawMatrixInput( ref _multiplierLeft, width ); var ret = DrawMatrixInput( ref _multiplierLeft, ref _constantLeft, width );
ret |= ImGui.Checkbox( "Invert Colors##Left", ref _invertLeft ); ret |= DrawMatrixTools( ref _multiplierLeft, ref _constantLeft );
if( ret ) if( ret )
{ {
Update(); Update();
@ -148,23 +270,56 @@ public partial class CombinedTexture
public void DrawMatrixInputRight( float width ) public void DrawMatrixInputRight( float width )
{ {
var ret = DrawMatrixInput( ref _multiplierRight, width ); var ret = DrawMatrixInput( ref _multiplierRight, ref _constantRight, width );
ret |= ImGui.Checkbox( "Invert Colors##Right", ref _invertRight ); ret |= DrawMatrixTools( ref _multiplierRight, ref _constantRight );
ImGui.SameLine(); ImGui.SetNextItemWidth( 75.0f * UiHelpers.Scale );
ImGui.SetNextItemWidth( 75 );
ImGui.DragInt( "##XOffset", ref _offsetX, 0.5f ); ImGui.DragInt( "##XOffset", ref _offsetX, 0.5f );
ret |= ImGui.IsItemDeactivatedAfterEdit(); ret |= ImGui.IsItemDeactivatedAfterEdit();
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetNextItemWidth( 75 ); ImGui.SetNextItemWidth( 75.0f * UiHelpers.Scale );
ImGui.DragInt( "Offsets##YOffset", ref _offsetY, 0.5f ); ImGui.DragInt( "Offsets##YOffset", ref _offsetY, 0.5f );
ret |= ImGui.IsItemDeactivatedAfterEdit(); 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<CombineOp>() )
{
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<Channels>() )
{
ImGui.SameLine();
var copy = ( _copyChannels & channel ) != 0;
if( ImGui.Checkbox( channel.ToString(), ref copy ) )
{
_copyChannels = copy ? ( _copyChannels | channel ) : ( _copyChannels & ~channel );
ret = true;
}
}
}
if( ret ) if( ret )
{ {
Update(); 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 ); using var table = ImRaii.Table( string.Empty, 5, ImGuiTableFlags.BordersInner | ImGuiTableFlags.SizingFixedFit );
if( !table ) if( !table )
@ -217,6 +372,110 @@ public partial class CombinedTexture
changes |= DragFloat( "##AB", inputWidth, ref multiplier.M43 ); changes |= DragFloat( "##AB", inputWidth, ref multiplier.M43 );
changes |= DragFloat( "##AA", inputWidth, ref multiplier.M44 ); 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; 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;
}
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.IO;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -68,6 +69,38 @@ public partial class CombinedTexture : IDisposable
_current.TextureWrap!.Height); _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) public void SaveAsTex(TextureManager textures, string path, TextureSaveType type, bool mipMaps)
=> SaveAs(textures, path, type, mipMaps, true); => SaveAs(textures, path, type, mipMaps, true);
@ -97,36 +130,22 @@ public partial class CombinedTexture : IDisposable
public void Update() public void Update()
{ {
Clean(); Clean();
if (_left.IsLoaded) switch (GetActualCombineOp())
{ {
if (_right.IsLoaded) case CombineOp.Invalid:
{ break;
_current = _centerStorage; case CombineOp.LeftCopy:
_mode = Mode.Custom;
}
else if (!_invertLeft && _multiplierLeft.IsIdentity)
{
_mode = Mode.LeftCopy; _mode = Mode.LeftCopy;
_current = _left; _current = _left;
} break;
else case CombineOp.RightCopy:
{
_current = _centerStorage;
_mode = Mode.Custom;
}
}
else if (_right.IsLoaded)
{
if (!_invertRight && _multiplierRight.IsIdentity)
{
_current = _right;
_mode = Mode.RightCopy; _mode = Mode.RightCopy;
} _current = _right;
else break;
{ default:
_current = _centerStorage;
_mode = Mode.Custom; _mode = Mode.Custom;
} _current = _centerStorage;
break;
} }
} }

View file

@ -90,7 +90,7 @@ public partial class ModEditWindow
if (ImGui.Selectable(newText, idx == _currentSaveAs)) if (ImGui.Selectable(newText, idx == _currentSaveAs))
_currentSaveAs = idx; _currentSaveAs = idx;
ImGuiUtil.HoverTooltip(newDesc); ImGuiUtil.SelectableHelpMarker(newDesc);
} }
} }
@ -114,73 +114,65 @@ public partial class ModEditWindow
SaveAsCombo(); SaveAsCombo();
ImGui.SameLine(); ImGui.SameLine();
MipMapInput(); 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); _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
_fileDialog.OpenSavePicker("Save Texture as TEX...", ".tex", fileName, ".tex", (a, b) => AddReloadTask(_left.Path, false);
{
if (a)
_center.SaveAsTex(_textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
}, _mod!.ModPath.FullName, _forceTextureStartPath);
_forceTextureStartPath = 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); var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path);
_fileDialog.OpenSavePicker("Save Texture as DDS...", ".dds", fileName, ".dds", (a, b) => _fileDialog.OpenSavePicker("Save Texture as TEX, DDS or PNG...", "Textures{.png,.dds,.tex},.tex,.dds,.png", fileName, ".tex", (a, b) =>
{ {
if (a) 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); }, _mod!.ModPath.FullName, _forceTextureStartPath);
_forceTextureStartPath = false; _forceTextureStartPath = false;
} }
ImGui.NewLine(); 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); _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1);
_fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) => AddReloadTask(_left.Path, false);
{
if (a)
_center.SaveAsPng(_textures, b);
}, _mod!.ModPath.FullName, _forceTextureStartPath);
_forceTextureStartPath = 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); _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1);
if (ImGuiUtil.DrawDisabledButton("Convert to BC7", buttonSize, AddReloadTask(_left.Path, false);
"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);
}
} }
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); _center.Draw(_textures, imageSize);
} }
private void AddReloadTask(string path) private void AddReloadTask(string path, bool right)
{ {
_center.SaveTask.ContinueWith(t => _center.SaveTask.ContinueWith(t =>
{ {
if (!t.IsCompletedSuccessfully) if (!t.IsCompletedSuccessfully)
return; return;
if (_left.Path != path) var tex = right ? _right : _left;
if (tex.Path != path)
return; return;
_dalamud.Framework.RunOnFrameworkThread(() => _left.Reload(_textures)); _dalamud.Framework.RunOnFrameworkThread(() => tex.Reload(_textures));
}); });
} }