mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Add a few texture manipulation tools.
This commit is contained in:
parent
3530e139d1
commit
42b874413d
4 changed files with 381 additions and 109 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 863d08bd83381bb7fe4a8d5c514f0ba55379336f
|
||||
Subproject commit c8394607addd29cb7f8ae3257f635a4486c40a63
|
||||
|
|
@ -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<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 )
|
||||
=> 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<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 )
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue