Fix some stuff, add Saving.

This commit is contained in:
Ottermandias 2022-09-23 15:21:30 +02:00
parent 47f5e14972
commit 1f2d5246fe
5 changed files with 248 additions and 139 deletions

View file

@ -46,13 +46,9 @@ public partial class CombinedTexture
var left = DataLeft( offset );
var right = DataRight( x, y );
var alpha = right.W + left.W * ( 1 - right.W );
if( alpha == 0 )
{
return;
}
var sum = ( right * right.W + left * left.W * ( 1 - right.W ) ) / alpha;
var rgba = new Rgba32( sum with { W = alpha } );
var rgba = alpha == 0
? new Rgba32()
: new Rgba32( ( ( right * right.W + left * left.W * ( 1 - right.W ) ) / alpha ) with { W = alpha } );
_centerStorage.RGBAPixels[ offset ] = rgba.R;
_centerStorage.RGBAPixels[ offset + 1 ] = rgba.G;
_centerStorage.RGBAPixels[ offset + 2 ] = rgba.B;

View file

@ -1,5 +1,7 @@
using System;
using System.IO;
using System.Numerics;
using Lumina.Data.Files;
using OtterTex;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
@ -10,6 +12,14 @@ namespace Penumbra.Import.Textures;
public partial class CombinedTexture : IDisposable
{
public enum TextureSaveType
{
AsIs,
Bitmap,
BC5,
BC7,
}
private enum Mode
{
Empty,
@ -29,6 +39,8 @@ public partial class CombinedTexture : IDisposable
public bool IsLoaded
=> _mode != Mode.Empty;
public Exception? SaveException { get; private set; } = null;
public void Draw( Vector2 size )
{
if( _mode == Mode.Custom && !_centerStorage.IsLoaded )
@ -44,81 +56,123 @@ public partial class CombinedTexture : IDisposable
public void SaveAsPng( string path )
{
if( !IsLoaded || _current == null )
if( !IsLoaded || _current == null )
{
return;
}
var image = Image.LoadPixelData< Rgba32 >( _current.RGBAPixels, _current.TextureWrap!.Width,
_current.TextureWrap!.Height );
image.Save( path, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression } );
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;
}
}
public void SaveAsDDS( string path, DXGIFormat format, bool fast, float threshold = 0.5f )
private void SaveAs( string path, TextureSaveType type, bool mipMaps, bool writeTex )
{
if( _current == null )
return;
switch( _mode )
if( _current == null || _mode == Mode.Empty )
{
case Mode.Empty: return;
case Mode.LeftCopy:
case Mode.RightCopy:
if( _centerStorage.BaseImage is ScratchImage s )
{
if( format != s.Meta.Format )
{
s = s.Convert( format, threshold );
}
return;
}
s.SaveDDS( path );
}
else
{
var image = ScratchImage.FromRGBA( _current.RGBAPixels, _current.TextureWrap!.Width,
_current.TextureWrap!.Height, out var i ).ThrowIfError( i );
image.SaveDDS( path ).ThrowIfError();
}
try
{
if( _current.BaseImage is not ScratchImage s )
{
s = ScratchImage.FromRGBA( _current.RGBAPixels, _current.TextureWrap!.Width,
_current.TextureWrap!.Height, out var i ).ThrowIfError( i );
}
break;
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 void SaveAs( bool success, string path, int type )
//{
// if( !success || _imageCenter == null || _wrapCenter == null )
// {
// return;
// }
//
// try
// {
// switch( type )
// {
// case 0:
// var img = Image.LoadPixelData< Rgba32 >( _imageCenter, _wrapCenter.Width, _wrapCenter.Height );
// img.Save( path, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression } );
// break;
// case 1:
// if( TextureImporter.RgbaBytesToTex( _imageCenter, _wrapCenter.Width, _wrapCenter.Height, out var tex ) )
// {
// File.WriteAllBytes( path, tex );
// }
//
// break;
// case 2:
// //ScratchImage.LoadDDS( _imageCenter, )
// //if( TextureImporter.RgbaBytesToDds( _imageCenter, _wrapCenter.Width, _wrapCenter.Height, out var dds ) )
////{
// // File.WriteAllBytes( path, dds );
////}
//
// break;
// }
// }
// catch( Exception e )
// {
// PluginLog.Error( $"Could not save image to {path}:\n{e}" );
// }
private static void SaveTex( string path, ScratchImage input )
{
var header = input.Meta.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.OpenWrite( path );
using var w = new BinaryWriter( stream );
header.Write( w );
w.Write( input.Pixels );
}
private static ScratchImage AddMipMaps( ScratchImage input, bool mipMaps )
=> mipMaps ? input.GenerateMipMaps() : 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.BC5UNorm;
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 )
{

View file

@ -43,6 +43,26 @@ public static class TexFileParser
}
}
public static void Write( this TexFile.TexHeader header, BinaryWriter w )
{
w.Write( ( uint )header.Type );
w.Write( ( uint )header.Format );
w.Write( header.Width );
w.Write( header.Height );
w.Write( header.Depth );
w.Write( header.MipLevels );
unsafe
{
w.Write( header.LodOffset[ 0 ] );
w.Write( header.LodOffset[ 1 ] );
w.Write( header.LodOffset[ 2 ] );
for( var i = 0; i < 13; ++i )
{
w.Write( header.OffsetToSurface[ i ] );
}
}
}
public static TexFile.TexHeader ToTexHeader( this TexMeta meta )
{
var ret = new TexFile.TexHeader()
@ -50,7 +70,7 @@ public static class TexFileParser
Height = ( ushort )meta.Height,
Width = ( ushort )meta.Width,
Depth = ( ushort )Math.Max( meta.Depth, 1 ),
MipLevels = ( ushort )Math.Min(meta.MipLevels, 13),
MipLevels = ( ushort )Math.Min( meta.MipLevels, 12 ),
Format = meta.Format.ToTexFormat(),
Type = meta.Dimension switch
{
@ -61,6 +81,31 @@ public static class TexFileParser
_ => 0,
},
};
unsafe
{
ret.LodOffset[ 0 ] = 0;
ret.LodOffset[ 1 ] = 1;
ret.LodOffset[ 2 ] = 2;
ret.OffsetToSurface[ 0 ] = 80;
var size = meta.Format.BitsPerPixel() * meta.Width * meta.Height / 8;
for( var i = 1; i < meta.MipLevels; ++i )
{
ret.OffsetToSurface[ i ] = ( uint )( 80 + size );
size >>= 2;
if( size == 0 )
{
ret.MipLevels = ( ushort )i;
break;
}
}
for( var i = ret.MipLevels; i < 13; ++i )
{
ret.OffsetToSurface[ i ] = 0;
}
}
return ret;
}

View file

@ -17,66 +17,6 @@ using Image = SixLabors.ImageSharp.Image;
namespace Penumbra.Import.Textures;
//public static class ScratchImageExtensions
//{
// public static Exception? SaveAsTex( this ScratchImage image, string path )
// {
// try
// {
// using var fileStream = File.OpenWrite( path );
// using var bw = new BinaryWriter( fileStream );
//
// bw.Write( ( uint )image.Meta.GetAttribute() );
// bw.Write( ( uint )image.Meta.GetFormat() );
// bw.Write( ( ushort )image.Meta.Width );
// bw.Write( ( ushort )image.Meta.Height );
// bw.Write( ( ushort )image.Meta.Depth );
// bw.Write( ( ushort )image.Meta.MipLevels );
// }
// catch( Exception e )
// {
// return e;
// }
//
// return null;
// }
//
// public static unsafe TexFile.TexHeader ToTexHeader( this ScratchImage image )
// {
// var ret = new TexFile.TexHeader()
// {
// Type = image.Meta.GetAttribute(),
// Format = image.Meta.GetFormat(),
// Width = ( ushort )image.Meta.Width,
// Height = ( ushort )image.Meta.Height,
// Depth = ( ushort )image.Meta.Depth,
// };
// ret.LodOffset[0] = 0;
// ret.LodOffset[1] = 1;
// ret.LodOffset[2] = 2;
// //foreach(var surface in image.Images)
// // ret.OffsetToSurface[ 0 ] = 80 + (image.P);
// return ret;
// }
//
// // Get all known flags for the TexFile.Attribute from the scratch image.
// private static TexFile.Attribute GetAttribute( this TexMeta meta )
// {
// var ret = meta.Dimension switch
// {
// TexDimension.Tex1D => TexFile.Attribute.TextureType1D,
// TexDimension.Tex2D => TexFile.Attribute.TextureType2D,
// TexDimension.Tex3D => TexFile.Attribute.TextureType3D,
// _ => ( TexFile.Attribute )0,
// };
// if( meta.IsCubeMap )
// ret |= TexFile.Attribute.TextureTypeCube;
// if( meta.Format.IsDepthStencil() )
// ret |= TexFile.Attribute.TextureDepthStencil;
// return ret;
// }
//}
public sealed class Texture : IDisposable
{
public enum FileType
@ -112,9 +52,6 @@ public sealed class Texture : IDisposable
public bool IsLoaded
=> TextureWrap != null;
public Texture()
{ }
public void Draw( Vector2 size )
{
if( TextureWrap != null )
@ -195,12 +132,15 @@ public sealed class Texture : IDisposable
Clean();
try
{
if( !File.Exists( path ) )
throw new FileNotFoundException();
var _ = System.IO.Path.GetExtension( Path ) switch
{
".dds" => LoadDds(),
".png" => LoadPng(),
".tex" => LoadTex(),
_ => true,
_ => throw new Exception($"Extension {System.IO.Path.GetExtension( Path )} unknown."),
};
Loaded?.Invoke( true );
}

View file

@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using ImGuiNET;
using OtterGui;
@ -19,7 +20,20 @@ public partial class ModEditWindow
private readonly FileDialogManager _dialogManager = ConfigWindow.SetupFileManager();
private bool _overlayCollapsed = true;
private DXGIFormat _currentFormat = DXGIFormat.R8G8B8A8UNorm;
private bool _addMipMaps = true;
private int _currentSaveAs = 0;
private static readonly (string, string)[] SaveAsStrings =
{
( "As Is", "Save the current texture with its own format without additional conversion or compression, if possible." ),
( "RGBA (Uncompressed)",
"Save the current texture as an uncompressed BGRA bitmap. This requires the most space but technically offers the best quality." ),
( "BC3 (Simple Compression)",
"Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality." ),
( "BC7 (Complex Compression)",
"Save the current texture compressed via BC7 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while." ),
};
private void DrawInputChild( string label, Texture tex, Vector2 size, Vector2 imageSize )
{
@ -37,7 +51,8 @@ public partial class ModEditWindow
_dialogManager );
var files = _editor!.TexFiles.Select( f => f.File.FullName )
.Concat( _editor.TexFiles.SelectMany( f => f.SubModUsage.Select( p => p.Item2.ToString() ) ) );
tex.PathSelectBox( "##combo", "Select the textures included in this mod on your drive or the ones they replace from the game files.", files);
tex.PathSelectBox( "##combo", "Select the textures included in this mod on your drive or the ones they replace from the game files.",
files );
if( tex == _left )
{
@ -52,6 +67,35 @@ public partial class ModEditWindow
tex.Draw( imageSize );
}
private void SaveAsCombo()
{
var (text, desc) = SaveAsStrings[ _currentSaveAs ];
ImGui.SetNextItemWidth( -ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X );
using var combo = ImRaii.Combo( "##format", text );
ImGuiUtil.HoverTooltip( desc );
if( !combo )
{
return;
}
foreach( var ((newText, newDesc), idx) in SaveAsStrings.WithIndex() )
{
if( ImGui.Selectable( newText, idx == _currentSaveAs ) )
{
_currentSaveAs = idx;
}
ImGuiUtil.HoverTooltip( newDesc );
}
}
private void MipMapInput()
{
ImGui.Checkbox( "##mipMaps", ref _addMipMaps );
ImGuiUtil.HoverTooltip(
"Add the appropriate number of MipMaps to the file." );
}
private void DrawOutputChild( Vector2 size, Vector2 imageSize )
{
using var child = ImRaii.Child( "Output", size, true );
@ -62,27 +106,57 @@ public partial class ModEditWindow
if( _center.IsLoaded )
{
SaveAsCombo();
ImGui.SameLine();
MipMapInput();
if( ImGui.Button( "Save as TEX", -Vector2.UnitX ) )
{
var fileName = Path.GetFileNameWithoutExtension( _left.Path.Length > 0 ? _left.Path : _right.Path );
_dialogManager.SaveFileDialog( "Save Texture as TEX...", ".tex", fileName, ".tex", ( a, b ) => { }, _mod!.ModPath.FullName );
_dialogManager.SaveFileDialog( "Save Texture as TEX...", ".tex", fileName, ".tex", ( a, b ) =>
{
if( a )
{
_center.SaveAsTex( b, ( CombinedTexture.TextureSaveType )_currentSaveAs, _addMipMaps );
}
}, _mod!.ModPath.FullName );
}
if( ImGui.Button( "Save as DDS", -Vector2.UnitX ) )
{
var fileName = Path.GetFileNameWithoutExtension( _right.Path.Length > 0 ? _right.Path : _left.Path );
_dialogManager.SaveFileDialog( "Save Texture as DDS...", ".dds", fileName, ".dds", ( a, b ) => { if( a ) _center.SaveAsDDS( b, _currentFormat, false ); }, _mod!.ModPath.FullName );
_dialogManager.SaveFileDialog( "Save Texture as DDS...", ".dds", fileName, ".dds", ( a, b ) =>
{
if( a )
{
_center.SaveAsDds( b, ( CombinedTexture.TextureSaveType )_currentSaveAs, _addMipMaps );
}
}, _mod!.ModPath.FullName );
}
ImGui.NewLine();
if( ImGui.Button( "Save as PNG", -Vector2.UnitX ) )
{
var fileName = Path.GetFileNameWithoutExtension( _right.Path.Length > 0 ? _right.Path : _left.Path );
_dialogManager.SaveFileDialog( "Save Texture as PNG...", ".png", fileName, ".png", ( a, b ) => { if (a) _center.SaveAsPng( b ); }, _mod!.ModPath.FullName );
_dialogManager.SaveFileDialog( "Save Texture as PNG...", ".png", fileName, ".png", ( a, b ) =>
{
if( a )
{
_center.SaveAsPng( b );
}
}, _mod!.ModPath.FullName );
}
ImGui.NewLine();
}
if( _center.SaveException != null )
{
ImGui.TextUnformatted( "Could not save file:" );
using var color = ImRaii.PushColor( ImGuiCol.Text, 0xFF0000FF );
ImGuiUtil.TextWrapped( _center.SaveException.ToString() );
}
_center.Draw( imageSize );
}