mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Fix some stuff, add Saving.
This commit is contained in:
parent
47f5e14972
commit
1f2d5246fe
5 changed files with 248 additions and 139 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue