This commit is contained in:
Ottermandias 2022-09-22 14:22:59 +02:00
parent aeb2e9facd
commit fe8f2e2fc5
3 changed files with 226 additions and 82 deletions

View file

@ -0,0 +1,140 @@
using System;
using System.IO;
using Lumina.Data.Files;
using Lumina.Extensions;
using OtterTex;
namespace Penumbra.Import.Textures;
public static class TexFileParser
{
public static ScratchImage Parse( Stream data )
{
using var r = new BinaryReader( data );
var header = r.ReadStructure< TexFile.TexHeader >();
var meta = header.ToTexMeta();
if( meta.Format == DXGIFormat.Unknown )
{
throw new Exception( $"Could not convert format {header.Format} to DXGI Format." );
}
if( meta.Dimension == TexDimension.Unknown )
{
throw new Exception( $"Could not obtain dimensionality from {header.Type}." );
}
var scratch = ScratchImage.Initialize( meta );
CopyData( scratch, r );
return scratch;
}
private static unsafe void CopyData( ScratchImage image, BinaryReader r )
{
fixed( byte* ptr = image.Pixels )
{
var span = new Span< byte >( ptr, image.Pixels.Length );
var readBytes = r.Read( span );
if( readBytes < image.Pixels.Length )
{
throw new Exception( $"Invalid data length {readBytes} < {image.Pixels.Length}." );
}
}
}
public static TexFile.TexHeader ToTexHeader( this TexMeta meta )
{
var ret = new TexFile.TexHeader()
{
Height = ( ushort )meta.Height,
Width = ( ushort )meta.Width,
Depth = ( ushort )Math.Max( meta.Depth, 1 ),
MipLevels = ( ushort )Math.Min(meta.MipLevels, 13),
Format = meta.Format.ToTexFormat(),
Type = meta.Dimension switch
{
_ when meta.IsCubeMap => TexFile.Attribute.TextureTypeCube,
TexDimension.Tex1D => TexFile.Attribute.TextureType1D,
TexDimension.Tex2D => TexFile.Attribute.TextureType2D,
TexDimension.Tex3D => TexFile.Attribute.TextureType3D,
_ => 0,
},
};
return ret;
}
public static TexMeta ToTexMeta( this TexFile.TexHeader header )
=> new()
{
Height = header.Height,
Width = header.Width,
Depth = Math.Max( header.Depth, ( ushort )1 ),
MipLevels = header.MipLevels,
ArraySize = 1,
Format = header.Format.ToDXGI(),
Dimension = header.Type.ToDimension(),
MiscFlags = header.Type.HasFlag( TexFile.Attribute.TextureTypeCube ) ? D3DResourceMiscFlags.TextureCube : 0,
MiscFlags2 = 0,
};
private static TexDimension ToDimension( this TexFile.Attribute attribute )
=> ( attribute & TexFile.Attribute.TextureTypeMask ) switch
{
TexFile.Attribute.TextureType1D => TexDimension.Tex1D,
TexFile.Attribute.TextureType2D => TexDimension.Tex2D,
TexFile.Attribute.TextureType3D => TexDimension.Tex3D,
_ => TexDimension.Unknown,
};
public static TexFile.TextureFormat ToTexFormat( this DXGIFormat format )
=> format switch
{
DXGIFormat.R8UNorm => TexFile.TextureFormat.L8,
DXGIFormat.A8UNorm => TexFile.TextureFormat.A8,
DXGIFormat.B4G4R4A4UNorm => TexFile.TextureFormat.B4G4R4A4,
DXGIFormat.B5G5R5A1UNorm => TexFile.TextureFormat.B5G5R5A1,
DXGIFormat.B8G8R8A8UNorm => TexFile.TextureFormat.B8G8R8A8,
DXGIFormat.B8G8R8X8UNorm => TexFile.TextureFormat.B8G8R8X8,
DXGIFormat.R32Float => TexFile.TextureFormat.R32F,
DXGIFormat.R16G16Float => TexFile.TextureFormat.R16G16F,
DXGIFormat.R32G32Float => TexFile.TextureFormat.R32G32F,
DXGIFormat.R16G16B16A16Float => TexFile.TextureFormat.R16G16B16A16F,
DXGIFormat.R32G32B32A32Float => TexFile.TextureFormat.R32G32B32A32F,
DXGIFormat.BC1UNorm => TexFile.TextureFormat.BC1,
DXGIFormat.BC2UNorm => TexFile.TextureFormat.BC2,
DXGIFormat.BC3UNorm => TexFile.TextureFormat.BC3,
DXGIFormat.BC5UNorm => TexFile.TextureFormat.BC5,
DXGIFormat.BC7UNorm => TexFile.TextureFormat.BC7,
DXGIFormat.R16G16B16A16Typeless => TexFile.TextureFormat.D16,
DXGIFormat.R24G8Typeless => TexFile.TextureFormat.D24S8,
DXGIFormat.R16Typeless => TexFile.TextureFormat.Shadow16,
_ => TexFile.TextureFormat.Unknown,
};
public static DXGIFormat ToDXGI( this TexFile.TextureFormat format )
=> format switch
{
TexFile.TextureFormat.L8 => DXGIFormat.R8UNorm,
TexFile.TextureFormat.A8 => DXGIFormat.A8UNorm,
TexFile.TextureFormat.B4G4R4A4 => DXGIFormat.B4G4R4A4UNorm,
TexFile.TextureFormat.B5G5R5A1 => DXGIFormat.B5G5R5A1UNorm,
TexFile.TextureFormat.B8G8R8A8 => DXGIFormat.B8G8R8A8UNorm,
TexFile.TextureFormat.B8G8R8X8 => DXGIFormat.B8G8R8X8UNorm,
TexFile.TextureFormat.R32F => DXGIFormat.R32Float,
TexFile.TextureFormat.R16G16F => DXGIFormat.R16G16Float,
TexFile.TextureFormat.R32G32F => DXGIFormat.R32G32Float,
TexFile.TextureFormat.R16G16B16A16F => DXGIFormat.R16G16B16A16Float,
TexFile.TextureFormat.R32G32B32A32F => DXGIFormat.R32G32B32A32Float,
TexFile.TextureFormat.BC1 => DXGIFormat.BC1UNorm,
TexFile.TextureFormat.BC2 => DXGIFormat.BC2UNorm,
TexFile.TextureFormat.BC3 => DXGIFormat.BC3UNorm,
TexFile.TextureFormat.BC5 => DXGIFormat.BC5UNorm,
TexFile.TextureFormat.BC7 => DXGIFormat.BC7UNorm,
TexFile.TextureFormat.D16 => DXGIFormat.R16G16B16A16Typeless,
TexFile.TextureFormat.D24S8 => DXGIFormat.R24G8Typeless,
TexFile.TextureFormat.Shadow16 => DXGIFormat.R16Typeless,
TexFile.TextureFormat.Shadow24 => DXGIFormat.R24G8Typeless,
_ => DXGIFormat.Unknown,
};
}

View file

@ -4,7 +4,6 @@ using System.IO;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Utility;
using ImGuiNET;
using ImGuiScene;
using Lumina.Data.Files;
@ -18,6 +17,66 @@ 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
@ -101,7 +160,7 @@ public sealed class Texture : IDisposable
ImGuiUtil.DrawTableColumn( "Format" );
ImGuiUtil.DrawTableColumn( t.Header.Format.ToString() );
ImGuiUtil.DrawTableColumn( "Mip Levels" );
ImGuiUtil.DrawTableColumn( t.Header.MipLevels.ToString()) ;
ImGuiUtil.DrawTableColumn( t.Header.MipLevels.ToString() );
ImGuiUtil.DrawTableColumn( "Data Size" );
ImGuiUtil.DrawTableColumn( $"{Functions.HumanReadableSize( t.ImageData.Length )} ({t.ImageData.Length} Bytes)" );
break;
@ -178,25 +237,36 @@ public sealed class Texture : IDisposable
private bool LoadTex()
{
Type = FileType.Tex;
var tex = System.IO.Path.IsPathRooted( Path )
? Dalamud.GameData.GameData.GetFileFromDisk< TexFile >( Path )
: Dalamud.GameData.GetFile< TexFile >( Path );
BaseImage = tex ?? throw new Exception( "Could not read .tex file." );
RGBAPixels = tex.GetRgbaImageData();
CreateTextureWrap( tex.Header.Width, tex.Header.Height );
using var stream = OpenTexStream();
var scratch = TexFileParser.Parse( stream );
BaseImage = scratch;
var rgba = scratch.GetRGBA( out var f ).ThrowIfError( f );
RGBAPixels = rgba.Pixels[ ..( f.Meta.Width * f.Meta.Height * f.Meta.Format.BitsPerPixel() / 8 ) ].ToArray();
CreateTextureWrap( scratch.Meta.Width, scratch.Meta.Height );
return true;
}
private Stream OpenTexStream()
{
if( System.IO.Path.IsPathRooted( Path ) )
{
return File.OpenRead( Path );
}
var file = Dalamud.GameData.GetFile( Path );
return file != null ? new MemoryStream( file.Data ) : throw new Exception( $"Unable to obtain \"{Path}\" from game files." );
}
private void CreateTextureWrap( int width, int height )
=> TextureWrap = Dalamud.PluginInterface.UiBuilder.LoadImageRaw( RGBAPixels, width, height, 4 );
private string? _tmpPath;
public void PathSelectBox( string label, string tooltip, IEnumerable<string> paths )
public void PathSelectBox( string label, string tooltip, IEnumerable< string > paths )
{
ImGui.SetNextItemWidth( -0.0001f );
var startPath = Path.Length > 0 ? Path : "Choose a modded texture here...";
using var combo = ImRaii.Combo( label, startPath );
var startPath = Path.Length > 0 ? Path : "Choose a modded texture here...";
using var combo = ImRaii.Combo( label, startPath );
if( combo )
{
foreach( var (path, idx) in paths.WithIndex() )
@ -208,13 +278,15 @@ public sealed class Texture : IDisposable
}
}
}
ImGuiUtil.HoverTooltip( tooltip );
}
public void PathInputBox( string label, string hint, string tooltip, string startPath, FileDialogManager manager )
{
_tmpPath ??= Path;
using var spacing = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( 3 * ImGuiHelpers.GlobalScale, ImGui.GetStyle().ItemSpacing.Y ) );
using var spacing = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing,
new Vector2( 3 * ImGuiHelpers.GlobalScale, ImGui.GetStyle().ItemSpacing.Y ) );
ImGui.SetNextItemWidth( -ImGui.GetFrameHeight() - 3 * ImGuiHelpers.GlobalScale );
ImGui.InputTextWithHint( label, hint, ref _tmpPath, Utf8GamePath.MaxGamePathLength );
if( ImGui.IsItemDeactivatedAfterEdit() )
@ -245,72 +317,4 @@ public sealed class Texture : IDisposable
manager.OpenFileDialog( "Open Image...", "Textures{.png,.dds,.tex}", UpdatePath, 1, startPath );
}
}
}
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;
}
private static TexFile.TextureFormat GetFormat( this TexMeta meta )
{
return meta.Format switch
{
_ => TexFile.TextureFormat.Unknown,
};
}
}

View file

@ -75,7 +75,7 @@ public partial class Mod
private List< FileRegistry > _availableFiles = null!;
private List< FileRegistry > _mtrlFiles = null!;
private List< FileRegistry > _mdlFiles = null!;
private List<FileRegistry> _texFiles = null!;
private List< FileRegistry > _texFiles = null!;
private readonly HashSet< Utf8GamePath > _usedPaths = new();
// All paths that are used in
@ -90,7 +90,7 @@ public partial class Mod
public IReadOnlyList< FileRegistry > MdlFiles
=> _mdlFiles;
public IReadOnlyList<FileRegistry> TexFiles
public IReadOnlyList< FileRegistry > TexFiles
=> _texFiles;
// Remove all path redirections where the pointed-to file does not exist.