Penumbra/Penumbra/Import/Textures/CombinedTexture.cs
2022-09-29 13:00:43 +02:00

242 lines
No EOL
6.5 KiB
C#

using System;
using System.IO;
using System.Numerics;
using Lumina.Data.Files;
using OtterTex;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using Image = SixLabors.ImageSharp.Image;
namespace Penumbra.Import.Textures;
public partial class CombinedTexture : IDisposable
{
public enum TextureSaveType
{
AsIs,
Bitmap,
BC5,
BC7,
}
private enum Mode
{
Empty,
LeftCopy,
RightCopy,
Custom,
}
private readonly Texture _left;
private readonly Texture _right;
private Texture? _current;
private Mode _mode = Mode.Empty;
private readonly Texture _centerStorage = new();
public bool IsLoaded
=> _mode != Mode.Empty;
public Exception? SaveException { get; private set; } = null;
public void Draw( Vector2 size )
{
if( _mode == Mode.Custom && !_centerStorage.IsLoaded )
{
var (width, height) = CombineImage();
_centerStorage.TextureWrap =
Dalamud.PluginInterface.UiBuilder.LoadImageRaw( _centerStorage.RGBAPixels, width, height, 4 );
}
_current?.Draw( size );
}
public void SaveAsPng( string path )
{
if( !IsLoaded || _current == null )
{
return;
}
try
{
var image = Image.LoadPixelData< Rgba32 >( _current.RGBAPixels, _current.TextureWrap!.Width,
_current.TextureWrap!.Height );
image.Save( path, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression } );
SaveException = null;
}
catch( Exception e )
{
SaveException = e;
}
}
private void SaveAs( string path, TextureSaveType type, bool mipMaps, bool writeTex )
{
if( _current == null || _mode == Mode.Empty )
{
return;
}
try
{
if( _current.BaseImage is not ScratchImage s )
{
s = ScratchImage.FromRGBA( _current.RGBAPixels, _current.TextureWrap!.Width,
_current.TextureWrap!.Height, out var i ).ThrowIfError( i );
}
var tex = type switch
{
TextureSaveType.AsIs => _current.Type is Texture.FileType.Bitmap or Texture.FileType.Png ? CreateUncompressed( s, mipMaps ) : s,
TextureSaveType.Bitmap => CreateUncompressed( s, mipMaps ),
TextureSaveType.BC5 => CreateCompressed( s, mipMaps, false ),
TextureSaveType.BC7 => CreateCompressed( s, mipMaps, true ),
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ),
};
if( !writeTex )
{
tex.SaveDDS( path );
}
else
{
SaveTex( path, tex );
}
SaveException = null;
}
catch( Exception e )
{
SaveException = e;
}
}
private static void SaveTex( string path, ScratchImage input )
{
var header = input.ToTexHeader();
if( header.Format == TexFile.TextureFormat.Unknown )
{
throw new Exception( $"Could not save tex file with format {input.Meta.Format}, not convertible to a valid .tex formats." );
}
using var stream = File.Open( path, File.Exists(path) ? FileMode.Truncate : FileMode.CreateNew);
using var w = new BinaryWriter( stream );
header.Write( w );
w.Write( input.Pixels );
}
private static ScratchImage AddMipMaps( ScratchImage input, bool mipMaps )
=> mipMaps
? input.GenerateMipMaps( Math.Min( 13, 1 + BitOperations.Log2( ( uint )Math.Max( input.Meta.Width, input.Meta.Height ) ) ) )
: input;
private static ScratchImage CreateUncompressed( ScratchImage input, bool mipMaps )
{
if( input.Meta.Format == DXGIFormat.B8G8R8A8UNorm )
{
return AddMipMaps( input, mipMaps );
}
if( input.Meta.Format.IsCompressed() )
{
input = input.Decompress( DXGIFormat.B8G8R8A8UNorm );
}
else
{
input = input.Convert( DXGIFormat.B8G8R8A8UNorm );
}
return AddMipMaps( input, mipMaps );
}
private static ScratchImage CreateCompressed( ScratchImage input, bool mipMaps, bool bc7 )
{
var format = bc7 ? DXGIFormat.BC7UNorm : DXGIFormat.BC3UNorm;
if( input.Meta.Format == format )
{
return input;
}
if( input.Meta.Format.IsCompressed() )
{
input = input.Decompress( DXGIFormat.B8G8R8A8UNorm );
}
input = AddMipMaps( input, mipMaps );
return input.Compress( format, CompressFlags.BC7Quick | CompressFlags.Parallel );
}
public void SaveAsTex( string path, TextureSaveType type, bool mipMaps )
=> SaveAs( path, type, mipMaps, true );
public void SaveAsDds( string path, TextureSaveType type, bool mipMaps )
=> SaveAs( path, type, mipMaps, false );
public CombinedTexture( Texture left, Texture right )
{
_left = left;
_right = right;
_left.Loaded += OnLoaded;
_right.Loaded += OnLoaded;
OnLoaded( false );
}
public void Dispose()
{
Clean();
_left.Loaded -= OnLoaded;
_right.Loaded -= OnLoaded;
}
private void OnLoaded( bool _ )
=> Update();
public void Update()
{
Clean();
if( _left.IsLoaded )
{
if( _right.IsLoaded )
{
_current = _centerStorage;
_mode = Mode.Custom;
}
else if( !_invertLeft && _multiplierLeft.IsIdentity )
{
_mode = Mode.LeftCopy;
_current = _left;
}
else
{
_current = _centerStorage;
_mode = Mode.Custom;
}
}
else if( _right.IsLoaded )
{
if( !_invertRight && _multiplierRight.IsIdentity )
{
_current = _right;
_mode = Mode.RightCopy;
}
else
{
_current = _centerStorage;
_mode = Mode.Custom;
}
}
}
private void Clean()
{
_centerStorage.Dispose();
_current = null;
_mode = Mode.Empty;
}
}