More parsing, mostly untested.

This commit is contained in:
Ottermandias 2022-06-16 12:28:31 +02:00
parent cc9f8cc84e
commit 5f8eac0ec1
8 changed files with 1573 additions and 592 deletions

View file

@ -0,0 +1,322 @@
using System;
using System.Runtime.InteropServices;
namespace Penumbra.Import.Dds;
[StructLayout( LayoutKind.Sequential )]
public struct DXT10Header
{
public DXGIFormat Format;
public D3DResourceDimension ResourceDimension;
public D3DResourceMiscFlags MiscFlags;
public uint ArraySize;
public D3DAlphaMode AlphaMode;
public ParseType ToParseType()
{
return Format switch
{
DXGIFormat.BC1Typeless => ParseType.DXT1,
DXGIFormat.BC1UNorm => ParseType.DXT1,
DXGIFormat.BC1UNormSRGB => ParseType.DXT1,
DXGIFormat.BC2Typeless => ParseType.DXT3,
DXGIFormat.BC2UNorm => ParseType.DXT3,
DXGIFormat.BC2UNormSRGB => ParseType.DXT3,
DXGIFormat.BC3Typeless => ParseType.DXT5,
DXGIFormat.BC3UNorm => ParseType.DXT5,
DXGIFormat.BC3UNormSRGB => ParseType.DXT5,
DXGIFormat.BC4Typeless => ParseType.BC4,
DXGIFormat.BC4SNorm => ParseType.BC4,
DXGIFormat.BC4UNorm => ParseType.BC4,
DXGIFormat.BC5Typeless => ParseType.BC5,
DXGIFormat.BC5SNorm => ParseType.BC5,
DXGIFormat.BC5UNorm => ParseType.BC5,
DXGIFormat.R8G8B8A8Typeless => ParseType.R8G8B8A8,
DXGIFormat.R8G8B8A8UNorm => ParseType.R8G8B8A8,
DXGIFormat.R8G8B8A8UNormSRGB => ParseType.R8G8B8A8,
DXGIFormat.R8G8B8A8UInt => ParseType.R8G8B8A8,
DXGIFormat.R8G8B8A8SNorm => ParseType.R8G8B8A8,
DXGIFormat.R8G8B8A8SInt => ParseType.R8G8B8A8,
DXGIFormat.B8G8R8A8Typeless => ParseType.B8G8R8A8,
DXGIFormat.B8G8R8A8UNorm => ParseType.B8G8R8A8,
DXGIFormat.B8G8R8A8UNormSRGB => ParseType.B8G8R8A8,
DXGIFormat.B8G8R8X8Typeless => ParseType.B8G8R8A8,
DXGIFormat.B8G8R8X8UNorm => ParseType.B8G8R8A8,
DXGIFormat.B8G8R8X8UNormSRGB => ParseType.B8G8R8A8,
DXGIFormat.B4G4R4A4UNorm => ParseType.B4G4R4A4,
DXGIFormat.B5G5R5A1UNorm => ParseType.B5G5R5A1,
DXGIFormat.B5G6R5UNorm => ParseType.B5G6R5,
DXGIFormat.BC6HSF16 => ParseType.Unsupported,
DXGIFormat.BC6HTypeless => ParseType.Unsupported,
DXGIFormat.BC6HUF16 => ParseType.Unsupported,
DXGIFormat.BC7Typeless => ParseType.Unsupported,
DXGIFormat.BC7UNorm => ParseType.Unsupported,
DXGIFormat.BC7UNormSRGB => ParseType.Unsupported,
DXGIFormat.Unknown => ParseType.Unsupported,
DXGIFormat.R32G32B32A32Typeless => ParseType.Unsupported,
DXGIFormat.R32G32B32A32Float => ParseType.Unsupported,
DXGIFormat.R32G32B32A32UInt => ParseType.Unsupported,
DXGIFormat.R32G32B32A32SInt => ParseType.Unsupported,
DXGIFormat.R32G32B32Typeless => ParseType.Unsupported,
DXGIFormat.R32G32B32Float => ParseType.Unsupported,
DXGIFormat.R32G32B32UInt => ParseType.Unsupported,
DXGIFormat.R32G32B32SInt => ParseType.Unsupported,
DXGIFormat.R16G16B16A16Typeless => ParseType.Unsupported,
DXGIFormat.R16G16B16A16Float => ParseType.Unsupported,
DXGIFormat.R16G16B16A16UNorm => ParseType.Unsupported,
DXGIFormat.R16G16B16A16UInt => ParseType.Unsupported,
DXGIFormat.R16G16B16A16SNorm => ParseType.Unsupported,
DXGIFormat.R16G16B16A16SInt => ParseType.Unsupported,
DXGIFormat.R32G32Typeless => ParseType.Unsupported,
DXGIFormat.R32G32Float => ParseType.Unsupported,
DXGIFormat.R32G32UInt => ParseType.Unsupported,
DXGIFormat.R32G32SInt => ParseType.Unsupported,
DXGIFormat.R32G8X24Typeless => ParseType.Unsupported,
DXGIFormat.D32FloatS8X24UInt => ParseType.Unsupported,
DXGIFormat.R32FloatX8X24Typeless => ParseType.Unsupported,
DXGIFormat.X32TypelessG8X24UInt => ParseType.Unsupported,
DXGIFormat.R10G10B10A2Typeless => ParseType.Unsupported,
DXGIFormat.R10G10B10A2UNorm => ParseType.Unsupported,
DXGIFormat.R10G10B10A2UInt => ParseType.Unsupported,
DXGIFormat.R11G11B10Float => ParseType.Unsupported,
DXGIFormat.R16G16Typeless => ParseType.Unsupported,
DXGIFormat.R16G16Float => ParseType.Unsupported,
DXGIFormat.R16G16UNorm => ParseType.Unsupported,
DXGIFormat.R16G16UInt => ParseType.Unsupported,
DXGIFormat.R16G16SNorm => ParseType.Unsupported,
DXGIFormat.R16G16SInt => ParseType.Unsupported,
DXGIFormat.R32Typeless => ParseType.Unsupported,
DXGIFormat.D32Float => ParseType.Unsupported,
DXGIFormat.R32Float => ParseType.Unsupported,
DXGIFormat.R32UInt => ParseType.Unsupported,
DXGIFormat.R32SInt => ParseType.Unsupported,
DXGIFormat.R24G8Typeless => ParseType.Unsupported,
DXGIFormat.D24UNormS8UInt => ParseType.Unsupported,
DXGIFormat.R24UNormX8Typeless => ParseType.Unsupported,
DXGIFormat.X24TypelessG8UInt => ParseType.Unsupported,
DXGIFormat.R8G8Typeless => ParseType.Unsupported,
DXGIFormat.R8G8UNorm => ParseType.Unsupported,
DXGIFormat.R8G8UInt => ParseType.Unsupported,
DXGIFormat.R8G8SNorm => ParseType.Unsupported,
DXGIFormat.R8G8SInt => ParseType.Unsupported,
DXGIFormat.R16Typeless => ParseType.Unsupported,
DXGIFormat.R16Float => ParseType.Unsupported,
DXGIFormat.D16UNorm => ParseType.Unsupported,
DXGIFormat.R16UNorm => ParseType.Unsupported,
DXGIFormat.R16UInt => ParseType.Unsupported,
DXGIFormat.R16SNorm => ParseType.Unsupported,
DXGIFormat.R16SInt => ParseType.Unsupported,
DXGIFormat.R8Typeless => ParseType.Unsupported,
DXGIFormat.R8UNorm => ParseType.Unsupported,
DXGIFormat.R8UInt => ParseType.Unsupported,
DXGIFormat.R8SNorm => ParseType.Unsupported,
DXGIFormat.R8SInt => ParseType.Unsupported,
DXGIFormat.A8UNorm => ParseType.Unsupported,
DXGIFormat.R1UNorm => ParseType.Unsupported,
DXGIFormat.R9G9B9E5SharedEXP => ParseType.Unsupported,
DXGIFormat.R8G8B8G8UNorm => ParseType.Unsupported,
DXGIFormat.G8R8G8B8UNorm => ParseType.Unsupported,
DXGIFormat.R10G10B10XRBiasA2UNorm => ParseType.Unsupported,
DXGIFormat.AYUV => ParseType.Unsupported,
DXGIFormat.Y410 => ParseType.Unsupported,
DXGIFormat.Y416 => ParseType.Unsupported,
DXGIFormat.NV12 => ParseType.Unsupported,
DXGIFormat.P010 => ParseType.Unsupported,
DXGIFormat.P016 => ParseType.Unsupported,
DXGIFormat.F420Opaque => ParseType.Unsupported,
DXGIFormat.YUY2 => ParseType.Unsupported,
DXGIFormat.Y210 => ParseType.Unsupported,
DXGIFormat.Y216 => ParseType.Unsupported,
DXGIFormat.NV11 => ParseType.Unsupported,
DXGIFormat.AI44 => ParseType.Unsupported,
DXGIFormat.IA44 => ParseType.Unsupported,
DXGIFormat.P8 => ParseType.Unsupported,
DXGIFormat.A8P8 => ParseType.Unsupported,
DXGIFormat.P208 => ParseType.Unsupported,
DXGIFormat.V208 => ParseType.Unsupported,
DXGIFormat.V408 => ParseType.Unsupported,
DXGIFormat.SamplerFeedbackMinMipOpaque => ParseType.Unsupported,
DXGIFormat.SamplerFeedbackMipRegionUsedOpaque => ParseType.Unsupported,
DXGIFormat.ForceUInt => ParseType.Unsupported,
_ => ParseType.Unsupported,
};
}
public enum DXGIFormat : uint
{
Unknown = 0,
R32G32B32A32Typeless = 1,
R32G32B32A32Float = 2,
R32G32B32A32UInt = 3,
R32G32B32A32SInt = 4,
R32G32B32Typeless = 5,
R32G32B32Float = 6,
R32G32B32UInt = 7,
R32G32B32SInt = 8,
R16G16B16A16Typeless = 9,
R16G16B16A16Float = 10,
R16G16B16A16UNorm = 11,
R16G16B16A16UInt = 12,
R16G16B16A16SNorm = 13,
R16G16B16A16SInt = 14,
R32G32Typeless = 15,
R32G32Float = 16,
R32G32UInt = 17,
R32G32SInt = 18,
R32G8X24Typeless = 19,
D32FloatS8X24UInt = 20,
R32FloatX8X24Typeless = 21,
X32TypelessG8X24UInt = 22,
R10G10B10A2Typeless = 23,
R10G10B10A2UNorm = 24,
R10G10B10A2UInt = 25,
R11G11B10Float = 26,
R8G8B8A8Typeless = 27,
R8G8B8A8UNorm = 28,
R8G8B8A8UNormSRGB = 29,
R8G8B8A8UInt = 30,
R8G8B8A8SNorm = 31,
R8G8B8A8SInt = 32,
R16G16Typeless = 33,
R16G16Float = 34,
R16G16UNorm = 35,
R16G16UInt = 36,
R16G16SNorm = 37,
R16G16SInt = 38,
R32Typeless = 39,
D32Float = 40,
R32Float = 41,
R32UInt = 42,
R32SInt = 43,
R24G8Typeless = 44,
D24UNormS8UInt = 45,
R24UNormX8Typeless = 46,
X24TypelessG8UInt = 47,
R8G8Typeless = 48,
R8G8UNorm = 49,
R8G8UInt = 50,
R8G8SNorm = 51,
R8G8SInt = 52,
R16Typeless = 53,
R16Float = 54,
D16UNorm = 55,
R16UNorm = 56,
R16UInt = 57,
R16SNorm = 58,
R16SInt = 59,
R8Typeless = 60,
R8UNorm = 61,
R8UInt = 62,
R8SNorm = 63,
R8SInt = 64,
A8UNorm = 65,
R1UNorm = 66,
R9G9B9E5SharedEXP = 67,
R8G8B8G8UNorm = 68,
G8R8G8B8UNorm = 69,
BC1Typeless = 70,
BC1UNorm = 71,
BC1UNormSRGB = 72,
BC2Typeless = 73,
BC2UNorm = 74,
BC2UNormSRGB = 75,
BC3Typeless = 76,
BC3UNorm = 77,
BC3UNormSRGB = 78,
BC4Typeless = 79,
BC4UNorm = 80,
BC4SNorm = 81,
BC5Typeless = 82,
BC5UNorm = 83,
BC5SNorm = 84,
B5G6R5UNorm = 85,
B5G5R5A1UNorm = 86,
B8G8R8A8UNorm = 87,
B8G8R8X8UNorm = 88,
R10G10B10XRBiasA2UNorm = 89,
B8G8R8A8Typeless = 90,
B8G8R8A8UNormSRGB = 91,
B8G8R8X8Typeless = 92,
B8G8R8X8UNormSRGB = 93,
BC6HTypeless = 94,
BC6HUF16 = 95,
BC6HSF16 = 96,
BC7Typeless = 97,
BC7UNorm = 98,
BC7UNormSRGB = 99,
AYUV = 100,
Y410 = 101,
Y416 = 102,
NV12 = 103,
P010 = 104,
P016 = 105,
F420Opaque = 106,
YUY2 = 107,
Y210 = 108,
Y216 = 109,
NV11 = 110,
AI44 = 111,
IA44 = 112,
P8 = 113,
A8P8 = 114,
B4G4R4A4UNorm = 115,
P208 = 130,
V208 = 131,
V408 = 132,
SamplerFeedbackMinMipOpaque,
SamplerFeedbackMipRegionUsedOpaque,
ForceUInt = 0xffffffff,
}
public enum D3DResourceDimension : int
{
Unknown = 0,
Buffer = 1,
Texture1D = 2,
Texture2D = 3,
Texture3D = 4,
}
[Flags]
public enum D3DResourceMiscFlags : uint
{
GenerateMips = 0x000001,
Shared = 0x000002,
TextureCube = 0x000004,
DrawIndirectArgs = 0x000010,
BufferAllowRawViews = 0x000020,
BufferStructured = 0x000040,
ResourceClamp = 0x000080,
SharedKeyedMutex = 0x000100,
GDICompatible = 0x000200,
SharedNTHandle = 0x000800,
RestrictedContent = 0x001000,
RestrictSharedResource = 0x002000,
RestrictSharedResourceDriver = 0x004000,
Guarded = 0x008000,
TilePool = 0x020000,
Tiled = 0x040000,
HWProtected = 0x080000,
SharedDisplayable,
SharedExclusiveWriter,
};
public enum D3DAlphaMode : int
{
Unknown = 0,
Straight = 1,
Premultiplied = 2,
Opaque = 3,
Custom = 4,
};
}

View file

@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Dalamud.Logging;
using Lumina.Data.Files;
using Lumina.Extensions;
namespace Penumbra.Import.Dds;
public class DdsFile
{
public const int DdsIdentifier = 0x20534444;
public readonly DdsHeader Header;
public readonly DXT10Header? DXT10Header;
private readonly byte[] _data;
public ReadOnlySpan< byte > Data
=> _data;
public ReadOnlySpan< byte > MipMap( int level )
{
var mipSize = ParseType switch
{
ParseType.Unsupported => 0,
ParseType.DXT1 => Header.Height * Header.Width / 2,
ParseType.BC4 => Header.Height * Header.Width / 2,
ParseType.DXT3 => Header.Height * Header.Width,
ParseType.DXT5 => Header.Height * Header.Width,
ParseType.BC5 => Header.Height * Header.Width,
ParseType.Greyscale => Header.Height * Header.Width,
ParseType.R4G4B4A4 => Header.Height * Header.Width * 2,
ParseType.B4G4R4A4 => Header.Height * Header.Width * 2,
ParseType.R5G5B5 => Header.Height * Header.Width * 2,
ParseType.B5G5R5 => Header.Height * Header.Width * 2,
ParseType.R5G6B5 => Header.Height * Header.Width * 2,
ParseType.B5G6R5 => Header.Height * Header.Width * 2,
ParseType.R5G5B5A1 => Header.Height * Header.Width * 2,
ParseType.B5G5R5A1 => Header.Height * Header.Width * 2,
ParseType.R8G8B8 => Header.Height * Header.Width * 3,
ParseType.B8G8R8 => Header.Height * Header.Width * 3,
ParseType.R8G8B8A8 => Header.Height * Header.Width * 4,
ParseType.B8G8R8A8 => Header.Height * Header.Width * 4,
_ => throw new ArgumentOutOfRangeException( nameof( ParseType ), ParseType, null ),
};
if( Header.MipMapCount < level )
{
throw new ArgumentOutOfRangeException( nameof( level ) );
}
var sum = 0;
for( var i = 0; i < level; ++i )
{
sum += mipSize;
mipSize = Math.Max( 16, mipSize >> 2 );
}
if( _data.Length < sum + mipSize )
{
throw new Exception( "Not enough data to encode image." );
}
return _data.AsSpan( sum, mipSize );
}
private byte[]? _rgbaData;
public readonly ParseType ParseType;
public ReadOnlySpan< byte > RgbaData
=> _rgbaData ??= ParseToRgba();
private DdsFile( ParseType type, DdsHeader header, byte[] data, DXT10Header? dxt10Header = null )
{
ParseType = type;
Header = header;
DXT10Header = dxt10Header;
_data = data;
}
private byte[] ParseToRgba()
{
return ParseType switch
{
ParseType.Unsupported => Array.Empty< byte >(),
ParseType.DXT1 => ImageParsing.DecodeDxt1( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.DXT3 => ImageParsing.DecodeDxt3( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.DXT5 => ImageParsing.DecodeDxt5( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.BC4 => ImageParsing.DecodeBc4( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.BC5 => ImageParsing.DecodeBc5( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.Greyscale => ImageParsing.DecodeUncompressedGreyscale( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.R4G4B4A4 => ImageParsing.DecodeUncompressedR4G4B4A4( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.B4G4R4A4 => ImageParsing.DecodeUncompressedB4G4R4A4( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.R5G5B5 => ImageParsing.DecodeUncompressedR5G5B5( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.B5G5R5 => ImageParsing.DecodeUncompressedB5G5R5( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.R5G6B5 => ImageParsing.DecodeUncompressedR5G6B5( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.B5G6R5 => ImageParsing.DecodeUncompressedB5G6R5( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.R5G5B5A1 => ImageParsing.DecodeUncompressedR5G5B5A1( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.B5G5R5A1 => ImageParsing.DecodeUncompressedB5G5R5A1( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.R8G8B8 => ImageParsing.DecodeUncompressedR8G8B8( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.B8G8R8 => ImageParsing.DecodeUncompressedB8G8R8( MipMap( 0 ), Header.Height, Header.Width ),
ParseType.R8G8B8A8 => _data.Length == Header.Width * Header.Height * 4 ? _data : _data[ ..( Header.Width * Header.Height * 4 ) ],
ParseType.B8G8R8A8 => ImageParsing.DecodeUncompressedB8G8R8A8( MipMap( 0 ), Header.Height, Header.Width ),
_ => throw new ArgumentOutOfRangeException(),
};
}
public static bool Load( Stream data, [NotNullWhen( true )] out DdsFile? file )
{
file = null;
try
{
using var br = new BinaryReader( data );
if( br.ReadUInt32() != DdsIdentifier )
{
return false;
}
var header = br.ReadStructure< DdsHeader >();
var dxt10 = header.PixelFormat.FourCC == PixelFormat.FourCCType.DX10 ? ( DXT10Header? )br.ReadStructure< DXT10Header >() : null;
var type = header.PixelFormat.ToParseType( dxt10 );
file = new DdsFile( type, header, br.ReadBytes( ( int )( br.BaseStream.Length - br.BaseStream.Position ) ), dxt10 );
return true;
}
catch( Exception e )
{
PluginLog.Error( $"Could not load DDS file:\n{e}" );
return false;
}
}
public bool ConvertToTex( out byte[] texBytes )
{
using var mem = new MemoryStream( _data.Length * 2 );
using( var bw = new BinaryWriter( mem ) )
{
var (format, mipLength) = WriteTexHeader( bw );
var bytes = format == TexFile.TextureFormat.R8G8B8X8 ? RgbaData : _data;
if( bytes.Length < mipLength )
{
throw new Exception( "Broken file. Not enough data." );
}
bw.Write( _data.AsSpan( 0, mipLength ) );
}
texBytes = mem.ToArray();
return true;
}
private (TexFile.TextureFormat, int) WriteTexHeader( BinaryWriter bw )
{
var (format, mipLength) = ConvertFormat( ParseType, Header.Height, Header.Width );
if( mipLength == 0 )
{
throw new Exception( "Invalid format to convert to tex." );
}
var mipCount = Header.MipMapCount;
if( format == TexFile.TextureFormat.R8G8B8X8 && ParseType != ParseType.R8G8B8A8 )
{
mipCount = 1;
}
bw.Write( ( uint )TexFile.Attribute.TextureType2D );
bw.Write( ( uint )format );
bw.Write( ( ushort )Header.Width );
bw.Write( ( ushort )Header.Height );
bw.Write( ( ushort )Header.Depth );
bw.Write( ( ushort )mipCount );
bw.Write( 0 );
bw.Write( 1 );
bw.Write( 2 );
var offset = 80;
var mipLengthSum = 0;
for( var i = 0; i < mipCount; ++i )
{
bw.Write( offset );
offset += mipLength;
mipLengthSum += mipLength;
mipLength = Math.Max( 16, mipLength >> 2 );
}
for( var i = mipCount; i < 13; ++i )
{
bw.Write( 0 );
}
return ( format, mipLengthSum );
}
public static (TexFile.TextureFormat, int) ConvertFormat( ParseType type, int height, int width )
{
return type switch
{
ParseType.Unsupported => ( TexFile.TextureFormat.Unknown, 0 ),
ParseType.DXT1 => ( TexFile.TextureFormat.DXT1, height * width / 2 ),
ParseType.DXT3 => ( TexFile.TextureFormat.DXT3, height * width * 2 ),
ParseType.DXT5 => ( TexFile.TextureFormat.DXT5, height * width * 2 ),
ParseType.BC4 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.BC5 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.Greyscale => ( TexFile.TextureFormat.A8, height * width ),
ParseType.R4G4B4A4 => ( TexFile.TextureFormat.R4G4B4A4, height * width * 2 ),
ParseType.B4G4R4A4 => ( TexFile.TextureFormat.R4G4B4A4, height * width * 2 ),
ParseType.R5G5B5 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.B5G5R5 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.R5G6B5 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.B5G6R5 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.R5G5B5A1 => ( TexFile.TextureFormat.R5G5B5A1, height * width * 2 ),
ParseType.B5G5R5A1 => ( TexFile.TextureFormat.R5G5B5A1, height * width * 2 ),
ParseType.R8G8B8 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.B8G8R8 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.R8G8B8A8 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
ParseType.B8G8R8A8 => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ),
};
}
}
public class TmpTexFile
{
public TexFile.TexHeader Header;
public byte[] RgbaData;
public void Load( BinaryReader br )
{
Header = br.ReadStructure< TexFile.TexHeader >();
var data = br.ReadBytes( ( int )( br.BaseStream.Length - br.BaseStream.Position ) );
RgbaData = Header.Format switch
{
TexFile.TextureFormat.L8 => ImageParsing.DecodeUncompressedGreyscale( data, Header.Height, Header.Width ),
TexFile.TextureFormat.A8 => ImageParsing.DecodeUncompressedGreyscale( data, Header.Height, Header.Width ),
TexFile.TextureFormat.DXT1 => ImageParsing.DecodeDxt1( data, Header.Height, Header.Width ),
TexFile.TextureFormat.DXT3 => ImageParsing.DecodeDxt3( data, Header.Height, Header.Width ),
TexFile.TextureFormat.DXT5 => ImageParsing.DecodeDxt5( data, Header.Height, Header.Width ),
TexFile.TextureFormat.A8R8G8B8 => ImageParsing.DecodeUncompressedB8G8R8A8( data, Header.Height, Header.Width ),
TexFile.TextureFormat.R8G8B8X8 => ImageParsing.DecodeUncompressedR8G8B8A8( data, Header.Height, Header.Width ),
TexFile.TextureFormat.A8R8G8B82 => ImageParsing.DecodeUncompressedR8G8B8A8( data, Header.Height, Header.Width ),
TexFile.TextureFormat.R4G4B4A4 => ImageParsing.DecodeUncompressedR4G4B4A4( data, Header.Height, Header.Width ),
TexFile.TextureFormat.R5G5B5A1 => ImageParsing.DecodeUncompressedR5G5B5A1( data, Header.Height, Header.Width ),
_ => throw new ArgumentOutOfRangeException(),
};
}
}

View file

@ -0,0 +1,109 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Penumbra.Import.Dds;
[StructLayout( LayoutKind.Sequential )]
public struct DdsHeader
{
public const int Size = 124;
public const uint MagicNumber = 'D' | ( 'D' << 8 ) | ( 'S' << 16 ) | ( ' ' << 24 );
private int _size;
public DdsFlags Flags;
public int Height;
public int Width;
public int PitchOrLinearSize;
public int Depth;
public int MipMapCount;
public int Reserved1;
public int Reserved2;
public int Reserved3;
public int Reserved4;
public int Reserved5;
public int Reserved6;
public int Reserved7;
public int Reserved8;
public int Reserved9;
public int ReservedA;
public int ReservedB;
public PixelFormat PixelFormat;
public DdsCaps1 Caps1;
public DdsCaps2 Caps2;
public uint Caps3;
public uint Caps4;
public int ReservedC;
[Flags]
public enum DdsFlags : uint
{
Caps = 0x00000001,
Height = 0x00000002,
Width = 0x00000004,
Pitch = 0x00000008,
PixelFormat = 0x00001000,
MipMapCount = 0x00020000,
LinearSize = 0x00080000,
Depth = 0x00800000,
Required = Caps | Height | Width | PixelFormat,
}
[Flags]
public enum DdsCaps1 : uint
{
Complex = 0x08,
MipMap = 0x400000,
Texture = 0x1000,
}
[Flags]
public enum DdsCaps2 : uint
{
CubeMap = 0x200,
CubeMapPositiveEX = 0x400,
CubeMapNegativeEX = 0x800,
CubeMapPositiveEY = 0x1000,
CubeMapNegativeEY = 0x2000,
CubeMapPositiveEZ = 0x4000,
CubeMapNegativeEZ = 0x8000,
Volume = 0x200000,
}
public void Write( BinaryWriter bw )
{
bw.Write( MagicNumber );
bw.Write( Size );
bw.Write( ( uint )Flags );
bw.Write( Height );
bw.Write( Width );
bw.Write( PitchOrLinearSize );
bw.Write( Depth );
bw.Write( MipMapCount );
bw.Write( Reserved1 );
bw.Write( Reserved2 );
bw.Write( Reserved3 );
bw.Write( Reserved4 );
bw.Write( Reserved5 );
bw.Write( Reserved6 );
bw.Write( Reserved7 );
bw.Write( Reserved8 );
bw.Write( Reserved9 );
bw.Write( ReservedA );
bw.Write( ReservedB );
PixelFormat.Write( bw );
bw.Write( ( uint )Caps1 );
bw.Write( ( uint )Caps2 );
bw.Write( Caps3 );
bw.Write( Caps4 );
bw.Write( ReservedC );
}
public void Write( byte[] bytes, int offset )
{
using var m = new MemoryStream( bytes, offset, bytes.Length - offset );
using var bw = new BinaryWriter( m );
Write( bw );
}
}

View file

@ -0,0 +1,580 @@
using System;
using System.Runtime.CompilerServices;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using SixLabors.ImageSharp.PixelFormats;
namespace Penumbra.Import.Dds;
public static partial class ImageParsing
{
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static Rgba32 Get565Color( ushort c )
{
var ret = new Rgba32
{
R = ( byte )( c & 0x1F ),
G = ( byte )( ( c >> 5 ) & 0x3F ),
B = ( byte )( c >> 11 ),
A = 0xFF,
};
ret.R = ( byte )( ( ret.R << 3 ) | ( ret.R >> 2 ) );
ret.G = ( byte )( ( ret.G << 2 ) | ( ret.G >> 3 ) );
ret.B = ( byte )( ( ret.B << 3 ) | ( ret.B >> 2 ) );
return ret;
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static (Rgba32, Rgba32) GetDxt1CombinedColors( bool c1Larger, Rgba32 c1, Rgba32 c2 )
{
if( c1Larger )
{
static byte C( byte a1, byte a2 )
=> ( byte )( ( 2 * a1 + a2 ) / 3 );
return ( new Rgba32( C( c1.R, c2.R ), C( c1.G, c2.G ), C( c1.B, c2.B ) ),
new Rgba32( C( c2.R, c1.R ), C( c2.G, c1.G ), C( c2.B, c1.B ) ) );
}
else
{
static byte C( byte a1, byte a2 )
=> ( byte )( ( a1 + a2 ) / 2 );
return ( new Rgba32( C( c1.R, c2.R ), C( c1.G, c2.G ), C( c1.B, c2.B ) ),
new Rgba32( 0, 0, 0, 0 ) );
}
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static unsafe byte* CopyBytes( byte* ptr, Rgba32 color, byte alpha )
{
*ptr++ = color.R;
*ptr++ = color.G;
*ptr++ = color.B;
*ptr++ = alpha;
return ptr;
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static unsafe byte* CopyBytes( byte* ptr, Rgba32 color )
=> CopyBytes( ptr, color, color.A );
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static unsafe byte* CopyBytesAlphaDown( byte* ptr, Rgba32 color, byte alpha )
=> CopyBytes( ptr, color, ( byte )( ( alpha & 0x0F ) | ( alpha << 4 ) ) );
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static unsafe byte* CopyBytesAlphaUp( byte* ptr, Rgba32 color, byte alpha )
=> CopyBytes( ptr, color, ( byte )( ( alpha & 0xF0 ) | ( alpha >> 4 ) ) );
private static void Verify( ReadOnlySpan< byte > data, int height, int width, int blockSize, int bytes )
{
if( data.Length % bytes != 0 )
{
throw new ArgumentException( $"Length {data.Length} not a multiple of {bytes} bytes.", nameof( data ) );
}
if( height * width > data.Length * blockSize * blockSize / bytes )
{
throw new ArgumentException( $"Not enough data encoded in {data.Length} to fill image of dimensions {height} * {width}.",
nameof( data ) );
}
if( height % blockSize != 0 )
{
throw new ArgumentException( $"Height must be a multiple of {blockSize}.", nameof( height ) );
}
if( width % blockSize != 0 )
{
throw new ArgumentException( $"Height must be a multiple of {blockSize}.", nameof( height ) );
}
}
private static unsafe byte* GetDxt1Colors( byte* ptr, Span< Rgba32 > colors )
{
var c1 = ( ushort )( *ptr | ( ptr[ 1 ] << 8 ) );
var c2 = ( ushort )( ptr[ 2 ] | ( ptr[ 3 ] << 8 ) );
colors[ 0 ] = Get565Color( c1 );
colors[ 1 ] = Get565Color( c2 );
( colors[ 2 ], colors[ 3 ] ) = GetDxt1CombinedColors( c1 > c2, colors[ 0 ], colors[ 1 ] );
return ptr + 4;
}
public static unsafe byte[] DecodeDxt1( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 4, 8 );
var ret = new byte[data.Length * 8];
Span< Rgba32 > colors = stackalloc Rgba32[4];
fixed( byte* r = ret, d = data )
{
var inputPtr = d;
for( var y = 0; y < height; y += 4 )
{
var outputPtr = r + y * width * 4;
for( var x = 0; x < width; x += 4 )
{
inputPtr = GetDxt1Colors( inputPtr, colors );
for( var j = 0; j < 4; ++j )
{
var outputPtr2 = outputPtr + 4 * ( x + j * width );
var colorMask = *inputPtr++;
outputPtr2 = CopyBytes( outputPtr2, colors[ colorMask & 0b11 ] );
outputPtr2 = CopyBytes( outputPtr2, colors[ ( colorMask >> 2 ) & 0b11 ] );
outputPtr2 = CopyBytes( outputPtr2, colors[ ( colorMask >> 4 ) & 0b11 ] );
CopyBytes( outputPtr2, colors[ ( colorMask >> 6 ) & 0b11 ] );
}
}
}
}
return ret;
}
public static unsafe byte[] DecodeDxt3( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 4, 16 );
var ret = new byte[data.Length * 4];
Span< Rgba32 > colors = stackalloc Rgba32[4];
fixed( byte* r = ret, d = data )
{
var inputPtr = d;
for( var y = 0; y < height; y += 4 )
{
var outputPtr = r + y * width * 4;
for( var x = 0; x < width; x += 4 )
{
var alphaPtr = inputPtr;
inputPtr = GetDxt1Colors( inputPtr + 8, colors );
for( var j = 0; j < 4; ++j )
{
var outputPtr2 = outputPtr + 4 * ( x + j * width );
var colorMask = *inputPtr++;
outputPtr2 = CopyBytesAlphaDown( outputPtr2, colors[ colorMask & 0b11 ], *alphaPtr );
outputPtr2 = CopyBytesAlphaUp( outputPtr2, colors[ ( colorMask >> 2 ) & 0b11 ], *alphaPtr++ );
outputPtr2 = CopyBytesAlphaDown( outputPtr2, colors[ ( colorMask >> 4 ) & 0b11 ], *alphaPtr );
CopyBytesAlphaUp( outputPtr2, colors[ ( colorMask >> 6 ) & 0b11 ], *alphaPtr++ );
}
}
}
}
return ret;
}
private static unsafe byte* Dxt5AlphaTable( byte* ptr, Span< byte > alphaValues )
{
var alphaLookup = stackalloc byte[8];
alphaLookup[ 0 ] = *ptr++;
alphaLookup[ 1 ] = *ptr++;
if( alphaLookup[ 0 ] > alphaLookup[ 1 ] )
{
alphaLookup[ 2 ] = ( byte )( ( 6 * alphaLookup[ 0 ] + alphaLookup[ 1 ] ) / 7 );
alphaLookup[ 3 ] = ( byte )( ( 5 * alphaLookup[ 0 ] + 2 * alphaLookup[ 1 ] ) / 7 );
alphaLookup[ 4 ] = ( byte )( ( 4 * alphaLookup[ 0 ] + 3 * alphaLookup[ 1 ] ) / 7 );
alphaLookup[ 5 ] = ( byte )( ( 3 * alphaLookup[ 0 ] + 4 * alphaLookup[ 1 ] ) / 7 );
alphaLookup[ 6 ] = ( byte )( ( 2 * alphaLookup[ 0 ] + 5 * alphaLookup[ 1 ] ) / 7 );
alphaLookup[ 7 ] = ( byte )( ( alphaLookup[ 0 ] + 6 * alphaLookup[ 1 ] ) / 7 );
}
else
{
alphaLookup[ 2 ] = ( byte )( ( 4 * alphaLookup[ 0 ] + alphaLookup[ 1 ] ) / 5 );
alphaLookup[ 3 ] = ( byte )( ( 3 * alphaLookup[ 0 ] + 3 * alphaLookup[ 1 ] ) / 5 );
alphaLookup[ 4 ] = ( byte )( ( 2 * alphaLookup[ 0 ] + 2 * alphaLookup[ 1 ] ) / 5 );
alphaLookup[ 5 ] = ( byte )( ( alphaLookup[ 0 ] + alphaLookup[ 1 ] ) / 5 );
alphaLookup[ 6 ] = byte.MinValue;
alphaLookup[ 7 ] = byte.MaxValue;
}
var alphaLong = ( ulong )*ptr++;
alphaLong |= ( ulong )*ptr++ << 8;
alphaLong |= ( ulong )*ptr++ << 16;
alphaLong |= ( ulong )*ptr++ << 24;
alphaLong |= ( ulong )*ptr++ << 32;
alphaLong |= ( ulong )*ptr++ << 40;
for( var i = 0; i < 16; ++i )
{
alphaValues[ i ] = alphaLookup[ ( alphaLong >> ( i * 3 ) ) & 0x07 ];
}
return ptr;
}
public static unsafe byte[] DecodeDxt5( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 4, 16 );
var ret = new byte[data.Length * 4];
Span< Rgba32 > colors = stackalloc Rgba32[4];
Span< byte > alphaValues = stackalloc byte[16];
fixed( byte* r = ret, d = data, a = alphaValues )
{
var inputPtr = d;
for( var y = 0; y < height; y += 4 )
{
var outputPtr = r + y * width * 4;
for( var x = 0; x < width; x += 4 )
{
inputPtr = Dxt5AlphaTable( inputPtr, alphaValues );
inputPtr = GetDxt1Colors( inputPtr, colors );
var alphaPtr = a;
for( var j = 0; j < 4; ++j )
{
var outputPtr2 = outputPtr + 4 * ( x + j * width );
var colorMask = *inputPtr++;
outputPtr2 = CopyBytesAlphaDown( outputPtr2, colors[ colorMask & 0b11 ], *alphaPtr++ );
outputPtr2 = CopyBytesAlphaUp( outputPtr2, colors[ ( colorMask >> 2 ) & 0b11 ], *alphaPtr++ );
outputPtr2 = CopyBytesAlphaDown( outputPtr2, colors[ ( colorMask >> 4 ) & 0b11 ], *alphaPtr++ );
CopyBytesAlphaUp( outputPtr2, colors[ ( colorMask >> 6 ) & 0b11 ], *alphaPtr++ );
}
}
}
}
return ret;
}
public static unsafe byte[] DecodeBc4( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 4, 8 );
var ret = new byte[data.Length * 8];
Span< byte > channelValues = stackalloc byte[16];
fixed( byte* r = ret, d = data, a = channelValues )
{
var inputPtr = d;
for( var y = 0; y < height; y += 4 )
{
var outputPtr = r + y * width * 4;
for( var x = 0; x < width; x += 4 )
{
inputPtr = Dxt5AlphaTable( inputPtr, channelValues );
var channelPtr = a;
for( var j = 0; j < 4; ++j )
{
var outputPtr2 = outputPtr + 4 * ( x + j * width );
outputPtr2 = CopyBytes( outputPtr2, new Rgba32( *channelPtr, *channelPtr, *channelPtr++, 0xFF ) );
outputPtr2 = CopyBytes( outputPtr2, new Rgba32( *channelPtr, *channelPtr, *channelPtr++, 0xFF ) );
outputPtr2 = CopyBytes( outputPtr2, new Rgba32( *channelPtr, *channelPtr, *channelPtr++, 0xFF ) );
CopyBytes( outputPtr2, new Rgba32( *channelPtr, *channelPtr, *channelPtr++, 0xFF ) );
}
}
}
}
return ret;
}
public static unsafe byte[] DecodeBc5( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 4, 16 );
var ret = new byte[data.Length * 4];
Span< byte > channel1 = stackalloc byte[16];
Span< byte > channel2 = stackalloc byte[16];
fixed( byte* r = ret, d = data, a = channel1, b = channel2 )
{
var inputPtr = d;
for( var y = 0; y < height; y += 4 )
{
var outputPtr = r + y * width * 4;
for( var x = 0; x < width; x += 4 )
{
inputPtr = Dxt5AlphaTable( inputPtr, channel1 );
inputPtr = Dxt5AlphaTable( inputPtr, channel2 );
var channel1Ptr = a;
var channel2Ptr = b;
for( var j = 0; j < 4; ++j )
{
var outputPtr2 = outputPtr + 4 * ( x + j * width );
outputPtr2 = CopyBytes( outputPtr2, new Rgba32( *channel1Ptr++, *channel2Ptr++, 0, 0xFF ) );
outputPtr2 = CopyBytes( outputPtr2, new Rgba32( *channel1Ptr++, *channel2Ptr++, 0, 0xFF ) );
outputPtr2 = CopyBytes( outputPtr2, new Rgba32( *channel1Ptr++, *channel2Ptr++, 0, 0xFF ) );
CopyBytes( outputPtr2, new Rgba32( *channel1Ptr++, *channel2Ptr++, 0, 0xFF ) );
}
}
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedGreyscale( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 1 );
var ret = new byte[data.Length * 4];
fixed( byte* r = ret, d = data )
{
var ptr = r;
var end = d + data.Length;
var input = d;
while( input != end )
{
*ptr++ = *input;
*ptr++ = *input;
*ptr++ = *input++;
*ptr++ = 0xFF;
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedR4G4B4A4( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 2 );
var ret = new byte[data.Length * 2];
fixed( byte* r = ret, d = data )
{
var ptr = r;
var input = ( ushort* )d;
foreach( var b in data )
{
*ptr++ = ( byte )( ( b << 4 ) | ( b & 0x0F ) );
*ptr++ = ( byte )( ( b >> 4 ) | ( b & 0xF0 ) );
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedB4G4R4A4( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 2 );
var ret = new byte[data.Length * 2];
fixed( byte* r = ret, d = data )
{
var ptr = r;
foreach( var b in new Span< ushort >( d, data.Length / 2 ) )
{
*ptr++ = ( byte )( ( ( b >> 8 ) & 0x0F ) | ( ( b >> 4 ) & 0xF0 ) );
*ptr++ = ( byte )( ( b & 0xF0 ) | ( ( b >> 4 ) & 0x0F ) );
*ptr++ = ( byte )( ( b & 0x0F ) | ( b << 4 ) );
*ptr++ = ( byte )( ( ( b >> 8 ) & 0xF0 ) | ( b >> 12 ) );
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedR5G5B5( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 2 );
var ret = new byte[data.Length * 2];
fixed( byte* r = ret, d = data )
{
var ptr = r;
foreach( var b in new Span< ushort >( d, data.Length / 2 ) )
{
*ptr++ = ( byte )( ( b & 0x1F ) | ( b << 3 ) );
var tmp = b & 0x03E0;
*ptr++ = ( byte )( ( tmp >> 2 ) | ( tmp >> 7 ) );
tmp = b & 0x7C00;
*ptr++ = ( byte )( ( tmp >> 12 ) | ( tmp >> 7 ) );
*ptr++ = 0xFF;
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedB5G5R5( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 2 );
var ret = new byte[data.Length * 2];
fixed( byte* r = ret, d = data )
{
var ptr = r;
foreach( var b in new Span< ushort >( d, data.Length / 2 ) )
{
var tmp = b & 0x7C00;
*ptr++ = ( byte )( ( tmp >> 12 ) | ( tmp >> 7 ) );
tmp = b & 0x03E0;
*ptr++ = ( byte )( ( tmp >> 2 ) | ( tmp >> 7 ) );
*ptr++ = ( byte )( ( b & 0x1F ) | ( b << 3 ) );
*ptr++ = 0xFF;
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedR5G6B5( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 2 );
var ret = new byte[data.Length * 2];
fixed( byte* r = ret, d = data )
{
var ptr = r;
foreach( var b in new Span< ushort >( d, data.Length / 2 ) )
{
*ptr++ = ( byte )( ( b & 0x1F ) | ( b << 3 ) );
var tmp = b & 0x07E0;
*ptr++ = ( byte )( ( tmp >> 3 ) | ( tmp >> 9 ) );
tmp = b & 0xF800;
*ptr++ = ( byte )( ( tmp >> 14 ) | ( tmp >> 9 ) );
*ptr++ = 0xFF;
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedB5G6R5( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 2 );
var ret = new byte[data.Length * 2];
fixed( byte* r = ret, d = data )
{
var ptr = r;
foreach( var b in new Span< ushort >( d, data.Length / 2 ) )
{
var tmp = b & 0xF800;
*ptr++ = ( byte )( ( tmp >> 14 ) | ( tmp >> 9 ) );
tmp = b & 0x07E0;
*ptr++ = ( byte )( ( tmp >> 3 ) | ( tmp >> 9 ) );
*ptr++ = ( byte )( ( b & 0x1F ) | ( b << 3 ) );
*ptr++ = 0xFF;
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedR5G5B5A1( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 2 );
var ret = new byte[data.Length * 2];
fixed( byte* r = ret, d = data )
{
var ptr = r;
foreach( var b in new Span< ushort >( d, data.Length / 2 ) )
{
*ptr++ = ( byte )( ( b & 0x1F ) | ( b << 3 ) );
var tmp = b & 0x03E0;
*ptr++ = ( byte )( ( tmp >> 2 ) | ( tmp >> 7 ) );
tmp = b & 0x7C00;
*ptr++ = ( byte )( ( tmp >> 12 ) | ( tmp >> 7 ) );
*ptr++ = 0xFF;
*ptr++ = ( byte )( b > 0x7FFF ? 0xFF : 0x00 );
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedB5G5R5A1( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 2 );
var ret = new byte[data.Length * 2];
fixed( byte* r = ret, d = data )
{
var ptr = r;
foreach( var b in new Span< ushort >( d, data.Length / 2 ) )
{
var tmp = b & 0x7C00;
*ptr++ = ( byte )( ( tmp >> 12 ) | ( tmp >> 7 ) );
tmp = b & 0x03E0;
*ptr++ = ( byte )( ( tmp >> 2 ) | ( tmp >> 7 ) );
*ptr++ = ( byte )( ( b & 0x1F ) | ( b << 3 ) );
*ptr++ = ( byte )( b > 0x7FFF ? 0xFF : 0x00 );
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedR8G8B8( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 3 );
var ret = new byte[data.Length * 4 / 3];
fixed( byte* r = ret, d = data )
{
var ptr = r;
var end = d + data.Length;
var input = d;
while( input != end )
{
*ptr++ = *input++;
*ptr++ = *input++;
*ptr++ = *input++;
*ptr++ = 0xFF;
}
}
return ret;
}
public static unsafe byte[] DecodeUncompressedB8G8R8( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 3 );
var ret = new byte[data.Length * 4 / 3];
fixed( byte* r = ret, d = data )
{
var ptr = r;
var end = d + data.Length;
var input = d;
while( input != end )
{
var b = *input++;
var g = *input++;
*ptr++ = *input++;
*ptr++ = g;
*ptr++ = b;
*ptr++ = 0xFF;
}
}
return ret;
}
public static byte[] DecodeUncompressedR8G8B8A8( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 4 );
var ret = new byte[data.Length];
data.CopyTo( ret );
return ret;
}
public static unsafe byte[] DecodeUncompressedB8G8R8A8( ReadOnlySpan< byte > data, int height, int width )
{
Verify( data, height, width, 1, 4 );
var ret = new byte[data.Length];
fixed( byte* r = ret, d = data )
{
var ptr = r;
var end = d + data.Length;
var input = d;
while( input != end )
{
var b = *input++;
var g = *input++;
*ptr++ = *input++;
*ptr++ = g;
*ptr++ = b;
*ptr++ = *input++;
}
}
return ret;
}
}

View file

@ -0,0 +1,134 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Penumbra.Import.Dds;
public enum ParseType
{
Unsupported,
DXT1,
DXT3,
DXT5,
BC4,
BC5,
Greyscale,
R4G4B4A4,
B4G4R4A4,
R5G5B5,
B5G5R5,
R5G6B5,
B5G6R5,
R5G5B5A1,
B5G5R5A1,
R8G8B8,
B8G8R8,
R8G8B8A8,
B8G8R8A8,
}
[StructLayout( LayoutKind.Sequential )]
public struct PixelFormat
{
public int Size;
public FormatFlags Flags;
public FourCCType FourCC;
public int RgbBitCount;
public uint RBitMask;
public uint GBitMask;
public uint BBitMask;
public uint ABitMask;
[Flags]
public enum FormatFlags : uint
{
AlphaPixels = 0x000001,
Alpha = 0x000002,
FourCC = 0x000004,
RGB = 0x000040,
YUV = 0x000200,
Luminance = 0x020000,
}
public enum FourCCType : uint
{
NoCompression = 0,
DXT1 = 'D' | ( 'X' << 8 ) | ( 'T' << 16 ) | ( '1' << 24 ),
DXT2 = 'D' | ( 'X' << 8 ) | ( 'T' << 16 ) | ( '2' << 24 ),
DXT3 = 'D' | ( 'X' << 8 ) | ( 'T' << 16 ) | ( '3' << 24 ),
DXT4 = 'D' | ( 'X' << 8 ) | ( 'T' << 16 ) | ( '4' << 24 ),
DXT5 = 'D' | ( 'X' << 8 ) | ( 'T' << 16 ) | ( '5' << 24 ),
DX10 = 'D' | ( 'X' << 8 ) | ( '1' << 16 ) | ( '0' << 24 ),
ATI1 = 'A' | ( 'T' << 8 ) | ( 'I' << 16 ) | ( '1' << 24 ),
BC4U = 'B' | ( 'C' << 8 ) | ( '4' << 16 ) | ( 'U' << 24 ),
BC45 = 'B' | ( 'C' << 8 ) | ( '4' << 16 ) | ( '5' << 24 ),
ATI2 = 'A' | ( 'T' << 8 ) | ( 'I' << 16 ) | ( '2' << 24 ),
BC5U = 'B' | ( 'C' << 8 ) | ( '5' << 16 ) | ( 'U' << 24 ),
BC55 = 'B' | ( 'C' << 8 ) | ( '5' << 16 ) | ( '5' << 24 ),
}
public void Write( BinaryWriter bw )
{
bw.Write( Size );
bw.Write( ( uint )Flags );
bw.Write( ( uint )FourCC );
bw.Write( RgbBitCount );
bw.Write( RBitMask );
bw.Write( GBitMask );
bw.Write( BBitMask );
bw.Write( ABitMask );
}
public ParseType ToParseType( DXT10Header? dxt10 )
{
return FourCC switch
{
FourCCType.NoCompression => HandleUncompressed(),
FourCCType.DXT1 => ParseType.DXT1,
FourCCType.DXT2 => ParseType.Unsupported,
FourCCType.DXT3 => ParseType.DXT3,
FourCCType.DXT4 => ParseType.Unsupported,
FourCCType.DXT5 => ParseType.DXT5,
FourCCType.DX10 => dxt10?.ToParseType() ?? ParseType.Unsupported,
FourCCType.ATI1 => ParseType.BC4,
FourCCType.BC4U => ParseType.BC4,
FourCCType.BC45 => ParseType.BC4,
FourCCType.ATI2 => ParseType.BC5,
FourCCType.BC5U => ParseType.BC5,
FourCCType.BC55 => ParseType.BC5,
_ => ParseType.Unsupported,
};
}
private ParseType HandleUncompressed()
{
switch( RgbBitCount )
{
case 8: return ParseType.Greyscale;
case 16:
if( ABitMask == 0xF000 )
{
return RBitMask > GBitMask ? ParseType.B4G4R4A4 : ParseType.R4G4B4A4;
}
if( Flags.HasFlag( FormatFlags.AlphaPixels ) )
{
return RBitMask > GBitMask ? ParseType.B5G5R5A1 : ParseType.R5G5B5A1;
}
if( GBitMask == 0x07E0 )
{
return RBitMask > GBitMask ? ParseType.B5G6R5 : ParseType.R5G6B5;
}
return RBitMask > GBitMask ? ParseType.B5G5R5 : ParseType.R5G5B5;
case 24: return RBitMask > GBitMask ? ParseType.B8G8R8 : ParseType.R8G8B8;
case 32: return RBitMask > GBitMask ? ParseType.B8G8R8A8 : ParseType.R8G8B8A8;
default: return ParseType.Unsupported;
}
}
}

View file

@ -0,0 +1,103 @@
using System;
using System.IO;
using Lumina.Data.Files;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Penumbra.Import.Dds;
public class TextureImporter
{
private static void WriteHeader( byte[] target, int width, int height )
{
using var mem = new MemoryStream( target );
using var bw = new BinaryWriter( mem );
bw.Write( ( uint )TexFile.Attribute.TextureType2D );
bw.Write( ( uint )TexFile.TextureFormat.A8R8G8B8 );
bw.Write( ( ushort )width );
bw.Write( ( ushort )height );
bw.Write( ( ushort )1 );
bw.Write( ( ushort )1 );
bw.Write( 0 );
bw.Write( 1 );
bw.Write( 2 );
bw.Write( 80 );
for( var i = 1; i < 13; ++i )
{
bw.Write( 0 );
}
}
public static unsafe bool RgbaBytesToDds( byte[] rgba, int width, int height, out byte[] ddsData )
{
var header = new DdsHeader()
{
Caps1 = DdsHeader.DdsCaps1.Complex | DdsHeader.DdsCaps1.Texture | DdsHeader.DdsCaps1.MipMap,
Depth = 1,
Flags = DdsHeader.DdsFlags.Required | DdsHeader.DdsFlags.Pitch | DdsHeader.DdsFlags.MipMapCount,
Height = height,
Width = width,
PixelFormat = new PixelFormat()
{
Flags = PixelFormat.FormatFlags.AlphaPixels | PixelFormat.FormatFlags.RGB,
FourCC = 0,
BBitMask = 0x000000FF,
GBitMask = 0x0000FF00,
RBitMask = 0x00FF0000,
ABitMask = 0xFF000000,
Size = 32,
RgbBitCount = 32,
},
};
ddsData = new byte[4 + DdsHeader.Size + rgba.Length];
header.Write( ddsData, 0 );
rgba.CopyTo( ddsData, DdsHeader.Size + 4 );
for( var i = 0; i < rgba.Length; i += 4 )
{
( ddsData[ DdsHeader.Size + i ], ddsData[ DdsHeader.Size + i + 2 ] )
= ( ddsData[ DdsHeader.Size + i + 2 ], ddsData[ DdsHeader.Size + i ] );
}
return true;
}
public static bool RgbaBytesToTex( byte[] rgba, int width, int height, out byte[] texData )
{
texData = Array.Empty< byte >();
if( rgba.Length != width * height * 4 )
{
return false;
}
texData = new byte[80 + width * height * 4];
WriteHeader( texData, width, height );
// RGBA to BGRA.
for( var i = 0; i < rgba.Length; i += 4 )
{
texData[ 80 + i + 0 ] = rgba[ i + 2 ];
texData[ 80 + i + 1 ] = rgba[ i + 1 ];
texData[ 80 + i + 2 ] = rgba[ i + 0 ];
texData[ 80 + i + 3 ] = rgba[ i + 3 ];
}
return true;
}
public static bool PngToTex( string inputFile, out byte[] texData )
{
using var file = File.OpenRead( inputFile );
var image = Image.Load< Bgra32 >( file );
var buffer = new byte[80 + image.Height * image.Width * 4];
WriteHeader( buffer, image.Width, image.Height );
var span = new Span< byte >( buffer, 80, buffer.Length - 80 );
image.CopyPixelDataTo( span );
texData = buffer;
return true;
}
public void Import( string inputFile )
{ }
}

View file

@ -1,553 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using Dalamud.Logging;
using Lumina.Data.Files;
using Lumina.Extensions;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Penumbra.Import.Textures;
[StructLayout( LayoutKind.Sequential )]
public struct PixelFormat
{
[Flags]
public enum FormatFlags : uint
{
AlphaPixels = 0x000001,
Alpha = 0x000002,
FourCC = 0x000004,
RGB = 0x000040,
YUV = 0x000200,
Luminance = 0x020000,
}
public enum FourCCType : uint
{
DXT1 = 'D' | ( 'X' << 8 ) | ( 'T' << 16 ) | ( '1' << 24 ),
DXT3 = 'D' | ( 'X' << 8 ) | ( 'T' << 16 ) | ( '3' << 24 ),
DXT5 = 'D' | ( 'X' << 8 ) | ( 'T' << 16 ) | ( '5' << 24 ),
DX10 = 'D' | ( 'X' << 8 ) | ( '1' << 16 ) | ( '0' << 24 ),
}
public int Size;
public FormatFlags Flags;
public FourCCType FourCC;
public int RgbBitCount;
public uint RBitMask;
public uint GBitMask;
public uint BBitMask;
public uint ABitMask;
public void Write( BinaryWriter bw )
{
bw.Write( Size );
bw.Write( ( uint )Flags );
bw.Write( ( uint )FourCC );
bw.Write( RgbBitCount );
bw.Write( RBitMask );
bw.Write( GBitMask );
bw.Write( BBitMask );
bw.Write( ABitMask );
}
}
[StructLayout( LayoutKind.Sequential )]
public struct DdsHeader
{
[Flags]
public enum DdsFlags : uint
{
Caps = 0x00000001,
Height = 0x00000002,
Width = 0x00000004,
Pitch = 0x00000008,
PixelFormat = 0x00001000,
MipMapCount = 0x00020000,
LinearSize = 0x00080000,
Depth = 0x00800000,
Required = Caps | Height | Width | PixelFormat,
}
[Flags]
public enum DdsCaps1 : uint
{
Complex = 0x08,
MipMap = 0x400000,
Texture = 0x1000,
}
[Flags]
public enum DdsCaps2 : uint
{
CubeMap = 0x200,
CubeMapPositiveEX = 0x400,
CubeMapNegativeEX = 0x800,
CubeMapPositiveEY = 0x1000,
CubeMapNegativeEY = 0x2000,
CubeMapPositiveEZ = 0x4000,
CubeMapNegativeEZ = 0x8000,
Volume = 0x200000,
}
public const int Size = 124;
private int _size;
public DdsFlags Flags;
public int Height;
public int Width;
public int PitchOrLinearSize;
public int Depth;
public int MipMapCount;
public int Reserved1;
public int Reserved2;
public int Reserved3;
public int Reserved4;
public int Reserved5;
public int Reserved6;
public int Reserved7;
public int Reserved8;
public int Reserved9;
public int ReservedA;
public int ReservedB;
public PixelFormat PixelFormat;
public DdsCaps1 Caps1;
public DdsCaps2 Caps2;
public uint Caps3;
public uint Caps4;
public int ReservedC;
public void Write( BinaryWriter bw )
{
bw.Write( ( byte )'D' );
bw.Write( ( byte )'D' );
bw.Write( ( byte )'S' );
bw.Write( ( byte )' ' );
bw.Write( Size );
bw.Write( ( uint )Flags );
bw.Write( Height );
bw.Write( Width );
bw.Write( PitchOrLinearSize );
bw.Write( Depth );
bw.Write( MipMapCount );
bw.Write( Reserved1 );
bw.Write( Reserved2 );
bw.Write( Reserved3 );
bw.Write( Reserved4 );
bw.Write( Reserved5 );
bw.Write( Reserved6 );
bw.Write( Reserved7 );
bw.Write( Reserved8 );
bw.Write( Reserved9 );
bw.Write( ReservedA );
bw.Write( ReservedB );
PixelFormat.Write( bw );
bw.Write( ( uint )Caps1 );
bw.Write( ( uint )Caps2 );
bw.Write( Caps3 );
bw.Write( Caps4 );
bw.Write( ReservedC );
}
public void Write( byte[] bytes, int offset )
{
using var m = new MemoryStream( bytes, offset, bytes.Length - offset );
using var bw = new BinaryWriter( m );
Write( bw );
}
}
[StructLayout( LayoutKind.Sequential )]
public struct DXT10Header
{
public enum DXGIFormat : uint
{
Unknown = 0,
R32G32B32A32Typeless = 1,
R32G32B32A32Float = 2,
R32G32B32A32UInt = 3,
R32G32B32A32SInt = 4,
R32G32B32Typeless = 5,
R32G32B32Float = 6,
R32G32B32UInt = 7,
R32G32B32SInt = 8,
R16G16B16A16Typeless = 9,
R16G16B16A16Float = 10,
R16G16B16A16UNorm = 11,
R16G16B16A16UInt = 12,
R16G16B16A16SNorm = 13,
R16G16B16A16SInt = 14,
R32G32Typeless = 15,
R32G32Float = 16,
R32G32UInt = 17,
R32G32SInt = 18,
R32G8X24Typeless = 19,
D32FloatS8X24UInt = 20,
R32FloatX8X24Typeless = 21,
X32TypelessG8X24UInt = 22,
R10G10B10A2Typeless = 23,
R10G10B10A2UNorm = 24,
R10G10B10A2UInt = 25,
R11G11B10Float = 26,
R8G8B8A8Typeless = 27,
R8G8B8A8UNorm = 28,
R8G8B8A8UNormSRGB = 29,
R8G8B8A8UInt = 30,
R8G8B8A8SNorm = 31,
R8G8B8A8SInt = 32,
R16G16Typeless = 33,
R16G16Float = 34,
R16G16UNorm = 35,
R16G16UInt = 36,
R16G16SNorm = 37,
R16G16SInt = 38,
R32Typeless = 39,
D32Float = 40,
R32Float = 41,
R32UInt = 42,
R32SInt = 43,
R24G8Typeless = 44,
D24UNormS8UInt = 45,
R24UNormX8Typeless = 46,
X24TypelessG8UInt = 47,
R8G8Typeless = 48,
R8G8UNorm = 49,
R8G8UInt = 50,
R8G8SNorm = 51,
R8G8SInt = 52,
R16Typeless = 53,
R16Float = 54,
D16UNorm = 55,
R16UNorm = 56,
R16UInt = 57,
R16SNorm = 58,
R16SInt = 59,
R8Typeless = 60,
R8UNorm = 61,
R8UInt = 62,
R8SNorm = 63,
R8SInt = 64,
A8UNorm = 65,
R1UNorm = 66,
R9G9B9E5SharedEXP = 67,
R8G8B8G8UNorm = 68,
G8R8G8B8UNorm = 69,
BC1Typeless = 70,
BC1UNorm = 71,
BC1UNormSRGB = 72,
BC2Typeless = 73,
BC2UNorm = 74,
BC2UNormSRGB = 75,
BC3Typeless = 76,
BC3UNorm = 77,
BC3UNormSRGB = 78,
BC4Typeless = 79,
BC4UNorm = 80,
BC4SNorm = 81,
BC5Typeless = 82,
BC5UNorm = 83,
BC5SNorm = 84,
B5G6R5UNorm = 85,
B5G5R5A1UNorm = 86,
B8G8R8A8UNorm = 87,
B8G8R8X8UNorm = 88,
R10G10B10XRBiasA2UNorm = 89,
B8G8R8A8Typeless = 90,
B8G8R8A8UNormSRGB = 91,
B8G8R8X8Typeless = 92,
B8G8R8X8UNormSRGB = 93,
BC6HTypeless = 94,
BC6HUF16 = 95,
BC6HSF16 = 96,
BC7Typeless = 97,
BC7UNorm = 98,
BC7UNormSRGB = 99,
AYUV = 100,
Y410 = 101,
Y416 = 102,
NV12 = 103,
P010 = 104,
P016 = 105,
F420Opaque = 106,
YUY2 = 107,
Y210 = 108,
Y216 = 109,
NV11 = 110,
AI44 = 111,
IA44 = 112,
P8 = 113,
A8P8 = 114,
B4G4R4A4UNorm = 115,
P208 = 130,
V208 = 131,
V408 = 132,
SamplerFeedbackMinMipOpaque,
SamplerFeedbackMipRegionUsedOpaque,
ForceUInt = 0xffffffff,
}
public enum D3DResourceDimension : int
{
Unknown = 0,
Buffer = 1,
Texture1D = 2,
Texture2D = 3,
Texture3D = 4,
}
[Flags]
public enum D3DResourceMiscFlags : uint
{
GenerateMips = 0x000001,
Shared = 0x000002,
TextureCube = 0x000004,
DrawIndirectArgs = 0x000010,
BufferAllowRawViews = 0x000020,
BufferStructured = 0x000040,
ResourceClamp = 0x000080,
SharedKeyedMutex = 0x000100,
GDICompatible = 0x000200,
SharedNTHandle = 0x000800,
RestrictedContent = 0x001000,
RestrictSharedResource = 0x002000,
RestrictSharedResourceDriver = 0x004000,
Guarded = 0x008000,
TilePool = 0x020000,
Tiled = 0x040000,
HWProtected = 0x080000,
SharedDisplayable,
SharedExclusiveWriter,
};
public enum D3DAlphaMode : int
{
Unknown = 0,
Straight = 1,
Premultiplied = 2,
Opaque = 3,
Custom = 4,
};
public DXGIFormat Format;
public D3DResourceDimension ResourceDimension;
public D3DResourceMiscFlags MiscFlags;
public uint ArraySize;
public D3DAlphaMode AlphaMode;
}
public class DdsFile
{
public const int DdsIdentifier = 0x20534444;
public DdsHeader Header;
public DXT10Header? DXT10Header;
public byte[] MainSurfaceData;
public byte[] RemainingSurfaceData;
private DdsFile( DdsHeader header, byte[] mainSurfaceData, byte[] remainingSurfaceData, DXT10Header? dXT10Header = null )
{
Header = header;
DXT10Header = dXT10Header;
MainSurfaceData = mainSurfaceData;
RemainingSurfaceData = remainingSurfaceData;
}
public static bool Load( Stream data, [NotNullWhen( true )] out DdsFile? file )
{
file = null;
try
{
using var br = new BinaryReader( data );
if( br.ReadUInt32() != DdsIdentifier )
{
return false;
}
var header = br.ReadStructure< DdsHeader >();
var dxt10 = header.PixelFormat.FourCC == PixelFormat.FourCCType.DX10 ? ( DXT10Header? )br.ReadStructure< DXT10Header >() : null;
file = new DdsFile( header, br.ReadBytes( ( int )( br.BaseStream.Length - br.BaseStream.Position ) ), Array.Empty< byte >(),
dxt10 );
return true;
}
catch( Exception e )
{
PluginLog.Error( $"Could not load DDS file:\n{e}" );
return false;
}
}
public bool ConvertToTex( out byte[] texBytes )
{
using var mem = new MemoryStream( MainSurfaceData.Length * 2 );
using( var bw = new BinaryWriter( mem ) )
{
var format = WriteTexHeader( bw );
bw.Write( ConvertBytes( MainSurfaceData, format ) );
}
texBytes = mem.ToArray();
return true;
}
private TexFile.TextureFormat WriteTexHeader( BinaryWriter bw )
{
var (format, mipLength) = ConvertFormat( Header.PixelFormat, Header.Height, Header.Width, DXT10Header );
bw.Write( ( uint )TexFile.Attribute.TextureType2D );
bw.Write( ( uint )format );
bw.Write( ( ushort )Header.Width );
bw.Write( ( ushort )Header.Height );
bw.Write( ( ushort )Header.Depth );
bw.Write( ( ushort )Header.MipMapCount );
bw.Write( 0 );
bw.Write( 1 );
bw.Write( 2 );
var offset = 80;
for( var i = 0; i < Header.MipMapCount; ++i )
{
bw.Write( offset );
offset += mipLength;
mipLength = Math.Max( 16, mipLength >> 2 );
}
for( var i = Header.MipMapCount; i < 13; ++i )
{
bw.Write( 0 );
}
return format;
}
private static byte[] ConvertBytes( byte[] ddsData, TexFile.TextureFormat format )
{
return format switch
{
_ => ddsData,
};
}
private static (TexFile.TextureFormat, int) ConvertFormat( PixelFormat format, int height, int width, DXT10Header? dxt10 )
=> format.FourCC switch
{
PixelFormat.FourCCType.DXT1 => ( TexFile.TextureFormat.DXT1, height * width / 2 ),
PixelFormat.FourCCType.DXT3 => ( TexFile.TextureFormat.DXT3, height * width * 4 ),
PixelFormat.FourCCType.DXT5 => ( TexFile.TextureFormat.DXT5, height * width ),
PixelFormat.FourCCType.DX10 => dxt10!.Value.Format switch
{
Textures.DXT10Header.DXGIFormat.A8UNorm => ( TexFile.TextureFormat.A8, height * width ),
Textures.DXT10Header.DXGIFormat.R8G8B8A8UInt => ( TexFile.TextureFormat.A8R8G8B8, height * width * 4 ),
Textures.DXT10Header.DXGIFormat.R8G8UNorm => ( TexFile.TextureFormat.L8, height * width ),
Textures.DXT10Header.DXGIFormat.B8G8R8X8UNorm => ( TexFile.TextureFormat.R8G8B8X8, height * width * 4 ),
Textures.DXT10Header.DXGIFormat.B4G4R4A4UNorm => ( TexFile.TextureFormat.R4G4B4A4, height * width * 2 ),
Textures.DXT10Header.DXGIFormat.B5G5R5A1UNorm => ( TexFile.TextureFormat.R5G5B5A1, height * width * 2 ),
Textures.DXT10Header.DXGIFormat.R32Float => ( TexFile.TextureFormat.R32F, height * width * 4 ),
Textures.DXT10Header.DXGIFormat.R32G32B32A32Float => ( TexFile.TextureFormat.R32G32B32A32F, height * width * 16 ),
Textures.DXT10Header.DXGIFormat.R16G16Float => ( TexFile.TextureFormat.R16G16F, height * width * 4 ),
Textures.DXT10Header.DXGIFormat.R16G16B16A16Float => ( TexFile.TextureFormat.R16G16B16A16F, height * width * 8 ),
Textures.DXT10Header.DXGIFormat.D16UNorm => ( TexFile.TextureFormat.D16, height * width * 2 ),
Textures.DXT10Header.DXGIFormat.D24UNormS8UInt => ( TexFile.TextureFormat.D24S8, height * width * 4 ),
_ => ( TexFile.TextureFormat.A8R8G8B8, height * width * 4 ),
},
_ => ( TexFile.TextureFormat.A8R8G8B8, height * width * 4 ),
};
}
public class TextureImporter
{
private static void WriteHeader( byte[] target, int width, int height )
{
using var mem = new MemoryStream( target );
using var bw = new BinaryWriter( mem );
bw.Write( ( uint )TexFile.Attribute.TextureType2D );
bw.Write( ( uint )TexFile.TextureFormat.A8R8G8B8 );
bw.Write( ( ushort )width );
bw.Write( ( ushort )height );
bw.Write( ( ushort )1 );
bw.Write( ( ushort )1 );
bw.Write( 0 );
bw.Write( 1 );
bw.Write( 2 );
bw.Write( 80 );
for( var i = 1; i < 13; ++i )
{
bw.Write( 0 );
}
}
public static unsafe bool RgbaBytesToDds( byte[] rgba, int width, int height, out byte[] ddsData )
{
var header = new DdsHeader()
{
Caps1 = DdsHeader.DdsCaps1.Complex | DdsHeader.DdsCaps1.Texture | DdsHeader.DdsCaps1.MipMap,
Depth = 1,
Flags = DdsHeader.DdsFlags.Required | DdsHeader.DdsFlags.Pitch | DdsHeader.DdsFlags.MipMapCount,
Height = height,
Width = width,
PixelFormat = new PixelFormat()
{
Flags = PixelFormat.FormatFlags.AlphaPixels | PixelFormat.FormatFlags.RGB,
FourCC = 0,
BBitMask = 0x000000FF,
GBitMask = 0x0000FF00,
RBitMask = 0x00FF0000,
ABitMask = 0xFF000000,
Size = 32,
RgbBitCount = 32,
},
};
ddsData = new byte[DdsHeader.Size + rgba.Length];
header.Write( ddsData, 0 );
rgba.CopyTo( ddsData, DdsHeader.Size );
for( var i = 0; i < rgba.Length; i += 4 )
{
( ddsData[ DdsHeader.Size + i ], ddsData[ DdsHeader.Size + i + 2 ] )
= ( ddsData[ DdsHeader.Size + i + 2 ], ddsData[ DdsHeader.Size + i ] );
}
return true;
}
public static bool RgbaBytesToTex( byte[] rgba, int width, int height, out byte[] texData )
{
texData = Array.Empty< byte >();
if( rgba.Length != width * height * 4 )
{
return false;
}
texData = new byte[80 + width * height * 4];
WriteHeader( texData, width, height );
// RGBA to BGRA.
for( var i = 0; i < rgba.Length; i += 4 )
{
texData[ 80 + i + 0 ] = rgba[ i + 2 ];
texData[ 80 + i + 1 ] = rgba[ i + 1 ];
texData[ 80 + i + 2 ] = rgba[ i + 0 ];
texData[ 80 + i + 3 ] = rgba[ i + 3 ];
}
return true;
}
public static bool PngToTex( string inputFile, out byte[] texData )
{
using var file = File.OpenRead( inputFile );
var image = Image.Load< Bgra32 >( file );
var buffer = new byte[80 + image.Height * image.Width * 4];
WriteHeader( buffer, image.Width, image.Height );
var span = new Span< byte >( buffer, 80, buffer.Length - 80 );
image.CopyPixelDataTo( span );
texData = buffer;
return true;
}
public void Import( string inputFile )
{ }
}

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Reflection;
using System.Threading.Tasks;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Logging;
@ -13,7 +14,7 @@ using Lumina.Data.Files;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.ByteString;
using Penumbra.Import.Textures;
using Penumbra.Import.Dds;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
@ -35,6 +36,10 @@ public partial class ModEditWindow
private Matrix4x4 _multiplierLeft = Matrix4x4.Identity;
private Matrix4x4 _multiplierRight = Matrix4x4.Identity;
private bool _invertLeft = false;
private bool _invertRight = false;
private int _offsetX = 0;
private int _offsetY = 0;
private readonly FileDialogManager _dialogManager = new();
@ -147,19 +152,7 @@ public partial class ModEditWindow
return ( null, 0, 0 );
}
f.ConvertToTex( out var bytes );
TexFile tex = new();
tex.GetType().GetProperty( "Data",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy )
?.SetValue( tex, bytes );
tex.GetType().GetProperty( "FileStream",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy )
?.SetValue( tex, new MemoryStream( tex.Data ) );
tex.GetType().GetProperty( "Reader",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy )
?.SetValue( tex, new BinaryReader( tex.FileStream ) );
tex.LoadFile();
return ( tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height );
return ( f.RgbaData.ToArray(), f.Header.Width, f.Header.Height );
}
catch( Exception e )
{
@ -172,10 +165,26 @@ public partial class ModEditWindow
{
try
{
if( fromDisk )
{
var tmp = new TmpTexFile();
using var stream = File.OpenRead( path );
using var br = new BinaryReader( stream );
tmp.Load(br);
return (tmp.RgbaData, tmp.Header.Width, tmp.Header.Height);
}
var tex = fromDisk ? Dalamud.GameData.GameData.GetFileFromDisk< TexFile >( path ) : Dalamud.GameData.GetFile< TexFile >( path );
return tex == null
? ( null, 0, 0 )
: ( tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height );
if( tex == null )
{
return ( null, 0, 0 );
}
var rgba = tex.Header.Format == TexFile.TextureFormat.A8R8G8B8
? ImageParsing.DecodeUncompressedR8G8B8A8( tex.ImageData, tex.Header.Height, tex.Header.Width )
: tex.GetRgbaImageData();
return ( rgba, tex.Header.Width, tex.Header.Height );
}
catch( Exception e )
{
@ -250,7 +259,7 @@ public partial class ModEditWindow
UpdateCenter();
}
private static Vector4 CappedVector( IReadOnlyList< byte >? bytes, int offset, Matrix4x4 transform )
private static Vector4 CappedVector( IReadOnlyList< byte >? bytes, int offset, Matrix4x4 transform, bool invert )
{
if( bytes == null )
{
@ -259,6 +268,11 @@ public partial class ModEditWindow
var rgba = new Rgba32( bytes[ offset ], bytes[ offset + 1 ], bytes[ offset + 2 ], bytes[ offset + 3 ] );
var transformed = Vector4.Transform( rgba.ToVector4(), transform );
if( invert )
{
transformed = new Vector4( 1 - transformed.X, 1 - transformed.Y, 1 - transformed.Z, transformed.W );
}
transformed.X = Math.Clamp( transformed.X, 0, 1 );
transformed.Y = Math.Clamp( transformed.Y, 0, 1 );
transformed.Z = Math.Clamp( transformed.Z, 0, 1 );
@ -266,11 +280,32 @@ public partial class ModEditWindow
return transformed;
}
private Vector4 DataLeft( int offset )
=> CappedVector( _imageLeft, offset, _multiplierLeft, _invertLeft );
private Vector4 DataRight( int x, int y )
{
if( _imageRight == null )
{
return Vector4.Zero;
}
x -= _offsetX;
y -= _offsetY;
if( x < 0 || x >= _wrapRight!.Width || y < 0 || y >= _wrapRight!.Height )
{
return Vector4.Zero;
}
var offset = ( y * _wrapRight!.Width + x ) * 4;
return CappedVector( _imageRight, offset, _multiplierRight, _invertRight );
}
private void AddPixels( int width, int x, int y )
{
var offset = ( y * width + x ) * 4;
var left = CappedVector( _imageLeft, offset, _multiplierLeft );
var right = CappedVector( _imageRight, offset, _multiplierRight );
var offset = ( width * y + x ) * 4;
var left = DataLeft( offset );
var right = DataRight( x, y );
var alpha = right.W + left.W * ( 1 - right.W );
if( alpha == 0 )
{
@ -287,14 +322,14 @@ public partial class ModEditWindow
private void UpdateCenter()
{
if( _imageLeft != null && _imageRight == null && _multiplierLeft.IsIdentity )
if( _imageLeft != null && _imageRight == null && _multiplierLeft.IsIdentity && !_invertLeft )
{
_imageCenter = _imageLeft;
_wrapCenter = _wrapLeft;
return;
}
if( _imageLeft == null && _imageRight != null && _multiplierRight.IsIdentity )
if( _imageLeft == null && _imageRight != null && _multiplierRight.IsIdentity && !_invertRight )
{
_imageCenter = _imageRight;
_wrapCenter = _wrapRight;
@ -308,22 +343,19 @@ public partial class ModEditWindow
if( _imageLeft != null || _imageRight != null )
{
var (width, height) = _imageLeft != null ? ( _wrapLeft!.Width, _wrapLeft.Height ) : ( _wrapRight!.Width, _wrapRight.Height );
if( _imageRight == null || _wrapRight!.Width == width && _wrapRight!.Height == height )
var (totalWidth, totalHeight) =
_imageLeft != null ? ( _wrapLeft!.Width, _wrapLeft.Height ) : ( _wrapRight!.Width, _wrapRight.Height );
_imageCenter = new byte[4 * totalWidth * totalHeight];
Parallel.For( 0, totalHeight - 1, ( y, _ ) =>
{
_imageCenter = new byte[4 * width * height];
for( var y = 0; y < height; ++y )
for( var x = 0; x < totalWidth; ++x )
{
for( var x = 0; x < width; ++x )
{
AddPixels( width, x, y );
}
AddPixels( totalWidth, x, y );
}
_wrapCenter = Dalamud.PluginInterface.UiBuilder.LoadImageRaw( _imageCenter, width, height, 4 );
return;
}
} );
_wrapCenter = Dalamud.PluginInterface.UiBuilder.LoadImageRaw( _imageCenter, totalWidth, totalHeight, 4 );
return;
}
_imageCenter = null;
@ -334,6 +366,7 @@ public partial class ModEditWindow
{
if( wrap != null )
{
ImGui.TextUnformatted( $"Image Dimensions: {wrap.Width} x {wrap.Height}" );
size = size with { Y = wrap.Height * size.X / wrap.Width };
ImGui.Image( wrap.ImGuiHandle, size );
}
@ -397,7 +430,7 @@ public partial class ModEditWindow
{
_dialogManager.Draw();
using var tab = ImRaii.TabItem( "Texture Import/Export" );
using var tab = ImRaii.TabItem( "Texture Import/Export (WIP)" );
if( !tab )
{
return;
@ -412,7 +445,7 @@ public partial class ModEditWindow
PathInputBox( "##ImageLeft", "Import Image...", string.Empty, 0 );
ImGui.NewLine();
if( DrawMatrixInput( leftRightWidth.X, ref _multiplierLeft ) )
if( DrawMatrixInput( leftRightWidth.X, ref _multiplierLeft ) || ImGui.Checkbox( "Invert##Left", ref _invertLeft ) )
{
UpdateCenter();
}
@ -465,7 +498,7 @@ public partial class ModEditWindow
PathInputBox( "##ImageRight", "Import Image...", string.Empty, 1 );
ImGui.NewLine();
if( DrawMatrixInput( leftRightWidth.X, ref _multiplierRight ) )
if( DrawMatrixInput( leftRightWidth.X, ref _multiplierRight ) || ImGui.Checkbox( "Invert##Right", ref _invertRight ) )
{
UpdateCenter();
}