mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Blep
This commit is contained in:
parent
aeb2e9facd
commit
fe8f2e2fc5
3 changed files with 226 additions and 82 deletions
140
Penumbra/Import/Textures/TexFileParser.cs
Normal file
140
Penumbra/Import/Textures/TexFileParser.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue