mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Prepare Texture import rework with OtterTex.
This commit is contained in:
parent
94096a8f3e
commit
9f3871eb6d
12 changed files with 318 additions and 1456 deletions
8
.gitmodules
vendored
8
.gitmodules
vendored
|
|
@ -1,4 +1,4 @@
|
|||
[submodule "OtterGui"]
|
||||
path = OtterGui
|
||||
url = git@github.com:Ottermandias/OtterGui.git
|
||||
branch = main
|
||||
[submodule "OtterGui"]
|
||||
path = OtterGui
|
||||
url = git@github.com:Ottermandias/OtterGui.git
|
||||
branch = main
|
||||
|
|
|
|||
|
|
@ -1,322 +0,0 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,236 +0,0 @@
|
|||
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,
|
||||
|
||||
ParseType.A16B16G16R16F => Header.Height * Header.Width * 8,
|
||||
_ => 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 ),
|
||||
ParseType.A16B16G16R16F => ImageParsing.DecodeUncompressedA16B16G16R16F( 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 );
|
||||
|
||||
if( type == ParseType.Unsupported )
|
||||
{
|
||||
PluginLog.Error( "DDS format unsupported." );
|
||||
return false;
|
||||
}
|
||||
|
||||
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.B8G8R8X8 ? 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.B8G8R8X8 && 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.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.BC5 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.Greyscale => ( TexFile.TextureFormat.A8, height * width ),
|
||||
ParseType.R4G4B4A4 => ( TexFile.TextureFormat.B4G4R4A4, height * width * 2 ),
|
||||
ParseType.B4G4R4A4 => ( TexFile.TextureFormat.B4G4R4A4, height * width * 2 ),
|
||||
ParseType.R5G5B5 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.B5G5R5 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.R5G6B5 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.B5G6R5 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.R5G5B5A1 => ( TexFile.TextureFormat.B5G5R5A1, height * width * 2 ),
|
||||
ParseType.B5G5R5A1 => ( TexFile.TextureFormat.B5G5R5A1, height * width * 2 ),
|
||||
ParseType.R8G8B8 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.B8G8R8 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.R8G8B8A8 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
ParseType.B8G8R8A8 => ( TexFile.TextureFormat.B8G8R8X8, height * width * 4 ),
|
||||
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
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 );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,601 +0,0 @@
|
|||
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 >> 11 ),
|
||||
G = ( byte )( ( c >> 5 ) & 0x3F ),
|
||||
B = ( byte )( c & 0x1F ),
|
||||
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;
|
||||
}
|
||||
|
||||
public static unsafe byte[] DecodeUncompressedA16B16G16R16F( ReadOnlySpan< byte > data, int height, int width )
|
||||
{
|
||||
Verify( data, height, width, 1, 8 );
|
||||
var ret = new byte[data.Length / 2];
|
||||
fixed( byte* r = ret, d = data )
|
||||
{
|
||||
var ptr = r;
|
||||
var input = ( Half* )d;
|
||||
var end = (Half*) (d + data.Length);
|
||||
while( input != end )
|
||||
{
|
||||
*ptr++ = ( byte )( byte.MaxValue * (float) *input++ );
|
||||
*ptr++ = ( byte )( byte.MaxValue * (float) *input++ );
|
||||
*ptr++ = ( byte )( byte.MaxValue * (float) *input++ );
|
||||
*ptr++ = ( byte )( byte.MaxValue * (float) *input++ );
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
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,
|
||||
|
||||
A16B16G16R16F,
|
||||
}
|
||||
|
||||
[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 ),
|
||||
D3D_A16B16G16R16 = 113,
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
FourCCType.D3D_A16B16G16R16 => ParseType.A16B16G16R16F,
|
||||
_ => 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,39 +28,6 @@ public static class TextureImporter
|
|||
}
|
||||
}
|
||||
|
||||
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 >();
|
||||
|
|
@ -72,6 +39,8 @@ public static class TextureImporter
|
|||
texData = new byte[80 + width * height * 4];
|
||||
WriteHeader( texData, width, height );
|
||||
rgba.CopyTo( texData.AsSpan( 80 ) );
|
||||
for( var i = 80; i < texData.Length; i += 4 )
|
||||
(texData[ i ], texData[i + 2]) = (texData[ i + 2], texData[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
@ -60,6 +61,9 @@
|
|||
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="OtterTex.dll">
|
||||
<HintPath>lib\OtterTex.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -74,8 +78,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="lib\DirectXTexC.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>DirectXTexC.dll</TargetPath>
|
||||
</None>
|
||||
<None Update="Penumbra.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,14 +12,221 @@ using ImGuiScene;
|
|||
using Lumina.Data.Files;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterTex;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Import.Dds;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
public struct Texture : IDisposable
|
||||
{
|
||||
// Path to the file we tried to load.
|
||||
public string Path = string.Empty;
|
||||
|
||||
// If the load failed, an exception is stored.
|
||||
public Exception? LoadError = null;
|
||||
|
||||
// The pixels of the main image in RGBA order.
|
||||
// Empty if LoadError != null or Path is empty.
|
||||
public byte[] RGBAPixels = Array.Empty< byte >();
|
||||
|
||||
// The ImGui wrapper to load the image.
|
||||
// null if LoadError != null or Path is empty.
|
||||
public TextureWrap? TextureWrap = null;
|
||||
|
||||
// The base image in whatever format it has.
|
||||
public object? BaseImage = null;
|
||||
|
||||
public Texture()
|
||||
{ }
|
||||
|
||||
private void Clean()
|
||||
{
|
||||
RGBAPixels = Array.Empty< byte >();
|
||||
TextureWrap?.Dispose();
|
||||
TextureWrap = null;
|
||||
( BaseImage as IDisposable )?.Dispose();
|
||||
BaseImage = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> Clean();
|
||||
|
||||
public bool Load( string path )
|
||||
{
|
||||
if( path == Path )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Path = path;
|
||||
Clean();
|
||||
return System.IO.Path.GetExtension( Path ) switch
|
||||
{
|
||||
".dds" => LoadDds(),
|
||||
".png" => LoadPng(),
|
||||
".tex" => LoadTex(),
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
private bool LoadDds()
|
||||
=> true;
|
||||
|
||||
private bool LoadPng()
|
||||
=> true;
|
||||
|
||||
private bool LoadTex()
|
||||
=> true;
|
||||
|
||||
public void PathInputBox( string label, string hint, string tooltip, string startPath, FileDialogManager manager )
|
||||
{
|
||||
var tmp = Path;
|
||||
using var spacing = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( 3 * ImGuiHelpers.GlobalScale, 0 ) );
|
||||
ImGui.SetNextItemWidth( -ImGui.GetFrameHeight() - 3 * ImGuiHelpers.GlobalScale );
|
||||
ImGui.InputTextWithHint( label, hint, ref tmp, Utf8GamePath.MaxGamePathLength );
|
||||
if( ImGui.IsItemDeactivatedAfterEdit() )
|
||||
{
|
||||
Load( tmp );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( tooltip );
|
||||
ImGui.SameLine();
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Folder.ToIconString(), new Vector2( ImGui.GetFrameHeight() ), string.Empty, false,
|
||||
true ) )
|
||||
{
|
||||
if( Penumbra.Config.DefaultModImportPath.Length > 0 )
|
||||
{
|
||||
startPath = Penumbra.Config.DefaultModImportPath;
|
||||
}
|
||||
|
||||
var texture = this;
|
||||
|
||||
void UpdatePath( bool success, List< string > paths )
|
||||
{
|
||||
if( success && paths.Count > 0 )
|
||||
{
|
||||
texture.Load( paths[ 0 ] );
|
||||
}
|
||||
}
|
||||
|
||||
manager.OpenFileDialog( "Open Image...", "Textures{.png,.dds,.tex}", UpdatePath, 1, startPath );
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture Combined( Texture left, Texture right, InputManipulations leftManips, InputManipulations rightManips )
|
||||
=> new Texture();
|
||||
}
|
||||
|
||||
public struct InputManipulations
|
||||
{
|
||||
public InputManipulations()
|
||||
{ }
|
||||
|
||||
public Matrix4x4 _multiplier = Matrix4x4.Identity;
|
||||
public bool _invert = false;
|
||||
public int _offsetX = 0;
|
||||
public int _offsetY = 0;
|
||||
public int _outputWidth = 0;
|
||||
public int _outputHeight = 0;
|
||||
|
||||
|
||||
private static Vector4 CappedVector( IReadOnlyList< byte >? bytes, int offset, Matrix4x4 transform, bool invert )
|
||||
{
|
||||
if( bytes == null )
|
||||
{
|
||||
return Vector4.Zero;
|
||||
}
|
||||
|
||||
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 );
|
||||
transformed.W = Math.Clamp( transformed.W, 0, 1 );
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private static bool DragFloat( string label, float width, ref float value )
|
||||
{
|
||||
var tmp = value;
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth( width );
|
||||
if( ImGui.DragFloat( label, ref tmp, 0.001f, -1f, 1f ) )
|
||||
{
|
||||
value = tmp;
|
||||
}
|
||||
|
||||
return ImGui.IsItemDeactivatedAfterEdit();
|
||||
}
|
||||
|
||||
|
||||
public bool DrawMatrixInput( float width )
|
||||
{
|
||||
using var table = ImRaii.Table( string.Empty, 5, ImGuiTableFlags.BordersInner | ImGuiTableFlags.SizingFixedFit );
|
||||
if( !table )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var changes = false;
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.Center( "R" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.Center( "G" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.Center( "B" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.Center( "A" );
|
||||
|
||||
var inputWidth = width / 6;
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text( "R " );
|
||||
changes |= DragFloat( "##RR", inputWidth, ref _multiplier.M11 );
|
||||
changes |= DragFloat( "##RG", inputWidth, ref _multiplier.M12 );
|
||||
changes |= DragFloat( "##RB", inputWidth, ref _multiplier.M13 );
|
||||
changes |= DragFloat( "##RA", inputWidth, ref _multiplier.M14 );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text( "G " );
|
||||
changes |= DragFloat( "##GR", inputWidth, ref _multiplier.M21 );
|
||||
changes |= DragFloat( "##GG", inputWidth, ref _multiplier.M22 );
|
||||
changes |= DragFloat( "##GB", inputWidth, ref _multiplier.M23 );
|
||||
changes |= DragFloat( "##GA", inputWidth, ref _multiplier.M24 );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text( "B " );
|
||||
changes |= DragFloat( "##BR", inputWidth, ref _multiplier.M31 );
|
||||
changes |= DragFloat( "##BG", inputWidth, ref _multiplier.M32 );
|
||||
changes |= DragFloat( "##BB", inputWidth, ref _multiplier.M33 );
|
||||
changes |= DragFloat( "##BA", inputWidth, ref _multiplier.M34 );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text( "A " );
|
||||
changes |= DragFloat( "##AR", inputWidth, ref _multiplier.M41 );
|
||||
changes |= DragFloat( "##AG", inputWidth, ref _multiplier.M42 );
|
||||
changes |= DragFloat( "##AB", inputWidth, ref _multiplier.M43 );
|
||||
changes |= DragFloat( "##AA", inputWidth, ref _multiplier.M44 );
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private string _pathLeft = string.Empty;
|
||||
|
|
@ -145,13 +352,13 @@ public partial class ModEditWindow
|
|||
{
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead( path );
|
||||
if( !DdsFile.Load( stream, out var f ) )
|
||||
{
|
||||
if (!ScratchImage.LoadDDS( path, out var f ))
|
||||
return ( null, 0, 0 );
|
||||
}
|
||||
|
||||
return ( f.RgbaData.ToArray(), f.Header.Width, f.Header.Height );
|
||||
if(!f.GetRGBA( out f ))
|
||||
return ( null, 0, 0 );
|
||||
|
||||
return ( f.Pixels[ ..(f.Meta.Width * f.Meta.Height * 4) ].ToArray(), f.Meta.Width, f.Meta.Height );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
|
|
@ -170,9 +377,7 @@ public partial class ModEditWindow
|
|||
return ( null, 0, 0 );
|
||||
}
|
||||
|
||||
var rgba = tex.Header.Format == TexFile.TextureFormat.B8G8R8A8
|
||||
? ImageParsing.DecodeUncompressedR8G8B8A8( tex.ImageData, tex.Header.Height, tex.Header.Width )
|
||||
: tex.GetRgbaImageData();
|
||||
var rgba = tex.GetRgbaImageData();
|
||||
return ( rgba, tex.Header.Width, tex.Header.Height );
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
@ -402,10 +607,11 @@ public partial class ModEditWindow
|
|||
|
||||
break;
|
||||
case 2:
|
||||
if( TextureImporter.RgbaBytesToDds( _imageCenter, _wrapCenter.Width, _wrapCenter.Height, out var dds ) )
|
||||
{
|
||||
File.WriteAllBytes( path, dds );
|
||||
}
|
||||
//ScratchImage.LoadDDS( _imageCenter, )
|
||||
//if( TextureImporter.RgbaBytesToDds( _imageCenter, _wrapCenter.Width, _wrapCenter.Height, out var dds ) )
|
||||
//{
|
||||
// File.WriteAllBytes( path, dds );
|
||||
//}
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -509,4 +715,4 @@ public partial class ModEditWindow
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
Penumbra/lib/DirectXTexC.dll
Normal file
BIN
Penumbra/lib/DirectXTexC.dll
Normal file
Binary file not shown.
BIN
Penumbra/lib/OtterTex.dll
Normal file
BIN
Penumbra/lib/OtterTex.dll
Normal file
Binary file not shown.
84
Penumbra/packages.lock.json
Normal file
84
Penumbra/packages.lock.json
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net6.0-windows7.0": {
|
||||
"EmbedIO": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.4.3, )",
|
||||
"resolved": "3.4.3",
|
||||
"contentHash": "YM6hpZNAfvbbixfG9T4lWDGfF0D/TqutbTROL4ogVcHKwPF1hp+xS3ABwd3cxxTxvDFkj/zZl57QgWuFA8Igxw==",
|
||||
"dependencies": {
|
||||
"Unosquare.Swan.Lite": "3.0.0"
|
||||
}
|
||||
},
|
||||
"SharpCompress": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.32.1, )",
|
||||
"resolved": "0.32.1",
|
||||
"contentHash": "9Cwj3lK/p7wkiBaQPCvaKINuHYuZ0ACDldA4M3o5ISSq7cjbbq3yqigTDBUoKjtyyXpqmQHUkw6fhLnjNF30ow=="
|
||||
},
|
||||
"SixLabors.ImageSharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.1.2, )",
|
||||
"resolved": "2.1.2",
|
||||
"contentHash": "In0pC521LqJXJXZgFVHegvSzES10KkKRN31McxqA1+fKtKsNe+EShWavBFQnKRlXCdeAmfx/wDjLILbvCaq+8Q==",
|
||||
"dependencies": {
|
||||
"System.Runtime.CompilerServices.Unsafe": "5.0.0",
|
||||
"System.Text.Encoding.CodePages": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.NETCore.Platforms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ=="
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA=="
|
||||
},
|
||||
"System.Text.Encoding.CodePages": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "5.0.0"
|
||||
}
|
||||
},
|
||||
"System.ValueTuple": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
|
||||
},
|
||||
"Unosquare.Swan.Lite": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "noPwJJl1Q9uparXy1ogtkmyAPGNfSGb0BLT1292nFH1jdMKje6o2kvvrQUvF9Xklj+IoiAI0UzF6Aqxlvo10lw==",
|
||||
"dependencies": {
|
||||
"System.ValueTuple": "4.5.0"
|
||||
}
|
||||
},
|
||||
"DirectXTex": {
|
||||
"type": "Project"
|
||||
},
|
||||
"directxtexc": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"DirectXTex": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"ottergui": {
|
||||
"type": "Project"
|
||||
},
|
||||
"ottertex": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"DirectXTexC": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"penumbra.gamedata": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue