From 7b41d97bd2b7de6b049b9bce98e7d3ddf5ff12f7 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:33:48 +0100 Subject: [PATCH 01/37] Add basis for ASTC decoding --- .editorconfig | 20 +- .gitattributes | 10 + .github/CONTRIBUTING.md | 38 +++ .github/workflows/build-and-test.yml | 22 +- .gitignore | 1 + LICENSE | 216 ++------------- README.md | 8 +- shared-infrastructure | 2 +- src/Directory.Build.props | 2 +- .../Formats/Ktx/KtxProcessor.cs | 62 ++++- .../Formats/Ktx2/Ktx2DecoderCore.cs | 35 ++- .../Formats/Ktx2/Ktx2Processor.cs | 84 ++++++ .../ImageSharp.Textures.csproj | 3 +- .../TextureFormats/Decoding/AstcDecoder.cs | 32 +++ .../TextureFormats/Decoding/RgbaAstc10x10.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc10x5.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc10x6.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc10x8.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc12x10.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc12x12.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc4x4.cs | 90 +++++++ .../TextureFormats/Decoding/RgbaAstc5x4.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc5x5.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc6x5.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc6x6.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc8x5.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc8x6.cs | 84 ++++++ .../TextureFormats/Decoding/RgbaAstc8x8.cs | 84 ++++++ tests/Directory.Build.props | 2 +- tests/Directory.Build.targets | 10 +- .../Astc/AstcAppliedPixelsComparisonTests.cs | 136 ++++++++++ .../Formats/Astc/AstcImageComparisonTests.cs | 74 ++++++ .../Formats/Astc/AstcKtx2DecoderTests.cs | 249 ++++++++++++++++++ .../Formats/Ktx/KtxDecoderTests.cs | 32 +++ .../ImageSharp.Textures.Tests.csproj | 5 +- tests/ImageSharp.Textures.Tests/TestImages.cs | 44 ++++ .../Baseline/AstcDebug/atlas_small_8x8.txt | 11 + .../Baseline/AstcDebug/footprint_10x10.txt | 11 + .../Baseline/AstcDebug/footprint_10x5.txt | 11 + .../Baseline/AstcDebug/footprint_12x10.txt | 11 + .../Images/Expected/ASTC/atlas_small_4x4.bmp | 3 + .../Images/Expected/ASTC/atlas_small_5x5.bmp | 3 + .../Images/Expected/ASTC/atlas_small_6x6.bmp | 3 + .../Images/Expected/ASTC/atlas_small_8x8.bmp | 3 + .../Images/Expected/ASTC/footprint_10x10.bmp | 3 + tests/Images/Expected/ASTC/footprint_10x5.bmp | 3 + tests/Images/Expected/ASTC/footprint_10x6.bmp | 3 + tests/Images/Expected/ASTC/footprint_10x8.bmp | 3 + .../Images/Expected/ASTC/footprint_12x10.bmp | 3 + .../Images/Expected/ASTC/footprint_12x12.bmp | 3 + tests/Images/Expected/ASTC/footprint_4x4.bmp | 3 + tests/Images/Expected/ASTC/footprint_5x4.bmp | 3 + tests/Images/Expected/ASTC/footprint_5x5.bmp | 3 + tests/Images/Expected/ASTC/footprint_6x5.bmp | 3 + tests/Images/Expected/ASTC/footprint_6x6.bmp | 3 + tests/Images/Expected/ASTC/footprint_8x5.bmp | 3 + tests/Images/Expected/ASTC/footprint_8x6.bmp | 3 + tests/Images/Expected/ASTC/footprint_8x8.bmp | 3 + tests/Images/Expected/ASTC/rgb_12x12.bmp | 3 + tests/Images/Expected/ASTC/rgb_4x4.bmp | 3 + tests/Images/Expected/ASTC/rgb_5x4.bmp | 3 + tests/Images/Expected/ASTC/rgb_6x6.bmp | 3 + tests/Images/Expected/ASTC/rgb_8x8.bmp | 3 + .../Expected/FlightHelmet_baseColor.png | 3 + .../Expected/GoldenGateBridge3/filelist.txt | 6 + .../Expected/GoldenGateBridge3/negx.jpg | 3 + .../Expected/GoldenGateBridge3/negy.jpg | 3 + .../Expected/GoldenGateBridge3/negz.jpg | 3 + .../Expected/GoldenGateBridge3/posx.jpg | 3 + .../Expected/GoldenGateBridge3/posy.jpg | 3 + .../Expected/GoldenGateBridge3/posz.jpg | 3 + .../Expected/GoldenGateBridge3/readme.txt | 13 + .../Iron_Bars/Iron_Bars_001_Opacity.jpg | 3 + .../Iron_Bars_001_ambientOcclusion.jpg | 3 + .../Iron_Bars/Iron_Bars_001_basecolor.jpg | 3 + .../Iron_Bars/Iron_Bars_001_height.png | 3 + .../Iron_Bars/Iron_Bars_001_normal.jpg | 3 + .../Iron_Bars_001_normal_unnormalized.png | 3 + .../Iron_Bars/Iron_Bars_001_roughness.jpg | 3 + tests/Images/Expected/Iron_Bars/readme.txt | 11 + tests/Images/Expected/Yokohama3/filelist.txt | 6 + tests/Images/Expected/Yokohama3/negx.jpg | 3 + tests/Images/Expected/Yokohama3/negy.jpg | 3 + tests/Images/Expected/Yokohama3/negz.jpg | 3 + tests/Images/Expected/Yokohama3/posx.jpg | 3 + tests/Images/Expected/Yokohama3/posy.jpg | 3 + tests/Images/Expected/Yokohama3/posz.jpg | 3 + tests/Images/Expected/Yokohama3/readme.txt | 13 + tests/Images/Input/Astc/atlas_small_4x4.astc | Bin 0 -> 65551 bytes tests/Images/Input/Astc/atlas_small_5x5.astc | Bin 0 -> 43280 bytes tests/Images/Input/Astc/atlas_small_6x6.astc | Bin 0 -> 29599 bytes tests/Images/Input/Astc/atlas_small_8x8.astc | Bin 0 -> 16400 bytes tests/Images/Input/Astc/checkerboard.astc | Bin 0 -> 80 bytes tests/Images/Input/Astc/checkered_10.astc | Bin 0 -> 1616 bytes tests/Images/Input/Astc/checkered_11.astc | Bin 0 -> 1952 bytes tests/Images/Input/Astc/checkered_12.astc | Bin 0 -> 2320 bytes tests/Images/Input/Astc/checkered_4.astc | Bin 0 -> 272 bytes tests/Images/Input/Astc/checkered_5.astc | Bin 0 -> 416 bytes tests/Images/Input/Astc/checkered_6.astc | Bin 0 -> 592 bytes tests/Images/Input/Astc/checkered_7.astc | Bin 0 -> 800 bytes tests/Images/Input/Astc/checkered_8.astc | Bin 0 -> 1040 bytes tests/Images/Input/Astc/checkered_9.astc | Bin 0 -> 1312 bytes tests/Images/Input/Astc/footprint_10x10.astc | Bin 0 -> 272 bytes tests/Images/Input/Astc/footprint_10x5.astc | Bin 0 -> 464 bytes tests/Images/Input/Astc/footprint_10x6.astc | Bin 0 -> 400 bytes tests/Images/Input/Astc/footprint_10x8.astc | Bin 0 -> 272 bytes tests/Images/Input/Astc/footprint_12x10.astc | Bin 0 -> 208 bytes tests/Images/Input/Astc/footprint_12x12.astc | Bin 0 -> 160 bytes tests/Images/Input/Astc/footprint_4x4.astc | Bin 0 -> 1040 bytes tests/Images/Input/Astc/footprint_5x4.astc | Bin 0 -> 912 bytes tests/Images/Input/Astc/footprint_5x5.astc | Bin 0 -> 800 bytes tests/Images/Input/Astc/footprint_6x5.astc | Bin 0 -> 688 bytes tests/Images/Input/Astc/footprint_6x6.astc | Bin 0 -> 592 bytes tests/Images/Input/Astc/footprint_8x5.astc | Bin 0 -> 464 bytes tests/Images/Input/Astc/footprint_8x6.astc | Bin 0 -> 400 bytes tests/Images/Input/Astc/footprint_8x8.astc | Bin 0 -> 272 bytes tests/Images/Input/Astc/rgb_12x12.astc | Bin 0 -> 7312 bytes tests/Images/Input/Astc/rgb_4x4.astc | Bin 0 -> 64528 bytes tests/Images/Input/Astc/rgb_5x4.astc | Bin 0 -> 51855 bytes tests/Images/Input/Astc/rgb_6x6.astc | Bin 0 -> 29200 bytes tests/Images/Input/Astc/rgb_8x8.astc | Bin 0 -> 16144 bytes .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/10.0/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/10.0/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/10.0/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/10.0/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/10.0/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/10.0/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/10.0/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/10.0/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/10.0/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/10.0/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/10.0/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/10.0/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/10.0/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/10.0/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/10.0/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/10.0/{flat YUY2.DDS => flat YUY2.dds} | 0 .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/10.1/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/10.1/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/10.1/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/10.1/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/10.1/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/10.1/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/10.1/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/10.1/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/10.1/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/10.1/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/10.1/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/10.1/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/10.1/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/10.1/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/10.1/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/10.1/{flat YUY2.DDS => flat YUY2.dds} | 0 .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/11.0/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/11.0/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/11.0/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/11.0/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/11.0/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/11.0/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/11.0/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/11.0/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/11.0/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/11.0/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/11.0/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/11.0/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/11.0/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/11.0/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/11.0/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/11.0/{flat YUY2.DDS => flat YUY2.dds} | 0 .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/11.1/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/11.1/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/11.1/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/11.1/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/11.1/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/11.1/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/11.1/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/11.1/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/11.1/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/11.1/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/11.1/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/11.1/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/11.1/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/11.1/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/11.1/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/11.1/{flat YUY2.DDS => flat YUY2.dds} | 0 .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/12.0/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/12.0/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/12.0/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/12.0/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/12.0/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/12.0/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/12.0/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/12.0/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/12.0/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/12.0/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/12.0/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/12.0/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/12.0/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/12.0/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/12.0/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/12.0/{flat YUY2.DDS => flat YUY2.dds} | 0 .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/12.1/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/12.1/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/12.1/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/12.1/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/12.1/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/12.1/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/12.1/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/12.1/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/12.1/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/12.1/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/12.1/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/12.1/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/12.1/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/12.1/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/12.1/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/12.1/{flat YUY2.DDS => flat YUY2.dds} | 0 .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/9.1/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/9.1/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/9.1/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/9.1/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/9.1/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/9.1/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/9.1/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/9.1/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/9.1/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/9.1/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/9.1/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/9.1/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/9.1/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/9.1/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/9.1/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/9.1/{flat YUY2.DDS => flat YUY2.dds} | 0 .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/9.2/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/9.2/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/9.2/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/9.2/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/9.2/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/9.2/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/9.2/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/9.2/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/9.2/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/9.2/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/9.2/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/9.2/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/9.2/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/9.2/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/9.2/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/9.2/{flat YUY2.DDS => flat YUY2.dds} | 0 .../{flat A8_UNORM.DDS => flat A8_UNORM.dds} | 0 .../Dds/9.3/{flat AYUV.DDS => flat AYUV.dds} | 0 ...R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} | 0 ...R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} | 0 ...B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} | 0 ...R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} | 0 ...R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} | 0 ..._SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} | 0 ...{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} | 0 ...{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} | 0 ...{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} | 0 ...{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} | 0 ...{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} | 0 ...{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} | 0 ...{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} | 0 ...{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} | 0 ...{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} | 0 ...{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} | 0 ...UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} | 0 .../Dds/9.3/{flat BGRA.DDS => flat BGRA.dds} | 0 .../Dds/9.3/{flat BPTC.DDS => flat BPTC.dds} | 0 ...lat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} | 0 .../Dds/9.3/{flat DXT1.DDS => flat DXT1.dds} | 0 .../Dds/9.3/{flat DXT2.DDS => flat DXT2.dds} | 0 .../Dds/9.3/{flat DXT3.DDS => flat DXT3.dds} | 0 .../Dds/9.3/{flat DXT4.DDS => flat DXT4.dds} | 0 .../Dds/9.3/{flat DXT5.DDS => flat DXT5.dds} | 0 .../Dds/9.3/{flat FP16.DDS => flat FP16.dds} | 0 .../Dds/9.3/{flat FP32.DDS => flat FP32.dds} | 0 ...8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} | 0 ...0A2_UINT.DDS => flat R10G10B10A2_UINT.dds} | 0 ...2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} | 0 ...DS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} | 0 ...B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} | 0 ..._FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} | 0 ...16_SINT.DDS => flat R16G16B16A16_SINT.dds} | 0 ..._SNORM.DDS => flat R16G16B16A16_SNORM.dds} | 0 ...16_UINT.DDS => flat R16G16B16A16_UINT.dds} | 0 ..._UNORM.DDS => flat R16G16B16A16_UNORM.dds} | 0 ...R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} | 0 ...t R16G16_SINT.DDS => flat R16G16_SINT.dds} | 0 ...R16G16_SNORM.DDS => flat R16G16_SNORM.dds} | 0 ...t R16G16_UINT.DDS => flat R16G16_UINT.dds} | 0 ...R16G16_UNORM.DDS => flat R16G16_UNORM.dds} | 0 ...{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} | 0 .../{flat R16_SINT.DDS => flat R16_SINT.dds} | 0 ...{flat R16_SNORM.DDS => flat R16_SNORM.dds} | 0 .../{flat R16_UINT.DDS => flat R16_UINT.dds} | 0 ...{flat R16_UNORM.DDS => flat R16_UNORM.dds} | 0 ..._FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} | 0 ...32_SINT.DDS => flat R32G32B32A32_SINT.dds} | 0 ...32_UINT.DDS => flat R32G32B32A32_UINT.dds} | 0 ...B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} | 0 ...32B32_SINT.DDS => flat R32G32B32_SINT.dds} | 0 ...32B32_UINT.DDS => flat R32G32B32_UINT.dds} | 0 ...R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} | 0 ...t R32G32_SINT.DDS => flat R32G32_SINT.dds} | 0 ...t R32G32_UINT.DDS => flat R32G32_UINT.dds} | 0 ...{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} | 0 .../{flat R32_SINT.DDS => flat R32_SINT.dds} | 0 .../{flat R32_UINT.DDS => flat R32_UINT.dds} | 0 ...G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} | 0 ...B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} | 0 ...G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} | 0 ...B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} | 0 ..._SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} | 0 ...8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} | 0 ...{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} | 0 ...lat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} | 0 ...{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} | 0 ...lat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} | 0 .../{flat R8_SINT.DDS => flat R8_SINT.dds} | 0 .../{flat R8_SNORM.DDS => flat R8_SNORM.dds} | 0 .../{flat R8_UINT.DDS => flat R8_UINT.dds} | 0 .../{flat R8_UNORM.DDS => flat R8_UNORM.dds} | 0 ...REDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} | 0 .../Dds/9.3/{flat RGBA.DDS => flat RGBA.dds} | 0 .../Dds/9.3/{flat Y210.DDS => flat Y210.dds} | 0 .../Dds/9.3/{flat Y216.DDS => flat Y216.dds} | 0 .../Dds/9.3/{flat Y410.DDS => flat Y410.dds} | 0 .../Dds/9.3/{flat Y416.DDS => flat Y416.dds} | 0 .../Dds/9.3/{flat YUY2.DDS => flat YUY2.dds} | 0 .../astc_ldr_10x5_FlightHelmet_baseColor.ktx2 | 3 + ...astc_ldr_12x10_FlightHelmet_baseColor.ktx2 | 3 + ...astc_ldr_12x12_FlightHelmet_baseColor.ktx2 | 3 + .../astc_ldr_4x4_FlightHelmet_baseColor.ktx2 | 3 + .../astc_ldr_5x4_Iron_Bars_001_normal.ktx2 | 3 + .../astc_ldr_6x5_FlightHelmet_baseColor.ktx2 | 3 + .../Input/Ktx2/astc_ldr_6x6_3dtex_7.ktx2 | 3 + .../astc_ldr_6x6_Iron_Bars_001_normal.ktx2 | 3 + .../Input/Ktx2/astc_ldr_6x6_arraytex_7.ktx2 | 3 + .../Ktx2/astc_ldr_6x6_arraytex_7_mipmap.ktx2 | 3 + .../Images/Input/Ktx2/astc_ldr_6x6_posx.ktx2 | 3 + .../astc_ldr_8x6_FlightHelmet_baseColor.ktx2 | 3 + .../astc_ldr_8x8_FlightHelmet_baseColor.ktx2 | 3 + .../Input/Ktx2/astc_ldr_cubemap_6x6.ktx2 | 3 + .../Input/Ktx2/astc_mipmap_ldr_10x5_posx.ktx2 | 3 + .../Ktx2/astc_mipmap_ldr_12x10_posx.ktx2 | 3 + .../Ktx2/astc_mipmap_ldr_12x12_posx.ktx2 | 3 + .../Input/Ktx2/astc_mipmap_ldr_4x4_posx.ktx2 | 3 + .../Input/Ktx2/astc_mipmap_ldr_6x5_posx.ktx2 | 3 + .../astc_mipmap_ldr_6x6_kodim17_fast.ktx2 | 3 + .../astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 | 3 + .../astc_mipmap_ldr_6x6_kodim17_medium.ktx2 | 3 + .../Input/Ktx2/astc_mipmap_ldr_6x6_posx.ktx2 | 3 + .../Input/Ktx2/astc_mipmap_ldr_6x6_posy.ktx2 | 3 + .../Input/Ktx2/astc_mipmap_ldr_6x6_posz.ktx2 | 3 + .../Input/Ktx2/astc_mipmap_ldr_8x6_posx.ktx2 | 3 + .../Input/Ktx2/astc_mipmap_ldr_8x8_posx.ktx2 | 3 + .../Ktx2/astc_mipmap_ldr_cubemap_6x6.ktx2 | 3 + .../Ktx2/invalid_color_model_for_basislz.ktx2 | 3 + .../Ktx2/invalid_face_count_and_padding.ktx2 | 3 + .../Ktx2/invalid_mip_layout_and_padding.ktx2 | 3 + .../Input/Ktx2/invalid_nul_on_kvd_val.ktx2 | 3 + tests/Images/Input/Ktx2/invalid_typesize.ktx2 | 3 + 928 files changed, 2360 insertions(+), 230 deletions(-) create mode 100644 .github/CONTRIBUTING.md create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs create mode 100644 src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs create mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/AstcAppliedPixelsComparisonTests.cs create mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs create mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs create mode 100644 tests/Images/Baseline/AstcDebug/atlas_small_8x8.txt create mode 100644 tests/Images/Baseline/AstcDebug/footprint_10x10.txt create mode 100644 tests/Images/Baseline/AstcDebug/footprint_10x5.txt create mode 100644 tests/Images/Baseline/AstcDebug/footprint_12x10.txt create mode 100644 tests/Images/Expected/ASTC/atlas_small_4x4.bmp create mode 100644 tests/Images/Expected/ASTC/atlas_small_5x5.bmp create mode 100644 tests/Images/Expected/ASTC/atlas_small_6x6.bmp create mode 100644 tests/Images/Expected/ASTC/atlas_small_8x8.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_10x10.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_10x5.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_10x6.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_10x8.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_12x10.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_12x12.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_4x4.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_5x4.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_5x5.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_6x5.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_6x6.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_8x5.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_8x6.bmp create mode 100644 tests/Images/Expected/ASTC/footprint_8x8.bmp create mode 100644 tests/Images/Expected/ASTC/rgb_12x12.bmp create mode 100644 tests/Images/Expected/ASTC/rgb_4x4.bmp create mode 100644 tests/Images/Expected/ASTC/rgb_5x4.bmp create mode 100644 tests/Images/Expected/ASTC/rgb_6x6.bmp create mode 100644 tests/Images/Expected/ASTC/rgb_8x8.bmp create mode 100644 tests/Images/Expected/FlightHelmet_baseColor.png create mode 100644 tests/Images/Expected/GoldenGateBridge3/filelist.txt create mode 100644 tests/Images/Expected/GoldenGateBridge3/negx.jpg create mode 100644 tests/Images/Expected/GoldenGateBridge3/negy.jpg create mode 100644 tests/Images/Expected/GoldenGateBridge3/negz.jpg create mode 100644 tests/Images/Expected/GoldenGateBridge3/posx.jpg create mode 100644 tests/Images/Expected/GoldenGateBridge3/posy.jpg create mode 100644 tests/Images/Expected/GoldenGateBridge3/posz.jpg create mode 100644 tests/Images/Expected/GoldenGateBridge3/readme.txt create mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg create mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg create mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg create mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_height.png create mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal.jpg create mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png create mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg create mode 100644 tests/Images/Expected/Iron_Bars/readme.txt create mode 100644 tests/Images/Expected/Yokohama3/filelist.txt create mode 100644 tests/Images/Expected/Yokohama3/negx.jpg create mode 100644 tests/Images/Expected/Yokohama3/negy.jpg create mode 100644 tests/Images/Expected/Yokohama3/negz.jpg create mode 100644 tests/Images/Expected/Yokohama3/posx.jpg create mode 100644 tests/Images/Expected/Yokohama3/posy.jpg create mode 100644 tests/Images/Expected/Yokohama3/posz.jpg create mode 100644 tests/Images/Expected/Yokohama3/readme.txt create mode 100644 tests/Images/Input/Astc/atlas_small_4x4.astc create mode 100644 tests/Images/Input/Astc/atlas_small_5x5.astc create mode 100644 tests/Images/Input/Astc/atlas_small_6x6.astc create mode 100644 tests/Images/Input/Astc/atlas_small_8x8.astc create mode 100644 tests/Images/Input/Astc/checkerboard.astc create mode 100644 tests/Images/Input/Astc/checkered_10.astc create mode 100644 tests/Images/Input/Astc/checkered_11.astc create mode 100644 tests/Images/Input/Astc/checkered_12.astc create mode 100644 tests/Images/Input/Astc/checkered_4.astc create mode 100644 tests/Images/Input/Astc/checkered_5.astc create mode 100644 tests/Images/Input/Astc/checkered_6.astc create mode 100644 tests/Images/Input/Astc/checkered_7.astc create mode 100644 tests/Images/Input/Astc/checkered_8.astc create mode 100644 tests/Images/Input/Astc/checkered_9.astc create mode 100644 tests/Images/Input/Astc/footprint_10x10.astc create mode 100644 tests/Images/Input/Astc/footprint_10x5.astc create mode 100644 tests/Images/Input/Astc/footprint_10x6.astc create mode 100644 tests/Images/Input/Astc/footprint_10x8.astc create mode 100644 tests/Images/Input/Astc/footprint_12x10.astc create mode 100644 tests/Images/Input/Astc/footprint_12x12.astc create mode 100644 tests/Images/Input/Astc/footprint_4x4.astc create mode 100644 tests/Images/Input/Astc/footprint_5x4.astc create mode 100644 tests/Images/Input/Astc/footprint_5x5.astc create mode 100644 tests/Images/Input/Astc/footprint_6x5.astc create mode 100644 tests/Images/Input/Astc/footprint_6x6.astc create mode 100644 tests/Images/Input/Astc/footprint_8x5.astc create mode 100644 tests/Images/Input/Astc/footprint_8x6.astc create mode 100644 tests/Images/Input/Astc/footprint_8x8.astc create mode 100644 tests/Images/Input/Astc/rgb_12x12.astc create mode 100644 tests/Images/Input/Astc/rgb_4x4.astc create mode 100644 tests/Images/Input/Astc/rgb_5x4.astc create mode 100644 tests/Images/Input/Astc/rgb_6x6.astc create mode 100644 tests/Images/Input/Astc/rgb_8x8.astc rename tests/Images/Input/Dds/10.0/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/10.0/{flat YUY2.DDS => flat YUY2.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/10.1/{flat YUY2.DDS => flat YUY2.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/11.0/{flat YUY2.DDS => flat YUY2.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/11.1/{flat YUY2.DDS => flat YUY2.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/12.0/{flat YUY2.DDS => flat YUY2.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/12.1/{flat YUY2.DDS => flat YUY2.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/9.1/{flat YUY2.DDS => flat YUY2.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/9.2/{flat YUY2.DDS => flat YUY2.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat A8_UNORM.DDS => flat A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat AYUV.DDS => flat AYUV.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat B4G4R4A4_UNORM.DDS => flat B4G4R4A4_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat B5G5R5A1_UNORM.DDS => flat B5G5R5A1_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat B5G6R5_UNORM.DDS => flat B5G6R5_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat B8G8R8A8_UNORM.DDS => flat B8G8R8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat B8G8R8A8_UNORM_SRGB.DDS => flat B8G8R8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat B8G8R8X8_UNORM.DDS => flat B8G8R8X8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat B8G8R8X8_UNORM_SRGB.DDS => flat B8G8R8X8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC1_UNORM.DDS => flat BC1_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC1_UNORM_SRGB.DDS => flat BC1_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC2_UNORM.DDS => flat BC2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC2_UNORM_SRGB.DDS => flat BC2_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC3_UNORM.DDS => flat BC3_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC3_UNORM_SRGB.DDS => flat BC3_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC4_SNORM.DDS => flat BC4_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC4_UNORM.DDS => flat BC4_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC5_SNORM.DDS => flat BC5_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC5_UNORM.DDS => flat BC5_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC6H_SF16.DDS => flat BC6H_SF16.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC6H_UF16.DDS => flat BC6H_UF16.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC7_UNORM.DDS => flat BC7_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BC7_UNORM_SRGB.DDS => flat BC7_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BGRA.DDS => flat BGRA.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BPTC.DDS => flat BPTC.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat BPTC_FLOAT.DDS => flat BPTC_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat DXT1.DDS => flat DXT1.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat DXT2.DDS => flat DXT2.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat DXT3.DDS => flat DXT3.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat DXT4.DDS => flat DXT4.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat DXT5.DDS => flat DXT5.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat FP16.DDS => flat FP16.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat FP32.DDS => flat FP32.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat G8R8_G8B8_UNORM.DDS => flat G8R8_G8B8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R10G10B10A2_UINT.DDS => flat R10G10B10A2_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R10G10B10A2_UNORM.DDS => flat R10G10B10A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R10G10B10_XR_BIAS_A2_UNORM.DDS => flat R10G10B10_XR_BIAS_A2_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R11G11B10_FLOAT.DDS => flat R11G11B10_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16B16A16_FLOAT.DDS => flat R16G16B16A16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16B16A16_SINT.DDS => flat R16G16B16A16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16B16A16_SNORM.DDS => flat R16G16B16A16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16B16A16_UINT.DDS => flat R16G16B16A16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16B16A16_UNORM.DDS => flat R16G16B16A16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16_FLOAT.DDS => flat R16G16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16_SINT.DDS => flat R16G16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16_SNORM.DDS => flat R16G16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16_UINT.DDS => flat R16G16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16G16_UNORM.DDS => flat R16G16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16_FLOAT.DDS => flat R16_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16_SINT.DDS => flat R16_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16_SNORM.DDS => flat R16_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16_UINT.DDS => flat R16_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R16_UNORM.DDS => flat R16_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32B32A32_FLOAT.DDS => flat R32G32B32A32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32B32A32_SINT.DDS => flat R32G32B32A32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32B32A32_UINT.DDS => flat R32G32B32A32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32B32_FLOAT.DDS => flat R32G32B32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32B32_SINT.DDS => flat R32G32B32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32B32_UINT.DDS => flat R32G32B32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32_FLOAT.DDS => flat R32G32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32_SINT.DDS => flat R32G32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32G32_UINT.DDS => flat R32G32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32_FLOAT.DDS => flat R32_FLOAT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32_SINT.DDS => flat R32_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R32_UINT.DDS => flat R32_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8B8A8_SINT.DDS => flat R8G8B8A8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8B8A8_SNORM.DDS => flat R8G8B8A8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8B8A8_UINT.DDS => flat R8G8B8A8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8B8A8_UNORM.DDS => flat R8G8B8A8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8B8A8_UNORM_SRGB.DDS => flat R8G8B8A8_UNORM_SRGB.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8_B8G8_UNORM.DDS => flat R8G8_B8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8_SINT.DDS => flat R8G8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8_SNORM.DDS => flat R8G8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8_UINT.DDS => flat R8G8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8G8_UNORM.DDS => flat R8G8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8_SINT.DDS => flat R8_SINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8_SNORM.DDS => flat R8_SNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8_UINT.DDS => flat R8_UINT.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R8_UNORM.DDS => flat R8_UNORM.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat R9G9B9E5_SHAREDEXP.DDS => flat R9G9B9E5_SHAREDEXP.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat RGBA.DDS => flat RGBA.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat Y210.DDS => flat Y210.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat Y216.DDS => flat Y216.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat Y410.DDS => flat Y410.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat Y416.DDS => flat Y416.dds} (100%) rename tests/Images/Input/Dds/9.3/{flat YUY2.DDS => flat YUY2.dds} (100%) create mode 100644 tests/Images/Input/Ktx2/astc_ldr_10x5_FlightHelmet_baseColor.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_12x10_FlightHelmet_baseColor.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_12x12_FlightHelmet_baseColor.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_4x4_FlightHelmet_baseColor.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_5x4_Iron_Bars_001_normal.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x5_FlightHelmet_baseColor.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_3dtex_7.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_Iron_Bars_001_normal.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_arraytex_7.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_arraytex_7_mipmap.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_8x6_FlightHelmet_baseColor.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_8x8_FlightHelmet_baseColor.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_ldr_cubemap_6x6.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_10x5_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_12x10_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_12x12_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_4x4_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x5_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posy.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posz.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_8x6_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_8x8_posx.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_cubemap_6x6.ktx2 create mode 100644 tests/Images/Input/Ktx2/invalid_color_model_for_basislz.ktx2 create mode 100644 tests/Images/Input/Ktx2/invalid_face_count_and_padding.ktx2 create mode 100644 tests/Images/Input/Ktx2/invalid_mip_layout_and_padding.ktx2 create mode 100644 tests/Images/Input/Ktx2/invalid_nul_on_kvd_val.ktx2 create mode 100644 tests/Images/Input/Ktx2/invalid_typesize.ktx2 diff --git a/.editorconfig b/.editorconfig index 2e3045fb..f579ff5d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -104,8 +104,8 @@ dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:war dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_operators = always_for_clarity:suggestion # Expression-level preferences -dotnet_style_object_initializer = true:warning -dotnet_style_collection_initializer = true:warning +dotnet_style_object_initializer = true:error +dotnet_style_collection_initializer = true:error dotnet_style_explicit_tuple_names = true:warning dotnet_style_prefer_inferred_tuple_names = true:warning dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning @@ -135,9 +135,9 @@ csharp_style_prefer_null_check_over_type_check = true:warning # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules [*.{cs,csx,cake}] # 'var' preferences -csharp_style_var_for_built_in_types = false:warning -csharp_style_var_when_type_is_apparent = false:warning -csharp_style_var_elsewhere = false:warning +csharp_style_var_for_built_in_types = false:error +csharp_style_var_when_type_is_apparent = false:error +csharp_style_var_elsewhere = false:error # Expression-bodied members csharp_style_expression_bodied_methods = true:warning csharp_style_expression_bodied_constructors = true:warning @@ -160,7 +160,10 @@ csharp_style_pattern_local_over_anonymous_function = true:warning csharp_style_deconstructed_variable_declaration = true:warning csharp_style_prefer_index_operator = true:warning csharp_style_prefer_range_operator = true:warning -csharp_style_implicit_object_creation_when_type_is_apparent = true:warning +csharp_style_implicit_object_creation_when_type_is_apparent = true:error +# ReSharper inspection severities +resharper_arrange_object_creation_when_type_evident_highlighting = error +resharper_arrange_object_creation_when_type_not_evident_highlighting = error # "Null" checking preferences csharp_style_throw_expression = true:warning csharp_style_conditional_delegate_call = true:warning @@ -172,6 +175,11 @@ dotnet_diagnostic.IDE0063.severity = suggestion csharp_using_directive_placement = outside_namespace:warning # Modifier preferences csharp_prefer_static_local_function = true:warning +# Primary constructor preferences +csharp_style_prefer_primary_constructors = false:none +# Collection preferences +dotnet_style_prefer_collection_expression = true:error +resharper_use_collection_expression_highlighting =true:error ########################################## # Unnecessary Code Rules diff --git a/.gitattributes b/.gitattributes index 3647a706..f7bd4d06 100644 --- a/.gitattributes +++ b/.gitattributes @@ -133,3 +133,13 @@ *.pnm filter=lfs diff=lfs merge=lfs -text *.wbmp filter=lfs diff=lfs merge=lfs -text *.exr filter=lfs diff=lfs merge=lfs -text +*.ico filter=lfs diff=lfs merge=lfs -text +*.cur filter=lfs diff=lfs merge=lfs -text +*.ani filter=lfs diff=lfs merge=lfs -text +*.heic filter=lfs diff=lfs merge=lfs -text +*.hif filter=lfs diff=lfs merge=lfs -text +*.avif filter=lfs diff=lfs merge=lfs -text +############################################################################### +# Handle ICC files by git lfs +############################################################################### +*.icc filter=lfs diff=lfs merge=lfs -text diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..02f8ea1e --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# How to contribute to SixLabors.ImageSharp.Textures + +#### **Did you find a bug?** + +- Please **ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/SixLabors/ImageSharp.Textures/issues). + +- If you're unable to find an open issue addressing the problem, please [open a new one](https://github.com/SixLabors/ImageSharp.Textures/issues/new). Be sure to include a **title, the applicable version, a clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. Please do not hijack existing issues. + +#### **Did you write a patch that fixes a bug?** + +* Open a new GitHub pull request with the patch. + +* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. + +* Before submitting, please ensure that your code matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules :cop:. + +#### **Do you intend to add a new feature or change an existing one?** + +* Suggest your change in the [Ideas Discussions Channel](https://github.com/SixLabors/ImageSharp.Textures/discussions?discussions_q=category%3AIdeas) and start writing code. + +* Do not open an issue on GitHub until you have collected positive feedback about the change. GitHub issues are primarily intended for bug reports and fixes. + +#### **Building** + + * When first cloning the repo, make sure to run `git submodule update --init --recursive` otherwise the submodules (e.g. `shared-infrastructure`) will be missing. + + * Run `dotnet build` in the root of the repo, or open the ImageSharp.Textures.sln file in Visual Studio and build from there. + +#### **Do you have questions about consuming the library or the source code?** + +* Ask any question about how to use SixLabors.ImageSharp in the [Help Discussions Channel](https://github.com/SixLabors/ImageSharp.Textures/discussions?discussions_q=category%3AHelp). + +#### Code of Conduct +This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. + +And please remember. SixLabors.ImageSharp is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible image processing available to all. Open Source can only exist with your help. + +Thanks for reading! diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1ba9cf86..7032b9a1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -31,7 +31,17 @@ jobs: if: "!contains(github.event.head_commit.message, '[skip ci]')" steps: - - uses: actions/checkout@v4 + - name: Git Config + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true + + - name: Git Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive # See https://github.com/actions/checkout/issues/165#issuecomment-657673315 - name: Create LFS file list @@ -50,14 +60,6 @@ jobs: - name: Install NuGet uses: NuGet/setup-nuget@v2 - - name: Setup Git - shell: bash - run: | - git config --global core.autocrlf false - git config --global core.longpaths true - git fetch --prune --unshallow - git submodule -q update --init --recursive - - name: Setup NuGet Cache uses: actions/cache@v4 id: nuget-cache @@ -93,7 +95,7 @@ jobs: path: tests/Images/ActualOutput/ - name: Update Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') with: flags: unittests diff --git a/.gitignore b/.gitignore index dd79bff9..26cb9a2a 100644 --- a/.gitignore +++ b/.gitignore @@ -169,6 +169,7 @@ ClientBin/ *.publishsettings node_modules/ bower_components/ +.DS_Store # RIA/Silverlight projects Generated_Code/ diff --git a/LICENSE b/LICENSE index 261eeb9e..a68eb678 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,43 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Six Labors Split License +Version 1.0, June 2022 +Copyright (c) Six Labors - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - 1. Definitions. +1. Definitions. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. + "Source" form shall mean the preferred form for making modifications, including but not limited to software source + code, documentation source, and configuration files. - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including + but not limited to compiled object code, generated documentation, and conversions to other media types. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. + "Work" (or "Works") shall mean any Six Labors software made available under the License, as indicated by a + copyright notice that is included in or attached to the work. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. + "Direct Package Dependency" shall mean any Work in Source or Object form that is installed directly by You. - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. + "Transitive Package Dependency" shall mean any Work in Object form that is installed indirectly by a third party + dependency unrelated to Six Labors. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). +2. License - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. + Works in Source or Object form are split licensed and may be licensed under the Apache License, Version 2.0 or a + Six Labors Commercial Use License. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." + Licenses are granted based upon You meeting the qualified criteria as stated. Once granted, + You must reference the granted license only in all documentation. - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. + Works in Source or Object form are licensed to You under the Apache License, Version 2.0 if. - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. + - You are consuming the Work in for use in software licensed under an Open Source or Source Available license. + - You are consuming the Work as a Transitive Package Dependency. + - You are consuming the Work as a Direct Package Dependency in the capacity of a For-profit company/individual with + less than 1M USD annual gross revenue. + - You are consuming the Work as a Direct Package Dependency in the capacity of a Non-profit organization + or Registered Charity. - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + For all other scenarios, Works in Source or Object form are licensed to You under the Six Labors Commercial License + which may be purchased by visiting https://sixlabors.com/pricing/. diff --git a/README.md b/README.md index 2b60fcfd..5be8e659 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ SixLabors.ImageSharp.Textures Currently decoding the following texture formats are supported: -- [DDS textures]() -- [KTX textures](http://paulbourke.net/dataformats/ktx/) -- [KTX2 textures](https://github.khronos.org/KTX-Specification/) +- [DDS textures](https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds) +- [KTX textures](https://registry.khronos.org/KTX/specs/1.0/ktxspec.v1.html) +- [KTX2 textures](https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html) with the following compressions: @@ -38,7 +38,7 @@ Encoding textures is **not** yet supported. PR are of course very welcome. ## License -- ImageSharp.Textures is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0) +- ImageSharp.Textures is licensed under the [Six Labors Split License, Version 1.0](https://github.com/SixLabors/ImageSharp.Textures/blob/main/LICENSE) ## Code of Conduct This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. diff --git a/shared-infrastructure b/shared-infrastructure index 353b9afe..57699ffb 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 353b9afe32a8000410312d17263407cd7bb82d19 +Subproject commit 57699ffb797bc2389c5d6cbb3b1800f2eb5fb947 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2813cc4b..e40e9b78 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -11,7 +11,7 @@ --> - + diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs index 88ac7f20..1f101694 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs @@ -41,7 +41,7 @@ internal class KtxProcessor /// The decoded mipmaps. public MipMap[] DecodeMipMaps(Stream stream, int width, int height, uint count) { - if (this.KtxHeader.GlTypeSize == 1) + if (this.KtxHeader.GlTypeSize is 0 or 1) { switch (this.KtxHeader.GlFormat) { @@ -86,13 +86,41 @@ public MipMap[] DecodeMipMaps(Stream stream, int width, int height, uint count) return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgb8Etc2: return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc4x4Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc5x4Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc5x5Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc6x5Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc6x6Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc8x5Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc8x6Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc8x8Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc10x5Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc10x6Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc10x8Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc10x10Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc12x10Khr: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.CompressedRgbaAstc12x12Khr: + return this.AllocateMipMaps(stream, width, height, count); } break; } } - if (this.KtxHeader.GlTypeSize == 2 || this.KtxHeader.GlTypeSize == 4) + if (this.KtxHeader.GlTypeSize is 2 or 4) { // TODO: endianess is not respected here. Use stream reader which respects endianess. switch (this.KtxHeader.GlInternalFormat) @@ -166,12 +194,40 @@ public CubemapTexture DecodeCubeMap(Stream stream, int width, int height) return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgb8Etc2: return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc4x4Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc5x4Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc5x5Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc6x5Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc6x6Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc8x5Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc8x6Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc8x8Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc10x5Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc10x6Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc10x8Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc10x10Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc12x10Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc12x12Khr: + return this.AllocateCubeMap(stream, width, height); } break; } - if (this.KtxHeader.GlTypeSize == 2 || this.KtxHeader.GlTypeSize == 4) + if (this.KtxHeader.GlTypeSize is 2 or 4) { // TODO: endianess is not respected here. Use stream reader which respects endianess. switch (this.KtxHeader.GlInternalFormat) diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs index 6decbef3..b5367090 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs @@ -69,6 +69,7 @@ public Texture DecodeTexture(Stream stream) int width = (int)this.ktxHeader.PixelWidth; int height = (int)this.ktxHeader.PixelHeight; + // Level indices start immediately after the header var levelIndices = new LevelIndex[this.ktxHeader.LevelCount]; for (int i = 0; i < levelIndices.Length; i++) { @@ -84,15 +85,39 @@ public Texture DecodeTexture(Stream stream) var ktxProcessor = new Ktx2Processor(this.ktxHeader); + Texture texture; if (this.ktxHeader.FaceCount == 6) { - CubemapTexture cubeMapTexture = ktxProcessor.DecodeCubeMap(stream, width, height, levelIndices); - return cubeMapTexture; + texture = ktxProcessor.DecodeCubeMap(stream, width, height, levelIndices); + } + else + { + var flatTexture = new FlatTexture(); + MipMap[] mipMaps = ktxProcessor.DecodeMipMaps(stream, width, height, levelIndices); + flatTexture.MipMaps.AddRange(mipMaps); + texture = flatTexture; } - var texture = new FlatTexture(); - MipMap[] mipMaps = ktxProcessor.DecodeMipMaps(stream, width, height, levelIndices); - texture.MipMaps.AddRange(mipMaps); + // Seek to the end of the file to ensure the entire stream is consumed. + // KTX2 files use byte offsets for mipmap data, so the stream position may not + // be at the end after reading. We need to find the furthest point read. + if (levelIndices.Length > 0) + { + long maxEndPosition = 0; + for (int i = 0; i < levelIndices.Length; i++) + { + long endPosition = (long)(levelIndices[i].ByteOffset + levelIndices[i].UncompressedByteLength); + if (endPosition > maxEndPosition) + { + maxEndPosition = endPosition; + } + } + + if (stream.Position < maxEndPosition && stream.CanSeek) + { + stream.Position = maxEndPosition; + } + } return texture; } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs index 4dbd30d2..3d3b1118 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs @@ -157,6 +157,48 @@ public MipMap[] DecodeMipMaps(Stream stream, int width, int height, LevelIndex[] return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_5x4_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_5x5_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_6x5_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_6x6_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x5_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x6_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x5_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x6_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x8_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x10_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_12x10_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_12x12_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_12x12_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); } throw new NotSupportedException("The pixel format is not supported"); @@ -286,6 +328,48 @@ public CubemapTexture DecodeCubeMap(Stream stream, int width, int height, LevelI return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_5x4_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_5x5_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_6x5_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_6x6_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x5_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x6_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x5_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x6_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x8_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x10_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_12x10_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_12x12_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_12x12_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); } throw new NotSupportedException("The pixel format is not supported"); diff --git a/src/ImageSharp.Textures/ImageSharp.Textures.csproj b/src/ImageSharp.Textures/ImageSharp.Textures.csproj index 8fadb42d..f793f7ba 100644 --- a/src/ImageSharp.Textures/ImageSharp.Textures.csproj +++ b/src/ImageSharp.Textures/ImageSharp.Textures.csproj @@ -39,7 +39,8 @@ - + + diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs new file mode 100644 index 00000000..4d5abb21 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// An ASTC decoder for various block footprints. +/// Based on the Google ASTC codec implementation. +/// +/// +/// +/// +internal static class AstcDecoder +{ + /// + /// Decodes an ASTC block into RGBA pixels. + /// + /// The 16-byte ASTC block data. + /// The width of the block footprint (4-12). + /// The height of the block footprint (4-12). + /// The output span for decoded RGBA pixels. + /// Optional flag to indicate if the output should be in sRGB color space. + public static void DecodeBlock(ReadOnlySpan blockData, int blockWidth, int blockHeight, Span decodedPixels, bool isSrgb = false) + { + AstcSharp.Footprint? footprint = AstcSharp.Footprint.FromDimensions(blockWidth, blockHeight); + if (footprint is null) + { + throw new ArgumentOutOfRangeException(nameof(blockWidth), "Block dimensions must be between 4 and 12."); + } + AstcSharp.Codec.DecompressBlock(blockData, footprint.Value, decodedPixels); + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs new file mode 100644 index 00000000..aa592b2a --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc10x10. + /// + internal readonly struct RgbaAstc10x10 : IBlock + { + public static Size BlockSize => new(10, 10); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 10; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs new file mode 100644 index 00000000..a2b0a4f5 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc10x5. + /// + internal readonly struct RgbaAstc10x5 : IBlock + { + public static Size BlockSize => new(10, 5); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 10; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs new file mode 100644 index 00000000..09babf7d --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc10x6. + /// + internal readonly struct RgbaAstc10x6 : IBlock + { + public static Size BlockSize => new(10, 6); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 10; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs new file mode 100644 index 00000000..a8626268 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc10x8. + /// + internal readonly struct RgbaAstc10x8 : IBlock + { + public static Size BlockSize => new(10, 8); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 10; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs new file mode 100644 index 00000000..a0a819c2 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc12x10. + /// + internal readonly struct RgbaAstc12x10 : IBlock + { + public static Size BlockSize => new(12, 10); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 12; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs new file mode 100644 index 00000000..b0539625 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc12x12. + /// + internal readonly struct RgbaAstc12x12 : IBlock + { + public static Size BlockSize => new(12, 12); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 12; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs new file mode 100644 index 00000000..1d626968 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc4x4. + /// + internal readonly struct RgbaAstc4x4 : IBlock + { + // See https://developer.nvidia.com/astc-texture-compression-for-game-assets + // https://chromium.googlesource.com/external/github.com/ARM-software/astc-encoder/+/HEAD/Docs/FormatOverview.md + public static Size BlockSize => new(4, 4); + + /// + // The 2D block footprints in ASTC range from 4x4 texels up to 12x12 texels, which all compress into 128-bit output blocks. + // By dividing 128 bits by the number of texels in the footprint, we derive the format bit rates which range from 8 bpt(128/(4*4)) down to 0.89 bpt(128/(12*12)). + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 4; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + // Decode the block + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + // Copy decoded pixels to output + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs new file mode 100644 index 00000000..344416d9 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc5x4. + /// + internal readonly struct RgbaAstc5x4 : IBlock + { + public static Size BlockSize => new(5, 4); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 5; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs new file mode 100644 index 00000000..9f1c76b4 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc5x5. + /// + internal readonly struct RgbaAstc5x5 : IBlock + { + public static Size BlockSize => new(5, 5); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 5; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs new file mode 100644 index 00000000..b3c08f8c --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc6x5. + /// + internal readonly struct RgbaAstc6x5 : IBlock + { + public static Size BlockSize => new(6, 5); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 6; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs new file mode 100644 index 00000000..545f2ae9 --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc6x6. + /// + internal readonly struct RgbaAstc6x6 : IBlock + { + public static Size BlockSize => new(6, 6); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 6; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs new file mode 100644 index 00000000..9ff23bff --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc8x5. + /// + internal readonly struct RgbaAstc8x5 : IBlock + { + public static Size BlockSize => new(8, 5); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 8; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs new file mode 100644 index 00000000..085a593a --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc8x6. + /// + internal readonly struct RgbaAstc8x6 : IBlock + { + public static Size BlockSize => new(8, 6); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 8; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs new file mode 100644 index 00000000..69e79c6a --- /dev/null +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +{ + /// + /// Texture compressed with RgbaAstc8x8. + /// + internal readonly struct RgbaAstc8x8 : IBlock + { + public static Size BlockSize => new(8, 8); + + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + + /// + public byte PixelDepthBytes => 4; + + /// + public byte DivSize => 8; + + /// + public byte CompressedBytesPerBlock => 16; + + /// + public bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; + int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; + if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) + { + AstcDecoder.DecodeBlock( + blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), + BlockSize.Width, + BlockSize.Height, + decodedBlock); + + for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) + { + for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) + { + int srcIndex = ((py * BlockSize.Width) + px) * 4; + int dstX = (bx * BlockSize.Width) + px; + int dstY = (by * BlockSize.Height) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + } +} diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 775062ad..527225fe 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -11,7 +11,7 @@ --> - + diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 403f68cc..c88a39e7 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -17,18 +17,14 @@ - - + + - - + - - - diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcAppliedPixelsComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcAppliedPixelsComparisonTests.cs new file mode 100644 index 00000000..4983b740 --- /dev/null +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcAppliedPixelsComparisonTests.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.IO; +using System.Linq; +using System.Text; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc +{ +// [Trait("Format", "Astc")] +// public class AstcAppliedPixelsComparisonTests +// { +// private static string AstInputDir => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "Astc"); +// private static string BaselineDir => Path.Combine(TestEnvironment.BaselineDirectoryFullPath, "AstcApplied"); +// private static string ActualDir => Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "AstcApplied"); + +// [Theory] +// [InlineData("atlas_small_8x8.astc", 8, 8)] +// [InlineData("footprint_10x5.astc", 10, 5)] +// [InlineData("footprint_12x10.astc", 12, 10)] +// [InlineData("footprint_10x10.astc", 10, 10)] +// public void AppliedPixels_MatchBaseline(string fileName, int blockW, int blockH) +// { +// Directory.CreateDirectory(ActualDir); + +// string path = Path.Combine(AstInputDir, fileName); +// Assert.True(File.Exists(path), "ASTC input file not found: " + path); + +// byte[] data = File.ReadAllBytes(path); +// var pixels = SixLabors.ImageSharp.Textures.TextureFormats.Decoding.AstcDecoder.ExtractAppliedColorPixelsForTests(data.AsSpan(0, 16), blockW, blockH); +// Assert.NotNull(pixels); + +// string baseName = Path.GetFileNameWithoutExtension(fileName); +// string baselinePath = Path.Combine(BaselineDir, baseName + ".raw"); +// string actualPath = Path.Combine(ActualDir, baseName + ".raw"); + +// // Write actual for debugging purposes +// File.WriteAllBytes(actualPath, pixels); + +// if (File.Exists(baselinePath)) +// { +// byte[] expected = File.ReadAllBytes(baselinePath); +// if (!expected.SequenceEqual(pixels)) +// { +// // Build a helpful diagnostic message with mismatches and ranges +// int expectedLen = expected.Length; +// int actualLen = pixels.Length; +// int maxLen = Math.Max(expectedLen, actualLen); + +// var mismatchIndices = new System.Collections.Generic.List(); +// for (int i = 0; i < maxLen; i++) +// { +// byte e = i < expectedLen ? expected[i] : (byte)0xFF; +// byte a = i < actualLen ? pixels[i] : (byte)0xFF; +// if (i >= expectedLen || i >= actualLen || e != a) +// { +// mismatchIndices.Add(i); +// } +// } + +// // Coalesce contiguous indices into ranges +// var ranges = new System.Collections.Generic.List<(int start, int end)>(); +// foreach (var idx in mismatchIndices) +// { +// if (ranges.Count == 0 || idx != ranges.Last().end + 1) +// { +// ranges.Add((idx, idx)); +// } +// else +// { +// var last = ranges.Last(); +// ranges[ranges.Count - 1] = (last.start, idx); +// } +// } + +// var sb = new StringBuilder(); +// sb.AppendLine($"Applied pixel mismatch for {fileName}."); +// sb.AppendLine($"Baseline: {baselinePath}"); +// sb.AppendLine($"Actual: {actualPath}"); +// sb.AppendLine($"Baseline length: {expectedLen} bytes"); +// sb.AppendLine($"Actual length: {actualLen} bytes"); +// sb.AppendLine($"Total mismatching byte positions: {mismatchIndices.Count}"); + +// if (ranges.Count > 0) +// { +// sb.AppendLine("Mismatch ranges (start..end):"); +// sb.AppendLine(string.Join(", ", ranges.Select(r => r.start == r.end ? r.start.ToString() : r.start + ".." + r.end))); +// } + +// // Show up to 10 sample mismatches with expected vs actual hex +// int samples = Math.Min(10, mismatchIndices.Count); +// if (samples > 0) +// { +// sb.AppendLine("Sample mismatches (index: expected -> actual):"); +// for (int i = 0; i < samples; i++) +// { +// int idx = mismatchIndices[i]; +// string eStr = idx < expectedLen ? expected[idx].ToString("X2") : "--"; +// string aStr = idx < actualLen ? pixels[idx].ToString("X2") : "--"; +// sb.AppendLine($" {idx}: {eStr} -> {aStr}"); +// } +// } + +// // Provide a small hex context around the first mismatch +// if (mismatchIndices.Count > 0) +// { +// int ctxIdx = mismatchIndices[0]; +// int ctxStart = Math.Max(0, ctxIdx - 8); +// int ctxEnd = Math.Min(maxLen - 1, ctxIdx + 8); +// sb.AppendLine($"Hex context around first mismatch (offset {ctxIdx}):"); +// var ctxLines = new System.Collections.Generic.List(); +// for (int i = ctxStart; i <= ctxEnd; i++) +// { +// string eStr = i < expectedLen ? expected[i].ToString("X2") : "--"; +// string aStr = i < actualLen ? pixels[i].ToString("X2") : "--"; +// string marker = i == ctxIdx ? "*" : " "; +// ctxLines.Add($"{marker}{i:D4}: {eStr} | {aStr}"); +// } +// sb.AppendLine(string.Join(Environment.NewLine, ctxLines)); +// } + +// throw new Exception(sb.ToString()); +// } +// } +// else +// { +// // Create baseline directory and populate baseline for future runs +// Directory.CreateDirectory(Path.GetDirectoryName(baselinePath)!); +// File.Copy(actualPath, baselinePath); +// } +// } +// } +} diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs new file mode 100644 index 00000000..c1281c02 --- /dev/null +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using AstcSharp; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; +using PixelRgba = SixLabors.ImageSharp.PixelFormats.Rgba32; +using TestUtilEnv = SixLabors.ImageSharp.Textures.Tests.TestUtilities.TestEnvironment; + +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; + +[Trait("Format", "Astc")] +public class AstcImageComparisonTests +{ + public static IEnumerable AstcTestFiles() + { + string inputDir = Path.Combine(TestUtilEnv.InputImagesDirectoryFullPath, "Astc"); + string expectedDir = Path.Combine(TestUtilEnv.SolutionDirectoryFullPath, "tests", "Images", "Expected", "ASTC"); + + if (!Directory.Exists(inputDir) || !Directory.Exists(expectedDir)) + { + yield break; + } + + foreach (string astc in Directory.GetFiles(inputDir, "*.astc")) + { + string baseName = Path.GetFileNameWithoutExtension(astc); + string expected = Path.Combine(expectedDir, baseName + ".bmp"); + if (File.Exists(expected)) + { + yield return new object[] { astc, expected }; + } + } + } + +#nullable enable + [Theory] + [MemberData(nameof(AstcTestFiles))] + public void DecodeAstcFiles_ProducesExpectedImages(string inputPath, string expectedPath) + { + using var expected = Image.Load(expectedPath); + byte[] inputData = File.ReadAllBytes(inputPath); + var inputFile = AstcFile.LoadFromMemory(inputData, out string? errorMessage); + int stride = expected.Width * 4; + byte[] decodedBuffer = new byte[stride * expected.Height]; + + Assert.NotNull(inputFile); + Assert.Null(errorMessage); + + Codec.DecompressToImage(inputFile, decodedBuffer, decodedBuffer.Length, stride); + using var actual = Image.LoadPixelData(decodedBuffer, expected.Width, expected.Height); + // Reading the buffer manually will result in x,y order instead of row,column + actual.Mutate(x => x.RotateFlip(RotateMode.Rotate180, FlipMode.Horizontal)); + + // Compare exact + // Use a tighter tolerant comparer threshold to enforce more accurate decoding. + var comparer = ImageComparer.TolerantPercentage(0.1f); + ImageSimilarityReport report = comparer.CompareImages(expected, actual); + + if (!report.IsEmpty) + { + // Save actual image for debugging + string outDir = TestUtilEnv.CreateOutputDirectory("ASTC"); + string outFile = Path.Combine(outDir, Path.GetFileName(inputPath) + ".actual.png"); + actual.SaveAsPng(outFile); + + // Also save a small text report + string reportFile = Path.ChangeExtension(outFile, ".diff.txt"); + File.WriteAllText(reportFile, report.ToString()); + } + + Assert.True(report.IsEmpty, $"Image difference too large for {Path.GetFileName(inputPath)} -> {report.DifferencePercentageString}"); + } +} diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs new file mode 100644 index 00000000..2bb20c58 --- /dev/null +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs @@ -0,0 +1,249 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Textures.Formats.Ktx2; +using SixLabors.ImageSharp.Textures.Tests.Enums; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; +using SixLabors.ImageSharp.Textures.TextureFormats; + +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; + +[Trait("Format", "Ktx2")] +[Trait("Compression", "Astc")] +public class AstcKtx2DecoderTests +{ + private static readonly Ktx2Decoder Ktx2Decoder = new(); + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_4x4_FlightHelmet)] + public void Ktx2Decoder_CanDecode_Astc_4x4_FlightHelmet(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + + var test = texture as FlatTexture; + + Image firstMipMap = test.MipMaps[0].GetImage(); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + var firstMipMapImage = firstMipMap as Image; + firstMipMapImage.SaveAsPng(@"C:\Users\ErikWhite\Downloads\image.png"); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_5x4_IronBars)] + public void Ktx2Decoder_CanDecode_Astc_5x4_IronBars(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x5_FlightHelmet)] + public void Ktx2Decoder_CanDecode_Astc_6x5_FlightHelmet(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_IronBars)] + public void Ktx2Decoder_CanDecode_Astc_6x6_IronBars(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_Posx)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Posx(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_8x6_FlightHelmet)] + public void Ktx2Decoder_CanDecode_Astc_8x6_FlightHelmet(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_8x8_FlightHelmet)] + public void Ktx2Decoder_CanDecode_Astc_8x8_FlightHelmet(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_10x5_FlightHelmet)] + public void Ktx2Decoder_CanDecode_Astc_10x5_FlightHelmet(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_12x10_FlightHelmet)] + public void Ktx2Decoder_CanDecode_Astc_12x10_FlightHelmet(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_12x12_FlightHelmet)] + public void Ktx2Decoder_CanDecode_Astc_12x12_FlightHelmet(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_4x4_Posx)] + public void Ktx2Decoder_CanDecode_Astc_4x4_Posx_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x5_Posx)] + public void Ktx2Decoder_CanDecode_Astc_6x5_Posx_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Posx)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Posx_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Posy)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Posy_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Posz)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Posz_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_8x6_Posx)] + public void Ktx2Decoder_CanDecode_Astc_8x6_Posx_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_8x8_Posx)] + public void Ktx2Decoder_CanDecode_Astc_8x8_Posx_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_10x5_Posx)] + public void Ktx2Decoder_CanDecode_Astc_10x5_Posx_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_12x10_Posx)] + public void Ktx2Decoder_CanDecode_Astc_12x10_Posx_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_12x12_Posx)] + public void Ktx2Decoder_CanDecode_Astc_12x12_Posx_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Fastest)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Fastest_Quality_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Fast)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Fast_Quality_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Medium)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Medium_Quality_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Volume, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_3dTex)] + public void Ktx2Decoder_CanDecode_Astc_6x6_VolumeTexture(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_ArrayTex)] + public void Ktx2Decoder_CanDecode_Astc_6x6_ArrayTexture(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_ArrayTex_Mipmap)] + public void Ktx2Decoder_CanDecode_Astc_6x6_ArrayTexture_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Cubemap, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_Cubemap_6x6)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Cubemap(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Cubemap, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_Cubemap_6x6)] + public void Ktx2Decoder_CanDecode_Astc_6x6_Cubemap_WithMipmaps(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); + } +} diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs index 1fbacc67..27e24bbe 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs @@ -48,5 +48,37 @@ public void KtxDecoder_CanDecode_Rgba8888(TestTextureProvider provider) var firstMipMapImage = firstMipMap as Image; firstMipMapImage.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); } + + + [Fact] + public void KtxDecoder_CanDecode() + { + // KTX 1.1 image with ASTC compression + using Texture texture = Texture.Load("D:\\fileconverters\\TestData\\Image\\KTX\\test_ktx_11.ktx"); + var flatTexture = texture as FlatTexture; + + Assert.NotNull(flatTexture?.MipMaps); + //Assert.Equal(8, flatTexture.MipMaps.Count); + //Assert.Equal(200, flatTexture.MipMaps[0].GetImage().Height); + //Assert.Equal(200, flatTexture.MipMaps[0].GetImage().Width); + //Assert.Equal(100, flatTexture.MipMaps[1].GetImage().Height); + //Assert.Equal(100, flatTexture.MipMaps[1].GetImage().Width); + //Assert.Equal(50, flatTexture.MipMaps[2].GetImage().Height); + //Assert.Equal(50, flatTexture.MipMaps[2].GetImage().Width); + //Assert.Equal(25, flatTexture.MipMaps[3].GetImage().Height); + //Assert.Equal(25, flatTexture.MipMaps[3].GetImage().Width); + //Assert.Equal(12, flatTexture.MipMaps[4].GetImage().Height); + //Assert.Equal(12, flatTexture.MipMaps[4].GetImage().Width); + //Assert.Equal(6, flatTexture.MipMaps[5].GetImage().Height); + //Assert.Equal(6, flatTexture.MipMaps[5].GetImage().Width); + //Assert.Equal(3, flatTexture.MipMaps[6].GetImage().Height); + //Assert.Equal(3, flatTexture.MipMaps[6].GetImage().Width); + //Assert.Equal(1, flatTexture.MipMaps[7].GetImage().Height); + //Assert.Equal(1, flatTexture.MipMaps[7].GetImage().Width); + Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + var firstMipMapImage = firstMipMap as Image; + firstMipMapImage.SaveAsPng(@"C:\Users\ErikWhite\Downloads\image.png"); + } } } diff --git a/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj b/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj index c3edc69b..ac9c7c4c 100644 --- a/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj +++ b/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -13,9 +13,6 @@ - - - diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index d3df36c1..f169ba27 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -12,5 +12,49 @@ public static class Ktx { public const string Rgba = "rgba8888.ktx"; } + + public static class Ktx2 + { + public static class Astc + { + // Flat textures with various block sizes + public const string Ldr_4x4_FlightHelmet = "astc_ldr_4x4_FlightHelmet_baseColor.ktx2"; + public const string Ldr_5x4_IronBars = "astc_ldr_5x4_Iron_Bars_001_normal.ktx2"; + public const string Ldr_6x5_FlightHelmet = "astc_ldr_6x5_FlightHelmet_baseColor.ktx2"; + public const string Ldr_6x6_IronBars = "astc_ldr_6x6_Iron_Bars_001_normal.ktx2"; + public const string Ldr_6x6_Posx = "astc_ldr_6x6_posx.ktx2"; + public const string Ldr_8x6_FlightHelmet = "astc_ldr_8x6_FlightHelmet_baseColor.ktx2"; + public const string Ldr_8x8_FlightHelmet = "astc_ldr_8x8_FlightHelmet_baseColor.ktx2"; + public const string Ldr_10x5_FlightHelmet = "astc_ldr_10x5_FlightHelmet_baseColor.ktx2"; + public const string Ldr_12x10_FlightHelmet = "astc_ldr_12x10_FlightHelmet_baseColor.ktx2"; + public const string Ldr_12x12_FlightHelmet = "astc_ldr_12x12_FlightHelmet_baseColor.ktx2"; + + // Volume textures + public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; + + // Array textures + public const string Ldr_6x6_ArrayTex = "astc_ldr_6x6_arraytex_7.ktx2"; + public const string Ldr_6x6_ArrayTex_Mipmap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; + + // Cubemap textures + public const string Ldr_Cubemap_6x6 = "astc_ldr_cubemap_6x6.ktx2"; + + // Mipmap tests with different quality settings + public const string Mipmap_Ldr_4x4_Posx = "astc_mipmap_ldr_4x4_posx.ktx2"; + public const string Mipmap_Ldr_6x5_Posx = "astc_mipmap_ldr_6x5_posx.ktx2"; + public const string Mipmap_Ldr_6x6_Fast = "astc_mipmap_ldr_6x6_kodim17_fast.ktx2"; + public const string Mipmap_Ldr_6x6_Medium = "astc_mipmap_ldr_6x6_kodim17_medium.ktx2"; + public const string Mipmap_Ldr_6x6_Fastest = "astc_mipmap_ldr_6x6_kodim17_fastest.ktx2"; + public const string Mipmap_Ldr_6x6_Posx = "astc_mipmap_ldr_6x6_posx.ktx2"; + public const string Mipmap_Ldr_6x6_Posy = "astc_mipmap_ldr_6x6_posy.ktx2"; + public const string Mipmap_Ldr_6x6_Posz = "astc_mipmap_ldr_6x6_posz.ktx2"; + public const string Mipmap_Ldr_8x6_Posx = "astc_mipmap_ldr_8x6_posx.ktx2"; + public const string Mipmap_Ldr_8x8_Posx = "astc_mipmap_ldr_8x8_posx.ktx2"; + public const string Mipmap_Ldr_10x5_Posx = "astc_mipmap_ldr_10x5_posx.ktx2"; + public const string Mipmap_Ldr_12x10_Posx = "astc_mipmap_ldr_12x10_posx.ktx2"; + public const string Mipmap_Ldr_12x12_Posx = "astc_mipmap_ldr_12x12_posx.ktx2"; + public const string Mipmap_Ldr_Cubemap_6x6 = "astc_mipmap_ldr_cubemap_6x6.ktx2"; + } + } } } diff --git a/tests/Images/Baseline/AstcDebug/atlas_small_8x8.txt b/tests/Images/Baseline/AstcDebug/atlas_small_8x8.txt new file mode 100644 index 00000000..693016bc --- /dev/null +++ b/tests/Images/Baseline/AstcDebug/atlas_small_8x8.txt @@ -0,0 +1,11 @@ +File: atlas_small_8x8.astc +EndpointRange: 0 +WeightRange: 2 +WeightGrid: 4x8 +IsDualPlane: False +PlaneSelector: 0 +EndpointValues: 0,255,0,0,0,0 +Endpoint[0]: R0=0,G0=0,B0=0,A0=255 | R1=255,G1=255,B1=255,A1=255 +Endpoint[1]: R0=0,G0=0,B0=0,A0=255 | R1=0,G1=0,B1=0,A1=255 +GridWeightsPlane0: 0,0,0,0,0,0,0,37,0,0,0,0,0,9,0,0,0,0,0,0,0,9,0,0,0,0,18,0,9,0,37,0 +GridWeightsPlane1: diff --git a/tests/Images/Baseline/AstcDebug/footprint_10x10.txt b/tests/Images/Baseline/AstcDebug/footprint_10x10.txt new file mode 100644 index 00000000..19656bc2 --- /dev/null +++ b/tests/Images/Baseline/AstcDebug/footprint_10x10.txt @@ -0,0 +1,11 @@ +File: footprint_10x10.astc +EndpointRange: 0 +WeightRange: 2 +WeightGrid: 4x8 +IsDualPlane: False +PlaneSelector: 0 +EndpointValues: 0,255,0,0,255,0 +Endpoint[0]: R0=0,G0=0,B0=0,A0=255 | R1=255,G1=255,B1=255,A1=255 +Endpoint[1]: R0=0,G0=0,B0=0,A0=255 | R1=0,G1=0,B1=0,A1=255 +GridWeightsPlane0: 0,0,0,0,0,0,0,37,0,0,0,0,0,0,9,0,0,0,0,0,0,0,9,0,0,0,18,0,46,0,37,18 +GridWeightsPlane1: diff --git a/tests/Images/Baseline/AstcDebug/footprint_10x5.txt b/tests/Images/Baseline/AstcDebug/footprint_10x5.txt new file mode 100644 index 00000000..5ada1c04 --- /dev/null +++ b/tests/Images/Baseline/AstcDebug/footprint_10x5.txt @@ -0,0 +1,11 @@ +File: footprint_10x5.astc +EndpointRange: 0 +WeightRange: 2 +WeightGrid: 4x8 +IsDualPlane: False +PlaneSelector: 0 +EndpointValues: 0,255,0,0,255,0 +Endpoint[0]: R0=0,G0=0,B0=0,A0=255 | R1=255,G1=255,B1=255,A1=255 +Endpoint[1]: R0=0,G0=0,B0=0,A0=255 | R1=0,G1=0,B1=0,A1=255 +GridWeightsPlane0: 0,0,0,0,0,0,0,37,0,0,0,0,0,0,9,0,0,0,0,0,0,0,9,0,0,0,18,0,18,9,37,18 +GridWeightsPlane1: diff --git a/tests/Images/Baseline/AstcDebug/footprint_12x10.txt b/tests/Images/Baseline/AstcDebug/footprint_12x10.txt new file mode 100644 index 00000000..b952a5db --- /dev/null +++ b/tests/Images/Baseline/AstcDebug/footprint_12x10.txt @@ -0,0 +1,11 @@ +File: footprint_12x10.astc +EndpointRange: 0 +WeightRange: 2 +WeightGrid: 4x8 +IsDualPlane: False +PlaneSelector: 0 +EndpointValues: 0,255,0,0,0,255 +Endpoint[0]: R0=0,G0=0,B0=0,A0=255 | R1=255,G1=255,B1=255,A1=255 +Endpoint[1]: R0=0,G0=0,B0=0,A0=255 | R1=0,G1=0,B1=0,A1=255 +GridWeightsPlane0: 0,0,0,0,0,0,0,37,0,0,0,0,0,0,9,0,0,0,0,0,0,0,9,0,0,0,18,0,46,0,37,9 +GridWeightsPlane1: diff --git a/tests/Images/Expected/ASTC/atlas_small_4x4.bmp b/tests/Images/Expected/ASTC/atlas_small_4x4.bmp new file mode 100644 index 00000000..27dbb5ce --- /dev/null +++ b/tests/Images/Expected/ASTC/atlas_small_4x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:960c183b5a1f139466dea2d3196bc58b426935545bd203793d2a745fd8dbc29e +size 262282 diff --git a/tests/Images/Expected/ASTC/atlas_small_5x5.bmp b/tests/Images/Expected/ASTC/atlas_small_5x5.bmp new file mode 100644 index 00000000..9dbc9152 --- /dev/null +++ b/tests/Images/Expected/ASTC/atlas_small_5x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:959bce6386ac4ee035cfbbbdbf9537e3f2a5ebd5795fbd531dea301a4c6f74ba +size 262282 diff --git a/tests/Images/Expected/ASTC/atlas_small_6x6.bmp b/tests/Images/Expected/ASTC/atlas_small_6x6.bmp new file mode 100644 index 00000000..841123a8 --- /dev/null +++ b/tests/Images/Expected/ASTC/atlas_small_6x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e70d5d7a9eb7c280f397eefe5c71a78530508fde1caebabe9fda39a91aa5847 +size 262282 diff --git a/tests/Images/Expected/ASTC/atlas_small_8x8.bmp b/tests/Images/Expected/ASTC/atlas_small_8x8.bmp new file mode 100644 index 00000000..719856d8 --- /dev/null +++ b/tests/Images/Expected/ASTC/atlas_small_8x8.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3ca7675d2e01e56fc63f2a2b59b2c852d2b5793e21a2e1125ccf81c8cdf3431 +size 262282 diff --git a/tests/Images/Expected/ASTC/footprint_10x10.bmp b/tests/Images/Expected/ASTC/footprint_10x10.bmp new file mode 100644 index 00000000..6be39794 --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_10x10.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:713880d96d562a92648bb306d57cddfac45805e99853dec6c7cf7ef461cfd795 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_10x5.bmp b/tests/Images/Expected/ASTC/footprint_10x5.bmp new file mode 100644 index 00000000..fc7cd4e9 --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_10x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f661da916564a6c8dee8cd6ae6961c95a48416788cf0f52649a31b5ff36bafb5 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_10x6.bmp b/tests/Images/Expected/ASTC/footprint_10x6.bmp new file mode 100644 index 00000000..e41db3ff --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_10x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa618100f436836c42104732573952180f39eafe3c64b87a99b264fb284487dc +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_10x8.bmp b/tests/Images/Expected/ASTC/footprint_10x8.bmp new file mode 100644 index 00000000..33c468a4 --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_10x8.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6be48ee1e53b3cc868a23dd3ca9510f8af3f8f47ae1417aee95daed15581a4e3 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_12x10.bmp b/tests/Images/Expected/ASTC/footprint_12x10.bmp new file mode 100644 index 00000000..2e863d5e --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_12x10.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f753526bd5c19656a44b837fac0c535470a7c5b9bef0c4cdbc8b3987fdf348c4 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_12x12.bmp b/tests/Images/Expected/ASTC/footprint_12x12.bmp new file mode 100644 index 00000000..3a6977ff --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_12x12.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7314cdfe475599d2c55645fad6b51379a129447931ae6b82538711942ecbee4 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_4x4.bmp b/tests/Images/Expected/ASTC/footprint_4x4.bmp new file mode 100644 index 00000000..c5d83d2e --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_4x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a16fbf67b4bb9a0ec3f3e3b78006f39610f7869e0cdb4bd945d660957d1090e +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_5x4.bmp b/tests/Images/Expected/ASTC/footprint_5x4.bmp new file mode 100644 index 00000000..9dd29e11 --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_5x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd3a8f13222fa52a43678b3d4269ac5dee826f9f2ffb69061650e5c4300fb1e6 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_5x5.bmp b/tests/Images/Expected/ASTC/footprint_5x5.bmp new file mode 100644 index 00000000..6f9543fc --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_5x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:273af3da91ccbfac3de9e04df9582e13808feda0fb263f52389238b96165105d +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_6x5.bmp b/tests/Images/Expected/ASTC/footprint_6x5.bmp new file mode 100644 index 00000000..8426e2c2 --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_6x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2623e574e64e88268571ec139aea89607fa7a4b149bb740cdcd37c55aac0d0b6 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_6x6.bmp b/tests/Images/Expected/ASTC/footprint_6x6.bmp new file mode 100644 index 00000000..a31d92ef --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_6x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b31d247567aa583f7c6cd1b0dc3ec311f97a2fe7893a2c4d82916786717ac622 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_8x5.bmp b/tests/Images/Expected/ASTC/footprint_8x5.bmp new file mode 100644 index 00000000..6cb11fdf --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_8x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d87633a26957084bec6973eb41f8946c5b7bccf2924e1c90c1690b4f926cf74 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_8x6.bmp b/tests/Images/Expected/ASTC/footprint_8x6.bmp new file mode 100644 index 00000000..f0de8688 --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_8x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6073c67e6b13bdc2688a2217119eb6bdee85f7507ce98b2d466fbf8db90b37b1 +size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_8x8.bmp b/tests/Images/Expected/ASTC/footprint_8x8.bmp new file mode 100644 index 00000000..d1f867de --- /dev/null +++ b/tests/Images/Expected/ASTC/footprint_8x8.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2f0ec842b80a95bffc742c66b2e19b85faf7736f0737f9a9da86ae965c839de +size 3210 diff --git a/tests/Images/Expected/ASTC/rgb_12x12.bmp b/tests/Images/Expected/ASTC/rgb_12x12.bmp new file mode 100644 index 00000000..29f43633 --- /dev/null +++ b/tests/Images/Expected/ASTC/rgb_12x12.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d70f50efa5c0bfc990e024be872ed270f43874e34a43a4d7d8a6fbfbb7f1ddc +size 193674 diff --git a/tests/Images/Expected/ASTC/rgb_4x4.bmp b/tests/Images/Expected/ASTC/rgb_4x4.bmp new file mode 100644 index 00000000..3874c6d0 --- /dev/null +++ b/tests/Images/Expected/ASTC/rgb_4x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ddf8ba01848174bdc1eadfaa4da351e9a41cf8acc238813dfd260219423f785 +size 193674 diff --git a/tests/Images/Expected/ASTC/rgb_5x4.bmp b/tests/Images/Expected/ASTC/rgb_5x4.bmp new file mode 100644 index 00000000..cf14c284 --- /dev/null +++ b/tests/Images/Expected/ASTC/rgb_5x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08d8d5d3181d8a12ec47ed00c477f6fe9f5d93069a0bf2ebfe1b9ce17b2f8f67 +size 193674 diff --git a/tests/Images/Expected/ASTC/rgb_6x6.bmp b/tests/Images/Expected/ASTC/rgb_6x6.bmp new file mode 100644 index 00000000..1e9dc2e3 --- /dev/null +++ b/tests/Images/Expected/ASTC/rgb_6x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54f44a68a25232bd4624400e93385ac3cc332a3cda293cb617911e42630d889a +size 193674 diff --git a/tests/Images/Expected/ASTC/rgb_8x8.bmp b/tests/Images/Expected/ASTC/rgb_8x8.bmp new file mode 100644 index 00000000..9ec02dd9 --- /dev/null +++ b/tests/Images/Expected/ASTC/rgb_8x8.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d612273927b74c4b07ee99ffa5028572b28689fc23f24c4c32ac40c3d6b7a7e6 +size 193674 diff --git a/tests/Images/Expected/FlightHelmet_baseColor.png b/tests/Images/Expected/FlightHelmet_baseColor.png new file mode 100644 index 00000000..121fa21d --- /dev/null +++ b/tests/Images/Expected/FlightHelmet_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e5567daf7a3ac83f6ebc04605f3605ac17c19a153b6b8aadd2596690209c34c +size 2778518 diff --git a/tests/Images/Expected/GoldenGateBridge3/filelist.txt b/tests/Images/Expected/GoldenGateBridge3/filelist.txt new file mode 100644 index 00000000..56558836 --- /dev/null +++ b/tests/Images/Expected/GoldenGateBridge3/filelist.txt @@ -0,0 +1,6 @@ +posx.jpg +negx.jpg +posy.jpg +negy.jpg +posz.jpg +negz.jpg diff --git a/tests/Images/Expected/GoldenGateBridge3/negx.jpg b/tests/Images/Expected/GoldenGateBridge3/negx.jpg new file mode 100644 index 00000000..4b420e53 --- /dev/null +++ b/tests/Images/Expected/GoldenGateBridge3/negx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:681d4598e743ff5ada3e8679c7241b0a338afedee094289fbc88818c44c37c97 +size 743985 diff --git a/tests/Images/Expected/GoldenGateBridge3/negy.jpg b/tests/Images/Expected/GoldenGateBridge3/negy.jpg new file mode 100644 index 00000000..ce1c8ac5 --- /dev/null +++ b/tests/Images/Expected/GoldenGateBridge3/negy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0fe09207f833fd7c9939ebaa28921da832f48e5b502135ff4e5f76e98bd2fd3 +size 586742 diff --git a/tests/Images/Expected/GoldenGateBridge3/negz.jpg b/tests/Images/Expected/GoldenGateBridge3/negz.jpg new file mode 100644 index 00000000..361149b8 --- /dev/null +++ b/tests/Images/Expected/GoldenGateBridge3/negz.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1eb32c8055b52ddf230bfe4846f78fa4ce03600541e7d3569d893dda0c14d3d7 +size 646123 diff --git a/tests/Images/Expected/GoldenGateBridge3/posx.jpg b/tests/Images/Expected/GoldenGateBridge3/posx.jpg new file mode 100644 index 00000000..3ae03904 --- /dev/null +++ b/tests/Images/Expected/GoldenGateBridge3/posx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a90ffea9ac8bf9bbfab93aa25ff23222a84decdc7d96c58bc6edbb1e9c9cce7 +size 721206 diff --git a/tests/Images/Expected/GoldenGateBridge3/posy.jpg b/tests/Images/Expected/GoldenGateBridge3/posy.jpg new file mode 100644 index 00000000..49fd8974 --- /dev/null +++ b/tests/Images/Expected/GoldenGateBridge3/posy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8e77108e821e7f11e16937152a700b6995ee996b7fedb50a4d6e9ee7d3f04db +size 674410 diff --git a/tests/Images/Expected/GoldenGateBridge3/posz.jpg b/tests/Images/Expected/GoldenGateBridge3/posz.jpg new file mode 100644 index 00000000..227469be --- /dev/null +++ b/tests/Images/Expected/GoldenGateBridge3/posz.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6f08f424f2c93f45c1525f756b1365c0a89651ca107d5fc9649b4370e25bb4b +size 616391 diff --git a/tests/Images/Expected/GoldenGateBridge3/readme.txt b/tests/Images/Expected/GoldenGateBridge3/readme.txt new file mode 100644 index 00000000..8b404c27 --- /dev/null +++ b/tests/Images/Expected/GoldenGateBridge3/readme.txt @@ -0,0 +1,13 @@ +Author +====== + +This is the work of Emil Persson, aka Humus. +http://www.humus.name + + + +License +======= + +This work is licensed under a Creative Commons Attribution 3.0 Unported License. +http://creativecommons.org/licenses/by/3.0/ diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg new file mode 100644 index 00000000..56f50812 --- /dev/null +++ b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7824d03409aa2059cf00de8c319b56df3a6805d9f76ea7a7c6242da0ff101ed +size 189930 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg new file mode 100644 index 00000000..88b5d484 --- /dev/null +++ b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fedb7076a37938b044428ac5ac608ff6f665358773c51c5412a8d22e9d1175e0 +size 195262 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg new file mode 100644 index 00000000..1a2950f1 --- /dev/null +++ b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35a10620acf7e73ffb55132b9e814ddbbaa45df1c445eefcef06b7458cb29967 +size 108801 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_height.png b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_height.png new file mode 100644 index 00000000..a0821106 --- /dev/null +++ b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2d24186ce5a5b9c718ad7b6365a7e22680ce85abfdbed8a37ad9e4205d4d012 +size 456564 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal.jpg new file mode 100644 index 00000000..27c64184 --- /dev/null +++ b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be3169943652ad478fe1f38569feb4aa1da0ff6db37a4cd22fec063fe8aea50f +size 329643 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png new file mode 100644 index 00000000..82ffa417 --- /dev/null +++ b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d525152f126ca682ac2ed54ed01a28300c88072582bc005d0846f556d99fbe24 +size 2143908 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg new file mode 100644 index 00000000..a48d7d33 --- /dev/null +++ b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:808018bf8dde084ac3e23493c3e26ea7c85335551cab20681454f6702d29eafc +size 118946 diff --git a/tests/Images/Expected/Iron_Bars/readme.txt b/tests/Images/Expected/Iron_Bars/readme.txt new file mode 100644 index 00000000..649d4903 --- /dev/null +++ b/tests/Images/Expected/Iron_Bars/readme.txt @@ -0,0 +1,11 @@ +Author +====== + +This is the work of Katsuagi from https://3dtextures.me. + + +License +======= + +This work is licensed under a Creative Commons Zero 1.0 +http://creativecommons.org/publicdomain/zero/1.0/legalcode diff --git a/tests/Images/Expected/Yokohama3/filelist.txt b/tests/Images/Expected/Yokohama3/filelist.txt new file mode 100644 index 00000000..56558836 --- /dev/null +++ b/tests/Images/Expected/Yokohama3/filelist.txt @@ -0,0 +1,6 @@ +posx.jpg +negx.jpg +posy.jpg +negy.jpg +posz.jpg +negz.jpg diff --git a/tests/Images/Expected/Yokohama3/negx.jpg b/tests/Images/Expected/Yokohama3/negx.jpg new file mode 100644 index 00000000..e6140aa5 --- /dev/null +++ b/tests/Images/Expected/Yokohama3/negx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b29bbd856363b3095f3447a1f47419b8a4d94032e3b8e69377a4f002b064d9b9 +size 1039406 diff --git a/tests/Images/Expected/Yokohama3/negy.jpg b/tests/Images/Expected/Yokohama3/negy.jpg new file mode 100644 index 00000000..97c86925 --- /dev/null +++ b/tests/Images/Expected/Yokohama3/negy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63401877a11eeef4016b947e46554f1e6e61ad60db7d5e7bebdd378d1751bb1d +size 994070 diff --git a/tests/Images/Expected/Yokohama3/negz.jpg b/tests/Images/Expected/Yokohama3/negz.jpg new file mode 100644 index 00000000..b8f54d55 --- /dev/null +++ b/tests/Images/Expected/Yokohama3/negz.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b52d48844e642f93443922088143cb466a7b46c9b3d31bb2b6a3f4b91c68787c +size 911570 diff --git a/tests/Images/Expected/Yokohama3/posx.jpg b/tests/Images/Expected/Yokohama3/posx.jpg new file mode 100644 index 00000000..6682af4f --- /dev/null +++ b/tests/Images/Expected/Yokohama3/posx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d28f2c6d384e919b93cf94d625677f3231032e691fa3832926f99f8752a8c56 +size 1036660 diff --git a/tests/Images/Expected/Yokohama3/posy.jpg b/tests/Images/Expected/Yokohama3/posy.jpg new file mode 100644 index 00000000..7bdeadbd --- /dev/null +++ b/tests/Images/Expected/Yokohama3/posy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:823f80f5dafac7c2dccf0b192867433e4e0f27d195541da02e99684ec28f79a4 +size 631474 diff --git a/tests/Images/Expected/Yokohama3/posz.jpg b/tests/Images/Expected/Yokohama3/posz.jpg new file mode 100644 index 00000000..7613d5ee --- /dev/null +++ b/tests/Images/Expected/Yokohama3/posz.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76cfa0cc922ac66794e5939b1f000edc051fc84200ef4663c4de02766e2f45d1 +size 871498 diff --git a/tests/Images/Expected/Yokohama3/readme.txt b/tests/Images/Expected/Yokohama3/readme.txt new file mode 100644 index 00000000..8b404c27 --- /dev/null +++ b/tests/Images/Expected/Yokohama3/readme.txt @@ -0,0 +1,13 @@ +Author +====== + +This is the work of Emil Persson, aka Humus. +http://www.humus.name + + + +License +======= + +This work is licensed under a Creative Commons Attribution 3.0 Unported License. +http://creativecommons.org/licenses/by/3.0/ diff --git a/tests/Images/Input/Astc/atlas_small_4x4.astc b/tests/Images/Input/Astc/atlas_small_4x4.astc new file mode 100644 index 0000000000000000000000000000000000000000..86ae047165ff33238ff5ee945d1f896499d6a52d GIT binary patch literal 65551 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�#<4OZ5m3ax`x~GM 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 F{{jEVJFfr$ literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/atlas_small_5x5.astc b/tests/Images/Input/Astc/atlas_small_5x5.astc new file mode 100644 index 0000000000000000000000000000000000000000..2831c90ed5159bea214808788e7ffc257cb529fc GIT binary patch literal 43280 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 literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/atlas_small_8x8.astc b/tests/Images/Input/Astc/atlas_small_8x8.astc new file mode 100644 index 0000000000000000000000000000000000000000..a8805f2851754a6e72240edcc481bd5b9d1c875c GIT binary patch literal 16400 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 literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/checkerboard.astc b/tests/Images/Input/Astc/checkerboard.astc new file mode 100644 index 0000000000000000000000000000000000000000..79acdca3732f0b72f25640c5df6b4b188aa1b847 GIT binary patch literal 80 icmWe$y)cG@gHeEi0f-nG7+hEx{sAe5&{YsfDjxtEr564G literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/checkered_10.astc b/tests/Images/Input/Astc/checkered_10.astc new file mode 100644 index 0000000000000000000000000000000000000000..e3b196578c32a6a1d8dc9b911c1a164bb2fc5310 GIT binary patch literal 1616 zcmY*Z3rtg27(VTVS`>Ogpges*QDJC!CeTeOVmqLiw#=Y*j16$8cZ$L?(I``Jf;@yZ zTCxewj-{mHU}Fr2m}SEkkIngDOlN}efdMn-T6Cy{>fQg}w!luCreE&;zw`b7`M%@% zb>lliAx}dHe0hXeU$XWU96U0@vI5e;Hp!J&;1MAfVi6E+L%NYD5fYZbb8WI_D{l`% z8a|GCiKL@rP123i3q1_iMxRcWoM$Si0#)r-r<>;x=tD-B3Vxga z$K+Cx-j&1QB-zNs?Wqq9The6I6T%Q}p!%`=AKV?^S7>gmel`Ey?McnCiQT=|WKK1d zrejZHsM5Q)FtE3DSkbmlx8O-<R%3&N5hluV=+k(MR-c&K6h#z{h=@V!yf6?v4F2q$xFSsD3lYN29(bW#!mC zGp7$Q)VKAFJZayNt@Y@JmFFJdW;phn)$^Q?rd?@T?h>ujrTDo%rL0yfMS;G3ft?5A zQ6HQi%f?bU6aKF7|A1{VTlsarA z#N4pxy&#krPWD~z6iY>+VVuHg|M>crlz4fute8Xswk%OO3`E|83fI^(v2t{>$hYIw9p_J^EQpG7|2Hq}u@< zqD%UD0wTLX`{ax8@ofJ+dQ@FKXU+`Y!-rE-Vlnc?^`}4gS&r}9_~O**7ky5G9y^~z z<$aNd7{+gz&Vz?XRTWyskq?vU#0jMm`&De&gqkd?}Xk4)Z^D*9+`TTpE9>|A6NK^MeXxLyV8z@J=IJwa}VRd9ifNy;5aQyzj zJRz<7P}Wy)hc^#S!)j%Sq|NI#Sqk)aKdYOiJN18t+4B;zjjwsbwDK=YV5G0B;6Q@Y zJlZ|?r!CA&6~*z13$m98f8MamUn=9HK8AD!q#804KrhF~ke!zGZ#T6AKMsTyL<(Hm z%R;=Lw3tHS3ex217FTO`C)Z|-1ewidizQDOjr!_aDrvhu>FejKgO+jnAP?cB>D02F z*|%{XM!o(h=Ix|A=x+MfUE+5i>9K-LZ zkJH1D%ke2L#YW_(;0OGy379{sA6Oqmv2b6({h|?-4fkIy_2B$2|5aq^xSQ|hN1>Z> zeyukP$AXSK>LI@`mLALT!2XdB^5hQM&is(OZV`7LH54`Y$En=!%Hc5Jih=zsZf6Py z9}g~_i+*8pztP~B8f-|U@az$ZG!w523P#z*E zK_)7X`a;n`VaSGVqR13W5D{lYGnt4AI|pV05HwNx3PTr_&9_NPzxKSF}G0)C4or*RJCzuKn#Yx+Nn7+zq#4V3joYl zMJdyW9Gr(@C=OZ?9hG^iz_>d#BrvJixEtlAv9&Tj&ePFlu0M5ZV1UiW_0dw%ak(AN z0|PK1+$7448OBhIE;IlB{jjj~bO=e{yHFyHT+{~^AnE1Iv*hz<4fa+Ob-Vm}8G6Cp zuY&3Af0og$4A6l1sv+ev>Tb||5&-9qIa+ksU10FBUTNQvf;gQ{R~5-cVK7(>2$U}j zk?T~ntCMf?c@`odlXe|Xr1XR8>Z-5PXv*EmWfl^hgIFx??q*b4%M+#}Hu?z@-LYO0 zoL{=9@Ie{sNc9r|Uqx|76wOOWX#iO7>g}|xk?SlW9o-{C&%t^F%B$Nf7xfP5-Ri*@ zO1;Y?y+OJg?G9M4Owak|>fO@~>h=2_uwqo-);m7ko{_4uH9-?ZdLz5|2|D51egZwG zvbiYctWVDT+$TAw4I7qO^wX-k;>jfsH*gS7f&(2PQfKUKpUuJ$! z%SZWr;=NOZ0Q9}8&l|(|13x@}u25uUK^R1rNPM-;ji0ofuV_I(0?@d6|N7BwxSp%& zL-kP-yN=eU2x|sw-uvnFX*PR!82ce=o}be?fzO9r$Q1y)D^{OQgdzxfwAYPhePLlK zDXy-#-=-JN);NnpsmAl|pJc6F6<~?~Ffy8iJP*$zpZD_()<@I_;Q8V(lB~l0BesGp z*J93O*X&<=$~0xKZayLet2jW_+KA$96kI)%}Em4DA?`z~W5y(ma<*(F+=5{~%Pd1im)|D{n6(hcsqZxR&&nhYg2en$f&oT`O z$&Kw4A9O>~Z(GUM`X3(GzAQj~T-J-{o5`3spewUR)5rT|%Xd2UAf7LUWh`!6QMyfy z$IK0T87_xRQs# zMdNEfv(kvge%-P*CcW!ZicL7=bb?Ss?ist$w%qJ zpQyeU`7s@$3+YvIik|h|DX;|^q>uzBj~==fcn8;G>`}oqbUfK<<83xGGQ!fNy?!n>Lw#V@K-P#Bv1IXu+s$wZWVZG-1#zt>%PfzSWxkNRi d$i;lDmjV7+Q!|rK;cnRdj-Q<^my5*^{tsNiNA~~# literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/checkered_12.astc b/tests/Images/Input/Astc/checkered_12.astc new file mode 100644 index 0000000000000000000000000000000000000000..a82583a9ed5bb9ef978b8f11021695d78527b236 GIT binary patch literal 2320 zcmZXVdrVVz6vuy^h*lRX7AOL4+JXfNL1nX$2p!UbwAxmj3WE_16S+|#7)2(JBAJ6p zcqu*sYq7Psf#Prx7{<-XkjO(AigQfHL%^XcP8}>h?tbT%>up)XKfjz$&-tG3IX!T{ z(Z$T3&Dw$x{$nBZ>NPp_;q!-wNzxXTk_M@~6^9Ta5lP3}+9D#{-O0D_swxi;3}8Mg zOr5|=ox3l^+TN|6r@NRAn4h948;gzEO@}B8@ zcXwdmjvXXPAm2AR(onPBagwxgaA{IzxDN|+o0DDr3kC^PT4?A}#6mYD2O$DQb*kSp zcSpU|G25$c^t^@}*;HJF!t{hV^^}LFy8&uq-c|KP`WO9uDSL(bRTX8$|&?~BnX(2B?P*8i?97ngPG7`~t& zC1u|}@KfRUC*QOQc+2_n`LVGg5yQ*n3Wd=KdXBmt&sOGPy;gb(3W9^1niyUvymE!b z0$w$ml<`jHbpHACprC8l7=CC-sZ34=JuTtoy#w)>x6-fICnna`GQ3WA|Nc0huPq7- z;aFww@q9Bz`!ZUgA1Y2D@y+1!6$8K{(32~j|9GU?{lblJ9l-w&1Sh|D z6Yth&G*^89AyD(`^6zH+LFom4_KKRsEm7;2r!-M|L}+8^?+g4dF!?2~=Y76wnC7?L zcJooZYCv^dHiv^(jzDR*uQ&$G!>Or#qW8neDJ#p~9{OFLj`}=Xp|<5d1O=H)@$rm5 zE-q)zEL;fl5=REl@LPlHSc9cLp5f!-+S_5iwrJzey=4KEUzUD)c_k)-pR^vW zHYaE0O5i1(9NsR_Yo%v&G%s(~EQTK%dim1P5qNRS`U##l@Rs>YODikq&SiL`vA@5g z19*Yb;V0H<$}fw1WoLKj5LiU(F&fLt3 z^?So*(RIN8pbeg+^Y!-X^*%nZKTmH)Pt;O+sr@OMOJw#k<+ za~_Ei6}tu$4B``q?_V7fkW@dAk3gb5`BF|jJYH6oM#Jz94xynrIlzmY^G|amn77)`{QQm%t(M`%Vv#5! z0{Eosn?3Z6IK36$-*0ap8_V#mt$lqXBhYVLz=a<>mQa3K^t)+OU0qQT!$(9MIZ{&t zJX>ET^xQI?x3Ot&hu9_yv%yRB$( zUg)Je?{b@#m`wQo5NP{#l}F=M9H;h$K)Mr!*ZkV4`gp#wqQzsnI}_?gXZyt_6YNj* z-Q=+)C+U0yGE6Q#vegdr=6zDfsLv|8gTG!j(`k@{Yzf4cmq&Rh89wCJ#GW)tFSSpT z#9CQr@tx1!#KxKTOB-#v*p&%#;7#|dRwpM11%W@h_yJ{lBj~gIefjwbg%2KB^3BcW z`=h&CIOcZ(<5u$;8+-9$?OKLUPj6{~{lovi{*64Wnm^rtgTdQdC}en@E-fuE5cnsn VkN=aoALLrzlav&NLLwo_{{U*k>68Ef literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/checkered_4.astc b/tests/Images/Input/Astc/checkered_4.astc new file mode 100644 index 0000000000000000000000000000000000000000..ac60716458fc4b169f60f9f1f045d58ad1f34e48 GIT binary patch literal 272 zcmWe$y)cG@gHeHj0f-nG7+6H5kNVZBK2z^FUMOSH_c${kCVef-67gL}n*RUiXJByQ zGztibba?!U<->;${QUfx9n1m3PhzhN7YT#qT{F}KRP3AlI(r#dfzteJ9WDZIo*!3T zcVv_3e}?}IY#c@#@)9Bg4a<%)0J;BDFR18x|JlW`Lzt7FA84fO1a?hU*^QD)ZJ-MD7;>H^?Se`DG`PD?>~G1`X^wq_Z7!g2CNc~`5D+Ue`NZ&UYvL}0k?l}QUCw| literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/checkered_5.astc b/tests/Images/Input/Astc/checkered_5.astc new file mode 100644 index 0000000000000000000000000000000000000000..0389c53d54b7d1ab03a84b91b3aefa2c07135632 GIT binary patch literal 416 zcmV;R0bl+TtD#&72mvSn04M+e0RRAHustobP)Lf_CJF}!`1tr~a22o%fhJ8{IRW1L z5JCuLh=++fJ|y=eXBH0+@bK_uFbXis%5)CGIskx!e0+RGs4h^MSZ;>6gf9ypA0HoS zum?-Z5#=c6=_26&|Nj^UP&=?vuEbs0)I%5nPyzU5P%&_|bFq2NOalN94-XFp&{FUe zomkozzlaP4Kn1`>IAF<4r5qz4$_0wx;NTxb2{Hq7%V=`Kg82CO2L}fv@pmhwVSQ$M z9Eiu`2?T>hkRZ#ii>E{Qb`T&B@9^+N5CD+ZOSJ7P{XGzag98I;C>rqhFEaApLJI%? z4*&pZP)SSay(c=3WFup6&N=60P)ShQw3UY4cm_W{z`#BRP)X3s$e`~*Y}*}G0RI3a z%?{9OPT+kI!yym|1OiuS5MPij=du=Il>%6wbI!qOXcN%j0>o8n92A^$&c_&Ka1~Il zVY%1h!~+2V0sj7Jhy#$uO3#`9Z5RLl{{a97up|&VM}Ko^|6CLXzz;x05IRjotv89T K_7Vu=;Naldf38pf literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/checkered_6.astc b/tests/Images/Input/Astc/checkered_6.astc new file mode 100644 index 0000000000000000000000000000000000000000..210dd340e128b789e0e9d22e81780a7ebae342cb GIT binary patch literal 592 zcmWe$y)cG@gVBJ20f-nG7+g;CZAiMfL`IOA`40%No#5MWyNmf$tU0p~!+(bVnHfwQ zq-q(CluhCJ&&dK6=l{sgf;o70Bp{yYDPrv8z zP%~l$y6(V#HV%FP74=p_;J^Xb6MUs&ozh3H=B(!fxhJ!Oc|yHj z`<^9HJPd#S|NpO&%M?0SpNp4iK0h!tnh&~kGkvIfbmu+GpFhkXz;c#vgV|NGcX58!KkeDYkQAUM2S0|K8fygD__LyD`x-rnEYe&=^S=l4){ckCeuECm2` zFaU72a9R8)@wAQ3tqt-)o+6a)y~*(Y`#KmEL}Ch*_LwukS=ti47$AwI%^m55>%UD! z3|D$n8ogPoV(60>zpfQ|7Kf!R644{4G7_>|Nli3fHNX%eQ`*w>h)kO*oUeAtO?l4R z72``y5uic@aF<-*=^I+?5dl8HBc9ut@5#x`m+=tsD0IKiedBuhj#?Fo#3=T$u|8AH zo8K@TNAIz>En9A`J;lv%dHs>vS91Dj+1QXI!{h%q%b6KMtQhnY+3|XW<9VK{iPQ-! z?Q)A;s1HZJ&-p;v@ZyC;1yq2cKNw4VjmbHrO5;s@z6izbNAh+V6HA`~5ClP#Z!c}n zHKtBIi!mT+Wg^x$Z+sQr|A{2TqRK3Z9u^N49Di~)6bfM&hCH2+wtDA64TMnWr#)?( zZMazTxJueqobGF**to&F^_`j1rHVMx9|#~{b!+pv9`)a??ZD&l;Dnw=tSg6l8z4{e zBs}r$qAzTY|cQRWJ!Lc3X3cZmd_LInXeait-ivALcXMcIa?8P<^k|^u%PR zLDv7t{||~PDU;^{=xZIny6GOqaa@S05wS<{iF#XIG`3%nS=%XewtZAkOvd^~+UFHwZM|?g~ s;Eh?iV{0r?VNcO1)i*L{nvJ%oij8P~c-IQ|dX-a_P=OXz(=Tf+!M~AdEtAF`+0M!YH!BQNhkm zF33PAC?seK2~r^hL8K7u9|aZFRB<&F&wYH$kLTt4Ufx%=$GaLD6#WG8dldxn^)3A! zlDxf@q<`mf9M{nSr&67r1Oe;yhlhQAu+b=rBnc-Hxtvl7x3_CF$s}AX()9E+tkqhr zdOe)YK0I`H!yeDZhW!7_OP$W)KpqbJ{j;+$NxIzz18lRgtSG|a@W8;?8B9?}M+yb} z{?0I`r*N${H01Nag5dQY9KbwZF2B6MJ3C5csRS=9oSeM9!8AQS-qr@Uv;>1{HOw&2 z&y$m|PPe_?+6t#qOG|EyuNMmOIBYf>3>*hnt7^5|4PRVb zU*FxqBO}Mho0~AlMIz}mTvwOR%lCP6lgY^YkHymIsVUSuojxDKz&$+_b#( l|BsI!A9g$P`T6s6`Mi=zqmiPJ^ZeW#O~Zaa%UUdw^aCvTFy{aO literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/checkered_9.astc b/tests/Images/Input/Astc/checkered_9.astc new file mode 100644 index 0000000000000000000000000000000000000000..b5c962a14fec5b1639e250cde842eb541d4aa59a GIT binary patch literal 1312 zcmXw(3s4(X6o&6+VY9Z71i}R5A&8f+o_aiXX-duc7Q?e z|FS8KB(c{4fFBHijZNl%d3nvxGYkP5m{wWL2n+$B2YLb|HymyOF~|ZVFhU`Mg(iu}ejLYI1?;kmiJER#iSLfqWzfj)z;d46@V;Kes~TD*YdYJSMQ z;Z3)`mQJWKAkUpyX?pbR^Yfe4fW=~AG$?fKE(t+AP_Egg==X`H+Ao^e)>AnXD0y(62sH>G=G}p}IS>E9%?7(bV+e{Q2?0M6R{dTh*fy zVn{#*C(vEePenVvrvL zGzb{N)_h;FcrF047XtBj3>kSpmZ;Tgl}aTS6SUhWcb$B0YD5yqLufqk`(Y!OE-psu z60ihJuJJH*R$Z2wlspgave78}h)oX;-tsE&VYAur$OtkJ@ziH^SL0m3Mft^U3SDA> zv)OFMFxa2*9>-{I%65GbSQ$!P`x^1Iw7~wzE7%2s9WMR%vpPADNG+>mixUo9Agg$K zy#$J&_S6Xa9_CG7cObzA*r2I2!|Bph&i+bIh6xySO79BKUN+UbuGf(!6T5sNy@IVd z*BL4{eka)2fc+6(&92PuEqY>3it(6%14@G)m*~dS2sA$`2tk7!W#; za;U{#+QH?PJCC$t6%!h<)F6E*OWAcq2Pi-ZG-AWBHnS@8W+~9Sfk##~xcFM=XJJup zAw{lt6s~A~I?i7Tcv>#l%4gt+OL*M+;EF!==)&LY=|db|!~>P`db15`5A08<%bShA zARf#5AbkI2=dZ5VA|50;$=BRF>M3=xUvPlOrGoDl#VMN|{<+w02I4X@k@MJt@vO&A z?lTs_^cW25YMlTn+m_O9quTftcrzez5Bbhs5ww*{ht%N|FFHGaDXQU z=ch4sx$|BTTx!X`{r*#wLJ<+cFapr@_V01;e-C*}N6S~95YX4g2O@9z|AFzzx}|*= zpevPpu!0G|`sBw;b{A1t-xz5kKA2GB R*8#t_>noM9u@VWx{0C`gEPMa} literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/footprint_10x10.astc b/tests/Images/Input/Astc/footprint_10x10.astc new file mode 100644 index 0000000000000000000000000000000000000000..6799cd08644ee3b49c2cd00ef70f057d2986eaa7 GIT binary patch literal 272 zcmWe$y)cH0i&24r0f-nG80^_2=ZjkGd-;`@VL#JPc@KN`#^bgL$o!S>Q#%lR6XoT_ zZ!a-4sH|O^d$+j0-k#mCT-0LPL70B7O!G-*Lf><}bHxRXjg5B$`Jcjq%j2ap#SKAx z6Xo5-Z?7Zt1LcLxMgAo??}VuTpSk@Z?;%Osp8Bqc&2DE~LGDZK$eRf>-$eO*v2Q&C z1B2?ywd;ZQn<#&5PW}FR0_)Bfj_RudSWT3dI;Q~nytXeK^;ZS(nkesljKV)$H!<() P+O@iC*Y>Vm8@e9=ccO2b literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/footprint_10x5.astc b/tests/Images/Input/Astc/footprint_10x5.astc new file mode 100644 index 0000000000000000000000000000000000000000..cbfe4f2644855fc34eedbb41f3ff215bf638582a GIT binary patch literal 464 zcmWe$y)cH0l~I9#0f-nG7$&o1FBc8E_H^nEAqSEa=+X*jhO(KJCJ^H+L z_hc65deNYDdlCAxm)lk$^CzEAU4-DXDnAC={|d=|{cO>oSu0`sojlkky=Styugg;- zjxf1?MRkjv&rGDoKvpAux%|C^}v9%%ntxcxx&LAnxu;&Yk)v2$vR&j;zx=lU~$ zEf1W3X)W8I`)}p`%5!Q@pUF_n)*)K<01%nA(Bh-)Fx5 zx4-YiYegCJo6BY%lb+A+-zjQw=rBTm`9|9WWd8h{sT~OZedhg7`};OrM(8&$6t!5j z8m4~|C+DObo|^u3T810hwPH5kU$f=4FKzhIf1i2z+5Wy6yJ7l)@fBdI{OMoPu%{o$pR|gnu6?}}kUyaYr2oo!H#k4aDd4~6y+iyFUA@jHYuk73Nej@9e zzPma9dhRppHw(MX*^N*izuCDAncsiEe9E-b4_P&)^{t5ee4m*;TG*{*GD5w1w{saX zKYe@ol)S|-^(M+6+e*H_p1^Cn!Il420I!MiQRmd}uLD?ZH#h?MtR~96&lA5Fg<9?} KbpKlvXbS+Paax!F literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/footprint_12x12.astc b/tests/Images/Input/Astc/footprint_12x12.astc new file mode 100644 index 0000000000000000000000000000000000000000..d94d0a6c0534e4f2db4a35e618a3170f50598d8a GIT binary patch literal 160 zcmWe$y)cG{hf#rn0f-nG7>t?xSBpCBc>Yk7Rafex+ec%j`rEEW2>yNMjsL5AwtReO z=`n3?%6}hYCi!Ynr;hCi_5Q0}ixB+v%#CNOds_B0GB_x2{J-FtG1F%kc9*PrL0)F% h=zr;D#!Skk<|PQeBUAYF!U-v@j8Dq{t^B1s8vqSLKaBtY literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/footprint_4x4.astc b/tests/Images/Input/Astc/footprint_4x4.astc new file mode 100644 index 0000000000000000000000000000000000000000..fdf3cd6b5f796f4bc1249b9a8466a1f4ffce06f0 GIT binary patch literal 1040 zcmYk4Ur19?9LHz(3Q9gKXLaYi$Ln;6rM<{tPOLRjQ!A)m!ary3x<9@+Oj<5u8b~#) zr36mVha)~7UTkh9!ei7f&!}t6)YF2wOZz8=bZT`xGx_#=lA)3&+p2DcbaKB zjgv^wmnM;YO1U&sqTUl zb?sVhEuMeV?-Ll&x7F7p`UH6(#kjAhdIsj!p!>tIb}zd2zJfVaH&+oZ>f838Gya7!<6nULBmea>TSRT!@Z&sHwq*4tlRRRRzywCgK4eST z$1KrcCNJDu?Ztij@IFS&tB|p*0=!?$ zH!Z7BUh4&Q;rTbnU;1iR>qvjLbmX>>HeX@09g3p!E^yA06yxgdFh<;BI`oFLPx)s?Yp> zsNwact4HW@HQrYXA2IW=urL_3TJgR*_@sT4%Uu~E#nYy>*w+I(>DW86kA}I*uxdS( z2j?#yI80G)P9uF@LHcvM^wiGm0?o#ItZ;HYC3B}3zyF|V1m>7W$p4D^hNk)E@C#7F q4;=qN{+|vQvBUrJIAQWKgaw87M$OiV;tHFAnm)nMI+gq9#r%J!SettQ literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/footprint_5x4.astc b/tests/Images/Input/Astc/footprint_5x4.astc new file mode 100644 index 0000000000000000000000000000000000000000..0683059469ee0cba5e0732cc0e373c48438b3b28 GIT binary patch literal 912 zcmYk4Uq};i0LSP30ap>@Bnm<&49@NDq}`dYX=mg1atjTS8bW&WB}?i-jwMCl9t>hs z6k8BP^A93ZRgWt=C-=EL-`}_TB5)(Zr zQdMs-;7hQ*bekP(8(^)b zM=yhFKO!uniHLn$_hDpIMrRp^!4esh(M3gL(C@CWJKDc1_y+^kE9+`rs!2vy8Qb<^ zpQByP+sMl34@F}L-x}&&`XfxuB$j69A3D|bwAqVJM(o|mHLV{J<$}w*_m9M zCct^^&G}(A-`1Gd&y$FljJ#JIow~n^jLXQpNElosQ!gPTO zsYAXc)aq-oLQ~g|)P7F07paLldEIxaIoHPKN>onwF+_|*;);XUeF_ohkd#OmERrb> z$zY_x7{zkPC&k8>`-1=Smv@I+d~V~vh)#1BJ{fg-lDTOD_%XUB3!iDC4VLKIENopp z<}M;G!!sh`(fvgtCBtcqG&n+LWOzZ+SnYSfyFzZCtK2UTwiGX%;NTId-et+U-+=pk zdU4~!rHx@~!{|rXdf~IHvW zd>kK(ZZDG>|L52TrEW)0o^~)bw7y`^9^NjhvhVI!)`t6x|F|~*)vvjsv@f<&`Ccp^ zlYTdl|He|~-rQR%Kt9kukp0!H|L#h&{R8v;)bsNznr?*UGsr90C)7`{XW#w0vIAk> zTK3Z)d#CKVzct4}noa%RM0yh-M_{(@1AYmJl%J3UKeHo{Xg5*2O)3I zUcWvt0hvE}cXHL5qntJkyE3BO0`1wCUoW2mwrrU+gThpxeL1FC^K7%?bYI9Z?3g~egSkg+$*j2TN37&LRQA;Y`DenH?wa>w zX_Z`u!oJ$ZE@q#Z$D;08E}u1zL#b#jke~DG_$<5GC#vRgvb~dBk7q}l<-UmaNT7YMLH6m~Oy7BLW=7;ZSC%56eZA{@C)CY_*$4C=kLnvM z-8a8v-bg|GcgEzwUz-Peb?(J++?d_m!7R$f_}u)=#0SeloGx4jhJUtyT0ti){DAS9 zbDeFbYz6yFV0`MX1*O08E}%R#{V}Q8=EXXk^;JEoeIPV2dV&&%tdT@-;_t?d3R~p0 z#n~ru$Zk>)P5RAgq`5`@W$|Qtc2!5qkd)k+S&9mbKYpj%vs;GxW+3x@GZSl&`H8iq Y6A=75&ipsuIm+cNUQUx|P~lh!0GYQ@6#xJL literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/footprint_6x5.astc b/tests/Images/Input/Astc/footprint_6x5.astc new file mode 100644 index 0000000000000000000000000000000000000000..d0d455d0bfbe88657447554b07ce424c94ec22c7 GIT binary patch literal 688 zcmWe$y)cH2l~I9#0f-nG7$&piP8Qa?^0f1S07uRH;&`Twu0Ccz>g;~ZbNeC3P*4u! zukrhFFYL#^I2fNN{fAxtkGOIeKd1i3y!Ida`XPL^^JTUdj`E#3a_YeL>ixBoSt9F& z^$uKwnLnAOaJse|f)BFK?^9jar+IOo0aKae`R3)WR{)xi~A7jC$pUY z+~2qFqT?hL&S#bLeuMPOzN=GxH&6E+%)O@X?%BTk=L+V}XD^-Y9JgjKr_cn3KVKh< z1J&oGy_=W!ZeJNp{h7LV|JvTk^+Wh-kBe+CoZ>rj&;jRbN@M@ zeWoww*=Fr?&5~=Fb)D&B=o#Ovf3YwAg=WbyeFz7d&y)7TF7HKL*$b$Da_U~pYkRS; z56oBU;QnNCyk949@}Cnzt3tv4l}Wj$lJZX{1*jjy=P^yOvrUO}O@Zn6O_>+^WMAwP zIfkO|K>0JNPyXdTkt+l8!SSP0_av_ENnIb9f3A~zlFD&?8ShDdoP-)?+y%K;=3br3 zy?HwK#A6=f`f#7dCQa3U0Bje3=|5xQ|fBgLl0QWaWf&c&j literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/footprint_6x6.astc b/tests/Images/Input/Astc/footprint_6x6.astc new file mode 100644 index 0000000000000000000000000000000000000000..47e038efd950f3aa773346512bf171d0bd151f82 GIT binary patch literal 592 zcmWe$y)cH2jZuMt0f-nG80^{8CyQ#Fd->J0!Jhf2d51lF{c2qYWd8KSt`W%m^|xaS zkom_ymv$id`&7pI1u7uCQ%f|#=9JJT&XI^=- zzi-A)nE4wGlF!II`>XOyRyR$KV{JW9zm92IoNZd2Ynog`&|i>!zG?eH)9%HB`Amk% zZ&K6b@}Ak2rO7E2{Ab$8m(!LuuP-gGE=`W1pngB|#Et!Z4GUrB1NG;~JeaHUV6V;t zS)hA?`p=j=_-pe()-?gFA84QNgV@jqwXtA6$iCEseYpwuN)tf#gTk}4Bd-(Y|NYFB zGyD58I$`GTXZ|h5ua%X}$Y3$EoH<@>KeMpDx)U;A+uc42neQAQUWCk#F3;~m@SP7f SOe}cM$nffSUA=eK{oMdz%lqB{ literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/footprint_8x5.astc b/tests/Images/Input/Astc/footprint_8x5.astc new file mode 100644 index 0000000000000000000000000000000000000000..4ef49d657dd4a259865df557c89edb457b41f523 GIT binary patch literal 464 zcmWe$y)cG@l~I9#0f-nG80NF*&J;Gg@|08S0>hurkDt$H@7-w}hRk1kGq?nLaZ1*k&H5MmN>-^LoT<}dle6I>nVp-J`~)>W%-`5yamnW3 zLYb4BmCp&X>i-4ujVuM3Wcarz90Box`gLqm;#^bag{H_U75xY57jiaal3Bl5$pxez zD4)}o5!;qBZ(54nq5_b4lPm<2q*=Ep?1Gv%$x<;%T6Bvdm_MK0voyG@pb_T2`Rtif ti>D#@#!SDJ_|3Ai4L2`S_SnQ0>hv0pT+01cWyThLFTW#9$bRVKl#0Q z3Nk;iUf5*ES%m(=`Nkp0{E7R6OOW{+?-x%&=3Ax^VvPigG&lpVeSKkSMd}CUzqv347Wj6w&CQvL VZmS)D%r}qqE_xJM!67wZWLd(E5Wt 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 @@ -8,8 +10,6 @@ namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; /// Based on the Google ASTC codec implementation. /// /// -/// -/// internal static class AstcDecoder { /// @@ -20,13 +20,31 @@ internal static class AstcDecoder /// The height of the block footprint (4-12). /// The output span for decoded RGBA pixels. /// Optional flag to indicate if the output should be in sRGB color space. + /// Thrown if the block dimensions are invalid. public static void DecodeBlock(ReadOnlySpan blockData, int blockWidth, int blockHeight, Span decodedPixels, bool isSrgb = false) { - AstcSharp.Footprint? footprint = AstcSharp.Footprint.FromDimensions(blockWidth, blockHeight); - if (footprint is null) - { - throw new ArgumentOutOfRangeException(nameof(blockWidth), "Block dimensions must be between 4 and 12."); - } - AstcSharp.Codec.DecompressBlock(blockData, footprint.Value, decodedPixels); + Footprint footprint = Footprint.FromFootprintType(FootprintFromDimensions(blockWidth, blockHeight)); + + AstcSharp.AstcDecoder.DecompressBlock(blockData, footprint, decodedPixels); } + + private static FootprintType FootprintFromDimensions(int width, int height) + => (width, height) switch + { + (4, 4) => FootprintType.Footprint4x4, + (5, 4) => FootprintType.Footprint5x4, + (5, 5) => FootprintType.Footprint5x5, + (6, 5) => FootprintType.Footprint6x5, + (6, 6) => FootprintType.Footprint6x6, + (8, 5) => FootprintType.Footprint8x5, + (8, 6) => FootprintType.Footprint8x6, + (8, 8) => FootprintType.Footprint8x8, + (10, 5) => FootprintType.Footprint10x5, + (10, 6) => FootprintType.Footprint10x6, + (10, 8) => FootprintType.Footprint10x8, + (10, 10) => FootprintType.Footprint10x10, + (12, 10) => FootprintType.Footprint12x10, + (12, 12) => FootprintType.Footprint12x12, + _ => throw new ArgumentOutOfRangeException(nameof(width), "Invalid footprint type."), + }; } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs index c1281c02..3e5a75b7 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using AstcSharp; +using AstcSharp.IO; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; using PixelRgba = SixLabors.ImageSharp.PixelFormats.Rgba32; @@ -33,22 +34,18 @@ public static IEnumerable AstcTestFiles() } } -#nullable enable [Theory] [MemberData(nameof(AstcTestFiles))] public void DecodeAstcFiles_ProducesExpectedImages(string inputPath, string expectedPath) { - using var expected = Image.Load(expectedPath); + using Image expected = Image.Load(expectedPath); byte[] inputData = File.ReadAllBytes(inputPath); - var inputFile = AstcFile.LoadFromMemory(inputData, out string? errorMessage); - int stride = expected.Width * 4; - byte[] decodedBuffer = new byte[stride * expected.Height]; + AstcFile inputFile = AstcFile.FromMemory(inputData); Assert.NotNull(inputFile); - Assert.Null(errorMessage); - Codec.DecompressToImage(inputFile, decodedBuffer, decodedBuffer.Length, stride); - using var actual = Image.LoadPixelData(decodedBuffer, expected.Width, expected.Height); + Span decodedBuffer = AstcDecoder.DecompressToImage(inputFile); + using Image actual = Image.LoadPixelData(decodedBuffer, expected.Width, expected.Height); // Reading the buffer manually will result in x,y order instead of row,column actual.Mutate(x => x.RotateFlip(RotateMode.Rotate180, FlipMode.Horizontal)); From 52ff1320e53bd9e94ded6d032e3e0f289ddf4c08 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:13:35 +0100 Subject: [PATCH 03/37] Fix comment tags --- .../TextureFormats/Decoding/AstcDecoder.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs index cd32e728..f2025298 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs @@ -6,10 +6,8 @@ namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; /// -/// An ASTC decoder for various block footprints. -/// Based on the Google ASTC codec implementation. +/// ASTC (Adaptive scalable texture compression) decoder for all valid block footprints. /// -/// internal static class AstcDecoder { /// From 2563be5b080d2873d8741f0eb5071ec32d158f39 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:15:19 +0100 Subject: [PATCH 04/37] Use strongly named AstcSharp --- src/ImageSharp.Textures/ImageSharp.Textures.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Textures/ImageSharp.Textures.csproj b/src/ImageSharp.Textures/ImageSharp.Textures.csproj index f793f7ba..bad97213 100644 --- a/src/ImageSharp.Textures/ImageSharp.Textures.csproj +++ b/src/ImageSharp.Textures/ImageSharp.Textures.csproj @@ -39,7 +39,7 @@ - + From 5df747f12650f3ba599a5b3e0424945b8e607b53 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:40:13 +0100 Subject: [PATCH 05/37] Remove temporary tests --- .../Astc/AstcAppliedPixelsComparisonTests.cs | 136 ------------------ .../Formats/Astc/AstcImageComparisonTests.cs | 71 --------- .../Formats/Ktx/KtxDecoderTests.cs | 32 ----- .../Baseline/AstcDebug/atlas_small_8x8.txt | 11 -- .../Baseline/AstcDebug/footprint_10x10.txt | 11 -- .../Baseline/AstcDebug/footprint_10x5.txt | 11 -- .../Baseline/AstcDebug/footprint_12x10.txt | 11 -- .../Images/Expected/ASTC/atlas_small_4x4.bmp | 3 - .../Images/Expected/ASTC/atlas_small_5x5.bmp | 3 - .../Images/Expected/ASTC/atlas_small_6x6.bmp | 3 - .../Images/Expected/ASTC/atlas_small_8x8.bmp | 3 - .../Images/Expected/ASTC/footprint_10x10.bmp | 3 - tests/Images/Expected/ASTC/footprint_10x5.bmp | 3 - tests/Images/Expected/ASTC/footprint_10x6.bmp | 3 - tests/Images/Expected/ASTC/footprint_10x8.bmp | 3 - .../Images/Expected/ASTC/footprint_12x10.bmp | 3 - .../Images/Expected/ASTC/footprint_12x12.bmp | 3 - tests/Images/Expected/ASTC/footprint_4x4.bmp | 3 - tests/Images/Expected/ASTC/footprint_5x4.bmp | 3 - tests/Images/Expected/ASTC/footprint_5x5.bmp | 3 - tests/Images/Expected/ASTC/footprint_6x5.bmp | 3 - tests/Images/Expected/ASTC/footprint_6x6.bmp | 3 - tests/Images/Expected/ASTC/footprint_8x5.bmp | 3 - tests/Images/Expected/ASTC/footprint_8x6.bmp | 3 - tests/Images/Expected/ASTC/footprint_8x8.bmp | 3 - tests/Images/Expected/ASTC/rgb_12x12.bmp | 3 - tests/Images/Expected/ASTC/rgb_4x4.bmp | 3 - tests/Images/Expected/ASTC/rgb_5x4.bmp | 3 - tests/Images/Expected/ASTC/rgb_6x6.bmp | 3 - tests/Images/Expected/ASTC/rgb_8x8.bmp | 3 - tests/Images/Input/Astc/atlas_small_4x4.astc | Bin 65551 -> 0 bytes tests/Images/Input/Astc/atlas_small_5x5.astc | Bin 43280 -> 0 bytes tests/Images/Input/Astc/atlas_small_6x6.astc | Bin 29599 -> 0 bytes tests/Images/Input/Astc/atlas_small_8x8.astc | Bin 16400 -> 0 bytes tests/Images/Input/Astc/checkerboard.astc | Bin 80 -> 0 bytes tests/Images/Input/Astc/checkered_10.astc | Bin 1616 -> 0 bytes tests/Images/Input/Astc/checkered_11.astc | Bin 1952 -> 0 bytes tests/Images/Input/Astc/checkered_12.astc | Bin 2320 -> 0 bytes tests/Images/Input/Astc/checkered_4.astc | Bin 272 -> 0 bytes tests/Images/Input/Astc/checkered_5.astc | Bin 416 -> 0 bytes tests/Images/Input/Astc/checkered_6.astc | Bin 592 -> 0 bytes tests/Images/Input/Astc/checkered_7.astc | Bin 800 -> 0 bytes tests/Images/Input/Astc/checkered_8.astc | Bin 1040 -> 0 bytes tests/Images/Input/Astc/checkered_9.astc | Bin 1312 -> 0 bytes tests/Images/Input/Astc/footprint_10x10.astc | Bin 272 -> 0 bytes tests/Images/Input/Astc/footprint_10x5.astc | Bin 464 -> 0 bytes tests/Images/Input/Astc/footprint_10x6.astc | Bin 400 -> 0 bytes tests/Images/Input/Astc/footprint_10x8.astc | Bin 272 -> 0 bytes tests/Images/Input/Astc/footprint_12x10.astc | Bin 208 -> 0 bytes tests/Images/Input/Astc/footprint_12x12.astc | Bin 160 -> 0 bytes tests/Images/Input/Astc/footprint_4x4.astc | Bin 1040 -> 0 bytes tests/Images/Input/Astc/footprint_5x4.astc | Bin 912 -> 0 bytes tests/Images/Input/Astc/footprint_5x5.astc | Bin 800 -> 0 bytes tests/Images/Input/Astc/footprint_6x5.astc | Bin 688 -> 0 bytes tests/Images/Input/Astc/footprint_6x6.astc | Bin 592 -> 0 bytes tests/Images/Input/Astc/footprint_8x5.astc | Bin 464 -> 0 bytes tests/Images/Input/Astc/footprint_8x6.astc | Bin 400 -> 0 bytes tests/Images/Input/Astc/footprint_8x8.astc | Bin 272 -> 0 bytes tests/Images/Input/Astc/rgb_12x12.astc | Bin 7312 -> 0 bytes tests/Images/Input/Astc/rgb_4x4.astc | Bin 64528 -> 0 bytes tests/Images/Input/Astc/rgb_5x4.astc | Bin 51855 -> 0 bytes tests/Images/Input/Astc/rgb_6x6.astc | Bin 29200 -> 0 bytes tests/Images/Input/Astc/rgb_8x8.astc | Bin 16144 -> 0 bytes 63 files changed, 352 deletions(-) delete mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/AstcAppliedPixelsComparisonTests.cs delete mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs delete mode 100644 tests/Images/Baseline/AstcDebug/atlas_small_8x8.txt delete mode 100644 tests/Images/Baseline/AstcDebug/footprint_10x10.txt delete mode 100644 tests/Images/Baseline/AstcDebug/footprint_10x5.txt delete mode 100644 tests/Images/Baseline/AstcDebug/footprint_12x10.txt delete mode 100644 tests/Images/Expected/ASTC/atlas_small_4x4.bmp delete mode 100644 tests/Images/Expected/ASTC/atlas_small_5x5.bmp delete mode 100644 tests/Images/Expected/ASTC/atlas_small_6x6.bmp delete mode 100644 tests/Images/Expected/ASTC/atlas_small_8x8.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_10x10.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_10x5.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_10x6.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_10x8.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_12x10.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_12x12.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_4x4.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_5x4.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_5x5.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_6x5.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_6x6.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_8x5.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_8x6.bmp delete mode 100644 tests/Images/Expected/ASTC/footprint_8x8.bmp delete mode 100644 tests/Images/Expected/ASTC/rgb_12x12.bmp delete mode 100644 tests/Images/Expected/ASTC/rgb_4x4.bmp delete mode 100644 tests/Images/Expected/ASTC/rgb_5x4.bmp delete mode 100644 tests/Images/Expected/ASTC/rgb_6x6.bmp delete mode 100644 tests/Images/Expected/ASTC/rgb_8x8.bmp delete mode 100644 tests/Images/Input/Astc/atlas_small_4x4.astc delete mode 100644 tests/Images/Input/Astc/atlas_small_5x5.astc delete mode 100644 tests/Images/Input/Astc/atlas_small_6x6.astc delete mode 100644 tests/Images/Input/Astc/atlas_small_8x8.astc delete mode 100644 tests/Images/Input/Astc/checkerboard.astc delete mode 100644 tests/Images/Input/Astc/checkered_10.astc delete mode 100644 tests/Images/Input/Astc/checkered_11.astc delete mode 100644 tests/Images/Input/Astc/checkered_12.astc delete mode 100644 tests/Images/Input/Astc/checkered_4.astc delete mode 100644 tests/Images/Input/Astc/checkered_5.astc delete mode 100644 tests/Images/Input/Astc/checkered_6.astc delete mode 100644 tests/Images/Input/Astc/checkered_7.astc delete mode 100644 tests/Images/Input/Astc/checkered_8.astc delete mode 100644 tests/Images/Input/Astc/checkered_9.astc delete mode 100644 tests/Images/Input/Astc/footprint_10x10.astc delete mode 100644 tests/Images/Input/Astc/footprint_10x5.astc delete mode 100644 tests/Images/Input/Astc/footprint_10x6.astc delete mode 100644 tests/Images/Input/Astc/footprint_10x8.astc delete mode 100644 tests/Images/Input/Astc/footprint_12x10.astc delete mode 100644 tests/Images/Input/Astc/footprint_12x12.astc delete mode 100644 tests/Images/Input/Astc/footprint_4x4.astc delete mode 100644 tests/Images/Input/Astc/footprint_5x4.astc delete mode 100644 tests/Images/Input/Astc/footprint_5x5.astc delete mode 100644 tests/Images/Input/Astc/footprint_6x5.astc delete mode 100644 tests/Images/Input/Astc/footprint_6x6.astc delete mode 100644 tests/Images/Input/Astc/footprint_8x5.astc delete mode 100644 tests/Images/Input/Astc/footprint_8x6.astc delete mode 100644 tests/Images/Input/Astc/footprint_8x8.astc delete mode 100644 tests/Images/Input/Astc/rgb_12x12.astc delete mode 100644 tests/Images/Input/Astc/rgb_4x4.astc delete mode 100644 tests/Images/Input/Astc/rgb_5x4.astc delete mode 100644 tests/Images/Input/Astc/rgb_6x6.astc delete mode 100644 tests/Images/Input/Astc/rgb_8x8.astc diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcAppliedPixelsComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcAppliedPixelsComparisonTests.cs deleted file mode 100644 index 4983b740..00000000 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcAppliedPixelsComparisonTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.IO; -using System.Linq; -using System.Text; -using SixLabors.ImageSharp.Textures.Tests.TestUtilities; -using Xunit; - -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc -{ -// [Trait("Format", "Astc")] -// public class AstcAppliedPixelsComparisonTests -// { -// private static string AstInputDir => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "Astc"); -// private static string BaselineDir => Path.Combine(TestEnvironment.BaselineDirectoryFullPath, "AstcApplied"); -// private static string ActualDir => Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "AstcApplied"); - -// [Theory] -// [InlineData("atlas_small_8x8.astc", 8, 8)] -// [InlineData("footprint_10x5.astc", 10, 5)] -// [InlineData("footprint_12x10.astc", 12, 10)] -// [InlineData("footprint_10x10.astc", 10, 10)] -// public void AppliedPixels_MatchBaseline(string fileName, int blockW, int blockH) -// { -// Directory.CreateDirectory(ActualDir); - -// string path = Path.Combine(AstInputDir, fileName); -// Assert.True(File.Exists(path), "ASTC input file not found: " + path); - -// byte[] data = File.ReadAllBytes(path); -// var pixels = SixLabors.ImageSharp.Textures.TextureFormats.Decoding.AstcDecoder.ExtractAppliedColorPixelsForTests(data.AsSpan(0, 16), blockW, blockH); -// Assert.NotNull(pixels); - -// string baseName = Path.GetFileNameWithoutExtension(fileName); -// string baselinePath = Path.Combine(BaselineDir, baseName + ".raw"); -// string actualPath = Path.Combine(ActualDir, baseName + ".raw"); - -// // Write actual for debugging purposes -// File.WriteAllBytes(actualPath, pixels); - -// if (File.Exists(baselinePath)) -// { -// byte[] expected = File.ReadAllBytes(baselinePath); -// if (!expected.SequenceEqual(pixels)) -// { -// // Build a helpful diagnostic message with mismatches and ranges -// int expectedLen = expected.Length; -// int actualLen = pixels.Length; -// int maxLen = Math.Max(expectedLen, actualLen); - -// var mismatchIndices = new System.Collections.Generic.List(); -// for (int i = 0; i < maxLen; i++) -// { -// byte e = i < expectedLen ? expected[i] : (byte)0xFF; -// byte a = i < actualLen ? pixels[i] : (byte)0xFF; -// if (i >= expectedLen || i >= actualLen || e != a) -// { -// mismatchIndices.Add(i); -// } -// } - -// // Coalesce contiguous indices into ranges -// var ranges = new System.Collections.Generic.List<(int start, int end)>(); -// foreach (var idx in mismatchIndices) -// { -// if (ranges.Count == 0 || idx != ranges.Last().end + 1) -// { -// ranges.Add((idx, idx)); -// } -// else -// { -// var last = ranges.Last(); -// ranges[ranges.Count - 1] = (last.start, idx); -// } -// } - -// var sb = new StringBuilder(); -// sb.AppendLine($"Applied pixel mismatch for {fileName}."); -// sb.AppendLine($"Baseline: {baselinePath}"); -// sb.AppendLine($"Actual: {actualPath}"); -// sb.AppendLine($"Baseline length: {expectedLen} bytes"); -// sb.AppendLine($"Actual length: {actualLen} bytes"); -// sb.AppendLine($"Total mismatching byte positions: {mismatchIndices.Count}"); - -// if (ranges.Count > 0) -// { -// sb.AppendLine("Mismatch ranges (start..end):"); -// sb.AppendLine(string.Join(", ", ranges.Select(r => r.start == r.end ? r.start.ToString() : r.start + ".." + r.end))); -// } - -// // Show up to 10 sample mismatches with expected vs actual hex -// int samples = Math.Min(10, mismatchIndices.Count); -// if (samples > 0) -// { -// sb.AppendLine("Sample mismatches (index: expected -> actual):"); -// for (int i = 0; i < samples; i++) -// { -// int idx = mismatchIndices[i]; -// string eStr = idx < expectedLen ? expected[idx].ToString("X2") : "--"; -// string aStr = idx < actualLen ? pixels[idx].ToString("X2") : "--"; -// sb.AppendLine($" {idx}: {eStr} -> {aStr}"); -// } -// } - -// // Provide a small hex context around the first mismatch -// if (mismatchIndices.Count > 0) -// { -// int ctxIdx = mismatchIndices[0]; -// int ctxStart = Math.Max(0, ctxIdx - 8); -// int ctxEnd = Math.Min(maxLen - 1, ctxIdx + 8); -// sb.AppendLine($"Hex context around first mismatch (offset {ctxIdx}):"); -// var ctxLines = new System.Collections.Generic.List(); -// for (int i = ctxStart; i <= ctxEnd; i++) -// { -// string eStr = i < expectedLen ? expected[i].ToString("X2") : "--"; -// string aStr = i < actualLen ? pixels[i].ToString("X2") : "--"; -// string marker = i == ctxIdx ? "*" : " "; -// ctxLines.Add($"{marker}{i:D4}: {eStr} | {aStr}"); -// } -// sb.AppendLine(string.Join(Environment.NewLine, ctxLines)); -// } - -// throw new Exception(sb.ToString()); -// } -// } -// else -// { -// // Create baseline directory and populate baseline for future runs -// Directory.CreateDirectory(Path.GetDirectoryName(baselinePath)!); -// File.Copy(actualPath, baselinePath); -// } -// } -// } -} diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs deleted file mode 100644 index 3e5a75b7..00000000 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcImageComparisonTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using AstcSharp; -using AstcSharp.IO; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; -using PixelRgba = SixLabors.ImageSharp.PixelFormats.Rgba32; -using TestUtilEnv = SixLabors.ImageSharp.Textures.Tests.TestUtilities.TestEnvironment; - -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; - -[Trait("Format", "Astc")] -public class AstcImageComparisonTests -{ - public static IEnumerable AstcTestFiles() - { - string inputDir = Path.Combine(TestUtilEnv.InputImagesDirectoryFullPath, "Astc"); - string expectedDir = Path.Combine(TestUtilEnv.SolutionDirectoryFullPath, "tests", "Images", "Expected", "ASTC"); - - if (!Directory.Exists(inputDir) || !Directory.Exists(expectedDir)) - { - yield break; - } - - foreach (string astc in Directory.GetFiles(inputDir, "*.astc")) - { - string baseName = Path.GetFileNameWithoutExtension(astc); - string expected = Path.Combine(expectedDir, baseName + ".bmp"); - if (File.Exists(expected)) - { - yield return new object[] { astc, expected }; - } - } - } - - [Theory] - [MemberData(nameof(AstcTestFiles))] - public void DecodeAstcFiles_ProducesExpectedImages(string inputPath, string expectedPath) - { - using Image expected = Image.Load(expectedPath); - byte[] inputData = File.ReadAllBytes(inputPath); - AstcFile inputFile = AstcFile.FromMemory(inputData); - - Assert.NotNull(inputFile); - - Span decodedBuffer = AstcDecoder.DecompressToImage(inputFile); - using Image actual = Image.LoadPixelData(decodedBuffer, expected.Width, expected.Height); - // Reading the buffer manually will result in x,y order instead of row,column - actual.Mutate(x => x.RotateFlip(RotateMode.Rotate180, FlipMode.Horizontal)); - - // Compare exact - // Use a tighter tolerant comparer threshold to enforce more accurate decoding. - var comparer = ImageComparer.TolerantPercentage(0.1f); - ImageSimilarityReport report = comparer.CompareImages(expected, actual); - - if (!report.IsEmpty) - { - // Save actual image for debugging - string outDir = TestUtilEnv.CreateOutputDirectory("ASTC"); - string outFile = Path.Combine(outDir, Path.GetFileName(inputPath) + ".actual.png"); - actual.SaveAsPng(outFile); - - // Also save a small text report - string reportFile = Path.ChangeExtension(outFile, ".diff.txt"); - File.WriteAllText(reportFile, report.ToString()); - } - - Assert.True(report.IsEmpty, $"Image difference too large for {Path.GetFileName(inputPath)} -> {report.DifferencePercentageString}"); - } -} diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs index 27e24bbe..1fbacc67 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs @@ -48,37 +48,5 @@ public void KtxDecoder_CanDecode_Rgba8888(TestTextureProvider provider) var firstMipMapImage = firstMipMap as Image; firstMipMapImage.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); } - - - [Fact] - public void KtxDecoder_CanDecode() - { - // KTX 1.1 image with ASTC compression - using Texture texture = Texture.Load("D:\\fileconverters\\TestData\\Image\\KTX\\test_ktx_11.ktx"); - var flatTexture = texture as FlatTexture; - - Assert.NotNull(flatTexture?.MipMaps); - //Assert.Equal(8, flatTexture.MipMaps.Count); - //Assert.Equal(200, flatTexture.MipMaps[0].GetImage().Height); - //Assert.Equal(200, flatTexture.MipMaps[0].GetImage().Width); - //Assert.Equal(100, flatTexture.MipMaps[1].GetImage().Height); - //Assert.Equal(100, flatTexture.MipMaps[1].GetImage().Width); - //Assert.Equal(50, flatTexture.MipMaps[2].GetImage().Height); - //Assert.Equal(50, flatTexture.MipMaps[2].GetImage().Width); - //Assert.Equal(25, flatTexture.MipMaps[3].GetImage().Height); - //Assert.Equal(25, flatTexture.MipMaps[3].GetImage().Width); - //Assert.Equal(12, flatTexture.MipMaps[4].GetImage().Height); - //Assert.Equal(12, flatTexture.MipMaps[4].GetImage().Width); - //Assert.Equal(6, flatTexture.MipMaps[5].GetImage().Height); - //Assert.Equal(6, flatTexture.MipMaps[5].GetImage().Width); - //Assert.Equal(3, flatTexture.MipMaps[6].GetImage().Height); - //Assert.Equal(3, flatTexture.MipMaps[6].GetImage().Width); - //Assert.Equal(1, flatTexture.MipMaps[7].GetImage().Height); - //Assert.Equal(1, flatTexture.MipMaps[7].GetImage().Width); - Image firstMipMap = flatTexture.MipMaps[0].GetImage(); - Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); - var firstMipMapImage = firstMipMap as Image; - firstMipMapImage.SaveAsPng(@"C:\Users\ErikWhite\Downloads\image.png"); - } } } diff --git a/tests/Images/Baseline/AstcDebug/atlas_small_8x8.txt b/tests/Images/Baseline/AstcDebug/atlas_small_8x8.txt deleted file mode 100644 index 693016bc..00000000 --- a/tests/Images/Baseline/AstcDebug/atlas_small_8x8.txt +++ /dev/null @@ -1,11 +0,0 @@ -File: atlas_small_8x8.astc -EndpointRange: 0 -WeightRange: 2 -WeightGrid: 4x8 -IsDualPlane: False -PlaneSelector: 0 -EndpointValues: 0,255,0,0,0,0 -Endpoint[0]: R0=0,G0=0,B0=0,A0=255 | R1=255,G1=255,B1=255,A1=255 -Endpoint[1]: R0=0,G0=0,B0=0,A0=255 | R1=0,G1=0,B1=0,A1=255 -GridWeightsPlane0: 0,0,0,0,0,0,0,37,0,0,0,0,0,9,0,0,0,0,0,0,0,9,0,0,0,0,18,0,9,0,37,0 -GridWeightsPlane1: diff --git a/tests/Images/Baseline/AstcDebug/footprint_10x10.txt b/tests/Images/Baseline/AstcDebug/footprint_10x10.txt deleted file mode 100644 index 19656bc2..00000000 --- a/tests/Images/Baseline/AstcDebug/footprint_10x10.txt +++ /dev/null @@ -1,11 +0,0 @@ -File: footprint_10x10.astc -EndpointRange: 0 -WeightRange: 2 -WeightGrid: 4x8 -IsDualPlane: False -PlaneSelector: 0 -EndpointValues: 0,255,0,0,255,0 -Endpoint[0]: R0=0,G0=0,B0=0,A0=255 | R1=255,G1=255,B1=255,A1=255 -Endpoint[1]: R0=0,G0=0,B0=0,A0=255 | R1=0,G1=0,B1=0,A1=255 -GridWeightsPlane0: 0,0,0,0,0,0,0,37,0,0,0,0,0,0,9,0,0,0,0,0,0,0,9,0,0,0,18,0,46,0,37,18 -GridWeightsPlane1: diff --git a/tests/Images/Baseline/AstcDebug/footprint_10x5.txt b/tests/Images/Baseline/AstcDebug/footprint_10x5.txt deleted file mode 100644 index 5ada1c04..00000000 --- a/tests/Images/Baseline/AstcDebug/footprint_10x5.txt +++ /dev/null @@ -1,11 +0,0 @@ -File: footprint_10x5.astc -EndpointRange: 0 -WeightRange: 2 -WeightGrid: 4x8 -IsDualPlane: False -PlaneSelector: 0 -EndpointValues: 0,255,0,0,255,0 -Endpoint[0]: R0=0,G0=0,B0=0,A0=255 | R1=255,G1=255,B1=255,A1=255 -Endpoint[1]: R0=0,G0=0,B0=0,A0=255 | R1=0,G1=0,B1=0,A1=255 -GridWeightsPlane0: 0,0,0,0,0,0,0,37,0,0,0,0,0,0,9,0,0,0,0,0,0,0,9,0,0,0,18,0,18,9,37,18 -GridWeightsPlane1: diff --git a/tests/Images/Baseline/AstcDebug/footprint_12x10.txt b/tests/Images/Baseline/AstcDebug/footprint_12x10.txt deleted file mode 100644 index b952a5db..00000000 --- a/tests/Images/Baseline/AstcDebug/footprint_12x10.txt +++ /dev/null @@ -1,11 +0,0 @@ -File: footprint_12x10.astc -EndpointRange: 0 -WeightRange: 2 -WeightGrid: 4x8 -IsDualPlane: False -PlaneSelector: 0 -EndpointValues: 0,255,0,0,0,255 -Endpoint[0]: R0=0,G0=0,B0=0,A0=255 | R1=255,G1=255,B1=255,A1=255 -Endpoint[1]: R0=0,G0=0,B0=0,A0=255 | R1=0,G1=0,B1=0,A1=255 -GridWeightsPlane0: 0,0,0,0,0,0,0,37,0,0,0,0,0,0,9,0,0,0,0,0,0,0,9,0,0,0,18,0,46,0,37,9 -GridWeightsPlane1: diff --git a/tests/Images/Expected/ASTC/atlas_small_4x4.bmp b/tests/Images/Expected/ASTC/atlas_small_4x4.bmp deleted file mode 100644 index 27dbb5ce..00000000 --- a/tests/Images/Expected/ASTC/atlas_small_4x4.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:960c183b5a1f139466dea2d3196bc58b426935545bd203793d2a745fd8dbc29e -size 262282 diff --git a/tests/Images/Expected/ASTC/atlas_small_5x5.bmp b/tests/Images/Expected/ASTC/atlas_small_5x5.bmp deleted file mode 100644 index 9dbc9152..00000000 --- a/tests/Images/Expected/ASTC/atlas_small_5x5.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:959bce6386ac4ee035cfbbbdbf9537e3f2a5ebd5795fbd531dea301a4c6f74ba -size 262282 diff --git a/tests/Images/Expected/ASTC/atlas_small_6x6.bmp b/tests/Images/Expected/ASTC/atlas_small_6x6.bmp deleted file mode 100644 index 841123a8..00000000 --- a/tests/Images/Expected/ASTC/atlas_small_6x6.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e70d5d7a9eb7c280f397eefe5c71a78530508fde1caebabe9fda39a91aa5847 -size 262282 diff --git a/tests/Images/Expected/ASTC/atlas_small_8x8.bmp b/tests/Images/Expected/ASTC/atlas_small_8x8.bmp deleted file mode 100644 index 719856d8..00000000 --- a/tests/Images/Expected/ASTC/atlas_small_8x8.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3ca7675d2e01e56fc63f2a2b59b2c852d2b5793e21a2e1125ccf81c8cdf3431 -size 262282 diff --git a/tests/Images/Expected/ASTC/footprint_10x10.bmp b/tests/Images/Expected/ASTC/footprint_10x10.bmp deleted file mode 100644 index 6be39794..00000000 --- a/tests/Images/Expected/ASTC/footprint_10x10.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:713880d96d562a92648bb306d57cddfac45805e99853dec6c7cf7ef461cfd795 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_10x5.bmp b/tests/Images/Expected/ASTC/footprint_10x5.bmp deleted file mode 100644 index fc7cd4e9..00000000 --- a/tests/Images/Expected/ASTC/footprint_10x5.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f661da916564a6c8dee8cd6ae6961c95a48416788cf0f52649a31b5ff36bafb5 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_10x6.bmp b/tests/Images/Expected/ASTC/footprint_10x6.bmp deleted file mode 100644 index e41db3ff..00000000 --- a/tests/Images/Expected/ASTC/footprint_10x6.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa618100f436836c42104732573952180f39eafe3c64b87a99b264fb284487dc -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_10x8.bmp b/tests/Images/Expected/ASTC/footprint_10x8.bmp deleted file mode 100644 index 33c468a4..00000000 --- a/tests/Images/Expected/ASTC/footprint_10x8.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6be48ee1e53b3cc868a23dd3ca9510f8af3f8f47ae1417aee95daed15581a4e3 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_12x10.bmp b/tests/Images/Expected/ASTC/footprint_12x10.bmp deleted file mode 100644 index 2e863d5e..00000000 --- a/tests/Images/Expected/ASTC/footprint_12x10.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f753526bd5c19656a44b837fac0c535470a7c5b9bef0c4cdbc8b3987fdf348c4 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_12x12.bmp b/tests/Images/Expected/ASTC/footprint_12x12.bmp deleted file mode 100644 index 3a6977ff..00000000 --- a/tests/Images/Expected/ASTC/footprint_12x12.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7314cdfe475599d2c55645fad6b51379a129447931ae6b82538711942ecbee4 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_4x4.bmp b/tests/Images/Expected/ASTC/footprint_4x4.bmp deleted file mode 100644 index c5d83d2e..00000000 --- a/tests/Images/Expected/ASTC/footprint_4x4.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a16fbf67b4bb9a0ec3f3e3b78006f39610f7869e0cdb4bd945d660957d1090e -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_5x4.bmp b/tests/Images/Expected/ASTC/footprint_5x4.bmp deleted file mode 100644 index 9dd29e11..00000000 --- a/tests/Images/Expected/ASTC/footprint_5x4.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd3a8f13222fa52a43678b3d4269ac5dee826f9f2ffb69061650e5c4300fb1e6 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_5x5.bmp b/tests/Images/Expected/ASTC/footprint_5x5.bmp deleted file mode 100644 index 6f9543fc..00000000 --- a/tests/Images/Expected/ASTC/footprint_5x5.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:273af3da91ccbfac3de9e04df9582e13808feda0fb263f52389238b96165105d -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_6x5.bmp b/tests/Images/Expected/ASTC/footprint_6x5.bmp deleted file mode 100644 index 8426e2c2..00000000 --- a/tests/Images/Expected/ASTC/footprint_6x5.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2623e574e64e88268571ec139aea89607fa7a4b149bb740cdcd37c55aac0d0b6 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_6x6.bmp b/tests/Images/Expected/ASTC/footprint_6x6.bmp deleted file mode 100644 index a31d92ef..00000000 --- a/tests/Images/Expected/ASTC/footprint_6x6.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b31d247567aa583f7c6cd1b0dc3ec311f97a2fe7893a2c4d82916786717ac622 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_8x5.bmp b/tests/Images/Expected/ASTC/footprint_8x5.bmp deleted file mode 100644 index 6cb11fdf..00000000 --- a/tests/Images/Expected/ASTC/footprint_8x5.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d87633a26957084bec6973eb41f8946c5b7bccf2924e1c90c1690b4f926cf74 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_8x6.bmp b/tests/Images/Expected/ASTC/footprint_8x6.bmp deleted file mode 100644 index f0de8688..00000000 --- a/tests/Images/Expected/ASTC/footprint_8x6.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6073c67e6b13bdc2688a2217119eb6bdee85f7507ce98b2d466fbf8db90b37b1 -size 3210 diff --git a/tests/Images/Expected/ASTC/footprint_8x8.bmp b/tests/Images/Expected/ASTC/footprint_8x8.bmp deleted file mode 100644 index d1f867de..00000000 --- a/tests/Images/Expected/ASTC/footprint_8x8.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c2f0ec842b80a95bffc742c66b2e19b85faf7736f0737f9a9da86ae965c839de -size 3210 diff --git a/tests/Images/Expected/ASTC/rgb_12x12.bmp b/tests/Images/Expected/ASTC/rgb_12x12.bmp deleted file mode 100644 index 29f43633..00000000 --- a/tests/Images/Expected/ASTC/rgb_12x12.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d70f50efa5c0bfc990e024be872ed270f43874e34a43a4d7d8a6fbfbb7f1ddc -size 193674 diff --git a/tests/Images/Expected/ASTC/rgb_4x4.bmp b/tests/Images/Expected/ASTC/rgb_4x4.bmp deleted file mode 100644 index 3874c6d0..00000000 --- a/tests/Images/Expected/ASTC/rgb_4x4.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ddf8ba01848174bdc1eadfaa4da351e9a41cf8acc238813dfd260219423f785 -size 193674 diff --git a/tests/Images/Expected/ASTC/rgb_5x4.bmp b/tests/Images/Expected/ASTC/rgb_5x4.bmp deleted file mode 100644 index cf14c284..00000000 --- a/tests/Images/Expected/ASTC/rgb_5x4.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08d8d5d3181d8a12ec47ed00c477f6fe9f5d93069a0bf2ebfe1b9ce17b2f8f67 -size 193674 diff --git a/tests/Images/Expected/ASTC/rgb_6x6.bmp b/tests/Images/Expected/ASTC/rgb_6x6.bmp deleted file mode 100644 index 1e9dc2e3..00000000 --- a/tests/Images/Expected/ASTC/rgb_6x6.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:54f44a68a25232bd4624400e93385ac3cc332a3cda293cb617911e42630d889a -size 193674 diff --git a/tests/Images/Expected/ASTC/rgb_8x8.bmp b/tests/Images/Expected/ASTC/rgb_8x8.bmp deleted file mode 100644 index 9ec02dd9..00000000 --- a/tests/Images/Expected/ASTC/rgb_8x8.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d612273927b74c4b07ee99ffa5028572b28689fc23f24c4c32ac40c3d6b7a7e6 -size 193674 diff --git a/tests/Images/Input/Astc/atlas_small_4x4.astc b/tests/Images/Input/Astc/atlas_small_4x4.astc deleted file mode 100644 index 86ae047165ff33238ff5ee945d1f896499d6a52d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65551 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�#<4OZ5m3ax`x~GM zECK%F)dxa4Ldq<<3dDjpx+M7J4i#(AGx>J=vkbx6lMI)&of4LP6`XShR-ZX|gAC6k zY8-~PlbNLxg@d@wI)fj?JP7q%p(iiO2P*q!0=%fiK0NGhzT|w$kJXch|E(jI3cT_) zHB++^!*2up=2zs_wPnkKah+$jm&-u@8R-;=;Ku(sXdRH%I)_zfB!2yx+`-VN`9~Gj z1pSZJivSaQ1Qzr?=vj8Gqz>fwaAdG*5lLW`Zn6!33Yljd07`y?!aKD+az9MrpR zqaLHa-)t5#R*;qn`1!6n3KGS1pI5t|b31I6JR4#506qKqb!^&AmyI4Kz&|Yen0_Wn zteu%?dM?XWj*5)3lxVI|Yo0uQYud*VT1e$;@XTT_@LlQI`F zVM9X&78QGFKRXQx2AidV91st4A5u)ZU!Jwadp0H0e|_ttG&Zu#I^fB<f1@P}?#>RfiUA%*>b*?_x zXsdnFJ7_JMCn$Dj(JND4Qos7-{+GUb5o3on-^zv;*1#DtI_f=3Z{7Bzt?eUaZ5v${ zSRy;YjF6p0izQj+2J%^+`H%qk5GGeXK1Hopz8Yuc4bKAn>+PG|g&wN~-G0dJ&^j=W zENttB>zwcA-MXz`4nUtLAy|Ci08>=2l1?F;_%2>#>ik(R^mDZ9D+j6rl`4$NO_z>7 z%w+ZSjwUP#>J`k|*>>MMYYn@!)3*(HpOQpe=Twdz+P*))?MP+Gd@M(uKr~1BDjxa0 z{MqH8>ONk*&C0pa>N}@!4X-04$)VNDSR2(Vg!Ha?)8R%C z*F<+CZ1sv)jS(mr9~k&qRaJzj+_LevE=_!YKaA#>E8??F!Tq8jOfQ6RrPG*&iH4}J z!2|uBiUJ<8K}iz3eC$o=+LB^reZ+fu~ z1W!)T56e!Y4UKeC^4le=`DO{yw*#pIC8l4ebBwB|$kZ&h%%q z%`a>YgZ|}kXRnVpZTUmNy3;@(0Y0PVEa(@aBND!CcO0`sg3CBg{7A=@m8M!e?lQZ9 zrBa!XTo)JLUi=WtXSaQZP~ZD4*3hS)+bQT_$h=2Bfy)$ul`@g>uYz$5M+h1!lEBZs z?vUzb7#h7ZPq>Ydf|l)f>7Oc66!kJT&$k{b96sq zZY=H@M#fb%*Dul0j5{_u^=3MnBo0M~^O_}fejJRfu4mu`d_+X4{2O+*`=c&(E0-ZG z{EXM1-Rts0u60Ps`U6yI3O?YMuLev;%3^7+@duYPd8TTA};)PhKur{&2_ zBdA%?(yzCy{{FBFnPCnnzi55B9adh@1>3!RRKB(xuT&A$4VU4ax} z4-X%@cs}cb0c9oVGT*z-2tN4PUJyVLrqk z*B)yafAXv^_7wUH@Y}b@d181$(1=#|#;gta=l!yoGW8|CAW^oAZ;jzdkuhPL6vk*0 zKRaSoK(3Au8B=gR66*MVGm@00ONHcy=ljnKLRRdo*AS?L%XRqfeW^YQ0%wABQyHM2 zQO1f3%L=U-yfcj8tI(`V1^H`4L{iX-tFzmK`&wsb+6$S+mip!@d!mL%jmYz-hJe5+U-3hU z!34C=y?(tVte2!Cn z?5TsQVJHol6`0f))ze2X-v%MpDSQKnnh{tknA zco}Dtzq-|(7dO~S-O0iDF1Y3D@CBzsdT2c@g?7MCdwxsK7BBdykW#Luw<4$I(-8QR zWj_lJB~wkd7ll#xzn4X0v?|6Vmw2O8(g^@hof>4)J?MOF{#+do@4x=c*wUcSwc92B zhYJf4;PoQ*t`LT`14rl-sbWV$$!1BXW}2=$MAZyWl!F{G&z z^=+-KK)u#Z?YY|bDy49d5Xm}4Zt=@#$^J~LmTuqu#WY@|@o%6EE&*b}P!fIJ;eY+8 z=U?_OsoyZpQp4>!|62!vbytM9KUmWzTAB4;4>5Lg%Bl0A9<3{b*9Y7xmsGBHdx@x- z>@I+L>m#3k`YgK3(pT)%6oi2P;Y=T`x)sqIFY$|2Qw~3Siul*lL%HVC*R+8tonp|x zmXg?(^-%ZV$hv#`&FcvU2kRfBPn=cq%JT}e5Z5s%JBzP_E)EDv7MFz^_*(fc?AUP=f1V!n2~QSBgiTpW2R1))i)1v)k?LS|2 zU~nqe2mVoswU5SF+yzS*cYiBR$0{+#ks>aT>lAmBz`$M26=wwfLkZ0%V8a12y~ruP`cR9|?1K za`T+Y`QtMUWH`UHNKy~!^1AM&%=FQ31o%$J)G(EoUMSJY`#3WT^m80fUk?*F)s`zT zTm8bCXVyQsZe@9Y(r&5!2IpEN;Clqu_&(VW&3@w;Akb#jfn=}#k=679e0?I<1bt8> zcuwj|2k*U3Fje>vZch=cL31z?wId&UsO`s-a**F$^~0#<9qb&w?Zsq&2`kbw7pF#j zdJ-uoJzy~Z7w|g|4RwSc3>Y~PITL>!)fIK(b2Rh)mtMqM>4mPv<3Rrqmtm`$B&Y?| zTz?RP`PUEc%ZOCYWIJ+vy3L^v|I4`cTepfhXCIL;vKO?4 z*RuA`*Tn~xowLEBBs5-D1*Xp^r0SClev`G4L1p8rqC9_%|A6y>Fu4-RN#|^5!y@UN;QjTJ zXK)Gbae7JP*?M}~C(B}fA8h7kL3e76C1;4ykmknvmlTd81Sd(0Ps8PDNSo{AOQ((_ zAQRFay-~r9J55)5m9!Q zUx!^B5#l4FT`wwb13j==f4_^J4}RdV6^l3-FXFg=?S`j%JH2l2-;=D~yH@+b+k2dE zn2BRG;_n0zzgT6Mbfdid##7mIxdRxMQ&Nc~i3JW<+|%hju4DL?1oQXrw42`7p2PHy z4gQ~5A z`l`M>KBPiF{LfeP)E+s&Cr3jPPZxO4)zn`i@qmRuF8^!LlNmb;?+bO0eZ90#h(q=kFBy2;GXu$ z7ByMDJ43h9(2^Pic;xyLi6s#!P#Qz)ItA*_9=35U>K?b752mb*s7`KCF8f_;Ki)w3 zSpJz`!vN$9rGLH*EYSI-n;txQQUdCAd5Os>Zd*l;s?ZWaED<)^#|*4iLGG;v{*i*J zG~^A26SU>})d>%GQ#Kh!Vbm4H_HF+};_tt*!e<*tscqrTN zfBc@?U@T?Nn!U1BwicDlLe|R6pioKHA}K|SGDHm_in3FLY*D1_NkXy|sg%(s%9^ZU zerI?*Pfwrk=k@vj?|D40*Xf$Mm+QXX=Q`K9&Uv5u)h)Rc`OPuU1|Q?Z^`k?#zVO&a z!6_VDll|6)#F331aj`y4s>xHM^v%uwnv%)SVGo$oGGc#xOvT7VHrStQwvN$HGBWxx z<>DM}SuCt++>&^OB;U&2UPVVN=u1WqPC_7M&_ zS{_J|FCCWhaKNh;)+*VU6%a`1x7iSi-1i#3p4}Cb?A!(UUB^p8kLmMQa7j3>K3fd@(-+F(j#(g?B8N9;J@Wv5w2PIzdHeY(6Oz>{V;3(@9s`X(aJnO!d%WQzV0+LbxAMZ zR^AMAyyGr2^a8`+XQ@}IAF)O4yT?}p%G_3-l$#UZM-*^FV+oZqr!7krQGO2o2pI1} z%b)RXd|tI~&Dw%3UlnkuKx035{NX&+1U!;;RTEqKpsgt(igwJ>`3NQW;Eq+FewtRO z$#5_^m`nARCWF0dR&RfJg0$*f3cC@mT&t$)#pyl%{5ihM2)8caU&DSM;b7W+V;VpG zb>NJCzm`%%pM029r;%1%T9WM)3HY54j}>x^obDaHcv+J7DYb5M=%WK}!s~9Cg`Azd z#I99yE#_SIR7gdM_^v%7SkU70Dt!KPG({eSwc}pLt^O zeb;sMhR^H25|wzo+ACT&zAicv{G7=|nhlyij*4HTt+QV7C9N%#U$ZXQczL~c*$GW* zsD6mSX1m*&T%os4l+xB7ylK`={jzy9>T-WjyH`ERpiBT7K2k?L=ktAc-!X z!*(~u@K&?+Oy3Usj}&xd$8v7DC$o=V)r4SUHW9u#G}anl78$)EP%-M!Zwd8Bz7>_| zdkl&*zvilKhJ1!2-(tfgxe!UzJJ4MK;yn#CEW0F(uFUp+OQB`K{oHUl9kb0(J@=%q&v#01>7Ee;QV3i6jf}X^D*Z zT!mP``x0@HSr+~grk)72iwa1JuwgJ3<;Q|oV-(5A^=lOsa|jDhuu1^`f`%H+7h=&- z8luh%G7=k8RfWKo&qDj6zrxIH|9*c6v0s$!>`tD%af8XEB7{u|N%|z#_Yn?$;Jen{ zn!=LxkstWEV{=4w`Eqjl&;}+SYlj2;9jMpDboqMgfV#)L9ta%|_Q~Uqks=p6Ev+ZG zfldJUW+%JaFu?;G2UeOgR&kT-&I=f9=kK{#9$tj#opfHj{?46_ju$R4nPjZSnhOEE zJQ!qLph<9!ve>?3hl4{<5R*y3u2m19adW}_;mzCH+JF!9<+JYt8)iGsCLY47U_tI^ za?9N)%-hR5n6*oQB7e$wz-T$Y|1yQzhn1FTc5H`@?M=6@gWpHUy`6qb0KVbrnH^Nt z`Sw4Rf%;vft?B7IcCwyd&`3U*jQk_Bx{!iAt(}}Un{T)HFgn#VvW!dmr$2&KdGhOW z@Cc%TCg1KbdkgVZG^{Mf0wDqaTU14G=OxAu-e~EFZxHX5r~5))4R;dv6Idx?$S8rB zleK#nCWUUK2z-3dzc+Rt>SN{bA}Lu}S&Pr_>`bF&XaDoQ7G>a9SGeKp`rzr*ih_$7 z>DLvoqrbc>H*QwbS&I+xFe+und_?(p9-GR(Ro1mcV3 zQW<6=rDfJCLMgWD=Fxp8Ur8gfgu9Q3vYN?`{N1?rP;PrdGZ)&bf9p&6D%all+ar{d zWOSW6J&<<^^fzr>A(BqOuG0*>zOtG9RE|1I z1do0eVlsC&v!60|8+)@mJ44eUg%Zkcr+kUvF%;A0%sHelE_ML}`Ik)FBQqvNK5u;} zoD?5MUaN%EIn)$y z>sjXd9lj^A`_d61x>9yf!s>GRnwa|+yjwVkzu}*$$H(_K{sn_fWtT<#8~>-*SCf&o zYXZ)p3;JLgOFwMhl9WIs%=a(9`Y|F>Sus3}pT|E7;$%c`Q&JLfK_4Ip3i@C{Kky@h z+w{EkC^h4QO~V>T1cuT|&iln40M0{jCfHT2g!zLCU)+FIPa%xc#}G}HrWI2Qg9 zB(~mO0c+UYkTI~~TReX5@R17G|F@tTPTYQb>1)TsGi%0x|8MWky<~z8fzem-e#0u@ z3*Bb8dCWM(Q~7H7Po3zuf8g8uH+<8GpGK#sh#o#GZO8Wc>(LIcT716_4!*wW>3|od zn~_%pIpTFL>shejmM$&l>}2eAlw!+b&tJo@%(s$+eu?vXz`3n)7enSEuRiSGI-N36 z;t*4sQzYyTkd0Y9-y)ug=ndJntn78e3jl=vAmOueSmO(hmRXPu@Hv?l3oiz+#Oo6|?Go*^m*GrLow zF4EHVo%LTa-!EQ&5&wV6zw3ixCi3@up{KW?FQC2?9w{wC_%|UQNVnd9@NH?)*)UZS zxKS>ywK`%30=!!2y)9+t6}$1cS^gVK5neK?rMd9F|I&ZRq72Vl#hMSa)Gq{rzQ-aX zg5!RE6huosfF-l~BV`2qgJEkVnI=WZ{b_5rj=UmGEQU>wa4Fv3_f6oP^HbuG|5tSU z^c%Cq`HI0_`NH>081WRwVoAZ0#tpzfqpA9{;o4a_uTD$a*iLzV2(+p^gMIQI`&7bL zp%eIDou}68Ns%4zxW3Fe_<;oe2KVZp-VZG>KC=g&vBUe4vrzfIj&(b53=@%m@IE{< z1M?|qQ`S7*cMQ%sHU2?g}Q;DSC- ztjZXfS{xsX_5$$1v+aLr6M3k_#49&_=9md_Fo!e3Q#* zYw7^?8E-Kv1Z7wAzoG!XGZ*oVvxMu*$y#I%te=0ItoMn^C6HAHtepSGBVlV=`a*mt ztYGGd_zhB0U`zbsPk$>SETu{-xi+-l8HJnt|tVoEXIW2kR9OP>=&yGJ+2^l@Eq`o5S4_ zH=>8r!8K91C5V8+C?-XwLOr&b(dNoi6x4?~GhKwW6?;Ukx^*s{LJ#L!+0^AXrp$+@ zFmK)`n;TGmu8MlMR_etaPOB|@NHvY>Wn3vUmYPlz=gxjEP{4K`nkw;?h`Vq|_VQNd zH4?Phx z@ppZI0PhzW(cO~rmwtesK)v2Y{m`;Rc6|gL;zchnUCrhG?$@~P3&kjiA2J!!$z$Ui z>Lkxr*M1hID6PE0=W*=L1eYt7IE4!3>%+ba7w>Y0ukSDd1lP0YBo`$_UGDSx9wx-`S zF~vZ7Fc$1{u!sJV=SMhx@%M}4T|vo&8_}=TezP0aUo;fLUzZ?QTwLc-4mRAd@+6rq z&W^_*4ssC>zO6!N+kS^L%e?vb@S*s@a)h`reFQ6Bf#nEmd9on$<0C-sVi#G$HT&*0 zZHc{>et6`R0+j3EnfO?T?9pOQuAZ*EhG^dbK!MvOmRLWqnuN7!*3gDnM-{v zSJh`P-v0kr`2YERDzPsL*`xQxR}SGp)sq;M_d? z!5jz(B~c7z zqR7m~lQk822M}Cf)DT79|4wTuA&5)p)puhIX=@o>=Y9qp~T=Ln7rWf|(Hl+IdR^P5~Lv*SBR)jzOTa|pKL&u^~VBx!vI z!tLfp`Y(PQ;b!+cHY~~p?rVF$frH60AK5<~h~w)gu*`u4bqf6n5q;?M=iT|0`=I*- zSjTVj{6G9Iv%W$=*Q%U8Jw1)caw*bqvamc{t>$}jX67&X@9DtAWZ%AX=UC?4TD8-@ zb2Es!o*!jSz4p)Z{VxCe`U3OU|NVYZxL*n^&lm83(DY$B&YDisGb!Zx@sT+__iuHJ zSpC`mL_|UUkaJfTJTKU`!(hJo({A2;_qV+G{r`p!P`{e=3;cX=J*;Oc@$YnBGTGUg zPG>SH@@_L637cqEr*$kVvBImi_g5fU*k%f^;BuFE>I_^g9!|%d^f^ffy(2 zj-@rT`3GLfit0sn`wTWl5sS32kG`gDp?}v7KO-OKw;F}*)1N*Dp-+AWdCwe?jXw9k zo^NrwXHE4i7XStEJC}0v?K`;Lz~;;K2ho*p5Z#?5e&k5<1pLX6u^Ti)F9-`^6c4Ui z2YGpm=U?m(z;jH^{&MNTx%tl^q;G~-gxnACeD!jZES4X_NAHA}g_i{k59%Oztek!q zTR}ukL{lKpk=k?M5Dd`SwTf6(;N=C9mCD8Kt10pw1C>rI<>Yo1PyEO_7D!Qi*0Srk zS_p5-U_+&sJyq7%sV2abx?Bwq@;Aux2-4b#ni zMbeBK>j&nB>~Yt3@O&#h5e_H^j*t~Y=VcV>h4gOdf5kx8u6OtiT&y93FxZMVe6fBU z&@vJKt4;$yL{eAj*UN%sRk1+?rtttb(C2FvI1-*^f4hn8f&5x6tn~}hGkRymsuQ1( zFjJrpUUTPe<(p|w3=+jpRK5k_Cw{Z`Ytol;J=$|{b(99s0W=N_cjPfS9De!BE_uN) zFg!Lh&R{YtCsxa>I+?)W3UHb_cq8Xvvh7Qk3NDmdJvnWh`h5?hwztUA_E#XJ6T7c> ziC*EbMvg34>?uYvl1i=HJeIq6y`$bldP)>=-W|Zww{U=h{g=#Ie*NB#c>4V*6R@k- z_@17bhF|0F+)?FTeBXrC5kddmy4lpoD}d?FpP#?T=)<(SrZhp! zXNl3U6`vk3*jIdiyD9htwo>EMv%*8v#q+ti1O(*fvhf z<;M>mw~# z+F3GcDI4Jo_`h~V>Y(u+l)?5@w8l=p!!yH%;|yI1PeOe@c^ie%pVp0SiErPd%uq%h zKG(m^GJaVSyJt8|msG88Sh>glOpjHDRED*G2c)xovX1?FA}>CB{dxX4CD4>Pm1`e1 zT&q}ei$A4y9}(SbQ=HAvNy)mI^{r>qEJeP+saz>~qhQ2q>w{}*3Y%g3-i=INhmoBc z*7dFp2RgwMd#UfWO{dy}U0YMRy9AIW_fGriB}zudPjYprK|uB#YTMSd`2M$Vx3@ok9`KJoZ|wGD$}GB{ z7Ug;6z|s$YwJ*wxzhA^V8TkwEFgEbuUL-0;&u*>cN0?fAYht#1Z|;1!1K~NCU;lRJSHVLO(5JQ2^X~ga`|{DFmP~Iccr@l1 z(-Q1AWqH>_-JlO>Y*lcEf9Zo^*7E5e`vBAfjOWyp#s&^Pe?cxNrxOj!m-iN*e`fBZ zc^~Scw)%}yua-0iYj<`FHo*Va(@_ZPW8a4#rD5q%R|i-g^+7^MAPipj=Rdt;AN_ zwa5@+$t%bU<)tEyNNll1euD8)#hU+DOSwC;=0_xxsv=@a#_Ft(2!RqYihDEer9E)h z#BR_I30WpYpl9uIjJMAMylaQh1qCoET;1#lhQ`tl3-;1+4o&>V-9WcO4h>Z+4^at8 z%6uIY2;ss4rkB3!aW@#t$Y$pHDb#UkxMlOme7kL&Y(7_f73gp^E@iv1WSQNc?~78a zt6v>Ca%5q+qurw2%WA1RJfeTX1>1cSHumZx(ebf|;Sz~=tz z?D2_}Ubi<&cJiMiW@0R#7hoQbo)p2ev(k55!{lZ@ok^jWsy_Emt{wHK3 zkI2Y@Lycd+edrjv<&xOY8*gD@OY<46x;%Tol3PuTA9eObyLbp+KkTCRQC}cmsmh$z z%XRcUkLppMqVLum6Lrg^%lx@1uI#C+JM&7EZn1A`q0F43R#T|9H-H5ZxF2Q>Klnj; z%;iF@{wn&l^iA7b?*iS*3NF5>|WTZPLXQ#d30f%ZCGf zecQCQHgPLad^F{U_bVK_-7)iP+Bi0SLa$1m9)y{Bdak5ZuY9!x-zqM~?Q4(DmF^AS zd*B3S*3YMxh>8mnWSJ~_FF06u{O>Z~AMLCm`QPvkXlK&`ytC%-h53G7W9a?9UBSl( z?Y~`*b-v%_d3)ry_W$$#e#7_t`uVr*J7BDb?Ib;X`ht58Z*DOL=l{*UH$1<+6}e7g zBjA4+J_zWC^8smTUEK%*;mLT|uKC+OfbOY6!M*yAeSlP>5dKn-*^;R%ZQsTr7swyT zPtN4Lvh?)Oa~qGj&ljJtZ=)!R_L+!AZ{<3&)y4YHu>GxYu42h{v{3@JbVazy*v%^g4t?wb zZv|&2maa%}=TLLy9<1#BHei>1rxKpuE0PiCD+NEN)0|})|FUnYQKXsODRw4dll7?& z-bxyRyI}hyc}2-Vj!wM5+0GpWt7isfnXkx6`;B}B)B>rCR;I+%cWvR1uufXVNB2WMxX(&2J!}hO(kKGBNx#eZg!epU9&Di z?R6N_zV|*aBM^(P^M0F4%@lNxKlXj@L)UAx-Q2xXXn3o;V(tmGj7rOL@#XpI5Wev# zcj#-+)sT2Oy+aW%}$+={*e3O-S}!y z9plUqzo|-ch`3rqx-eDTn2XFNi~;)F-R}tATDRuPcam{Ih%?MT%2vCT3^QN4GV9(W z5Z;>_Xd}gTc0&Q*!E@h|Z6JK|_THF%hE>bdwzg|A?Wom1XWLd=Ox!8wSjpi;g>Z|U zl7KCki}%VnOJB5EL{qSOWh2}I55J{}4kx4@PlI%tGUn$4CL7kBDg9VfRRQav&~()z zYxPJmam(1$R%*5Pkn5RLYc(oawjW!)2DXd)Y)d?M2zvD_my@rTrJ%)ajV7vMSxZ0c zCT6Q=EZQS5e&>%rW#}JBw?UTio)>WlP!Jbu3Ucc6o!OmDdsOVT*>99nNZs_ABv0{? z4eyJ%B(-!RfUfm}!3ERj&6kIgS^jPa&-D-D;9%=3zlu3XO2tcLqtcX65J)5&wE>#5cIzkI&4r22vSVbce?$4>)YeRW$^ zR|&#ZuamfW(3S$*zc*7%j9K~w4X*!td&l1X^y#!TmOi%cK2>(d3TA)SM_}>!_U}J+ zDm8V!egCBIkSM6m`2)&TC$u=<5e0YMAae-(ABgUzl$%C|aDJ@pMT5_azh68*0acJ% zDJw6FeAdwSKe#2h%Q!xGoKDiE%Eh3CYp+d0Ih@B;(zSS>z9{l~RTLq#2^Yldc`i4U zaw8)eQ8`CIbvH#_k&=Y-Z)W+|SliPv3o`7t8^$eO&(hM(Ei4S~7s<|g^-7HOeS~G> zLi)U+w?;#3!nczZJva)Eh$b^79_k5yZ%~!*2xUTgl0#UAoOD%T9@CCn8;Jxu_o>u! zT@hrGc05!1qX+mm-CcbjITezxg>Fvc{|MuGPkHt|LC+-4uczRDlsckWbbCBb@NtqO z&vLC=C~xY)>j%5+-Hts;BS+pnUzNM{U0*x6Jt1=@)v5 zN&$WM+nojr8K3u6Y?V|1_kVv{b)b%Ph;^Vs>y>U2aRnVi6Qj|9zXSqe%n8Kh62i!0 z`ID15P~HiP00GTkN6TV=EdGO#xw@e$s$^fb6+v7Petx2BH2DgOT)R1)o($i4Nx}u( z@U;^f*r=N#D9|kr;c~HktJbr(7!b1aaoNnJMD&)^`w|!7Qr2;BGq(_8Yg1jZ1Kj0s z(AYM@^Yl*?xX*R6~%Pp>cDH1*IJViZ?{*er?eki$NDe7kbE2)Eckbh2EC zEH7p7ZRATSiNbJ6`cWts+8FgQbNlVfS3mHz_BpWVk@4C5^(~jfZQ^NSR0tn=zoW0v z8!x-^>ZR9mVG#a%XVc@T1H*%QE{a1mZ3qW%C`{+z&gU^oveb%AAXS$&gu4=i@QUqc zhW2XaLb-$f&Z9!FS|5~82ES<)BzzO^|26e-YtQ3Z=IbqGK;LTC_+7bU;?qeiIg4Wz z=qHEzmn#n~K_os*d^~f&`p^FSQzjEN)=NWqN*WtPAoEJWeEz0s`iH9xa!PgA<-Ri* zDn2zO1wouK2&X5)2KIjdqK$Y%-?7jZfkgayzCUF`R+Qp_Lnr4>wNx4>i5v(0blW{X z>|PpkB<%9xV-{&ZCt=xsG>9ytuF4g8PAnGuZ7B~@@~@?Y>5F+14ebc9os&6_fAuN| zUx`@6J3kb|T=2jCg=a`#V|}1~9`DG!y~MJg{*vLALCJ$Xw;=zi59W7shnSGgxS&tK zn~PzcpCo^|=OjNdEL%xC&|f7`48r};y>xzakMfw*yZ+n|t}`I#dxc;BwU!Qb3wvB& z^TFarJst89CRxE!p5iwF_4k`&h|M5 zwfYtH+=ij#)mPTxc@)(?lB(6#eIOFt-)WTe?Lk&;AyvB`;q&spuGQN%g&f#*2LG0(k08sqybDVL-8DZ~?2;s|!PzALu?Dv#Wm z$2$&Qh9~^NvDiNg(*J)M!fD5*lnsP4oD$CGaA+S>z)q9^&HHq@DvX}l(ZiEynePK+KZhw>6ym-M4!tPo63_1702zqHUd zoRjoc#E{bc7md$z9Ph>+T9$Dt7W8GCWo0utQF(vl0n6y0ca#IT z26it=da?S8*^V~M3)c2-T8h#9{0|m@)gnF-0a?%h{ILJ?7ycLg_5a|VwHp8*n145> zr05%9WD(Z>4}{Au>H|K|2Ym3ni}t|a;1GQNKL7lH6hI(dTrrf!uaBX*bQfSGAYHM5 zpumrcrPB@^FXh{DhS8U;oOq()!494_;TIe(XXW#~`WtTUz8NzY$ty8zBSXOIF5(&D z#oA##s=F~_0pF}E!1J_Qx)zr1K^D}R00FDBDIy&F6);=kEm`wF#l$b8LETmTvqPZw zpe|Jj?SDCX@BXMoi|B?GK0tSPz3W288EX^Xyq+_$9UrLG+w^?01H(~&9^~1YLMBF& zr=Lx^>{sTlPb*w}pAIxzx`EJA`R}SISGA$P2eMX^4k{=&l`V|i4I)CidT`RGU z?MXfN4-^CR^Zg|a){MOxOvU1mG_dcB-Jo2D1;Q_l7vqv|BjN%g)47rQTREHjnbX}Y zy6)5n0a$1w=`G1O+&&YCPqP9b-4oJpEczWVEJP+?q)v%X8dXoh9N7s_6)h*VK?iCg9yUOz_ z4tL_*^K8NBuOp+m(tTo%wYS?*`Ieo=s~tscpU0e#RmOA5P?Wgb+EVWy#o8_rG!~Y! zr^x%{OM1M?pEAm43Kn|=vdSe}#Sj{2TRk3;{MZs<{MGmO?lFCH`^|ujU2ifG-8I!* zuC_yO&90T#iBAwx^)zo~7vdCbac|GpI6@}1diZ4fotpIeLP_lHCDvkcwL4V#V$Uo| zjalDnJ9=l}PJQtAhZ9!QABw*2#&B@3Dbxj%UmjvM-)}0E$Xs!}1n9TCJ_YYT`_{sg zdhv=PI~8efoaqO;G-N-A_x|F;f6<3(OVQ7rcH)Qd3ZkSGKa5)h!Le~xVgAEj`}2JX zJ$Pf0D$Qg1A;d%yOaFqSR&UWQzkB|`bq()C2PWGwDlYMbikoV2I&QlP@s8IgNdFu$ z$u`w9HZ~TcX}3<3SWfV_ObTa@%M`9{vWSK$BUeS=$y-ET)D z!ZoTO{hItv>U6b$i$w3sdux|=3E1%x4yI^tetk__gE0#J{zZJxZx0K={cMU_z&FhA zLk!mS>JF4Hhxh?llFWgA*bM%PWi0%|;pa!Kw%Ij%L+mrJb)H<2q}LBp^%}iCbIg-T z<2#oIllK%+(CaflznVVMEqZgo4>Pm@_FIm-7?>$n?KjVL_L5D2@StBiCsZ`A{@R>B z!&eS;aehCZuPji~`6b}XV-erLvJZ;B6a8JBTU}~(w`TB8q}PvTLm;{G{>U&U zo$22r%xL0V7D;tw&nca@9qL$p=AK{q2^@suP_|WuSjJx8yz^p8G(VNjp40Ilc*XbC zAwM#bIf<veol|-uzH`>j?uid?S|u(LOu0KJwlC~6eFmy zcbxg?>(MO}Q+^vJsKkh?fv;X&h!?AB#~{r1wl0J3y`Kv^nUC&f<+**rkbrJ^z@YF1 zhi3Yd4E3LyL2|X>jk~W>5}xfWT|;=qTu16%9<800ccj9M?ey!h9tykw8ddPHQ_`rTV6dJD-2OLP7~P zWviE+6>5LE+qTLB=10@n&!xMEZ$GJ2NW%sh0X`yy0*@^be3CRZWk-LXkr%Opa_h&R-X13 z7q!>@+TISOf5jMVhFm+jwqpvKm2aI4SdIWaVE>PEYcnmgcC0sT!&s84_t@<{|S1?_~_v`|HSYtco!%OEoQnXudA8|G-FTLljn5xvGdXm<- z`iK#=S|zU{`eCzW?H;E__S+DS^}9dzwD-ULnWw!M_1EJ5=Hha)yv>i8 z##-FguJ+xzN_0bk;f|+WBS3$Rvp^9ysdw{>+Dr9Qld<7;V|-at;Q#U&vn~=dACG?4&YPx3 zXb^NS6^j36ubf<1p<|B1{^Q-v?->?8OdlWS=1K_fiw=qm3OY+bwiUjb=uD#KpKn}N zDCX~vR4twR<@cFlMEk_;*4vSTNIS2(I5x@JUr$L=UyDIn5ZU|J>8$Y8lmvCq2XMfc zV#VVmC9w1Zg2z}|`a!(TU;2X8gKs7YDgiGZ>R(uEp8s6mA2Y1ptQ-(rslp!_<9L9I zkBUN?lmiZOCo*3rPv2IssbaF({Us}iUy-o}_Z_v`_e|UUx@5k_vby_uWuhc>r%O=I zd0B%)mV(sUQ*A)E(0=Z&+LEJK*BZ-&6=gtI-0t&p{LO`X++VtPeGXk5AG>xPK79Ey z=r6^@vyygRxEf~NjM0oy<6^s&mAiX*IQZkRo7Z2tA}xuLI}{e76PT%mUG+6UK5*Q{ z-!ZwK*7iorC@we{CmRx)*Z$e}M1C&WQ@Ec;LpUUb=^tZzqlC5p>Rw)Exc6z#gPqbV zW)L}!iA!^#uRa(!XqgU1$800^eyZQ@xBKTorvd3rCcV9K9Ja~Fq5_Pt%E@T=MiGcN zVILD`KE3a+?P+`K>yI&F=9Ru<@4K(*FFio{-feP(h_)8J>ylcfg8zbx#oC^wzUKb-Cje=?Ib$=i{@?vBsJ3w2&)13@}u5ZRvpguYQg5^)Jo?qF+>Ws!Pvd2=xH>>Ez77Yf|I zo1A^~dOZ1fX@KkE`(fex$PpHv|Jk2~{hGh={2%ydtzN(=`4|3Sj4qyU5$}J>K<_E* z3<<7C3Kt$L`{)7qkIt7CKPlI`;CE8|HG37&$V4PJrs<;fs2ImwCc*~mcc~wn4Kpr*|JHpto+f^W6n~q+c=eFM_GH@&^UoHDnd6Ob zng{G)J+b#gS~zOH+DSp;jI$dN=;J?}dW0MnG}-E0-VNdUB5cMoBbj*9>LBY_vrv+J zm{>?3ZgXe4mRd#R8Su9qcs1iytYa-A7sRHnmZn8l8Z)bmnRSkg8eVs4QV`0830!Ga zPd>TvwTx_)fgO|wEf6^Bskd>`C%JKO=sKZ+cvS5cmRm$o@ur}Abtup+L~EkztsZB! z1wR`uxdrnl;Cs#LdAtMQ$@6%hmxmR{kNFX>gslnl`hleoEbaa!i`X&ut+KwiNy`OE zk-xV}@8_ARFth79glw1yr`X z43jcBqqRTxrH?}jQ)ISf9IUT5o|SjyxH93|Ue$xYNkZjZ0;EXCSG8{JSp(uw^lrW8KQdj#hMI~vU*Ye#3|8>7YL+Ig6 z?RImc(P3jZ99GZ^^-iX4j1qaDIu zfIZ?qrlL$x;I%9==HpqSg?>4Wgaw+IH&IHQzQuEp&`$2A^@gP)XFBT2szgze>&nJ! zrcW+gs_#XYR7-bKda*6*rc#J5H!Qnl@G9$|oISs^d_m3U`oz%dbYC4UMVHl;Eg>N{ zUuO}Y<)O3G-bhW$qMsE}%T7HPJvjpUw=-YFHq24TldtZz=}JHO-kkc<@-3g%&Ni&r z!_E$L8*T)z&29>h2)h6)dq7~o%-v24($n{!Ia16j zSKDvaZwKk^Nof{sFUWQK+w1kek!qwv(|dWL+I!1D!WNhai5VH`tf$TWdR>mR&`Ej3srJl1+BASNl>-E)Qk;d_IY8_`Z`KH;LwH*Go&Z+8b;BN?ha#Y5ETGX`E zIVEAR`FpA0QvZS+EPHB*M&8vRYPGtCM*gbZ_sRu#@=Oaqq4HnFTFR|FVutA@MB6@~ zLHVbxGa5F&q|zOCx4D|*fj-Yc&uF#At5c@Y+FD`<0pT@17H4a`5bQQ&^QjHRp-u40qN)5=cCH3YXx#zij?K>YFi>RrKh=P@skTI4=> z3|Ihmhw`Q`@8gg?*OZ06z1sF%0@CBQCdI8$oo~ILs7PkvJx)y(gGYz;{rvnOK4(ip zg4${f#0RtXXQ+rS=nM4)eZdh?19Wte*T&cB9j8)~f4Y|GG1!heoH9Ks)A~!Ph=|3& zeyqzbHW{Wc=tFLJpgO$r`cYqaZtdxJ928M>uE zDqiAH@-BRZ<(nu-|ITffkTAqk)ZS9N^rWfPcw?P%gpAYp6!=WKY!>`STVt-tvh08S zx87ce|NZxRfPmc$>j7z&y^rq(z5-YeBpNGDZJ8fTFs9^Nuq$qR?e$L!N{dKN9PcLK zRz_!(SOo63z4H8Qh2lC%+q~G z?7~})7x5A6qU$fp_{?O4rQz>nkfEGx%bPM~hUw4p8ZC}5FTY_9@&6c~rZsk7mc~D? zb3a2t-J)Vb?%}aFO=_m}Re=7XtS+<+qsug$(mQ=h^T~5J4ztUq+iSaB(aF#vHt}3e zM&0rDa4}u%9Sz^QLN3t0c-#)rl;;)d%{kavQ1N2(xlxUMi3O4{KX4_s zy7R4^_75vO^%D_79SptnyjbIdbY5HANFKG_BxCGxy`<~-rd5ZyV=xF&e|Bc(DXFS-cz(yJ^xyOS z$^UNe&(HS55Z@&S_??emTH0O3H}faH|CC{Wa$&w_#e*1efPYhfUpr>KBnu4l{?BiE zW-kS@Ew#8cjotNH?^CcR9(TqHic!1o1#^H$l7wb(xZCLOn)P*|thSALypC^$rCG8irJ7ePVp<*iY@a|hmMjYkH-nVhz(ROHC#UyQws zdyK11g7S4-Uc|`WQKpF#<)UBKT&|&|g<0kV^Lh6}sSEM;4~hD%Etv&JtL~NFW1e9S z4`Hrc<-LIdxI*8a{sBCpxHfyt$+Zh;T)6pLi2?x`fwzY=zZZfv;6xM?+`J~jsB9zJAG z>IfWYPnk%W7=(EEQ=K;_gs(@$W%0e@%mKQ(U*kL%9|ulslv_hjXp17!`65l+aCYH$ zvSSTg4nclFdC6YVQCCTFSL4fxnJ$vN!MC&=p3mRa&wTP|N%AMj8!Jbv8;dtS`K(QP zbFLWt6F);j?02speqKf43YO5s-o7TtcRQYNBIq@j>`GSra*~wjtz^vpBRS`{c5}k} zFUm}LYo0iGGv%egffO=!{{3fTb}#uxC_lQI40H$!@s#k=%su@U@#Eld_H0H5%O2N* z{EP)U1^5OE^8qVg1LCof$23(nKcMq?8G_8^EW4RI!F9Yklwu@t^-&J53e2wr$Y%Wy z|1^bv85hcZzQ`5XLo~(kIclNovC`Rbd(?&4p1Zv-vr<51hBP^oLlD|I@~I$&bx;mq z^*iE4MRxP=Ln`o}0@nVs-{h7ZmR8(I00+JW{D)iieB6zr_uzW;W0|o8l^tdsN7KdI zK6$wt3_l*E_HO1q-)VZya*W1zr*j(Uv@Ut12Y3)=(;Rt;FECx2Sh9J3{uUifG2)0z z5d=C|s`>r=t-2#TNF_SVgJ@zctqLFL>k=yXdrXGA0$_jKs-$Ea37#K8Aio1$`DrY( z%8sA;(qk!8Xf+1X&r38{UAhSEX`Vlv!>P!dT79kPOBkd;Aih9AQUiV8W<-dN1b^!m zS;-a-iV^$yvA4;`8Oo9>-R~z6Mt!iqb;cV%)w@o}GGQg43kAI2O0bW*|7GgbIhWg{ z>Z+#j3xr>SufvIF8qUAf!ah&@q8F2YTne}*vc4V!docfUF!OEn>8o>e-X16iL|T2q z?Nl!2ihte8o7XeRh=#_IBXQjzxVWwUeS&Eev&v7w^-`bY?75zB zhx_|5ElF3%DyYWAg#0SRhl?7v5d;1PbaaPLD$}nDRVV00fWHA9-RBcO>xZ!637bdQ zh`sD#ijsj3m6nldV@MC|ClXd~e<#dYQ93xN=v)WonGSfEgb?B#{q858D~>uq$;9w> zF-1~7q$#;@n58E-b72IYUUZ7fSzDMKk$v|rg}$%fr{i4OM9^mXwR4vHp_~Yhoz~Z~ zsuW>;qo%S-kUPCG-=n(SJu6_P+p_Rx?9RHFMV2HA}=ELc{axK!?~6s11+<&r^)ma^4R2S zVnR3KF?c7s6WtE*pOGRho^b=y%~|$AV{$#Z^7~ZXqiGyd%TfgXqCp>wWmVbmGlf#0 z!0wq788z6FnPxUmkHF9Sb{DMg{+1C@KbUX-)(^vwzVWwxzNjBabgee=@g$rwVzmW9 z#0I)ZV>xt`wLgWIq8*S;qY@K|i0_cy_7_Zq4sk40qNNFP9I0YQ=8!W z)jOMKLqAWea#GM+6@&35Sbn00^T4ZE0a6`jdqH8(%)~R|aLuyy?hyaj?@*@3--w7^ z7rX9wgB&5-@4rm^W#)1xojroiRxEnh=W04&Z&s~mDD`E19oat)Ev>b2dl z9y!X9Q6rt<@+IywUM1mA{L5n(@Q*?KaIB9!c9+%t`$RpKY=_&4BcOlbzec^*pY5}{ zJoe$;l`_HSF~}`k25uJ)@>5E*SA|`~4ANFS;5^4c;6-`8mQ67OwA+6addi-u;D=$=06l zYAmpQgBX(Y^q_aM@&+=;SFCR+-_K;TBZreo)iV;UFEFQKGQKACFw7`gHG>>aKN6}Z z+DGo&gez=rX5$*#B3GV=$d=19#y8{uo$c)2Sj1>&4ENhKfsfPV7KfZu3C!TQAO2!- zk2mlFyp?u4*u)qy zfg6dK8M!ph?v*fKWhBT;W+W5FwbHwWwa;r}v(r`NvNJFhYkSU&zD3cM4n?7@Q@B}; zB*9r7>2GB8NzGsB(g^JTLH>ii{n@Q|)>~RK?O1Chh_{xq{F_ce2J(gUwk*=A;3d1_ zNw|ZBq8r42tTD&j`gt`#vRV1km%{HZKO?Sopm$?m-yoVleq{Dy@aw1KGxW)yFL{XS z9A7H$Fk_eF@v44Y`|%8RLRkM+de+U%5Tf5wh>xxd_Mez}rk1Ps-Esd#1E7P=c58IK zFqQnpk5~Oz%`@2|NG-23lho@I{q>uPbkkGw znYqfNK|t@!8y2VfilFIgxL=n9x_TbyPLB~2Xi;b?c4oV zcK;{-9}smhc)vi_M-#EOdQh@JuO)kt;0)#U?kckIKZzt)aZCcf|HA(`O=tgAPw21K z#w|LLVHgYDV+N(3IxPH~{e}O0z(1MJ)@~F2*jRN><3mhv;0;*+S(HmMy~S3DGQ`QJXLS0ag1ZmyWt^~Wt%sPh@c#PlP~MX5=Jib@u^?r);dO_H18Xo<6}yuxB!d)i0Uu74Pi4PWm|3dWLc2gtGp4;5 zdI|Qwo|j*#&y{)m{_)W&Ke}JBk5BQvW&>rVwCfjX{_jP;M74#UBkLP(+uHJhi3tt5 z$B3VZv+KjM;$s!B)@e^&q>V7U=H)7!g!O$CXEZzo{z-OOoY6`l;a?4OK%hy zTD{TsnjjJB=uvYry>hUnHfe5VcGsOqAueIh!rIxl4I45LS|7;b2%=t5Zk2T+1NlLR zaseRc^geg>3w9b%P)iT)5^*_%zsNbo>Y)QM6E_m>nWH-tp_VkW zXKsdo^01S1AD1DDLtkV#sy=qHGrQLFJxs@^@b#ubVjQWut~aFkwKs4i=l?bK<p@sr$1QRl^jTB%2*~5tXA_ z)|+Z#4m`q0sY4hh5!VxcvzE6#++gg~)7UCG4)QaTFNtXZVR%@(FHDg*ae~p&_szX` z`7aa?*bU&od7D3;qVw!Txq^TzQLq|0X}Swb?W65dWM1|8M-u zqefr(>tEmJNkxB0Cmja$!_)gZN|!HamgSXs+mwNNX`stUH|a8BT+^MY_lc&3`q%ki zq42ZZbt{k`YIqzv#cv7jE!(xyb(98Q@J>7HNQJufBQ_cLu;tpdBrUHbDd40?l$fXH zjda}25KE-bfbXb7-5n*>XgZu1ko}@85hE)PTkp=O;vg%+I#QWb=un=#vf6Dt;7^9x zJ4F`XQIX0STu8ep$0tK9z)Z@aM&c!;7t}cNs9%8#=Tx=qu4c~hD}Nl+ZtEV-dKF1p z)jj#JJv6hef=B41@=|U>u8l&+n@z7)wc8$cr@n|x@C(WjF8sIz^a*DC`J&UzpcpG= zEdR;7@~lJi9v?_fO4EIo8WrnFP6rk1HSoYW9ZtV5kqDB{a4GCzJOo;aCPc-CNmdnF%v!j z?3gBB1>W(bmw_F|^6)M$4sxJ&?Rj^iP0H2C-4{x^7_HCRBh+#1vV{h=e!28RD*ZpPeR0g0@k_Pej$J9Vw{l;53FKbyPP!gK z&wPeU(GSo|9AvirL;r9C*W)RBh##3Xp#FC&IOD!|ZtZKnMI6@!u>S?S#g*GY8IZ6n zAH{@Ttjd;t`H=D`Fy+@A?mQo`6KPrcI)ACUH%wrCT|oiVk3-)>Bhj)=j+Q|D3-FJ% z)uX~-#M`y?UmN@3f6I4)$7lABTo;&BLStKu>1R*<@BL-Kn*(OHe13SUHq8Xb5{e2J^sCuhKBTpC+)?-UC!4EocrNn@oDU6`WTMuW@1& zP3IxfqV?i-4zkLlJ0wg8%7|(o7wOmu@>xyln=L%Pe`Oteb9@}&6IY%|L{SE+M3)3I zX}K_`M1wXq<|JEh?iX);@H47G>7w4dWJBx4dQtbfYt@%4pR_$syNysDTAy`r7IhY- zDXwDHavRbXRNO+!+D;KVIgFVGqeiTR3LL|f#4VO z6g;GVc^X@zJ=Xr=ZyHzfXSF0Ov|U2YNJMU9$o=RFP;$B5XUpXfPDS1^~h z-X92b?;2uX8i~fp!!J9Qr&=?h$4&`9gjp+eASYbft{KTTJyKY?OO65Lm1V2L@HJZf z9MpW0Hn6AMD=_FcC{cNkfN?#n@&@2x`QE(ECFh*yVn%o0q0?dG4Z@YE&*=l6BWLB*-BGe0_)K3hTDe~=D4q#E4-Rpqbplicj;fx zEG`Ume=_5@lMbJ%9JbPi&hj#i@Tm~M*A>OwV1;?&x73rrOkDwZKkAfz0gFV3e z`=9mS@UH(_1NJhwHso8wH}qdX`v|hz?*ihV{@I#8K1*IUFDbVL(}%PaGJlQBgW4nx z-->x02jX9@Ivtt%t2sXFw4i%3z|)=yy&JxTP!oj_P0Ze8(xuEkT-P^NF9fveT%WEI zl)VL`SKPW7J;(oz ze_$u3m7{Zu`Gns|RLk45fC$Sx3Av4u)yn-)#lBpuM#q)tN%Hsz-B*48%qFiaijGq; z5a9PY8#I~2vm`!oiU^nRT$CkMAD=y5roL3%4f2V)$A@gpT!tjZ#k1a>*^p1}mlWe^ z^iRx|_2{N<)VCCAd$e^aE7xEBcxxZmN#EtY0m4Vr-#;^e&?<~p-*3;8RO<5mRtUe; zMZmoG_cPqDoY?-}-#R@2KU=}G(joXhfz^Nf;@a7Zqfpg6_EV&oex_K>{VW-D<{Sr^ z?thyZfX_wMN@;v;P&wO8V%@c3-eHq`QXAhj!DF4sx-mU- z`E>2u#F)@@Q12O5Za;}taXs+SR9P4Y_QMAiI|5TMP;98Pk4F$^N`BmW_Ts!N5m!_cQgD7bluU^nl1^>k zz!Q7=Bd*)D-_%z7yyy?!vfAb0@=WG%h_-wA7}VE^yfU*;cfNyRNS!d`-3jck+g4{Q zQ=yJkr*_FJOBFnwji09OCM!QbfmX>o*9Y=pJzoV$%FudU{phK;Q6S%A&Sph9K^oV7 z`le0F0=|Jm%PBK#k-odFM%8E-cpiIfC&c(rF9`DjvH}?GV#}2Gm8(U2b{^qfPuUi2 z;?2cA`h8^oSj%sXP%5&D&Pthipyi?NjKlUnkk0;0BSTzFDAh=oeRfp|t#=6E2NtWL zHKr&6f}y>#C4gVTMHVwR&_-yRG;W_m0=%K(QDyEVg%8>ZIS%LxTBxgGnc-m`Ouw;x zKi9&J{pWrBN8bs=e`>>6xXT8h*O$GPu<+ig^G$Fv8 z1xARbt}Hw)$jSlsi}3NFwX=F#6r_qd9n#G?Al^D}X|U#BmAw_*dNugDwbJfj=RJnp zE+sBOkVtJyjZMQFUcT+O#=W>PX?95skfr1^dO4#(+HtMk?Lk zt*mBZ+Wx#%IFQHv>(uv|d>F?}BvfxO%wdF~B7(SPvPf8rm=odUg^4ZR$Y&}Epa z{3G`b^mI0^){rxF_-(+o)?3X8CNei3UD5~s-{2?Mwky_QQXPt$VVxqmkU+%g{_HGu;B9eid#R;$ast>P>J7OaP_CT8=?d%@ zXa?_F-GKBKa)dLj+@`!A*wcBQw$7MWHQ-9LNpvq^6P3G6mK)YcS$RVD6VtcZ;NXux z6y-jofxmW@RZA+T)^SJ9p#*4@;NeApDl#Dw+^^}QpZ|Y(P z;GchytqOj<3(Tu5vhR?EbYlarZ@%KZ2>zaXw&MrTn-3sGxZE!&(^+VCPtU`659DUv z(>GoNJ}crq(~xr%@gwQjQp3n02ArLbkxwjUvb2HmECPxolscf1!Wikl#I>eMgVU~-wU&{* z3KYPDhdJEqW)rOpZ4!E{rU71|O|#5=kfHyBHf>8-26pz3eWxAy553&`;YETv=!c6l zpfJcv0H3l=X@w)vQx-$Ra$(qDE)EG072^QV7pa}ayTgddRN4G6<1PcQYXpXC(i`&| zz%dyA6Tg2i!56?E(F*z-}?vn;k~Q0>6???x1-TRwbIk|}zai5I zYn#!(FQDi0Uviz`{sDRUs~L^Wf6MtZ_W}878i@b!f5kr-pMrdLL+*bg{+SRK$Yl~) z$^$c}E4AM579z)Yiwv3wNKM-4d^?$WWxdAEZod(sgO^Kv+wq)Z0S17Y^XnDOMooyB zNAY{-mitMJonlWQdHH^MW8ZA})sPdx;Q%`wr%BW7MB%R$Gb@+Rg!gO1eFvr-&lo|n zyTk@9&?}&SO1UkfshFWW1;0VbkLbp$Ofb+Hn#ZWgz3we=bPv@Ki zeA{?7%OY}_=kyXooPz^+`rT6Zu#y(78c8v~KSRYg^BNygPn|O@SB7pr%`6%9^G#~$Fi|}a1w~hLgBDhVA$$vcL zDMgS8@a9)=lpUtL&2d|QwQYugKGSYm;*y>KRNeV}cpI?2)0DMO+zVD-QftVOwmVq{1x49-^X z}It~Bfc0rs{p*0N0NdVtRj<^z_-rn;I+CLPa260HkP(4eHmJ9obQ z*tj=%%!vpBq^25j=EvV_W8;nZ@9c2;D@?}4?w`+}{f0tuataIE;D?kI+`pZi2%+NM zW35{6(NK9=)zyh?h{>8@3Fc+XqwJ_o>IyWKU(n5@!H3ieo}%14)d?(A^WC49AB4t% z{_p*rZ;KAx=NMRP@x1}_m#POkZ_D<-Bw<8n3*Q4if4+g*;hS44{C)VovSqq~eP79i zDwASTr1Q1jt9z~k`*)4~z4_G`EP|;B?~kFwk1=LHR1gcw6Z=q*eNn?MP8d^vL z*Y?cH+Dsb`X2(>!&7DYx*N^`m+X3EBZ}#L3yyDxy+})J!Z2(WOVJD~*F<>|AxXlLe zhwrx~ui*nf=E(f=%T*Mo0e;-G6KBcH3N-RTeR>x-U+>X!B`s%udHmIajsx|SRrBit z$xZ8C9QJ$f-Z8LlaVU`9B)7NwXQ7s;C=E^zuS`C3h-vWD;(C=_BLS`{uApO-l=)^| zeyuMW1A9$dM;n3x)P{OJQ?CQ`(Mn3xF*e*W9v|MV{#sq5!478Lwq^zpYxn0Lp*&>- zlHYuP8l(AE`U45wi-un}>R;2FE_|ADv@OlbBAp=rQ!rIU;)fwE!KX&uU<8j`sCber zyu353O)uSlm5%#-R^YOa_=8<();aP454FJjmrAmw;!WQR%ZZ@x(Q$bO+kB36er*`@ zHoh820C<420#!#$x-Wg4A;fIc2YjLCfvMxv{?Wjopdc#fe{C(f(MBpYm3v-=75b?nJS*2`{0VJ; zF?+czNfso7T@X~7TWa_HH$h?2C}4BFj9n>ITFM%=qXTwnJBrjg)@Pjyg4pHFwDk+I2+*d=yyS3jLS^!f;b~MH`QIm05i>) z=Assbmg`JFv^u*QEcGkDMd{65+h=~Vuki^mn(c>)6ZdMdNmb7pEj zF88Gz&+eg3a}U0D4{rin3Bo2K;t`#&cux{SL5I;!HL^HT3^)%@WSo-LM{mzBwud}_ z0=wJthO7u>2ksyQ_yp15o!%j=AKnn1`M13Z4J|KkGr+o`)?1XUR8mnvgErzB#6P&0 zHv@zE;)Dk%e4UM6sw&wN&sXb!e(L><`cuI{zk3Y>4Tq2%WKCvcVOo@pw^|)y`YN#N z%uNq^Y_fi_$|p_Q&Istul<8Kve9+Z?ndL(NAS6J5)eLXReb{efpkHiUo-R#nO}%`) z&t#35vB~1d^kqhGk0Ftvqj+iX#`@BOoj3^8>;3OiKGl(L@G*x`Vl`UIf@M_?>M{hq zgW;beB?3g&p(}-9HRFqE$+ek3W`g8lPWVD{EFG$`nEgmHzydq1i5vDN&MS*wj=y}< zg9rKlmBql;V9r~ax&7F#(T(Skr)FYh`rOrN0T#r7{OGQ&tE5`aE&rrQXa>x8SjlJa zBhC>YTzmV(STRB#)zfUJEPbgpAP{-ywDR(pm#!wIOUVgMxgb6Lc_~mHrINKBgH<}$ zRR680o0bpuXA7!@nE~_1e!tIn!zcn=qELgy@&x4sx_==-WT0A;zArC2BS|0=&@C#}6>o7`>BEe3mQ$zw1e>dNIr_#-HH6AYRaJqAL3- zG&@g};j?K+^uf7JG}y|_k?!G$f0jP|?hB6)4lRGNjq%y_?q)RV!r-hM*uNyZ6LFM9 z-R<2mZ82>`9MLni)$;Sz)q3faW-?YDwoEQ804_$Y<1w1hbA3kZ+0eHS#g33Q3)kiE zY>U!Xj*66u#cHxtLZA|8YcWB&HL@_P?mqX3q5W3AcvCuV(&-?-k3&Hr>*BG(V@qHj zYv4x0YcR*RM0Uj-1NB>pczFWGH2beXuj`%1*>s$jfh4~VW%a^A^n8uebp{y{xFqXr zdus=z0+-zIBeuFaSx1>Ty=dZn5HyjhO2?f$E4c5Y&9Q_#>(@>A!Tic=kNX=1N<@i+ zoIe4FvR0ZrN8hY>efQp#c}*wZanN72umKwaq!3iGc~?Oiq(QlN2%U7H zC?am($80=ho>jiCt>y7l1>lLI&sU$#B6v^5-Tp>~fIU4mVQOsX)ZD@=uEp2RVE@ST zOLgOBygJ%)Z5Bz=Tj(gA<|bb zfR{Q#n?HM6+R}^=YkYD<9@g{^3;Cu2L2n9TT-`R)I^s;)*O}fM;WZAiHLD=88R0cs7>c-v5{_97z@( z2l#J;ChJS#GTW1BWO=ncOJIKDTbgU|zWf>=l>cRDj$Vwm`*2c80JS34?4$R6l@9c7 zGHzIl3VFcOd+SP-=r9FiKBWR-;3y3eEfUP@M(r2eV+<1Y;71a_uK|1|F`%~Y@KMCg zCW-BpRG`nBou~Oqj4yNgHAzEccO$Sn?cQW>I}s(?WMeb8i=TtM8P%A{bhGK*`QhSl z-bJ`QHuX#;0zbAsI5_lRs#}`{3sJLHrbP#&i_=zbSAqFNv)7dC4zhRq7rT}fkpF0A z*Y!J@w?l>H3TxdufWLt-9YWg_+uFxI{jdo^CzFS2{Vd6)u?Rx1bAvHrp(pcbMt!_v zp0Y>3XJjdzw42*cH)984_2W&)`r%q)>)l>9WFnlf^Vi-9BWO_z!?AdJbBjXK2g>l> zqc8Ho`7dzrpw=z+P0DrtW)j4~17HYp2XohKVZwC!gT2#%c1rIui6pY>=M|5I_Hx znL7E(dRVsRB)wzPqyE%2O{;v-_LO=xZj(}O_WRSnr4L1RC2so$^yPuxnazz)2s|DO zAfrrg4$%;=wV`hSVr$pVwRMpHbBRG}v_yw>-tl*ro9F}@9|}TYz^GqF37lUpamYd8 z`_ES*_9-2SjeUQF(Mo!3RMzxFqv5s*{x1+Ot*Ob(1Bj~^kNM@yr8AX5zhJys5iwz{ z74AJ8Jt|I6xcOVlrT&5)WCuCmg6x1D{ozRDY(@d8AiIEvhmSVRB@72cCj;>i69d8j z)+hcu_&4I8B~Bel$&{-X-4h+#yGe}>pX)tD$Um?((GT^z!yWWf-F<0#=tqxG59pIs zrn7+GXXL7YQz3t0y_0jbivap_LmCf9!`m+j-&wwNnH%5{s5ey+5}JDQ8nE^p;H%e| zUo1p1?cgiR9^MZ?fAd6`lPh9rImb|jn9l(r7oU!fZ#hvQ z2K1}%;x&rUuP-Duh{U(ap6b<4$0IB#`)yuaoN=eznKxlK{|+3wCOpKX*Cfo={FZCRcco#&-Dba}Qb+kFci zr}T`g_efkG->u}-kz0l|G;;T&tK-qPrq#D#)lKjAu`e<)`=;$g=C`<&VRIE8~ccrvlIe>gMIgmpn>G(2eSdN2Wv)a+3H@VjVw zD#t#@$cP4CW>sbcctTBgcDtc4NRY2JI7~=VGjua_69yB8`(`9*EQ+}knQ|dhL}1~{ F{{jEVJFfr$ diff --git a/tests/Images/Input/Astc/atlas_small_5x5.astc b/tests/Images/Input/Astc/atlas_small_5x5.astc deleted file mode 100644 index 2831c90ed5159bea214808788e7ffc257cb529fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43280 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 diff --git a/tests/Images/Input/Astc/atlas_small_8x8.astc b/tests/Images/Input/Astc/atlas_small_8x8.astc deleted file mode 100644 index a8805f2851754a6e72240edcc481bd5b9d1c875c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16400 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 diff --git a/tests/Images/Input/Astc/checkerboard.astc b/tests/Images/Input/Astc/checkerboard.astc deleted file mode 100644 index 79acdca3732f0b72f25640c5df6b4b188aa1b847..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80 icmWe$y)cG@gHeEi0f-nG7+hEx{sAe5&{YsfDjxtEr564G diff --git a/tests/Images/Input/Astc/checkered_10.astc b/tests/Images/Input/Astc/checkered_10.astc deleted file mode 100644 index e3b196578c32a6a1d8dc9b911c1a164bb2fc5310..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1616 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 diff --git a/tests/Images/Input/Astc/checkered_12.astc b/tests/Images/Input/Astc/checkered_12.astc deleted file mode 100644 index a82583a9ed5bb9ef978b8f11021695d78527b236..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2320 zcmZXVdrVVz6vuy^h*lRX7AOL4+JXfNL1nX$2p!UbwAxmj3WE_16S+|#7)2(JBAJ6p zcqu*sYq7Psf#Prx7{<-XkjO(AigQfHL%^XcP8}>h?tbT%>up)XKfjz$&-tG3IX!T{ z(Z$T3&Dw$x{$nBZ>NPp_;q!-wNzxXTk_M@~6^9Ta5lP3}+9D#{-O0D_swxi;3}8Mg zOr5|=ox3l^+TN|6r@NRAn4h948;gzEO@}B8@ zcXwdmjvXXPAm2AR(onPBagwxgaA{IzxDN|+o0DDr3kC^PT4?A}#6mYD2O$DQb*kSp zcSpU|G25$c^t^@}*;HJF!t{hV^^}LFy8&uq-c|KP`WO9uDSL(bRTX8$|&?~BnX(2B?P*8i?97ngPG7`~t& zC1u|}@KfRUC*QOQc+2_n`LVGg5yQ*n3Wd=KdXBmt&sOGPy;gb(3W9^1niyUvymE!b z0$w$ml<`jHbpHACprC8l7=CC-sZ34=JuTtoy#w)>x6-fICnna`GQ3WA|Nc0huPq7- z;aFww@q9Bz`!ZUgA1Y2D@y+1!6$8K{(32~j|9GU?{lblJ9l-w&1Sh|D z6Yth&G*^89AyD(`^6zH+LFom4_KKRsEm7;2r!-M|L}+8^?+g4dF!?2~=Y76wnC7?L zcJooZYCv^dHiv^(jzDR*uQ&$G!>Or#qW8neDJ#p~9{OFLj`}=Xp|<5d1O=H)@$rm5 zE-q)zEL;fl5=REl@LPlHSc9cLp5f!-+S_5iwrJzey=4KEUzUD)c_k)-pR^vW zHYaE0O5i1(9NsR_Yo%v&G%s(~EQTK%dim1P5qNRS`U##l@Rs>YODikq&SiL`vA@5g z19*Yb;V0H<$}fw1WoLKj5LiU(F&fLt3 z^?So*(RIN8pbeg+^Y!-X^*%nZKTmH)Pt;O+sr@OMOJw#k<+ za~_Ei6}tu$4B``q?_V7fkW@dAk3gb5`BF|jJYH6oM#Jz94xynrIlzmY^G|amn77)`{QQm%t(M`%Vv#5! z0{Eosn?3Z6IK36$-*0ap8_V#mt$lqXBhYVLz=a<>mQa3K^t)+OU0qQT!$(9MIZ{&t zJX>ET^xQI?x3Ot&hu9_yv%yRB$( zUg)Je?{b@#m`wQo5NP{#l}F=M9H;h$K)Mr!*ZkV4`gp#wqQzsnI}_?gXZyt_6YNj* z-Q=+)C+U0yGE6Q#vegdr=6zDfsLv|8gTG!j(`k@{Yzf4cmq&Rh89wCJ#GW)tFSSpT z#9CQr@tx1!#KxKTOB-#v*p&%#;7#|dRwpM11%W@h_yJ{lBj~gIefjwbg%2KB^3BcW z`=h&CIOcZ(<5u$;8+-9$?OKLUPj6{~{lovi{*64Wnm^rtgTdQdC}en@E-fuE5cnsn VkN=aoALLrzlav&NLLwo_{{U*k>68Ef diff --git a/tests/Images/Input/Astc/checkered_4.astc b/tests/Images/Input/Astc/checkered_4.astc deleted file mode 100644 index ac60716458fc4b169f60f9f1f045d58ad1f34e48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 272 zcmWe$y)cG@gHeHj0f-nG7+6H5kNVZBK2z^FUMOSH_c${kCVef-67gL}n*RUiXJByQ zGztibba?!U<->;${QUfx9n1m3PhzhN7YT#qT{F}KRP3AlI(r#dfzteJ9WDZIo*!3T zcVv_3e}?}IY#c@#@)9Bg4a<%)0J;BDFR18x|JlW`Lzt7FA84fO1a?hU*^QD)ZJ-MD7;>H^?Se`DG`PD?>~G1`X^wq_Z7!g2CNc~`5D+Ue`NZ&UYvL}0k?l}QUCw| diff --git a/tests/Images/Input/Astc/checkered_5.astc b/tests/Images/Input/Astc/checkered_5.astc deleted file mode 100644 index 0389c53d54b7d1ab03a84b91b3aefa2c07135632..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 416 zcmV;R0bl+TtD#&72mvSn04M+e0RRAHustobP)Lf_CJF}!`1tr~a22o%fhJ8{IRW1L z5JCuLh=++fJ|y=eXBH0+@bK_uFbXis%5)CGIskx!e0+RGs4h^MSZ;>6gf9ypA0HoS zum?-Z5#=c6=_26&|Nj^UP&=?vuEbs0)I%5nPyzU5P%&_|bFq2NOalN94-XFp&{FUe zomkozzlaP4Kn1`>IAF<4r5qz4$_0wx;NTxb2{Hq7%V=`Kg82CO2L}fv@pmhwVSQ$M z9Eiu`2?T>hkRZ#ii>E{Qb`T&B@9^+N5CD+ZOSJ7P{XGzag98I;C>rqhFEaApLJI%? z4*&pZP)SSay(c=3WFup6&N=60P)ShQw3UY4cm_W{z`#BRP)X3s$e`~*Y}*}G0RI3a z%?{9OPT+kI!yym|1OiuS5MPij=du=Il>%6wbI!qOXcN%j0>o8n92A^$&c_&Ka1~Il zVY%1h!~+2V0sj7Jhy#$uO3#`9Z5RLl{{a97up|&VM}Ko^|6CLXzz;x05IRjotv89T K_7Vu=;Naldf38pf diff --git a/tests/Images/Input/Astc/checkered_6.astc b/tests/Images/Input/Astc/checkered_6.astc deleted file mode 100644 index 210dd340e128b789e0e9d22e81780a7ebae342cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 592 zcmWe$y)cG@gVBJ20f-nG7+g;CZAiMfL`IOA`40%No#5MWyNmf$tU0p~!+(bVnHfwQ zq-q(CluhCJ&&dK6=l{sgf;o70Bp{yYDPrv8z zP%~l$y6(V#HV%FP74=p_;J^Xb6MUs&ozh3H=B(!fxhJ!Oc|yHj z`<^9HJPd#S|NpO&%M?0SpNp4iK0h!tnh&~kGkvIfbmu+GpFhkXz;c#vgV|NGcX58!KkeDYkQAUM2S0|K8fygD__LyD`x-rnEYe&=^S=l4){ckCeuECm2` zFaU72a9R8)@wAQ3tqt-)o+6a)y~*(Y`#KmEL}Ch*_LwukS=ti47$AwI%^m55>%UD! z3|D$n8ogPoV(60>zpfQ|7Kf!R644{4G7_>|Nli3fHNX%eQ`*w>h)kO*oUeAtO?l4R z72``y5uic@aF<-*=^I+?5dl8HBc9ut@5#x`m+=tsD0IKiedBuhj#?Fo#3=T$u|8AH zo8K@TNAIz>En9A`J;lv%dHs>vS91Dj+1QXI!{h%q%b6KMtQhnY+3|XW<9VK{iPQ-! z?Q)A;s1HZJ&-p;v@ZyC;1yq2cKNw4VjmbHrO5;s@z6izbNAh+V6HA`~5ClP#Z!c}n zHKtBIi!mT+Wg^x$Z+sQr|A{2TqRK3Z9u^N49Di~)6bfM&hCH2+wtDA64TMnWr#)?( zZMazTxJueqobGF**to&F^_`j1rHVMx9|#~{b!+pv9`)a??ZD&l;Dnw=tSg6l8z4{e zBs}r$qAzTY|cQRWJ!Lc3X3cZmd_LInXeait-ivALcXMcIa?8P<^k|^u%PR zLDv7t{||~PDU;^{=xZIny6GOqaa@S05wS<{iF#XIG`3%nS=%XewtZAkOvd^~+UFHwZM|?g~ s;Eh?iV{0r?VNcO1)i*L{nvJ%oij8P~c-IQ|dX-a_P=OXz(=Tf+!M~AdEtAF`+0M!YH!BQNhkm zF33PAC?seK2~r^hL8K7u9|aZFRB<&F&wYH$kLTt4Ufx%=$GaLD6#WG8dldxn^)3A! zlDxf@q<`mf9M{nSr&67r1Oe;yhlhQAu+b=rBnc-Hxtvl7x3_CF$s}AX()9E+tkqhr zdOe)YK0I`H!yeDZhW!7_OP$W)KpqbJ{j;+$NxIzz18lRgtSG|a@W8;?8B9?}M+yb} z{?0I`r*N${H01Nag5dQY9KbwZF2B6MJ3C5csRS=9oSeM9!8AQS-qr@Uv;>1{HOw&2 z&y$m|PPe_?+6t#qOG|EyuNMmOIBYf>3>*hnt7^5|4PRVb zU*FxqBO}Mho0~AlMIz}mTvwOR%lCP6lgY^YkHymIsVUSuojxDKz&$+_b#( l|BsI!A9g$P`T6s6`Mi=zqmiPJ^ZeW#O~Zaa%UUdw^aCvTFy{aO diff --git a/tests/Images/Input/Astc/checkered_9.astc b/tests/Images/Input/Astc/checkered_9.astc deleted file mode 100644 index b5c962a14fec5b1639e250cde842eb541d4aa59a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1312 zcmXw(3s4(X6o&6+VY9Z71i}R5A&8f+o_aiXX-duc7Q?e z|FS8KB(c{4fFBHijZNl%d3nvxGYkP5m{wWL2n+$B2YLb|HymyOF~|ZVFhU`Mg(iu}ejLYI1?;kmiJER#iSLfqWzfj)z;d46@V;Kes~TD*YdYJSMQ z;Z3)`mQJWKAkUpyX?pbR^Yfe4fW=~AG$?fKE(t+AP_Egg==X`H+Ao^e)>AnXD0y(62sH>G=G}p}IS>E9%?7(bV+e{Q2?0M6R{dTh*fy zVn{#*C(vEePenVvrvL zGzb{N)_h;FcrF047XtBj3>kSpmZ;Tgl}aTS6SUhWcb$B0YD5yqLufqk`(Y!OE-psu z60ihJuJJH*R$Z2wlspgave78}h)oX;-tsE&VYAur$OtkJ@ziH^SL0m3Mft^U3SDA> zv)OFMFxa2*9>-{I%65GbSQ$!P`x^1Iw7~wzE7%2s9WMR%vpPADNG+>mixUo9Agg$K zy#$J&_S6Xa9_CG7cObzA*r2I2!|Bph&i+bIh6xySO79BKUN+UbuGf(!6T5sNy@IVd z*BL4{eka)2fc+6(&92PuEqY>3it(6%14@G)m*~dS2sA$`2tk7!W#; za;U{#+QH?PJCC$t6%!h<)F6E*OWAcq2Pi-ZG-AWBHnS@8W+~9Sfk##~xcFM=XJJup zAw{lt6s~A~I?i7Tcv>#l%4gt+OL*M+;EF!==)&LY=|db|!~>P`db15`5A08<%bShA zARf#5AbkI2=dZ5VA|50;$=BRF>M3=xUvPlOrGoDl#VMN|{<+w02I4X@k@MJt@vO&A z?lTs_^cW25YMlTn+m_O9quTftcrzez5Bbhs5ww*{ht%N|FFHGaDXQU z=ch4sx$|BTTx!X`{r*#wLJ<+cFapr@_V01;e-C*}N6S~95YX4g2O@9z|AFzzx}|*= zpevPpu!0G|`sBw;b{A1t-xz5kKA2GB R*8#t_>noM9u@VWx{0C`gEPMa} diff --git a/tests/Images/Input/Astc/footprint_10x10.astc b/tests/Images/Input/Astc/footprint_10x10.astc deleted file mode 100644 index 6799cd08644ee3b49c2cd00ef70f057d2986eaa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 272 zcmWe$y)cH0i&24r0f-nG80^_2=ZjkGd-;`@VL#JPc@KN`#^bgL$o!S>Q#%lR6XoT_ zZ!a-4sH|O^d$+j0-k#mCT-0LPL70B7O!G-*Lf><}bHxRXjg5B$`Jcjq%j2ap#SKAx z6Xo5-Z?7Zt1LcLxMgAo??}VuTpSk@Z?;%Osp8Bqc&2DE~LGDZK$eRf>-$eO*v2Q&C z1B2?ywd;ZQn<#&5PW}FR0_)Bfj_RudSWT3dI;Q~nytXeK^;ZS(nkesljKV)$H!<() P+O@iC*Y>Vm8@e9=ccO2b diff --git a/tests/Images/Input/Astc/footprint_10x5.astc b/tests/Images/Input/Astc/footprint_10x5.astc deleted file mode 100644 index cbfe4f2644855fc34eedbb41f3ff215bf638582a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 464 zcmWe$y)cH0l~I9#0f-nG7$&o1FBc8E_H^nEAqSEa=+X*jhO(KJCJ^H+L z_hc65deNYDdlCAxm)lk$^CzEAU4-DXDnAC={|d=|{cO>oSu0`sojlkky=Styugg;- zjxf1?MRkjv&rGDoKvpAux%|C^}v9%%ntxcxx&LAnxu;&Yk)v2$vR&j;zx=lU~$ zEf1W3X)W8I`)}p`%5!Q@pUF_n)*)K<01%nA(Bh-)Fx5 zx4-YiYegCJo6BY%lb+A+-zjQw=rBTm`9|9WWd8h{sT~OZedhg7`};OrM(8&$6t!5j z8m4~|C+DObo|^u3T810hwPH5kU$f=4FKzhIf1i2z+5Wy6yJ7l)@fBdI{OMoPu%{o$pR|gnu6?}}kUyaYr2oo!H#k4aDd4~6y+iyFUA@jHYuk73Nej@9e zzPma9dhRppHw(MX*^N*izuCDAncsiEe9E-b4_P&)^{t5ee4m*;TG*{*GD5w1w{saX zKYe@ol)S|-^(M+6+e*H_p1^Cn!Il420I!MiQRmd}uLD?ZH#h?MtR~96&lA5Fg<9?} KbpKlvXbS+Paax!F diff --git a/tests/Images/Input/Astc/footprint_12x12.astc b/tests/Images/Input/Astc/footprint_12x12.astc deleted file mode 100644 index d94d0a6c0534e4f2db4a35e618a3170f50598d8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmWe$y)cG{hf#rn0f-nG7>t?xSBpCBc>Yk7Rafex+ec%j`rEEW2>yNMjsL5AwtReO z=`n3?%6}hYCi!Ynr;hCi_5Q0}ixB+v%#CNOds_B0GB_x2{J-FtG1F%kc9*PrL0)F% h=zr;D#!Skk<|PQeBUAYF!U-v@j8Dq{t^B1s8vqSLKaBtY diff --git a/tests/Images/Input/Astc/footprint_4x4.astc b/tests/Images/Input/Astc/footprint_4x4.astc deleted file mode 100644 index fdf3cd6b5f796f4bc1249b9a8466a1f4ffce06f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1040 zcmYk4Ur19?9LHz(3Q9gKXLaYi$Ln;6rM<{tPOLRjQ!A)m!ary3x<9@+Oj<5u8b~#) zr36mVha)~7UTkh9!ei7f&!}t6)YF2wOZz8=bZT`xGx_#=lA)3&+p2DcbaKB zjgv^wmnM;YO1U&sqTUl zb?sVhEuMeV?-Ll&x7F7p`UH6(#kjAhdIsj!p!>tIb}zd2zJfVaH&+oZ>f838Gya7!<6nULBmea>TSRT!@Z&sHwq*4tlRRRRzywCgK4eST z$1KrcCNJDu?Ztij@IFS&tB|p*0=!?$ zH!Z7BUh4&Q;rTbnU;1iR>qvjLbmX>>HeX@09g3p!E^yA06yxgdFh<;BI`oFLPx)s?Yp> zsNwact4HW@HQrYXA2IW=urL_3TJgR*_@sT4%Uu~E#nYy>*w+I(>DW86kA}I*uxdS( z2j?#yI80G)P9uF@LHcvM^wiGm0?o#ItZ;HYC3B}3zyF|V1m>7W$p4D^hNk)E@C#7F q4;=qN{+|vQvBUrJIAQWKgaw87M$OiV;tHFAnm)nMI+gq9#r%J!SettQ diff --git a/tests/Images/Input/Astc/footprint_5x4.astc b/tests/Images/Input/Astc/footprint_5x4.astc deleted file mode 100644 index 0683059469ee0cba5e0732cc0e373c48438b3b28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 912 zcmYk4Uq};i0LSP30ap>@Bnm<&49@NDq}`dYX=mg1atjTS8bW&WB}?i-jwMCl9t>hs z6k8BP^A93ZRgWt=C-=EL-`}_TB5)(Zr zQdMs-;7hQ*bekP(8(^)b zM=yhFKO!uniHLn$_hDpIMrRp^!4esh(M3gL(C@CWJKDc1_y+^kE9+`rs!2vy8Qb<^ zpQByP+sMl34@F}L-x}&&`XfxuB$j69A3D|bwAqVJM(o|mHLV{J<$}w*_m9M zCct^^&G}(A-`1Gd&y$FljJ#JIow~n^jLXQpNElosQ!gPTO zsYAXc)aq-oLQ~g|)P7F07paLldEIxaIoHPKN>onwF+_|*;);XUeF_ohkd#OmERrb> z$zY_x7{zkPC&k8>`-1=Smv@I+d~V~vh)#1BJ{fg-lDTOD_%XUB3!iDC4VLKIENopp z<}M;G!!sh`(fvgtCBtcqG&n+LWOzZ+SnYSfyFzZCtK2UTwiGX%;NTId-et+U-+=pk zdU4~!rHx@~!{|rXdf~IHvW zd>kK(ZZDG>|L52TrEW)0o^~)bw7y`^9^NjhvhVI!)`t6x|F|~*)vvjsv@f<&`Ccp^ zlYTdl|He|~-rQR%Kt9kukp0!H|L#h&{R8v;)bsNznr?*UGsr90C)7`{XW#w0vIAk> zTK3Z)d#CKVzct4}noa%RM0yh-M_{(@1AYmJl%J3UKeHo{Xg5*2O)3I zUcWvt0hvE}cXHL5qntJkyE3BO0`1wCUoW2mwrrU+gThpxeL1FC^K7%?bYI9Z?3g~egSkg+$*j2TN37&LRQA;Y`DenH?wa>w zX_Z`u!oJ$ZE@q#Z$D;08E}u1zL#b#jke~DG_$<5GC#vRgvb~dBk7q}l<-UmaNT7YMLH6m~Oy7BLW=7;ZSC%56eZA{@C)CY_*$4C=kLnvM z-8a8v-bg|GcgEzwUz-Peb?(J++?d_m!7R$f_}u)=#0SeloGx4jhJUtyT0ti){DAS9 zbDeFbYz6yFV0`MX1*O08E}%R#{V}Q8=EXXk^;JEoeIPV2dV&&%tdT@-;_t?d3R~p0 z#n~ru$Zk>)P5RAgq`5`@W$|Qtc2!5qkd)k+S&9mbKYpj%vs;GxW+3x@GZSl&`H8iq Y6A=75&ipsuIm+cNUQUx|P~lh!0GYQ@6#xJL diff --git a/tests/Images/Input/Astc/footprint_6x5.astc b/tests/Images/Input/Astc/footprint_6x5.astc deleted file mode 100644 index d0d455d0bfbe88657447554b07ce424c94ec22c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 688 zcmWe$y)cH2l~I9#0f-nG7$&piP8Qa?^0f1S07uRH;&`Twu0Ccz>g;~ZbNeC3P*4u! zukrhFFYL#^I2fNN{fAxtkGOIeKd1i3y!Ida`XPL^^JTUdj`E#3a_YeL>ixBoSt9F& z^$uKwnLnAOaJse|f)BFK?^9jar+IOo0aKae`R3)WR{)xi~A7jC$pUY z+~2qFqT?hL&S#bLeuMPOzN=GxH&6E+%)O@X?%BTk=L+V}XD^-Y9JgjKr_cn3KVKh< z1J&oGy_=W!ZeJNp{h7LV|JvTk^+Wh-kBe+CoZ>rj&;jRbN@M@ zeWoww*=Fr?&5~=Fb)D&B=o#Ovf3YwAg=WbyeFz7d&y)7TF7HKL*$b$Da_U~pYkRS; z56oBU;QnNCyk949@}Cnzt3tv4l}Wj$lJZX{1*jjy=P^yOvrUO}O@Zn6O_>+^WMAwP zIfkO|K>0JNPyXdTkt+l8!SSP0_av_ENnIb9f3A~zlFD&?8ShDdoP-)?+y%K;=3br3 zy?HwK#A6=f`f#7dCQa3U0Bje3=|5xQ|fBgLl0QWaWf&c&j diff --git a/tests/Images/Input/Astc/footprint_6x6.astc b/tests/Images/Input/Astc/footprint_6x6.astc deleted file mode 100644 index 47e038efd950f3aa773346512bf171d0bd151f82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 592 zcmWe$y)cH2jZuMt0f-nG80^{8CyQ#Fd->J0!Jhf2d51lF{c2qYWd8KSt`W%m^|xaS zkom_ymv$id`&7pI1u7uCQ%f|#=9JJT&XI^=- zzi-A)nE4wGlF!II`>XOyRyR$KV{JW9zm92IoNZd2Ynog`&|i>!zG?eH)9%HB`Amk% zZ&K6b@}Ak2rO7E2{Ab$8m(!LuuP-gGE=`W1pngB|#Et!Z4GUrB1NG;~JeaHUV6V;t zS)hA?`p=j=_-pe()-?gFA84QNgV@jqwXtA6$iCEseYpwuN)tf#gTk}4Bd-(Y|NYFB zGyD58I$`GTXZ|h5ua%X}$Y3$EoH<@>KeMpDx)U;A+uc42neQAQUWCk#F3;~m@SP7f SOe}cM$nffSUA=eK{oMdz%lqB{ diff --git a/tests/Images/Input/Astc/footprint_8x5.astc b/tests/Images/Input/Astc/footprint_8x5.astc deleted file mode 100644 index 4ef49d657dd4a259865df557c89edb457b41f523..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 464 zcmWe$y)cG@l~I9#0f-nG80NF*&J;Gg@|08S0>hurkDt$H@7-w}hRk1kGq?nLaZ1*k&H5MmN>-^LoT<}dle6I>nVp-J`~)>W%-`5yamnW3 zLYb4BmCp&X>i-4ujVuM3Wcarz90Box`gLqm;#^bag{H_U75xY57jiaal3Bl5$pxez zD4)}o5!;qBZ(54nq5_b4lPm<2q*=Ep?1Gv%$x<;%T6Bvdm_MK0voyG@pb_T2`Rtif ti>D#@#!SDJ_|3Ai4L2`S_SnQ0>hv0pT+01cWyThLFTW#9$bRVKl#0Q z3Nk;iUf5*ES%m(=`Nkp0{E7R6OOW{+?-x%&=3Ax^VvPigG&lpVeSKkSMd}CUzqv347Wj6w&CQvL VZmS)D%r}qqE_xJM!67wZWLd(E5Wt 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 + /// Decompresses ASTC-compressed image data to RGBA pixels. + /// + /// The compressed block data. + /// The width of the texture. + /// The height of the texture. + /// The width of the block footprint. + /// The height of the block footprint. + /// The number of compressed bytes per block. + /// The decompressed RGBA pixel data. + public static byte[] DecompressImage( + byte[] blockData, + int width, + int height, + int blockWidth, + int blockHeight, + byte compressedBytesPerBlock) + { + int blocksWide = (width + blockWidth - 1) / blockWidth; + int blocksHigh = (height + blockHeight - 1) / blockHeight; + byte[] decompressedData = new byte[width * height * 4]; + byte[] decodedBlock = new byte[blockWidth * blockHeight * 4]; + + int blockIndex = 0; + + for (int by = 0; by < blocksHigh; by++) + { + for (int bx = 0; bx < blocksWide; bx++) + { + int blockDataOffset = blockIndex * compressedBytesPerBlock; + if (blockDataOffset + compressedBytesPerBlock <= blockData.Length) + { + DecodeBlock( + blockData.AsSpan(blockDataOffset, compressedBytesPerBlock), + blockWidth, + blockHeight, + decodedBlock); + + for (int py = 0; py < blockHeight && ((by * blockHeight) + py) < height; py++) + { + for (int px = 0; px < blockWidth && ((bx * blockWidth) + px) < width; px++) + { + int srcIndex = ((py * blockWidth) + px) * 4; + int dstX = (bx * blockWidth) + px; + int dstY = (by * blockHeight) + py; + int dstIndex = ((dstY * width) + dstX) * 4; + + decompressedData[dstIndex] = decodedBlock[srcIndex]; + decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; + decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; + decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; + } + } + } + + blockIndex++; + } + } + + return decompressedData; + } + private static FootprintType FootprintFromDimensions(int width, int height) => (width, height) switch { diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs index aa592b2a..6d523a8b 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs index a2b0a4f5..624a011a 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs index 09babf7d..5103a308 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs index a8626268..d7f4e3e7 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs index a0a819c2..1034e9f7 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs index b0539625..b49889b5 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs index 1d626968..30498f55 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs @@ -39,52 +39,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - // Decode the block - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - // Copy decoded pixels to output - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs index 344416d9..00d8ed35 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs index 9f1c76b4..22b1956f 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs index b3c08f8c..5b7f058e 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs index 545f2ae9..da16d0e9 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs index 9ff23bff..3ef70590 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs index 085a593a..48233a7a 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs index 69e79c6a..603b2ffd 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs @@ -35,50 +35,7 @@ public Image GetImage(byte[] blockData, int width, int height) } /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int blocksWide = (width + BlockSize.Width - 1) / BlockSize.Width; - int blocksHigh = (height + BlockSize.Height - 1) / BlockSize.Height; - byte[] decompressedData = new byte[width * height * 4]; - byte[] decodedBlock = new byte[BlockSize.Width * BlockSize.Height * 4]; - - int blockIndex = 0; - - for (int by = 0; by < blocksHigh; by++) - { - for (int bx = 0; bx < blocksWide; bx++) - { - int blockDataOffset = blockIndex * this.CompressedBytesPerBlock; - if (blockDataOffset + this.CompressedBytesPerBlock <= blockData.Length) - { - AstcDecoder.DecodeBlock( - blockData.AsSpan(blockDataOffset, this.CompressedBytesPerBlock), - BlockSize.Width, - BlockSize.Height, - decodedBlock); - - for (int py = 0; py < BlockSize.Height && ((by * BlockSize.Height) + py) < height; py++) - { - for (int px = 0; px < BlockSize.Width && ((bx * BlockSize.Width) + px) < width; px++) - { - int srcIndex = ((py * BlockSize.Width) + px) * 4; - int dstX = (bx * BlockSize.Width) + px; - int dstY = (by * BlockSize.Height) + py; - int dstIndex = ((dstY * width) + dstX) * 4; - - decompressedData[dstIndex] = decodedBlock[srcIndex]; - decompressedData[dstIndex + 1] = decodedBlock[srcIndex + 1]; - decompressedData[dstIndex + 2] = decodedBlock[srcIndex + 2]; - decompressedData[dstIndex + 3] = decodedBlock[srcIndex + 3]; - } - } - } - - blockIndex++; - } - } - - return decompressedData; - } + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } } From d8d9d44b1bee3a1483abc97a0dfcec8697bb25e5 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:37:59 +0100 Subject: [PATCH 07/37] Remove debug code --- .../Formats/Astc/AstcKtx2DecoderTests.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs index 2bb20c58..0157fb83 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs @@ -22,13 +22,6 @@ public void Ktx2Decoder_CanDecode_Astc_4x4_FlightHelmet(TestTextureProvider prov { using Texture texture = provider.GetTexture(Ktx2Decoder); provider.SaveTextures(texture); - - var test = texture as FlatTexture; - - Image firstMipMap = test.MipMaps[0].GetImage(); - Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); - var firstMipMapImage = firstMipMap as Image; - firstMipMapImage.SaveAsPng(@"C:\Users\ErikWhite\Downloads\image.png"); } [Theory] From 8e86c7c4f1722977120bd9221c681343d339a7a7 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:03:01 +0100 Subject: [PATCH 08/37] Add ASTC block size reference tests --- .../Formats/Ktx2/Ktx2AstcDecoderTests.cs | 56 +++++++++++++++++++ tests/ImageSharp.Textures.Tests/TestImages.cs | 15 +++++ .../Ktx2/ldr-rgb32-astc_10x10_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_10x5_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_10x6_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_10x8_unorm.ktx2 | 3 + .../Ktx2/ldr-rgb32-astc_12x10_unorm.ktx2 | 3 + .../Ktx2/ldr-rgb32-astc_12x12_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_4x4_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_5x4_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_5x5_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_6x5_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_6x6_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_8x5_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_8x6_unorm.ktx2 | 3 + .../Input/Ktx2/ldr-rgb32-astc_8x8_unorm.ktx2 | 3 + ...stcDecoder_CanDecode_Rgba32_Blocksizes.png | 3 + 17 files changed, 116 insertions(+) create mode 100644 tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_10x10_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_10x5_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_10x6_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_10x8_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_12x10_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_12x12_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_4x4_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_5x4_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_5x5_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_6x5_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_6x6_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_8x5_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_8x6_unorm.ktx2 create mode 100644 tests/Images/Input/Ktx2/ldr-rgb32-astc_8x8_unorm.ktx2 create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes.png diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs new file mode 100644 index 00000000..7e343bee --- /dev/null +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Textures.Formats.Ktx2; +using SixLabors.ImageSharp.Textures.Tests.Enums; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; +using SixLabors.ImageSharp.Textures.TextureFormats; + +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx; + +[Trait("Format", "Ktx")] +[Trait("Format", "Astc")] +public class Ktx2AstcDecoderTests +{ + private static readonly Ktx2Decoder KtxDecoder = new(); + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_4x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_5x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_5x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_6x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_6x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_8x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_8x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_8x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_10x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_10x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_10x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_10x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_12x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_12x12)] + public void Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + FlatTexture flatTexture = texture as FlatTexture; + + Assert.NotNull(flatTexture?.MipMaps); + Assert.Single(flatTexture.MipMaps); + + Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + Assert.Equal(256, firstMipMap.Width); + Assert.Equal(256, firstMipMap.Height); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + + Image firstMipMapImage = firstMipMap as Image; + + // Note that the comparer is given a high threshold to allow for the lossy compression of ASTC, + // especially at larger block sizes, but the output is still expected to be very similar to the reference image. + firstMipMapImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(2.0f), provider, appendPixelTypeToFileName: false); + } +} diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index f169ba27..904b09ba 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -28,6 +28,21 @@ public static class Astc public const string Ldr_10x5_FlightHelmet = "astc_ldr_10x5_FlightHelmet_baseColor.ktx2"; public const string Ldr_12x10_FlightHelmet = "astc_ldr_12x10_FlightHelmet_baseColor.ktx2"; public const string Ldr_12x12_FlightHelmet = "astc_ldr_12x12_FlightHelmet_baseColor.ktx2"; + public const string Rgb32Unorm_4x4 = "ldr-rgb32-astc_4x4_unorm.ktx2"; + public const string Rgb32Unorm_5x4 = "ldr-rgb32-astc_5x4_unorm.ktx2"; + public const string Rgb32Unorm_5x5 = "ldr-rgb32-astc_5x5_unorm.ktx2"; + public const string Rgb32Unorm_6x5 = "ldr-rgb32-astc_6x5_unorm.ktx2"; + public const string Rgb32Unorm_6x6 = "ldr-rgb32-astc_6x6_unorm.ktx2"; + public const string Rgb32Unorm_8x5 = "ldr-rgb32-astc_8x5_unorm.ktx2"; + public const string Rgb32Unorm_8x6 = "ldr-rgb32-astc_8x6_unorm.ktx2"; + public const string Rgb32Unorm_8x8 = "ldr-rgb32-astc_8x8_unorm.ktx2"; + public const string Rgb32Unorm_10x5 = "ldr-rgb32-astc_10x5_unorm.ktx2"; + public const string Rgb32Unorm_10x6 = "ldr-rgb32-astc_10x6_unorm.ktx2"; + public const string Rgb32Unorm_10x8 = "ldr-rgb32-astc_10x8_unorm.ktx2"; + public const string Rgb32Unorm_10x10 = "ldr-rgb32-astc_10x10_unorm.ktx2"; + public const string Rgb32Unorm_12x10 = "ldr-rgb32-astc_12x10_unorm.ktx2"; + public const string Rgb32Unorm_12x12 = "ldr-rgb32-astc_12x12_unorm.ktx2"; + // Volume textures public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x10_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x10_unorm.ktx2 new file mode 100644 index 00000000..9089c850 --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x10_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3734df509022fada680c6a395c7fb8590e41809f1f6ec846faaec35ccb15b3ac +size 11024 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x5_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x5_unorm.ktx2 new file mode 100644 index 00000000..a6ba6cf2 --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x5_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f9d6eb2708a73a262018312bd3e3b3d9710f5b1d82de69c0bcef327fa81bd1d +size 21840 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x6_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x6_unorm.ktx2 new file mode 100644 index 00000000..4bab9587 --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x6_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5cb096d280f6719324f6f8d81968af2e2c329b45a62ccbb707ee314ad648fb1 +size 18096 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x8_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x8_unorm.ktx2 new file mode 100644 index 00000000..2fb665af --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x8_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6839f598ee7627ffe1e733c0a25deff5382d301db1e9634aa55fd37314e1adf4 +size 13520 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_12x10_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_12x10_unorm.ktx2 new file mode 100644 index 00000000..3ae65865 --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_12x10_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:532cde930a627861de5b56e4ae56c53b20154dd3c2d6531f9260dfe12f98946e +size 9360 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_12x12_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_12x12_unorm.ktx2 new file mode 100644 index 00000000..ca28a47e --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_12x12_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a9bdd7ace29d9d17279d87f28013b7828008112b92cc70b5f4574281597782b +size 7952 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_4x4_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_4x4_unorm.ktx2 new file mode 100644 index 00000000..d337075a --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_4x4_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:adf039abfacb9178ff2c9a7dd8054fb1f11b426c0e99183dc813ca9e6147cb60 +size 65744 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_5x4_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_5x4_unorm.ktx2 new file mode 100644 index 00000000..3ff12ede --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_5x4_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cce8bd95e3d52b2f307bdd89df80ebc75b800310381e549289a077afb1859b16 +size 53456 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_5x5_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_5x5_unorm.ktx2 new file mode 100644 index 00000000..9ba1974b --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_5x5_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1febd58f0e39d009b1b9f01bde1a8dcffcb3e3daf4fb59318e25d0a3afa46e4b +size 43472 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_6x5_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_6x5_unorm.ktx2 new file mode 100644 index 00000000..b71b0732 --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_6x5_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbf3649fc8b4c40bbc3a1425ef68647fdfc3a6a186a74b3df0307074c5a9dd23 +size 35984 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_6x6_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_6x6_unorm.ktx2 new file mode 100644 index 00000000..43c7ef11 --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_6x6_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:350bf2abd9acdfd717c1943deb51df358958382efbbf35830e4e693994fa563b +size 29792 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x5_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x5_unorm.ktx2 new file mode 100644 index 00000000..8a73154d --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x5_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5261f0d5592d557682bf8193edef1fd0cbe2f833989d7d6a382b49ae0274c4c +size 26832 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x6_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x6_unorm.ktx2 new file mode 100644 index 00000000..eefab560 --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x6_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e264bfff2112aacf47de725939c027d8beb873512ce0b813a9292570312078a1 +size 22224 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x8_unorm.ktx2 b/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x8_unorm.ktx2 new file mode 100644 index 00000000..1793a2f3 --- /dev/null +++ b/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x8_unorm.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd37bcf4858bbb2555a940a1894f03bdbc333feba906a2a503ba05733483a98f +size 16592 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes.png new file mode 100644 index 00000000..b72f0c27 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:667534774a3855e5aaaeb318d30955266e11031f1a1f4eb85f0fcb7b40c8c41f +size 128497 From 710f018f0c9a00b43d184c4e35af60833e90f362 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:11:06 +0100 Subject: [PATCH 09/37] Update naming of ktx2 test files --- .../Formats/Ktx2/Ktx2AstcDecoderTests.cs | 32 ++--- tests/ImageSharp.Textures.Tests/TestImages.cs | 120 +++++++++--------- ...10x10_unorm.ktx2 => astc-rgb32-10x10.ktx2} | 0 ...c_10x5_unorm.ktx2 => astc-rgb32-10x5.ktx2} | 0 ...c_10x6_unorm.ktx2 => astc-rgb32-10x6.ktx2} | 0 ...c_10x8_unorm.ktx2 => astc-rgb32-10x8.ktx2} | 0 ...12x10_unorm.ktx2 => astc-rgb32-12x10.ktx2} | 0 ...12x12_unorm.ktx2 => astc-rgb32-12x12.ktx2} | 0 ...stc_4x4_unorm.ktx2 => astc-rgb32-4x4.ktx2} | 0 ...stc_5x4_unorm.ktx2 => astc-rgb32-5x4.ktx2} | 0 ...stc_5x5_unorm.ktx2 => astc-rgb32-5x5.ktx2} | 0 ...stc_6x5_unorm.ktx2 => astc-rgb32-6x5.ktx2} | 0 ...stc_6x6_unorm.ktx2 => astc-rgb32-6x6.ktx2} | 0 ...stc_8x5_unorm.ktx2 => astc-rgb32-8x5.ktx2} | 0 ...stc_8x6_unorm.ktx2 => astc-rgb32-8x6.ktx2} | 0 ...stc_8x8_unorm.ktx2 => astc-rgb32-8x8.ktx2} | 0 16 files changed, 75 insertions(+), 77 deletions(-) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_10x10_unorm.ktx2 => astc-rgb32-10x10.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_10x5_unorm.ktx2 => astc-rgb32-10x5.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_10x6_unorm.ktx2 => astc-rgb32-10x6.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_10x8_unorm.ktx2 => astc-rgb32-10x8.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_12x10_unorm.ktx2 => astc-rgb32-12x10.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_12x12_unorm.ktx2 => astc-rgb32-12x12.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_4x4_unorm.ktx2 => astc-rgb32-4x4.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_5x4_unorm.ktx2 => astc-rgb32-5x4.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_5x5_unorm.ktx2 => astc-rgb32-5x5.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_6x5_unorm.ktx2 => astc-rgb32-6x5.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_6x6_unorm.ktx2 => astc-rgb32-6x6.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_8x5_unorm.ktx2 => astc-rgb32-8x5.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_8x6_unorm.ktx2 => astc-rgb32-8x6.ktx2} (100%) rename tests/Images/Input/Ktx2/{ldr-rgb32-astc_8x8_unorm.ktx2 => astc-rgb32-8x8.ktx2} (100%) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs index 7e343bee..841351f8 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs @@ -10,29 +10,29 @@ using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; using SixLabors.ImageSharp.Textures.TextureFormats; -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx2; -[Trait("Format", "Ktx")] +[Trait("Format", "Ktx2")] [Trait("Format", "Astc")] public class Ktx2AstcDecoderTests { private static readonly Ktx2Decoder KtxDecoder = new(); [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_4x4)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_5x4)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_5x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_6x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_6x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_8x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_8x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_8x8)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_10x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_10x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_10x8)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_10x10)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_12x10)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32Unorm_12x12)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_4x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_5x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_5x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_6x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_6x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_8x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_8x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_8x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_10x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_10x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_10x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_10x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_12x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_12x12)] public void Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider provider) { using Texture texture = provider.GetTexture(KtxDecoder); diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index 904b09ba..c448a226 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -1,75 +1,73 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Tests +namespace SixLabors.ImageSharp.Textures.Tests; + +/// +/// Class that contains all the relative test image paths in the Images/Input/Formats directory. +/// +public static class TestImages { - /// - /// Class that contains all the relative test image paths in the Images/Input/Formats directory. - /// - public static class TestImages + public static class Ktx { - public static class Ktx - { - public const string Rgba = "rgba8888.ktx"; - } + public const string Rgba = "rgba8888.ktx"; + } - public static class Ktx2 + public static class Ktx2 + { + public static class Astc { - public static class Astc - { - // Flat textures with various block sizes - public const string Ldr_4x4_FlightHelmet = "astc_ldr_4x4_FlightHelmet_baseColor.ktx2"; - public const string Ldr_5x4_IronBars = "astc_ldr_5x4_Iron_Bars_001_normal.ktx2"; - public const string Ldr_6x5_FlightHelmet = "astc_ldr_6x5_FlightHelmet_baseColor.ktx2"; - public const string Ldr_6x6_IronBars = "astc_ldr_6x6_Iron_Bars_001_normal.ktx2"; - public const string Ldr_6x6_Posx = "astc_ldr_6x6_posx.ktx2"; - public const string Ldr_8x6_FlightHelmet = "astc_ldr_8x6_FlightHelmet_baseColor.ktx2"; - public const string Ldr_8x8_FlightHelmet = "astc_ldr_8x8_FlightHelmet_baseColor.ktx2"; - public const string Ldr_10x5_FlightHelmet = "astc_ldr_10x5_FlightHelmet_baseColor.ktx2"; - public const string Ldr_12x10_FlightHelmet = "astc_ldr_12x10_FlightHelmet_baseColor.ktx2"; - public const string Ldr_12x12_FlightHelmet = "astc_ldr_12x12_FlightHelmet_baseColor.ktx2"; - public const string Rgb32Unorm_4x4 = "ldr-rgb32-astc_4x4_unorm.ktx2"; - public const string Rgb32Unorm_5x4 = "ldr-rgb32-astc_5x4_unorm.ktx2"; - public const string Rgb32Unorm_5x5 = "ldr-rgb32-astc_5x5_unorm.ktx2"; - public const string Rgb32Unorm_6x5 = "ldr-rgb32-astc_6x5_unorm.ktx2"; - public const string Rgb32Unorm_6x6 = "ldr-rgb32-astc_6x6_unorm.ktx2"; - public const string Rgb32Unorm_8x5 = "ldr-rgb32-astc_8x5_unorm.ktx2"; - public const string Rgb32Unorm_8x6 = "ldr-rgb32-astc_8x6_unorm.ktx2"; - public const string Rgb32Unorm_8x8 = "ldr-rgb32-astc_8x8_unorm.ktx2"; - public const string Rgb32Unorm_10x5 = "ldr-rgb32-astc_10x5_unorm.ktx2"; - public const string Rgb32Unorm_10x6 = "ldr-rgb32-astc_10x6_unorm.ktx2"; - public const string Rgb32Unorm_10x8 = "ldr-rgb32-astc_10x8_unorm.ktx2"; - public const string Rgb32Unorm_10x10 = "ldr-rgb32-astc_10x10_unorm.ktx2"; - public const string Rgb32Unorm_12x10 = "ldr-rgb32-astc_12x10_unorm.ktx2"; - public const string Rgb32Unorm_12x12 = "ldr-rgb32-astc_12x12_unorm.ktx2"; - + // Flat textures with various block sizes + public const string Ldr_4x4_FlightHelmet = "astc_ldr_4x4_FlightHelmet_baseColor.ktx2"; + public const string Ldr_5x4_IronBars = "astc_ldr_5x4_Iron_Bars_001_normal.ktx2"; + public const string Ldr_6x5_FlightHelmet = "astc_ldr_6x5_FlightHelmet_baseColor.ktx2"; + public const string Ldr_6x6_IronBars = "astc_ldr_6x6_Iron_Bars_001_normal.ktx2"; + public const string Ldr_6x6_Posx = "astc_ldr_6x6_posx.ktx2"; + public const string Ldr_8x6_FlightHelmet = "astc_ldr_8x6_FlightHelmet_baseColor.ktx2"; + public const string Ldr_8x8_FlightHelmet = "astc_ldr_8x8_FlightHelmet_baseColor.ktx2"; + public const string Ldr_10x5_FlightHelmet = "astc_ldr_10x5_FlightHelmet_baseColor.ktx2"; + public const string Ldr_12x10_FlightHelmet = "astc_ldr_12x10_FlightHelmet_baseColor.ktx2"; + public const string Ldr_12x12_FlightHelmet = "astc_ldr_12x12_FlightHelmet_baseColor.ktx2"; + public const string Rgb32_4x4 = "astc-rgb32-4x4.ktx2"; + public const string Rgb32_5x4 = "astc-rgb32-5x4.ktx2"; + public const string Rgb32_5x5 = "astc-rgb32-5x5.ktx2"; + public const string Rgb32_6x5 = "astc-rgb32-6x5.ktx2"; + public const string Rgb32_6x6 = "astc-rgb32-6x6.ktx2"; + public const string Rgb32_8x5 = "astc-rgb32-8x5.ktx2"; + public const string Rgb32_8x6 = "astc-rgb32-8x6.ktx2"; + public const string Rgb32_8x8 = "astc-rgb32-8x8.ktx2"; + public const string Rgb32_10x5 = "astc-rgb32-10x5.ktx2"; + public const string Rgb32_10x6 = "astc-rgb32-10x6.ktx2"; + public const string Rgb32_10x8 = "astc-rgb32-10x8.ktx2"; + public const string Rgb32_10x10 = "astc-rgb32-10x10.ktx2"; + public const string Rgb32_12x10 = "astc-rgb32-12x10.ktx2"; + public const string Rgb32_12x12 = "astc-rgb32-12x12.ktx2"; - // Volume textures - public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; + // Volume textures + public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; - // Array textures - public const string Ldr_6x6_ArrayTex = "astc_ldr_6x6_arraytex_7.ktx2"; - public const string Ldr_6x6_ArrayTex_Mipmap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; + // Array textures + public const string Ldr_6x6_ArrayTex = "astc_ldr_6x6_arraytex_7.ktx2"; + public const string Ldr_6x6_ArrayTex_Mipmap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; - // Cubemap textures - public const string Ldr_Cubemap_6x6 = "astc_ldr_cubemap_6x6.ktx2"; + // Cubemap textures + public const string Ldr_Cubemap_6x6 = "astc_ldr_cubemap_6x6.ktx2"; - // Mipmap tests with different quality settings - public const string Mipmap_Ldr_4x4_Posx = "astc_mipmap_ldr_4x4_posx.ktx2"; - public const string Mipmap_Ldr_6x5_Posx = "astc_mipmap_ldr_6x5_posx.ktx2"; - public const string Mipmap_Ldr_6x6_Fast = "astc_mipmap_ldr_6x6_kodim17_fast.ktx2"; - public const string Mipmap_Ldr_6x6_Medium = "astc_mipmap_ldr_6x6_kodim17_medium.ktx2"; - public const string Mipmap_Ldr_6x6_Fastest = "astc_mipmap_ldr_6x6_kodim17_fastest.ktx2"; - public const string Mipmap_Ldr_6x6_Posx = "astc_mipmap_ldr_6x6_posx.ktx2"; - public const string Mipmap_Ldr_6x6_Posy = "astc_mipmap_ldr_6x6_posy.ktx2"; - public const string Mipmap_Ldr_6x6_Posz = "astc_mipmap_ldr_6x6_posz.ktx2"; - public const string Mipmap_Ldr_8x6_Posx = "astc_mipmap_ldr_8x6_posx.ktx2"; - public const string Mipmap_Ldr_8x8_Posx = "astc_mipmap_ldr_8x8_posx.ktx2"; - public const string Mipmap_Ldr_10x5_Posx = "astc_mipmap_ldr_10x5_posx.ktx2"; - public const string Mipmap_Ldr_12x10_Posx = "astc_mipmap_ldr_12x10_posx.ktx2"; - public const string Mipmap_Ldr_12x12_Posx = "astc_mipmap_ldr_12x12_posx.ktx2"; - public const string Mipmap_Ldr_Cubemap_6x6 = "astc_mipmap_ldr_cubemap_6x6.ktx2"; - } + // Mipmap tests with different quality settings + public const string Mipmap_Ldr_4x4_Posx = "astc_mipmap_ldr_4x4_posx.ktx2"; + public const string Mipmap_Ldr_6x5_Posx = "astc_mipmap_ldr_6x5_posx.ktx2"; + public const string Mipmap_Ldr_6x6_Fast = "astc_mipmap_ldr_6x6_kodim17_fast.ktx2"; + public const string Mipmap_Ldr_6x6_Medium = "astc_mipmap_ldr_6x6_kodim17_medium.ktx2"; + public const string Mipmap_Ldr_6x6_Fastest = "astc_mipmap_ldr_6x6_kodim17_fastest.ktx2"; + public const string Mipmap_Ldr_6x6_Posx = "astc_mipmap_ldr_6x6_posx.ktx2"; + public const string Mipmap_Ldr_6x6_Posy = "astc_mipmap_ldr_6x6_posy.ktx2"; + public const string Mipmap_Ldr_6x6_Posz = "astc_mipmap_ldr_6x6_posz.ktx2"; + public const string Mipmap_Ldr_8x6_Posx = "astc_mipmap_ldr_8x6_posx.ktx2"; + public const string Mipmap_Ldr_8x8_Posx = "astc_mipmap_ldr_8x8_posx.ktx2"; + public const string Mipmap_Ldr_10x5_Posx = "astc_mipmap_ldr_10x5_posx.ktx2"; + public const string Mipmap_Ldr_12x10_Posx = "astc_mipmap_ldr_12x10_posx.ktx2"; + public const string Mipmap_Ldr_12x12_Posx = "astc_mipmap_ldr_12x12_posx.ktx2"; + public const string Mipmap_Ldr_Cubemap_6x6 = "astc_mipmap_ldr_cubemap_6x6.ktx2"; } } } diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x10_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-10x10.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_10x10_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-10x10.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x5_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-10x5.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_10x5_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-10x5.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x6_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-10x6.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_10x6_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-10x6.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_10x8_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-10x8.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_10x8_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-10x8.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_12x10_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-12x10.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_12x10_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-12x10.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_12x12_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-12x12.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_12x12_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-12x12.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_4x4_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-4x4.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_4x4_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-4x4.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_5x4_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-5x4.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_5x4_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-5x4.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_5x5_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-5x5.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_5x5_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-5x5.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_6x5_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-6x5.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_6x5_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-6x5.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_6x6_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-6x6.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_6x6_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-6x6.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x5_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-8x5.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_8x5_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-8x5.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x6_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-8x6.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_8x6_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-8x6.ktx2 diff --git a/tests/Images/Input/Ktx2/ldr-rgb32-astc_8x8_unorm.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-8x8.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/ldr-rgb32-astc_8x8_unorm.ktx2 rename to tests/Images/Input/Ktx2/astc-rgb32-8x8.ktx2 From 3d9fd537ceb63535e0d07af819054d21d7ef8665 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:45:27 +0100 Subject: [PATCH 10/37] Add ASTC 8x8 test for KTX1 --- .../Formats/Ktx/KtxAstcDecoderTests.cs | 43 +++++++++++++++++++ tests/ImageSharp.Textures.Tests/TestImages.cs | 5 +++ tests/Images/Input/Ktx/astc-rgba32-8x8.ktx | 3 ++ ...stcDecoder_CanDecode_Rgba32_Blocksizes.png | 3 ++ 4 files changed, 54 insertions(+) create mode 100644 tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxAstcDecoderTests.cs create mode 100644 tests/Images/Input/Ktx/astc-rgba32-8x8.ktx create mode 100644 tests/Images/ReferenceOutput/KtxAstcDecoder_CanDecode_Rgba32_Blocksizes.png diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxAstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxAstcDecoderTests.cs new file mode 100644 index 00000000..2934ac76 --- /dev/null +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxAstcDecoderTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Textures.Formats.Ktx; +using SixLabors.ImageSharp.Textures.Tests.Enums; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; +using SixLabors.ImageSharp.Textures.TextureFormats; + +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx; + +[Trait("Format", "Ktx")] +[Trait("Format", "Astc")] +public class KtxAstcDecoderTests +{ + private static readonly KtxDecoder KtxDecoder = new(); + + [Theory] + [WithFile(TestTextureFormat.Ktx, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx.Astc.Rgb32_8x8)] + public void KtxAstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + FlatTexture flatTexture = texture as FlatTexture; + + Assert.NotNull(flatTexture?.MipMaps); + Assert.Single(flatTexture.MipMaps); + + Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + Assert.Equal(256, firstMipMap.Width); + Assert.Equal(256, firstMipMap.Height); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + + Image firstMipMapImage = firstMipMap as Image; + + // Note that the comparer is given a high threshold to allow for the lossy compression of ASTC, + // especially at larger block sizes, but the output is still expected to be very similar to the reference image. + firstMipMapImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(2.0f), provider, appendPixelTypeToFileName: false); + } +} diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index c448a226..a2ebcfa1 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -11,6 +11,11 @@ public static class TestImages public static class Ktx { public const string Rgba = "rgba8888.ktx"; + + public static class Astc + { + public const string Rgb32_8x8 = "astc-rgba32-8x8.ktx"; + } } public static class Ktx2 diff --git a/tests/Images/Input/Ktx/astc-rgba32-8x8.ktx b/tests/Images/Input/Ktx/astc-rgba32-8x8.ktx new file mode 100644 index 00000000..0e2893a4 --- /dev/null +++ b/tests/Images/Input/Ktx/astc-rgba32-8x8.ktx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:046f6f04070340bfaa465129a49e4ea082923a5de83fc9d819d6abea4bf779cf +size 114788 diff --git a/tests/Images/ReferenceOutput/KtxAstcDecoder_CanDecode_Rgba32_Blocksizes.png b/tests/Images/ReferenceOutput/KtxAstcDecoder_CanDecode_Rgba32_Blocksizes.png new file mode 100644 index 00000000..6a18547c --- /dev/null +++ b/tests/Images/ReferenceOutput/KtxAstcDecoder_CanDecode_Rgba32_Blocksizes.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebd9984f80e75676d74bdb22d32c923a6c248df13b8817dcecb8e0627707cc2e +size 82338 From 37e067f0752844e00be39672acb51d4607206750 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:12:53 +0100 Subject: [PATCH 11/37] Add unorm and srgb test cases --- .../Formats/Ktx2/Ktx2AstcDecoderTests.cs | 79 ++++++++++++++++++- tests/ImageSharp.Textures.Tests/TestImages.cs | 30 +++++++ .../Ktx2/valid_ASTC_10x10_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_10x10_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_10x5_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_10x5_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_10x6_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_10x6_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_10x8_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_10x8_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_12x10_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_12x12_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_4x4_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_5x4_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_5x4_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_5x5_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_5x5_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_6x5_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_6x5_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_6x6_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_6x6_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_8x5_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_8x5_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_8x6_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_8x6_UNORM_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_8x8_SRGB_BLOCK_2D.ktx2 | 3 + .../Ktx2/valid_ASTC_8x8_UNORM_BLOCK_2D.ktx2 | 3 + ...stcDecoder_CanDecode_Rgba32_Srgb_10x10.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Srgb_10x5.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Srgb_10x6.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Srgb_10x8.png | 3 + ...stcDecoder_CanDecode_Rgba32_Srgb_12x10.png | 3 + ...stcDecoder_CanDecode_Rgba32_Srgb_12x12.png | 3 + ...2AstcDecoder_CanDecode_Rgba32_Srgb_4x4.png | 3 + ...2AstcDecoder_CanDecode_Rgba32_Srgb_5x4.png | 3 + ...2AstcDecoder_CanDecode_Rgba32_Srgb_5x5.png | 3 + ...2AstcDecoder_CanDecode_Rgba32_Srgb_6x5.png | 3 + ...2AstcDecoder_CanDecode_Rgba32_Srgb_6x6.png | 3 + ...2AstcDecoder_CanDecode_Rgba32_Srgb_8x5.png | 3 + ...2AstcDecoder_CanDecode_Rgba32_Srgb_8x6.png | 3 + ...2AstcDecoder_CanDecode_Rgba32_Srgb_8x8.png | 3 + ...tcDecoder_CanDecode_Rgba32_Unorm_10x10.png | 3 + ...stcDecoder_CanDecode_Rgba32_Unorm_10x5.png | 3 + ...stcDecoder_CanDecode_Rgba32_Unorm_10x6.png | 3 + ...stcDecoder_CanDecode_Rgba32_Unorm_10x8.png | 3 + ...tcDecoder_CanDecode_Rgba32_Unorm_12x10.png | 3 + ...tcDecoder_CanDecode_Rgba32_Unorm_12x12.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Unorm_4x4.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Unorm_5x4.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Unorm_5x5.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Unorm_6x5.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Unorm_6x6.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Unorm_8x5.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Unorm_8x6.png | 3 + ...AstcDecoder_CanDecode_Rgba32_Unorm_8x8.png | 3 + 58 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_10x10_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_10x10_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_10x5_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_10x5_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_10x6_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_10x6_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_10x8_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_10x8_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_12x10_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_12x12_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_4x4_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_5x4_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_5x4_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_5x5_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_5x5_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_6x5_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_6x5_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_6x6_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_6x6_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_8x5_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_8x5_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_8x6_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_8x6_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_8x8_SRGB_BLOCK_2D.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_8x8_UNORM_BLOCK_2D.ktx2 create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x10.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x5.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x6.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x8.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_12x10.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_12x12.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_4x4.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_5x4.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_5x5.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_6x5.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_6x6.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x5.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x6.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x8.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x10.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x5.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x6.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x8.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_12x10.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_12x12.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_4x4.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_5x4.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_5x5.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_6x5.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_6x6.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x5.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x6.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x8.png diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs index 841351f8..4cf3d347 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Text.RegularExpressions; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Formats.Ktx2; using SixLabors.ImageSharp.Textures.Tests.Enums; @@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx2; [Trait("Format", "Ktx2")] [Trait("Format", "Astc")] -public class Ktx2AstcDecoderTests +public partial class Ktx2AstcDecoderTests { private static readonly Ktx2Decoder KtxDecoder = new(); @@ -53,4 +54,80 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider prov // especially at larger block sizes, but the output is still expected to be very similar to the reference image. firstMipMapImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(2.0f), provider, appendPixelTypeToFileName: false); } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_4x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_5x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_5x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_6x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_6x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_8x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_8x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_8x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_10x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_10x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_10x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_10x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_12x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_12x12)] + public void Ktx2AstcDecoder_CanDecode_Rgba32_Unorm(TestTextureProvider provider) + { + string blockSize = GetBlockSizeFromFileName(provider.InputFile); + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + FlatTexture flatTexture = texture as FlatTexture; + + Assert.NotNull(flatTexture?.MipMaps); + Assert.Single(flatTexture.MipMaps); + + Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + Assert.Equal(16, firstMipMap.Width); + Assert.Equal(16, firstMipMap.Height); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + + (firstMipMap as Image).CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.05f), provider, testOutputDetails: $"{blockSize}"); + } + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_4x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_5x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_5x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_6x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_6x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_8x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_8x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_8x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_10x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_10x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_10x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_10x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_12x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_sRgb_12x12)] + public void Ktx2AstcDecoder_CanDecode_Rgba32_Srgb(TestTextureProvider provider) + { + string blockSize = GetBlockSizeFromFileName(provider.InputFile); + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + FlatTexture flatTexture = texture as FlatTexture; + + Assert.NotNull(flatTexture?.MipMaps); + Assert.Single(flatTexture.MipMaps); + + Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + Assert.Equal(16, firstMipMap.Width); + Assert.Equal(16, firstMipMap.Height); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + + (firstMipMap as Image).CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.05f), provider, testOutputDetails: $"{blockSize}"); + } + + private static string GetBlockSizeFromFileName(string fileName) + { + Match match = GetBlockSizeFromFileName().Match(fileName); + + return match.Success ? match.Value : string.Empty; + } + + [GeneratedRegex(@"(\d+x\d+)")] + private static partial Regex GetBlockSizeFromFileName(); } diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index a2ebcfa1..46c0fccf 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -48,6 +48,36 @@ public static class Astc public const string Rgb32_12x10 = "astc-rgb32-12x10.ktx2"; public const string Rgb32_12x12 = "astc-rgb32-12x12.ktx2"; + public const string Rgb32_sRgb_4x4 = "valid_ASTC_4x4_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_5x4 = "valid_ASTC_5x4_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_5x5 = "valid_ASTC_5x5_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_6x5 = "valid_ASTC_6x5_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_6x6 = "valid_ASTC_6x6_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_8x5 = "valid_ASTC_8x5_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_8x6 = "valid_ASTC_8x6_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_8x8 = "valid_ASTC_8x8_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_10x5 = "valid_ASTC_10x5_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_10x6 = "valid_ASTC_10x6_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_10x8 = "valid_ASTC_10x8_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_10x10 = "valid_ASTC_10x10_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_12x10 = "valid_ASTC_12x10_SRGB_BLOCK_2D.ktx2"; + public const string Rgb32_sRgb_12x12 = "valid_ASTC_12x12_SRGB_BLOCK_2D.ktx2"; + + public const string Rgb32_UNORM_4x4 = "valid_ASTC_4x4_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_5x4 = "valid_ASTC_5x4_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_5x5 = "valid_ASTC_5x5_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_6x5 = "valid_ASTC_6x5_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_6x6 = "valid_ASTC_6x6_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_8x5 = "valid_ASTC_8x5_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_8x6 = "valid_ASTC_8x6_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_8x8 = "valid_ASTC_8x8_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_10x5 = "valid_ASTC_10x5_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_10x6 = "valid_ASTC_10x6_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_10x8 = "valid_ASTC_10x8_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_10x10 = "valid_ASTC_10x10_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_12x10 = "valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_UNORM_12x12 = "valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2"; + // Volume textures public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; diff --git a/tests/Images/Input/Ktx2/valid_ASTC_10x10_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_10x10_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..44fa4a48 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_10x10_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:179183db8cb2f1d66bf064b5555790906c0e83e6034db53a322b383acfafcd33 +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_10x10_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_10x10_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..b5bb9290 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_10x10_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:533bab3b7990c5f283595f35e7340ef77975372661c8541b369f0bc51754ecc3 +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_10x5_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_10x5_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..b8f3744e --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_10x5_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8e9ac64c222117a5ee9dc65c0b5488e0bca98d16ad714d2c91f55f93e3a31be +size 352 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_10x5_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_10x5_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..40e13fb5 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_10x5_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:516a7a67c584836c2f01bce00bea27fef5f5a3d2d0c7afe93dafeb5c169fc355 +size 352 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_10x6_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_10x6_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..65285e5c --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_10x6_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0314740d4e2d869d4935aa5eb16f0e6c195fb01ab4ef17c34069946a04850af1 +size 320 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_10x6_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_10x6_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..74adaf23 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_10x6_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f87cdb572e3dfd17b66f1beb7821b56b1e7838effc4dbdc8c20c450d58b1b8c6 +size 320 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_10x8_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_10x8_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..119a0f0b --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_10x8_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe27c258349ba760e2bcf20eb1fee6a1b332be8d4e5364c2eadbf6381d6a640f +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_10x8_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_10x8_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..95829e40 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_10x8_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42b7708149d39b9eecc76b60c10b6d79ae9b2e5904cbe77799d299b201123df2 +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_12x10_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_12x10_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..e265b30a --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_12x10_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b9638a8adac865a9955b9c13af3f80dd30f77711c1b8931c5b9bef95ffab4a8 +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..7a88ecdd --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bb0bb30011574a796786d1d93ecbccd1cc91d585f4a685ecc6627dea61d98cc +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_12x12_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_12x12_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..46e0c53f --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_12x12_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad07ce4c69e459141ef864f1d8eaf86f851ddd2b283073890c6ffb6b501dfee6 +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..372866cf --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52c52e9581bf36e9dc0e1c178e21e2c4c25eb520a3a4ded3b7ff534abda83667 +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_4x4_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_4x4_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..77635ccf --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_4x4_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4a8bb46326439994e152e620700f12b0d090d7069702ceb2f15320693aea9f9 +size 480 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..a268409f --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89871b4df0314bd68b3fb30b8fa7de9702029bed255bc0f22d09864ff8242362 +size 480 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_5x4_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_5x4_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..46869ef7 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_5x4_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79ffea95e9cd8c4d7524bfe9b6862c1795c724b19392208fb7b4ecd33a667cdf +size 480 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_5x4_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_5x4_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..26e917cf --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_5x4_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffcb506ca972e68c470af50aaf26e9c687c6a63cd68a1ede00dcfe5ecb786e27 +size 480 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_5x5_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_5x5_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..245b0336 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_5x5_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d68cb3cf797346691d7de4313870b58f04b290f562e7dc797527f8594cd53f08 +size 480 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_5x5_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_5x5_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..7a3be45b --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_5x5_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a43bf736c6e7a88453284ad3820e545859582cc50a969940549bdd59a113395e +size 480 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_6x5_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_6x5_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..8465bf20 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_6x5_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95c42b019169a33646886a28246d54a5435a75350ca514f4fe1eb4be9522d48b +size 416 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_6x5_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_6x5_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..cc236894 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_6x5_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:321c7133a313b87da891b56571ed26e96f09a993daead2ef16d65fa660b9c9a3 +size 416 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_6x6_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_6x6_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..b34b1fa4 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_6x6_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38fc0dbc5920b4c90c6982b8b4459447dd86a76ef496349a33230fab9d57c8d8 +size 368 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_6x6_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_6x6_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..248b9a43 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_6x6_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:536e580ad30969214a7c8510fe89a61e9c632be535225dd61f31e606822b1086 +size 368 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_8x5_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_8x5_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..c7f321af --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_8x5_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eab82cb6499bff8c402361880603ec4db484e5fe7bf6f3d6d15d9b78c9da5ebc +size 352 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_8x5_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_8x5_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..b0ec3d69 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_8x5_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1c0aaf84c461da681571ce1335146acc0e88d6669853a29577b10dd8476a6a9 +size 352 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_8x6_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_8x6_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..7e612cae --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_8x6_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4715a22d329b107b8e84224489f9743b42cbaf740d1213df9a35112b8008291 +size 320 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_8x6_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_8x6_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..3e08c23a --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_8x6_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:425fef4e127b8b505482cffbcbdc3279c87d2860062377b6fdb9219301eb51f2 +size 320 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_8x8_SRGB_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_8x8_SRGB_BLOCK_2D.ktx2 new file mode 100644 index 00000000..ad606fdd --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_8x8_SRGB_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef826457d9c0c6797aabad6530398feccead690213a556fe0960769b6aaf98a8 +size 288 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_8x8_UNORM_BLOCK_2D.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_8x8_UNORM_BLOCK_2D.ktx2 new file mode 100644 index 00000000..60da1caf --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_8x8_UNORM_BLOCK_2D.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee6be1f8eebb06f6d312d20848d3bed7f7ffb70b12163a4b822826f523a906a9 +size 288 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x10.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x10.png new file mode 100644 index 00000000..b7859c46 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b77dd1564ec9452df5e4b8c71be4b65809ee513b84e83a385b27d1c53b6d2f07 +size 475 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x5.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x5.png new file mode 100644 index 00000000..637f711f --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40f0e6160fda4625f56a9c2a4da0154781aa61b5aeb03c426c92cfe565b3dab8 +size 396 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x6.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x6.png new file mode 100644 index 00000000..052f1ac6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2baccdb9e775a730fa1ad38beedb2f3674602c3c583f6fdf6d35baf2d76d2da1 +size 438 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x8.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x8.png new file mode 100644 index 00000000..593a0365 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_10x8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed3bd9fcd8b1bf5e9214204deee1adbd30af5649f3549fcdf3248e99b226b9b2 +size 384 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_12x10.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_12x10.png new file mode 100644 index 00000000..c5d7b0bb --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_12x10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4de083f86ecedca4298d8db172000fe56edf7993deeebad718b20679b93fa11 +size 443 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_12x12.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_12x12.png new file mode 100644 index 00000000..3d2787f9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_12x12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b80ef91c2e480f370b49a110e36385c437ab83f2ae2bc84034240d896a392a8b +size 457 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_4x4.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_4x4.png new file mode 100644 index 00000000..a123f4f4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_4x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9cff4006b0483486df0d4d32bd55328b615987dfe65c76a2676bea86424a234 +size 384 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_5x4.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_5x4.png new file mode 100644 index 00000000..a12a7a69 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_5x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6179453f57d3711594c64f4e4ee913d460e81c537b85b1ed240d36b168d4f3ce +size 460 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_5x5.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_5x5.png new file mode 100644 index 00000000..29cd2256 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_5x5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd375c9b74a64145d3093a4020efaae0c1ab443fbb885d103aeb7a3d57970c9c +size 589 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_6x5.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_6x5.png new file mode 100644 index 00000000..34e8d5cb --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_6x5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1d235bf6d5a4685ddd3fa31fc912994c7acb098e5445174b8c5516c06fffc92 +size 544 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_6x6.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_6x6.png new file mode 100644 index 00000000..f0368e89 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_6x6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5583508ef1dbb46d35e002ba0841c3c45523df512d4183a4a811dbc59d1fde05 +size 504 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x5.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x5.png new file mode 100644 index 00000000..c6c09b56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71e3d9dc1307a2afac70b4eaf1b3c4f2be8e013d79533984613ca12a0e60a7b8 +size 435 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x6.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x6.png new file mode 100644 index 00000000..a52503c9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bceeb7a936cd629ce909d2b7a99ab4d1686b7baeab41179e12bd3354fcb6f1c7 +size 520 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x8.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x8.png new file mode 100644 index 00000000..98c95207 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_8x8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56d817a096705e39cb76f5505ddf34c1d012d16bfc40fc43939317bb0af632c8 +size 385 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x10.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x10.png new file mode 100644 index 00000000..d0f0d932 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8378b4b6a70c4414f9f549b009e01ddc934c28623e985e0818b357a68a96b5c +size 373 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x5.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x5.png new file mode 100644 index 00000000..c9943a80 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57140275641276969e3e6d16f4d9ef36975026267d81072fc270ef1fffd75ea9 +size 388 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x6.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x6.png new file mode 100644 index 00000000..a8421169 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6beb53de172583ed13a37499f7c7df85283911c572bea8149eeac1ee7ebdeee5 +size 350 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x8.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x8.png new file mode 100644 index 00000000..e0645247 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_10x8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:271e3e222374be3e48597759d72566a908afdb422c77175b7c2149c5836ce0b0 +size 352 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_12x10.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_12x10.png new file mode 100644 index 00000000..da1775e8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_12x10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70c48f806cb55b20ccc44e57714506453c34789b20bde10feea51b2c56d742f1 +size 485 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_12x12.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_12x12.png new file mode 100644 index 00000000..67d37ce3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_12x12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6dd4e0fa30523f6b4ba6d0a8142d09440b71e6a42e6c3bc272539444175c316e +size 558 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_4x4.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_4x4.png new file mode 100644 index 00000000..6bb0f52c --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_4x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a908971e007eb33f0990f0fa63c0ba6a8faaa8034bd0ed161b2aa092a59b78a +size 381 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_5x4.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_5x4.png new file mode 100644 index 00000000..2f548c43 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_5x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcdeaf19046832ea27917c042f1b801820980ddaa86fdf58ab713f130dccd0d7 +size 421 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_5x5.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_5x5.png new file mode 100644 index 00000000..590b3da9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_5x5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be4c2d4c8658ac6cb7897bafd184d17bcc80007b90267703b2c36a4a625b34a1 +size 562 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_6x5.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_6x5.png new file mode 100644 index 00000000..eb7083aa --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_6x5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14f87a9677902b09218e7ac844e392304fd14b90ac9bb6a41015477bfdadf4b1 +size 481 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_6x6.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_6x6.png new file mode 100644 index 00000000..b40edcdc --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_6x6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b895db282773205885753c650a89eaea773cce21d47c8558d4c33f5f64ab0ab3 +size 465 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x5.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x5.png new file mode 100644 index 00000000..404ca851 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:601b2f20a5d891c947f40940def171bbc36741cbd2708dadd335130376f0b227 +size 389 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x6.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x6.png new file mode 100644 index 00000000..1ff3182c --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8074f9f74efc1f1775e14ff9514762ec9764b2ed16ee45cf6dd5330aa1da9aeb +size 385 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x8.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x8.png new file mode 100644 index 00000000..cb6f1d82 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Unorm_8x8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bbae80913ee6f5c24f04da0612364bda8e092f5349fe2acf494c9ac9bed005e +size 352 From 0b1639a65fa941621c74b6ecc413e626c83fd516 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:45:35 +0100 Subject: [PATCH 12/37] UNORM -> Unorm --- .../Formats/Ktx2/Ktx2AstcDecoderTests.cs | 28 +++++++++---------- tests/ImageSharp.Textures.Tests/TestImages.cs | 28 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs index 4cf3d347..3e6e419d 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs @@ -56,20 +56,20 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider prov } [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_4x4)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_5x4)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_5x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_6x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_6x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_8x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_8x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_8x8)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_10x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_10x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_10x8)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_10x10)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_12x10)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_UNORM_12x12)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_4x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_5x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_5x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_6x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_6x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_8x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_8x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_8x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_10x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_10x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_10x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_10x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_12x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_12x12)] public void Ktx2AstcDecoder_CanDecode_Rgba32_Unorm(TestTextureProvider provider) { string blockSize = GetBlockSizeFromFileName(provider.InputFile); diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index 46c0fccf..056dce9f 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -63,20 +63,20 @@ public static class Astc public const string Rgb32_sRgb_12x10 = "valid_ASTC_12x10_SRGB_BLOCK_2D.ktx2"; public const string Rgb32_sRgb_12x12 = "valid_ASTC_12x12_SRGB_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_4x4 = "valid_ASTC_4x4_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_5x4 = "valid_ASTC_5x4_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_5x5 = "valid_ASTC_5x5_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_6x5 = "valid_ASTC_6x5_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_6x6 = "valid_ASTC_6x6_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_8x5 = "valid_ASTC_8x5_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_8x6 = "valid_ASTC_8x6_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_8x8 = "valid_ASTC_8x8_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_10x5 = "valid_ASTC_10x5_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_10x6 = "valid_ASTC_10x6_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_10x8 = "valid_ASTC_10x8_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_10x10 = "valid_ASTC_10x10_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_12x10 = "valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2"; - public const string Rgb32_UNORM_12x12 = "valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_4x4 = "valid_ASTC_4x4_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_5x4 = "valid_ASTC_5x4_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_5x5 = "valid_ASTC_5x5_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_6x5 = "valid_ASTC_6x5_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_6x6 = "valid_ASTC_6x6_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_8x5 = "valid_ASTC_8x5_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_8x6 = "valid_ASTC_8x6_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_8x8 = "valid_ASTC_8x8_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_10x5 = "valid_ASTC_10x5_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_10x6 = "valid_ASTC_10x6_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_10x8 = "valid_ASTC_10x8_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_10x10 = "valid_ASTC_10x10_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_12x10 = "valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Unorm_12x12 = "valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2"; // Volume textures public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; From f9c119cde7241ef7e48cf32c3e9e62f81d8b2dcc Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:28:19 +0100 Subject: [PATCH 13/37] Update block size test data --- .../Formats/Ktx2/Ktx2AstcDecoderTests.cs | 30 +++++++++---------- tests/ImageSharp.Textures.Tests/TestImages.cs | 28 ++++++++--------- tests/Images/Input/Ktx2/astc-rgb32-10x10.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-10x5.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-10x6.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-10x8.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-12x10.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-12x12.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-4x4.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-5x4.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-5x5.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-6x5.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-6x6.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-8x5.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-8x6.ktx2 | 3 -- tests/Images/Input/Ktx2/astc-rgb32-8x8.ktx2 | 3 -- .../Images/Input/Ktx2/astc_rgba32_10x10.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_10x5.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_10x6.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_10x8.ktx2 | 3 ++ .../Images/Input/Ktx2/astc_rgba32_12x10.ktx2 | 3 ++ .../Images/Input/Ktx2/astc_rgba32_12x12.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_4x4.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_5x4.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_5x5.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_6x5.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_6x6.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_8x5.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_8x6.ktx2 | 3 ++ tests/Images/Input/Ktx2/astc_rgba32_8x8.ktx2 | 3 ++ ...stcDecoder_CanDecode_Rgba32_Blocksizes.png | 4 +-- 31 files changed, 73 insertions(+), 73 deletions(-) delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-10x10.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-10x5.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-10x6.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-10x8.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-12x10.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-12x12.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-4x4.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-5x4.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-5x5.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-6x5.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-6x6.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-8x5.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-8x6.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc-rgb32-8x8.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_10x10.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_10x5.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_10x6.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_10x8.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_12x10.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_12x12.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_4x4.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_5x4.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_5x5.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_6x5.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_6x6.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_8x5.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_8x6.ktx2 create mode 100644 tests/Images/Input/Ktx2/astc_rgba32_8x8.ktx2 diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs index 3e6e419d..9f3a2e57 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs @@ -20,20 +20,20 @@ public partial class Ktx2AstcDecoderTests private static readonly Ktx2Decoder KtxDecoder = new(); [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_4x4)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_5x4)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_5x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_6x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_6x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_8x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_8x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_8x8)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_10x5)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_10x6)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_10x8)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_10x10)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_12x10)] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_12x12)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_4x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_5x4)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_5x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_6x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_6x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_8x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_8x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_8x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_10x5)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_10x6)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_10x8)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_10x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_12x10)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgba32_12x12)] public void Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider provider) { using Texture texture = provider.GetTexture(KtxDecoder); @@ -52,7 +52,7 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider prov // Note that the comparer is given a high threshold to allow for the lossy compression of ASTC, // especially at larger block sizes, but the output is still expected to be very similar to the reference image. - firstMipMapImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(2.0f), provider, appendPixelTypeToFileName: false); + firstMipMapImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(4.0f), provider, appendPixelTypeToFileName: false); } [Theory] diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index 056dce9f..e3584be4 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -33,20 +33,20 @@ public static class Astc public const string Ldr_10x5_FlightHelmet = "astc_ldr_10x5_FlightHelmet_baseColor.ktx2"; public const string Ldr_12x10_FlightHelmet = "astc_ldr_12x10_FlightHelmet_baseColor.ktx2"; public const string Ldr_12x12_FlightHelmet = "astc_ldr_12x12_FlightHelmet_baseColor.ktx2"; - public const string Rgb32_4x4 = "astc-rgb32-4x4.ktx2"; - public const string Rgb32_5x4 = "astc-rgb32-5x4.ktx2"; - public const string Rgb32_5x5 = "astc-rgb32-5x5.ktx2"; - public const string Rgb32_6x5 = "astc-rgb32-6x5.ktx2"; - public const string Rgb32_6x6 = "astc-rgb32-6x6.ktx2"; - public const string Rgb32_8x5 = "astc-rgb32-8x5.ktx2"; - public const string Rgb32_8x6 = "astc-rgb32-8x6.ktx2"; - public const string Rgb32_8x8 = "astc-rgb32-8x8.ktx2"; - public const string Rgb32_10x5 = "astc-rgb32-10x5.ktx2"; - public const string Rgb32_10x6 = "astc-rgb32-10x6.ktx2"; - public const string Rgb32_10x8 = "astc-rgb32-10x8.ktx2"; - public const string Rgb32_10x10 = "astc-rgb32-10x10.ktx2"; - public const string Rgb32_12x10 = "astc-rgb32-12x10.ktx2"; - public const string Rgb32_12x12 = "astc-rgb32-12x12.ktx2"; + public const string Rgba32_4x4 = "astc_rgba32_4x4.ktx2"; + public const string Rgba32_5x4 = "astc_rgba32_5x4.ktx2"; + public const string Rgba32_5x5 = "astc_rgba32_5x5.ktx2"; + public const string Rgba32_6x5 = "astc_rgba32_6x5.ktx2"; + public const string Rgba32_6x6 = "astc_rgba32_6x6.ktx2"; + public const string Rgba32_8x5 = "astc_rgba32_8x5.ktx2"; + public const string Rgba32_8x6 = "astc_rgba32_8x6.ktx2"; + public const string Rgba32_8x8 = "astc_rgba32_8x8.ktx2"; + public const string Rgba32_10x5 = "astc_rgba32_10x5.ktx2"; + public const string Rgba32_10x6 = "astc_rgba32_10x6.ktx2"; + public const string Rgba32_10x8 = "astc_rgba32_10x8.ktx2"; + public const string Rgba32_10x10 = "astc_rgba32_10x10.ktx2"; + public const string Rgba32_12x10 = "astc_rgba32_12x10.ktx2"; + public const string Rgba32_12x12 = "astc_rgba32_12x12.ktx2"; public const string Rgb32_sRgb_4x4 = "valid_ASTC_4x4_SRGB_BLOCK_2D.ktx2"; public const string Rgb32_sRgb_5x4 = "valid_ASTC_5x4_SRGB_BLOCK_2D.ktx2"; diff --git a/tests/Images/Input/Ktx2/astc-rgb32-10x10.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-10x10.ktx2 deleted file mode 100644 index 9089c850..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-10x10.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3734df509022fada680c6a395c7fb8590e41809f1f6ec846faaec35ccb15b3ac -size 11024 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-10x5.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-10x5.ktx2 deleted file mode 100644 index a6ba6cf2..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-10x5.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f9d6eb2708a73a262018312bd3e3b3d9710f5b1d82de69c0bcef327fa81bd1d -size 21840 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-10x6.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-10x6.ktx2 deleted file mode 100644 index 4bab9587..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-10x6.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5cb096d280f6719324f6f8d81968af2e2c329b45a62ccbb707ee314ad648fb1 -size 18096 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-10x8.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-10x8.ktx2 deleted file mode 100644 index 2fb665af..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-10x8.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6839f598ee7627ffe1e733c0a25deff5382d301db1e9634aa55fd37314e1adf4 -size 13520 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-12x10.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-12x10.ktx2 deleted file mode 100644 index 3ae65865..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-12x10.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:532cde930a627861de5b56e4ae56c53b20154dd3c2d6531f9260dfe12f98946e -size 9360 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-12x12.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-12x12.ktx2 deleted file mode 100644 index ca28a47e..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-12x12.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5a9bdd7ace29d9d17279d87f28013b7828008112b92cc70b5f4574281597782b -size 7952 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-4x4.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-4x4.ktx2 deleted file mode 100644 index d337075a..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-4x4.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adf039abfacb9178ff2c9a7dd8054fb1f11b426c0e99183dc813ca9e6147cb60 -size 65744 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-5x4.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-5x4.ktx2 deleted file mode 100644 index 3ff12ede..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-5x4.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cce8bd95e3d52b2f307bdd89df80ebc75b800310381e549289a077afb1859b16 -size 53456 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-5x5.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-5x5.ktx2 deleted file mode 100644 index 9ba1974b..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-5x5.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1febd58f0e39d009b1b9f01bde1a8dcffcb3e3daf4fb59318e25d0a3afa46e4b -size 43472 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-6x5.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-6x5.ktx2 deleted file mode 100644 index b71b0732..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-6x5.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cbf3649fc8b4c40bbc3a1425ef68647fdfc3a6a186a74b3df0307074c5a9dd23 -size 35984 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-6x6.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-6x6.ktx2 deleted file mode 100644 index 43c7ef11..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-6x6.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:350bf2abd9acdfd717c1943deb51df358958382efbbf35830e4e693994fa563b -size 29792 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-8x5.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-8x5.ktx2 deleted file mode 100644 index 8a73154d..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-8x5.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f5261f0d5592d557682bf8193edef1fd0cbe2f833989d7d6a382b49ae0274c4c -size 26832 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-8x6.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-8x6.ktx2 deleted file mode 100644 index eefab560..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-8x6.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e264bfff2112aacf47de725939c027d8beb873512ce0b813a9292570312078a1 -size 22224 diff --git a/tests/Images/Input/Ktx2/astc-rgb32-8x8.ktx2 b/tests/Images/Input/Ktx2/astc-rgb32-8x8.ktx2 deleted file mode 100644 index 1793a2f3..00000000 --- a/tests/Images/Input/Ktx2/astc-rgb32-8x8.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd37bcf4858bbb2555a940a1894f03bdbc333feba906a2a503ba05733483a98f -size 16592 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_10x10.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_10x10.ktx2 new file mode 100644 index 00000000..2334aeda --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_10x10.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:512c8f4d0352492ac70fb8d72c69802980cefd3de0ccfe179653f74028e1a548 +size 11088 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_10x5.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_10x5.ktx2 new file mode 100644 index 00000000..76bd7adb --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_10x5.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80c0ea7f71b120a09e8c3a1ac39e6677e0cc2a663f30d0c7b349d93386741314 +size 21904 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_10x6.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_10x6.ktx2 new file mode 100644 index 00000000..26940296 --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_10x6.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fababed3062e1a82b51ad4ca443423fa2cab21ff74166ff24cd5a7583f82da6 +size 18160 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_10x8.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_10x8.ktx2 new file mode 100644 index 00000000..084e8eaf --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_10x8.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2c61a06ee3f2301c7acc1518808b934373349ba4938cdc3c40057b651143b40 +size 13584 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_12x10.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_12x10.ktx2 new file mode 100644 index 00000000..3ee93205 --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_12x10.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78353f3d9f0fdf2ecdff1bcae1c51832c00465beb3f2922ac2e6753693329686 +size 9424 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_12x12.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_12x12.ktx2 new file mode 100644 index 00000000..5b7d251f --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_12x12.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:944495207e2b869e0cc47f3b4537b5df284b01ee36066fcaac42fb7ce867c269 +size 8016 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_4x4.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_4x4.ktx2 new file mode 100644 index 00000000..ae15d16b --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_4x4.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e21c1242774ef15aacc2ca950a849c9dc3a3e296515faa1ee876bd51c3306136 +size 65808 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_5x4.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_5x4.ktx2 new file mode 100644 index 00000000..fdcb8eea --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_5x4.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d8d410a26c834736b596ae08a3a423c9d4bd899cd9729a09b4abc9cbba84b02 +size 53520 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_5x5.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_5x5.ktx2 new file mode 100644 index 00000000..ec54874c --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_5x5.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed1330f50aa9061a760daefe7285cf4ca71e1105f6e2c867b3f5373735b238b8 +size 43536 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_6x5.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_6x5.ktx2 new file mode 100644 index 00000000..8f3ab4d6 --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_6x5.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c95a1ba7ced72135f72fa3b1d94a51e2b4c2b42cbd086b6e84774fb5708d4aa +size 36048 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_6x6.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_6x6.ktx2 new file mode 100644 index 00000000..c5c8b7e2 --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_6x6.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efb357573e877eabc378059496e7572e1de35529db3898dd3100c7a55ba42af3 +size 29856 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_8x5.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_8x5.ktx2 new file mode 100644 index 00000000..115ef10a --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_8x5.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bf0481dea0eabbe6f1d7dcae23531fb25f3c7bb6c8f2e8c64018cd843de2bd4 +size 26896 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_8x6.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_8x6.ktx2 new file mode 100644 index 00000000..6f0c684f --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_8x6.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:419038f24a8e6516a376dcbcb4c733969e5649acf478b598125acaba7b84a272 +size 22288 diff --git a/tests/Images/Input/Ktx2/astc_rgba32_8x8.ktx2 b/tests/Images/Input/Ktx2/astc_rgba32_8x8.ktx2 new file mode 100644 index 00000000..305fbf1b --- /dev/null +++ b/tests/Images/Input/Ktx2/astc_rgba32_8x8.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf90e732eb27f5ab902fc04cd149d04af59e00667932dbe898391607457da83b +size 16656 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes.png index b72f0c27..598f93a5 100644 --- a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes.png +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:667534774a3855e5aaaeb318d30955266e11031f1a1f4eb85f0fcb7b40c8c41f -size 128497 +oid sha256:72bf89c439781bb8e9bf582589fec9e2dcfd3581472d1d8182b445a1099b5879 +size 164227 From a149d52312180dde074c381f6d532d6ac1325491 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:34:43 +0100 Subject: [PATCH 14/37] Use tighter comparison for KTX1 tests --- .../Formats/Ktx/KtxAstcDecoderTests.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxAstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxAstcDecoderTests.cs index 2934ac76..71482d92 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxAstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxAstcDecoderTests.cs @@ -6,7 +6,6 @@ using SixLabors.ImageSharp.Textures.Tests.Enums; using SixLabors.ImageSharp.Textures.Tests.TestUtilities; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; -using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; using SixLabors.ImageSharp.Textures.TextureFormats; @@ -36,8 +35,6 @@ public void KtxAstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider provi Image firstMipMapImage = firstMipMap as Image; - // Note that the comparer is given a high threshold to allow for the lossy compression of ASTC, - // especially at larger block sizes, but the output is still expected to be very similar to the reference image. - firstMipMapImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(2.0f), provider, appendPixelTypeToFileName: false); + firstMipMapImage.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); } } From 310f0128741cb5ee077ed3b59298b737d199170b Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:54:26 +0100 Subject: [PATCH 15/37] Add placeholder tests for supercompressed ASTC --- .../Formats/Ktx2/Ktx2AstcDecoderTests.cs | 27 +++++++++++++++++++ tests/ImageSharp.Textures.Tests/TestImages.cs | 8 ++++++ .../valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_1.ktx2 | 3 +++ .../valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_9.ktx2 | 3 +++ .../valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_1.ktx2 | 3 +++ .../valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_9.ktx2 | 3 +++ ...anDecode_Rgba32_Supercompressed_ZLIB_1.png | 3 +++ ...anDecode_Rgba32_Supercompressed_ZLIB_9.png | 3 +++ ...anDecode_Rgba32_Supercompressed_ZSTD_1.png | 3 +++ ...anDecode_Rgba32_Supercompressed_ZSTD_9.png | 3 +++ 10 files changed, 59 insertions(+) create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_1.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_9.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_1.ktx2 create mode 100644 tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_9.ktx2 create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZLIB_1.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZLIB_9.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZSTD_1.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZSTD_9.png diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs index 9f3a2e57..d1796790 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; +using System.IO; using System.Text.RegularExpressions; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Formats.Ktx2; @@ -121,6 +123,31 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Srgb(TestTextureProvider provider) (firstMipMap as Image).CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.05f), provider, testOutputDetails: $"{blockSize}"); } + [Theory(Skip = "Supercompression support not yet implemented")] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_4x4_Zlib1)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_4x4_Zlib9)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_4x4_Zstd1)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_4x4_Zstd9)] + public void Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed(TestTextureProvider provider) + { + string fileName = Path.GetFileNameWithoutExtension(provider.InputFile); + string compressionDetails = fileName.Contains("ZLIB", StringComparison.Ordinal) ? fileName.Substring(fileName.IndexOf("ZLIB", StringComparison.Ordinal)) : fileName.Substring(fileName.IndexOf("ZSTD", StringComparison.Ordinal)); + + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + FlatTexture flatTexture = texture as FlatTexture; + + Assert.NotNull(flatTexture?.MipMaps); + Assert.Single(flatTexture.MipMaps); + + Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + Assert.Equal(16, firstMipMap.Width); + Assert.Equal(16, firstMipMap.Height); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + + (firstMipMap as Image).CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.05f), provider, testOutputDetails: $"{compressionDetails}"); + } + private static string GetBlockSizeFromFileName(string fileName) { Match match = GetBlockSizeFromFileName().Match(fileName); diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index e3584be4..593ff90b 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -78,6 +78,14 @@ public static class Astc public const string Rgb32_Unorm_12x10 = "valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2"; public const string Rgb32_Unorm_12x12 = "valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2"; + // Supercompressed textures (ZLIB) + public const string Rgb32_Unorm_4x4_Zlib1 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_1.ktx2"; + public const string Rgb32_Unorm_4x4_Zlib9 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_9.ktx2"; + + // Supercompressed textures (ZSTD) + public const string Rgb32_Unorm_4x4_Zstd1 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_1.ktx2"; + public const string Rgb32_Unorm_4x4_Zstd9 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_9.ktx2"; + // Volume textures public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; diff --git a/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_1.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_1.ktx2 new file mode 100644 index 00000000..ac28ec16 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_1.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce6c7aff34040079b4067b6ee19b7fc338621f9e453b0e6d379bcac4e0d7c4c8 +size 299 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_9.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_9.ktx2 new file mode 100644 index 00000000..e15efa89 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_9.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68cce8fa95fd5484817c4a70f34c97ebb25a09a1f0a7db731e13e92bff1908e4 +size 299 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_1.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_1.ktx2 new file mode 100644 index 00000000..0ed01195 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_1.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b9f8cba0659da234461cb621d10b5f02a01592e352385e429e8d7d3d8bed929 +size 297 diff --git a/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_9.ktx2 b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_9.ktx2 new file mode 100644 index 00000000..112fcf56 --- /dev/null +++ b/tests/Images/Input/Ktx2/valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_9.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f3a8c9b7b5c5e426f9a6986274c266230671d27cd7b10b6d1c0650b9833ac56 +size 297 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZLIB_1.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZLIB_1.png new file mode 100644 index 00000000..6bb0f52c --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZLIB_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a908971e007eb33f0990f0fa63c0ba6a8faaa8034bd0ed161b2aa092a59b78a +size 381 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZLIB_9.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZLIB_9.png new file mode 100644 index 00000000..6bb0f52c --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZLIB_9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a908971e007eb33f0990f0fa63c0ba6a8faaa8034bd0ed161b2aa092a59b78a +size 381 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZSTD_1.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZSTD_1.png new file mode 100644 index 00000000..6bb0f52c --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZSTD_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a908971e007eb33f0990f0fa63c0ba6a8faaa8034bd0ed161b2aa092a59b78a +size 381 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZSTD_9.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZSTD_9.png new file mode 100644 index 00000000..6bb0f52c --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed_ZSTD_9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a908971e007eb33f0990f0fa63c0ba6a8faaa8034bd0ed161b2aa092a59b78a +size 381 From aceb57a49fecd771a240dddba9ac31353e0fd21f Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:26:57 +0100 Subject: [PATCH 16/37] Add cubemap decoding test --- .../Ktx2/Ktx2AstcDecoderCubemapTests.cs | 65 +++++++++++++++++++ ...tx2AstcDecoder_CanDecode_Astc_6x6_negx.png | 3 + ...tx2AstcDecoder_CanDecode_Astc_6x6_negy.png | 3 + ...tx2AstcDecoder_CanDecode_Astc_6x6_negz.png | 3 + ...tx2AstcDecoder_CanDecode_Astc_6x6_posx.png | 3 + ...tx2AstcDecoder_CanDecode_Astc_6x6_posy.png | 3 + ...tx2AstcDecoder_CanDecode_Astc_6x6_posz.png | 3 + 7 files changed, 83 insertions(+) create mode 100644 tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negx.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negy.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negz.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posx.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posy.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posz.png diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs new file mode 100644 index 00000000..a2fb6f13 --- /dev/null +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Textures.Formats.Ktx2; +using SixLabors.ImageSharp.Textures.Tests.Enums; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; +using SixLabors.ImageSharp.Textures.TextureFormats; + +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx2; + +[Trait("Format", "Ktx2")] +[Trait("Format", "Astc")] +public class Ktx2AstcDecoderCubemapTests +{ + private static readonly Ktx2Decoder KtxDecoder = new(); + + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Cubemap, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_Cubemap_6x6)] + public void Ktx2AstcDecoder_CanDecode_Astc_6x6(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + CubemapTexture cubemapTexture = texture as CubemapTexture; + + using Image posXImage = cubemapTexture.PositiveX.MipMaps[0].GetImage(); + (posXImage as Image).CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: "posx"); + + using Image negXImage = cubemapTexture.NegativeX.MipMaps[0].GetImage(); + (negXImage as Image).CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: "negx"); + + using Image posYImage = cubemapTexture.PositiveY.MipMaps[0].GetImage(); + (posYImage as Image).CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: "posy"); + + using Image negYImage = cubemapTexture.NegativeY.MipMaps[0].GetImage(); + (negYImage as Image).CompareToReferenceOutput( + ImageComparer.TolerantPercentage(3.0f), + provider, + testOutputDetails: "negy"); + + using Image posZImage = cubemapTexture.PositiveZ.MipMaps[0].GetImage(); + (posZImage as Image).CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: "posz"); + + using Image negZImage = cubemapTexture.NegativeZ.MipMaps[0].GetImage(); + (negZImage as Image).CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: "negz"); + } +} diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negx.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negx.png new file mode 100644 index 00000000..2f898e90 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negx.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04535fdbc632fc946f1bdce0229bbe17f15ef9ede7b089d893a7539d3428093b +size 4456232 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negy.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negy.png new file mode 100644 index 00000000..653b279e --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8615a15cfbac32d0c440520e70af99bd1af3c64b6651f4dffe990007afe470d5 +size 4520170 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negz.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negz.png new file mode 100644 index 00000000..464ad67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negz.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76e41968dc617f55c19913a844790f432d345fd7de069b8ae91ce8b258536961 +size 4188624 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posx.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posx.png new file mode 100644 index 00000000..a0afbf06 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posx.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a244bc0d7e9ff1b82dae09678f18f0dc7ed7c082757c402bcfe2f62afbf8a4f +size 4433772 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posy.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posy.png new file mode 100644 index 00000000..c8554e46 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ddc6b41b993735daf6bc947cb90347ff5d387aa236ed3a2d614a296c0d9f2797 +size 2820406 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posz.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posz.png new file mode 100644 index 00000000..3820a0e4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posz.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc4b3ecdb34d3ba6f02c6ea5f78a7fa99904ab81ec0538863694ad1fdfb8b33 +size 3910835 From 5e8a097804f4c5e42caa13ab8c5d5f98bd91c1ff Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:57:02 +0100 Subject: [PATCH 17/37] Add test for mipmap levels --- .../Formats/Ktx2/Ktx2AstcDecoderTests.cs | 34 ++++++++++++++----- .../Ktx2AstcDecoder_CanDecode_MipMaps_0.png | 3 ++ .../Ktx2AstcDecoder_CanDecode_MipMaps_1.png | 3 ++ .../Ktx2AstcDecoder_CanDecode_MipMaps_2.png | 3 ++ .../Ktx2AstcDecoder_CanDecode_MipMaps_3.png | 3 ++ .../Ktx2AstcDecoder_CanDecode_MipMaps_4.png | 3 ++ 6 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_0.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_1.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_2.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_3.png create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_4.png diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs index d1796790..a485e394 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Text.RegularExpressions; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Formats.Ktx2; @@ -45,15 +43,16 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Blocksizes(TestTextureProvider prov Assert.NotNull(flatTexture?.MipMaps); Assert.Single(flatTexture.MipMaps); - Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + using Image firstMipMap = flatTexture.MipMaps[0].GetImage(); Assert.Equal(256, firstMipMap.Width); Assert.Equal(256, firstMipMap.Height); Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); Image firstMipMapImage = firstMipMap as Image; - // Note that the comparer is given a high threshold to allow for the lossy compression of ASTC, + // Note that the comparer is given a higher threshold to allow for the lossy compression of ASTC, // especially at larger block sizes, but the output is still expected to be very similar to the reference image. + // A single reference image is used to save on the amount of test data otherwise required for each block size. firstMipMapImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(4.0f), provider, appendPixelTypeToFileName: false); } @@ -82,7 +81,7 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Unorm(TestTextureProvider provider) Assert.NotNull(flatTexture?.MipMaps); Assert.Single(flatTexture.MipMaps); - Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + using Image firstMipMap = flatTexture.MipMaps[0].GetImage(); Assert.Equal(16, firstMipMap.Width); Assert.Equal(16, firstMipMap.Height); Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); @@ -115,7 +114,7 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Srgb(TestTextureProvider provider) Assert.NotNull(flatTexture?.MipMaps); Assert.Single(flatTexture.MipMaps); - Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + using Image firstMipMap = flatTexture.MipMaps[0].GetImage(); Assert.Equal(16, firstMipMap.Width); Assert.Equal(16, firstMipMap.Height); Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); @@ -123,6 +122,23 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Srgb(TestTextureProvider provider) (firstMipMap as Image).CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.05f), provider, testOutputDetails: $"{blockSize}"); } + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_ArrayTex_Mipmap)] + public void Ktx2AstcDecoder_CanDecode_MipMaps(TestTextureProvider provider) + { + int mimMapLevel = 0; + + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + FlatTexture flatTexture = texture as FlatTexture; + + foreach (MipMap mipMap in flatTexture.MipMaps) + { + using Image image = mipMap.GetImage(); + (image as Image).CompareToReferenceOutput(ImageComparer.Exact, provider, testOutputDetails: $"{mimMapLevel++}"); + } + } + [Theory(Skip = "Supercompression support not yet implemented")] [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_4x4_Zlib1)] [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Unorm_4x4_Zlib9)] @@ -131,7 +147,9 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Srgb(TestTextureProvider provider) public void Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed(TestTextureProvider provider) { string fileName = Path.GetFileNameWithoutExtension(provider.InputFile); - string compressionDetails = fileName.Contains("ZLIB", StringComparison.Ordinal) ? fileName.Substring(fileName.IndexOf("ZLIB", StringComparison.Ordinal)) : fileName.Substring(fileName.IndexOf("ZSTD", StringComparison.Ordinal)); + string compressionDetails = fileName.Contains("ZLIB", StringComparison.Ordinal) + ? fileName[fileName.IndexOf("ZLIB", StringComparison.Ordinal)..] + : fileName[fileName.IndexOf("ZSTD", StringComparison.Ordinal)..]; using Texture texture = provider.GetTexture(KtxDecoder); provider.SaveTextures(texture); @@ -140,7 +158,7 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Supercompressed(TestTextureProvider Assert.NotNull(flatTexture?.MipMaps); Assert.Single(flatTexture.MipMaps); - Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + using Image firstMipMap = flatTexture.MipMaps[0].GetImage(); Assert.Equal(16, firstMipMap.Width); Assert.Equal(16, firstMipMap.Height); Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_0.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_0.png new file mode 100644 index 00000000..9211d075 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfd253f555e9d6b5a3493c93f6bfdfcfa7d9cab759d7994e544dd5b72f0753a9 +size 107 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_1.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_1.png new file mode 100644 index 00000000..00b6da1a --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0313d2900eae51bd5f56b609839d130f6c6ec17320f7595973a36bbcdee84df +size 100 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_2.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_2.png new file mode 100644 index 00000000..47c4dead --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b708d4e34181c551722491a8d68b6b2d79de3a041f3b79cb49728bed60c015e1 +size 99 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_3.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_3.png new file mode 100644 index 00000000..134de01e --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd189117b29ddb0fbcaf8def1a5d90c0426be7241008cbea0aa470d184bb619b +size 97 diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_4.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_4.png new file mode 100644 index 00000000..8af3b126 --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_MipMaps_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aef68645bf32626a51b9420b91a3ad2f1af01cfa8aebb929d2144c1f3944b624 +size 91 From 52058fc349bf84d248fa4e9e937bef941b7c5a4a Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:16:49 +0100 Subject: [PATCH 18/37] Improve and reorganize flat tests --- .../Formats/Astc/AstcKtx2DecoderTests.cs | 242 ------------------ ...erTests.cs => Ktx2AstcDecoderFlatTests.cs} | 2 +- 2 files changed, 1 insertion(+), 243 deletions(-) delete mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs rename tests/ImageSharp.Textures.Tests/Formats/Ktx2/{Ktx2AstcDecoderTests.cs => Ktx2AstcDecoderFlatTests.cs} (99%) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs deleted file mode 100644 index 0157fb83..00000000 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/AstcKtx2DecoderTests.cs +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Textures.Formats.Ktx2; -using SixLabors.ImageSharp.Textures.Tests.Enums; -using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; -using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; -using SixLabors.ImageSharp.Textures.TextureFormats; - -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; - -[Trait("Format", "Ktx2")] -[Trait("Compression", "Astc")] -public class AstcKtx2DecoderTests -{ - private static readonly Ktx2Decoder Ktx2Decoder = new(); - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_4x4_FlightHelmet)] - public void Ktx2Decoder_CanDecode_Astc_4x4_FlightHelmet(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_5x4_IronBars)] - public void Ktx2Decoder_CanDecode_Astc_5x4_IronBars(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x5_FlightHelmet)] - public void Ktx2Decoder_CanDecode_Astc_6x5_FlightHelmet(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_IronBars)] - public void Ktx2Decoder_CanDecode_Astc_6x6_IronBars(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_Posx)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Posx(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_8x6_FlightHelmet)] - public void Ktx2Decoder_CanDecode_Astc_8x6_FlightHelmet(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_8x8_FlightHelmet)] - public void Ktx2Decoder_CanDecode_Astc_8x8_FlightHelmet(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_10x5_FlightHelmet)] - public void Ktx2Decoder_CanDecode_Astc_10x5_FlightHelmet(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_12x10_FlightHelmet)] - public void Ktx2Decoder_CanDecode_Astc_12x10_FlightHelmet(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_12x12_FlightHelmet)] - public void Ktx2Decoder_CanDecode_Astc_12x12_FlightHelmet(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_4x4_Posx)] - public void Ktx2Decoder_CanDecode_Astc_4x4_Posx_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x5_Posx)] - public void Ktx2Decoder_CanDecode_Astc_6x5_Posx_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Posx)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Posx_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Posy)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Posy_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Posz)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Posz_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_8x6_Posx)] - public void Ktx2Decoder_CanDecode_Astc_8x6_Posx_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_8x8_Posx)] - public void Ktx2Decoder_CanDecode_Astc_8x8_Posx_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_10x5_Posx)] - public void Ktx2Decoder_CanDecode_Astc_10x5_Posx_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_12x10_Posx)] - public void Ktx2Decoder_CanDecode_Astc_12x10_Posx_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_12x12_Posx)] - public void Ktx2Decoder_CanDecode_Astc_12x12_Posx_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Fastest)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Fastest_Quality_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Fast)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Fast_Quality_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_6x6_Medium)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Medium_Quality_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Volume, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_3dTex)] - public void Ktx2Decoder_CanDecode_Astc_6x6_VolumeTexture(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_ArrayTex)] - public void Ktx2Decoder_CanDecode_Astc_6x6_ArrayTexture(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_ArrayTex_Mipmap)] - public void Ktx2Decoder_CanDecode_Astc_6x6_ArrayTexture_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Cubemap, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_Cubemap_6x6)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Cubemap(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Cubemap, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Mipmap_Ldr_Cubemap_6x6)] - public void Ktx2Decoder_CanDecode_Astc_6x6_Cubemap_WithMipmaps(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(Ktx2Decoder); - provider.SaveTextures(texture); - } -} diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs similarity index 99% rename from tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs index a485e394..472aa467 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx2; [Trait("Format", "Ktx2")] [Trait("Format", "Astc")] -public partial class Ktx2AstcDecoderTests +public partial class Ktx2AstcDecoderFlatTests { private static readonly Ktx2Decoder KtxDecoder = new(); From e5c2e5e7088b500a6c84fc8542b4dcb981680f48 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:23:04 +0100 Subject: [PATCH 19/37] Clean up test images list --- tests/ImageSharp.Textures.Tests/TestImages.cs | 34 +++---------------- tests/Images/Expected/Yokohama3/filelist.txt | 6 ---- tests/Images/Expected/Yokohama3/negx.jpg | 3 -- tests/Images/Expected/Yokohama3/negy.jpg | 3 -- tests/Images/Expected/Yokohama3/negz.jpg | 3 -- tests/Images/Expected/Yokohama3/posx.jpg | 3 -- tests/Images/Expected/Yokohama3/posy.jpg | 3 -- tests/Images/Expected/Yokohama3/posz.jpg | 3 -- tests/Images/Expected/Yokohama3/readme.txt | 13 ------- 9 files changed, 4 insertions(+), 67 deletions(-) delete mode 100644 tests/Images/Expected/Yokohama3/filelist.txt delete mode 100644 tests/Images/Expected/Yokohama3/negx.jpg delete mode 100644 tests/Images/Expected/Yokohama3/negy.jpg delete mode 100644 tests/Images/Expected/Yokohama3/negz.jpg delete mode 100644 tests/Images/Expected/Yokohama3/posx.jpg delete mode 100644 tests/Images/Expected/Yokohama3/posy.jpg delete mode 100644 tests/Images/Expected/Yokohama3/posz.jpg delete mode 100644 tests/Images/Expected/Yokohama3/readme.txt diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index 593ff90b..e9e9fa33 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -23,16 +23,6 @@ public static class Ktx2 public static class Astc { // Flat textures with various block sizes - public const string Ldr_4x4_FlightHelmet = "astc_ldr_4x4_FlightHelmet_baseColor.ktx2"; - public const string Ldr_5x4_IronBars = "astc_ldr_5x4_Iron_Bars_001_normal.ktx2"; - public const string Ldr_6x5_FlightHelmet = "astc_ldr_6x5_FlightHelmet_baseColor.ktx2"; - public const string Ldr_6x6_IronBars = "astc_ldr_6x6_Iron_Bars_001_normal.ktx2"; - public const string Ldr_6x6_Posx = "astc_ldr_6x6_posx.ktx2"; - public const string Ldr_8x6_FlightHelmet = "astc_ldr_8x6_FlightHelmet_baseColor.ktx2"; - public const string Ldr_8x8_FlightHelmet = "astc_ldr_8x8_FlightHelmet_baseColor.ktx2"; - public const string Ldr_10x5_FlightHelmet = "astc_ldr_10x5_FlightHelmet_baseColor.ktx2"; - public const string Ldr_12x10_FlightHelmet = "astc_ldr_12x10_FlightHelmet_baseColor.ktx2"; - public const string Ldr_12x12_FlightHelmet = "astc_ldr_12x12_FlightHelmet_baseColor.ktx2"; public const string Rgba32_4x4 = "astc_rgba32_4x4.ktx2"; public const string Rgba32_5x4 = "astc_rgba32_5x4.ktx2"; public const string Rgba32_5x5 = "astc_rgba32_5x5.ktx2"; @@ -78,6 +68,10 @@ public static class Astc public const string Rgb32_Unorm_12x10 = "valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2"; public const string Rgb32_Unorm_12x12 = "valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2"; + // Textures with several levels of MipMaps + public const string Ldr_6x6_ArrayTex_Mipmap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; + public const string Mipmap_Ldr_Cubemap_6x6 = "astc_mipmap_ldr_cubemap_6x6.ktx2"; + // Supercompressed textures (ZLIB) public const string Rgb32_Unorm_4x4_Zlib1 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_1.ktx2"; public const string Rgb32_Unorm_4x4_Zlib9 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_9.ktx2"; @@ -89,28 +83,8 @@ public static class Astc // Volume textures public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; - // Array textures - public const string Ldr_6x6_ArrayTex = "astc_ldr_6x6_arraytex_7.ktx2"; - public const string Ldr_6x6_ArrayTex_Mipmap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; - // Cubemap textures public const string Ldr_Cubemap_6x6 = "astc_ldr_cubemap_6x6.ktx2"; - - // Mipmap tests with different quality settings - public const string Mipmap_Ldr_4x4_Posx = "astc_mipmap_ldr_4x4_posx.ktx2"; - public const string Mipmap_Ldr_6x5_Posx = "astc_mipmap_ldr_6x5_posx.ktx2"; - public const string Mipmap_Ldr_6x6_Fast = "astc_mipmap_ldr_6x6_kodim17_fast.ktx2"; - public const string Mipmap_Ldr_6x6_Medium = "astc_mipmap_ldr_6x6_kodim17_medium.ktx2"; - public const string Mipmap_Ldr_6x6_Fastest = "astc_mipmap_ldr_6x6_kodim17_fastest.ktx2"; - public const string Mipmap_Ldr_6x6_Posx = "astc_mipmap_ldr_6x6_posx.ktx2"; - public const string Mipmap_Ldr_6x6_Posy = "astc_mipmap_ldr_6x6_posy.ktx2"; - public const string Mipmap_Ldr_6x6_Posz = "astc_mipmap_ldr_6x6_posz.ktx2"; - public const string Mipmap_Ldr_8x6_Posx = "astc_mipmap_ldr_8x6_posx.ktx2"; - public const string Mipmap_Ldr_8x8_Posx = "astc_mipmap_ldr_8x8_posx.ktx2"; - public const string Mipmap_Ldr_10x5_Posx = "astc_mipmap_ldr_10x5_posx.ktx2"; - public const string Mipmap_Ldr_12x10_Posx = "astc_mipmap_ldr_12x10_posx.ktx2"; - public const string Mipmap_Ldr_12x12_Posx = "astc_mipmap_ldr_12x12_posx.ktx2"; - public const string Mipmap_Ldr_Cubemap_6x6 = "astc_mipmap_ldr_cubemap_6x6.ktx2"; } } } diff --git a/tests/Images/Expected/Yokohama3/filelist.txt b/tests/Images/Expected/Yokohama3/filelist.txt deleted file mode 100644 index 56558836..00000000 --- a/tests/Images/Expected/Yokohama3/filelist.txt +++ /dev/null @@ -1,6 +0,0 @@ -posx.jpg -negx.jpg -posy.jpg -negy.jpg -posz.jpg -negz.jpg diff --git a/tests/Images/Expected/Yokohama3/negx.jpg b/tests/Images/Expected/Yokohama3/negx.jpg deleted file mode 100644 index e6140aa5..00000000 --- a/tests/Images/Expected/Yokohama3/negx.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b29bbd856363b3095f3447a1f47419b8a4d94032e3b8e69377a4f002b064d9b9 -size 1039406 diff --git a/tests/Images/Expected/Yokohama3/negy.jpg b/tests/Images/Expected/Yokohama3/negy.jpg deleted file mode 100644 index 97c86925..00000000 --- a/tests/Images/Expected/Yokohama3/negy.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63401877a11eeef4016b947e46554f1e6e61ad60db7d5e7bebdd378d1751bb1d -size 994070 diff --git a/tests/Images/Expected/Yokohama3/negz.jpg b/tests/Images/Expected/Yokohama3/negz.jpg deleted file mode 100644 index b8f54d55..00000000 --- a/tests/Images/Expected/Yokohama3/negz.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b52d48844e642f93443922088143cb466a7b46c9b3d31bb2b6a3f4b91c68787c -size 911570 diff --git a/tests/Images/Expected/Yokohama3/posx.jpg b/tests/Images/Expected/Yokohama3/posx.jpg deleted file mode 100644 index 6682af4f..00000000 --- a/tests/Images/Expected/Yokohama3/posx.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d28f2c6d384e919b93cf94d625677f3231032e691fa3832926f99f8752a8c56 -size 1036660 diff --git a/tests/Images/Expected/Yokohama3/posy.jpg b/tests/Images/Expected/Yokohama3/posy.jpg deleted file mode 100644 index 7bdeadbd..00000000 --- a/tests/Images/Expected/Yokohama3/posy.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:823f80f5dafac7c2dccf0b192867433e4e0f27d195541da02e99684ec28f79a4 -size 631474 diff --git a/tests/Images/Expected/Yokohama3/posz.jpg b/tests/Images/Expected/Yokohama3/posz.jpg deleted file mode 100644 index 7613d5ee..00000000 --- a/tests/Images/Expected/Yokohama3/posz.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76cfa0cc922ac66794e5939b1f000edc051fc84200ef4663c4de02766e2f45d1 -size 871498 diff --git a/tests/Images/Expected/Yokohama3/readme.txt b/tests/Images/Expected/Yokohama3/readme.txt deleted file mode 100644 index 8b404c27..00000000 --- a/tests/Images/Expected/Yokohama3/readme.txt +++ /dev/null @@ -1,13 +0,0 @@ -Author -====== - -This is the work of Emil Persson, aka Humus. -http://www.humus.name - - - -License -======= - -This work is licensed under a Creative Commons Attribution 3.0 Unported License. -http://creativecommons.org/licenses/by/3.0/ From f26130bada9aae9deb8f2c4c20df0432921c07b6 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:57:02 +0100 Subject: [PATCH 20/37] Add large image test --- .../Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs | 16 ++++++++++++++++ tests/ImageSharp.Textures.Tests/TestImages.cs | 5 ++--- ...x2AstcDecoder_CanDecode_Rgba32_Srgb_Large.png | 3 +++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_Large.png diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs index 472aa467..c1ab4373 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs @@ -122,6 +122,22 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Srgb(TestTextureProvider provider) (firstMipMap as Image).CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.05f), provider, testOutputDetails: $"{blockSize}"); } + [Theory] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Srgb_Large)] + public void Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_Large(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + FlatTexture flatTexture = texture as FlatTexture; + + using Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + Assert.Equal(2048, firstMipMap.Width); + Assert.Equal(2048, firstMipMap.Height); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + + (firstMipMap as Image).CompareToReferenceOutput(ImageComparer.Exact, provider); + } + [Theory] [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_ArrayTex_Mipmap)] public void Ktx2AstcDecoder_CanDecode_MipMaps(TestTextureProvider provider) diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index e9e9fa33..b552d693 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -68,6 +68,8 @@ public static class Astc public const string Rgb32_Unorm_12x10 = "valid_ASTC_12x10_UNORM_BLOCK_2D.ktx2"; public const string Rgb32_Unorm_12x12 = "valid_ASTC_12x12_UNORM_BLOCK_2D.ktx2"; + public const string Rgb32_Srgb_Large = "astc_ldr_10x5_FlightHelmet_baseColor.ktx2"; + // Textures with several levels of MipMaps public const string Ldr_6x6_ArrayTex_Mipmap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; public const string Mipmap_Ldr_Cubemap_6x6 = "astc_mipmap_ldr_cubemap_6x6.ktx2"; @@ -80,9 +82,6 @@ public static class Astc public const string Rgb32_Unorm_4x4_Zstd1 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_1.ktx2"; public const string Rgb32_Unorm_4x4_Zstd9 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_9.ktx2"; - // Volume textures - public const string Ldr_6x6_3dTex = "astc_ldr_6x6_3dtex_7.ktx2"; - // Cubemap textures public const string Ldr_Cubemap_6x6 = "astc_ldr_cubemap_6x6.ktx2"; } diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_Large.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_Large.png new file mode 100644 index 00000000..04cebc1a --- /dev/null +++ b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_Large.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6274ba573a2f6dae924e1aa40b586ca89329d5846cd5cf31b29188586e68c285 +size 1450103 From 297f80256e33883252da37858db99697b7649658 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:04:16 +0100 Subject: [PATCH 21/37] Tidy up test image naming --- .../Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs | 16 ++++++++-------- .../Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs | 2 +- tests/ImageSharp.Textures.Tests/TestImages.cs | 4 ++-- ...Ktx2AstcDecoder_CanDecode_All_Faces_negX.png} | 0 ...Ktx2AstcDecoder_CanDecode_All_Faces_negY.png} | 0 ...Ktx2AstcDecoder_CanDecode_All_Faces_negZ.png} | 0 ...Ktx2AstcDecoder_CanDecode_All_Faces_posX.png} | 0 ...Ktx2AstcDecoder_CanDecode_All_Faces_posY.png} | 0 ...Ktx2AstcDecoder_CanDecode_All_Faces_posZ.png} | 0 9 files changed, 11 insertions(+), 11 deletions(-) rename tests/Images/ReferenceOutput/{Ktx2AstcDecoder_CanDecode_Astc_6x6_negx.png => Ktx2AstcDecoder_CanDecode_All_Faces_negX.png} (100%) rename tests/Images/ReferenceOutput/{Ktx2AstcDecoder_CanDecode_Astc_6x6_negy.png => Ktx2AstcDecoder_CanDecode_All_Faces_negY.png} (100%) rename tests/Images/ReferenceOutput/{Ktx2AstcDecoder_CanDecode_Astc_6x6_negz.png => Ktx2AstcDecoder_CanDecode_All_Faces_negZ.png} (100%) rename tests/Images/ReferenceOutput/{Ktx2AstcDecoder_CanDecode_Astc_6x6_posx.png => Ktx2AstcDecoder_CanDecode_All_Faces_posX.png} (100%) rename tests/Images/ReferenceOutput/{Ktx2AstcDecoder_CanDecode_Astc_6x6_posy.png => Ktx2AstcDecoder_CanDecode_All_Faces_posY.png} (100%) rename tests/Images/ReferenceOutput/{Ktx2AstcDecoder_CanDecode_Astc_6x6_posz.png => Ktx2AstcDecoder_CanDecode_All_Faces_posZ.png} (100%) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs index a2fb6f13..63e8d375 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderCubemapTests.cs @@ -19,8 +19,8 @@ public class Ktx2AstcDecoderCubemapTests private static readonly Ktx2Decoder KtxDecoder = new(); [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Cubemap, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_Cubemap_6x6)] - public void Ktx2AstcDecoder_CanDecode_Astc_6x6(TestTextureProvider provider) + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Cubemap, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Srgb_6x6_Cube)] + public void Ktx2AstcDecoder_CanDecode_All_Faces(TestTextureProvider provider) { using Texture texture = provider.GetTexture(KtxDecoder); provider.SaveTextures(texture); @@ -30,36 +30,36 @@ public void Ktx2AstcDecoder_CanDecode_Astc_6x6(TestTextureProvider provider) (posXImage as Image).CompareToReferenceOutput( ImageComparer.Exact, provider, - testOutputDetails: "posx"); + testOutputDetails: "posX"); using Image negXImage = cubemapTexture.NegativeX.MipMaps[0].GetImage(); (negXImage as Image).CompareToReferenceOutput( ImageComparer.Exact, provider, - testOutputDetails: "negx"); + testOutputDetails: "negX"); using Image posYImage = cubemapTexture.PositiveY.MipMaps[0].GetImage(); (posYImage as Image).CompareToReferenceOutput( ImageComparer.Exact, provider, - testOutputDetails: "posy"); + testOutputDetails: "posY"); using Image negYImage = cubemapTexture.NegativeY.MipMaps[0].GetImage(); (negYImage as Image).CompareToReferenceOutput( ImageComparer.TolerantPercentage(3.0f), provider, - testOutputDetails: "negy"); + testOutputDetails: "negY"); using Image posZImage = cubemapTexture.PositiveZ.MipMaps[0].GetImage(); (posZImage as Image).CompareToReferenceOutput( ImageComparer.Exact, provider, - testOutputDetails: "posz"); + testOutputDetails: "posZ"); using Image negZImage = cubemapTexture.NegativeZ.MipMaps[0].GetImage(); (negZImage as Image).CompareToReferenceOutput( ImageComparer.Exact, provider, - testOutputDetails: "negz"); + testOutputDetails: "negZ"); } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs index c1ab4373..b8efc920 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2AstcDecoderFlatTests.cs @@ -139,7 +139,7 @@ public void Ktx2AstcDecoder_CanDecode_Rgba32_Srgb_Large(TestTextureProvider prov } [Theory] - [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Ldr_6x6_ArrayTex_Mipmap)] + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Astc.Rgb32_Srgb_6x6_MipMap)] public void Ktx2AstcDecoder_CanDecode_MipMaps(TestTextureProvider provider) { int mimMapLevel = 0; diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index b552d693..42294299 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -71,7 +71,7 @@ public static class Astc public const string Rgb32_Srgb_Large = "astc_ldr_10x5_FlightHelmet_baseColor.ktx2"; // Textures with several levels of MipMaps - public const string Ldr_6x6_ArrayTex_Mipmap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; + public const string Rgb32_Srgb_6x6_MipMap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; public const string Mipmap_Ldr_Cubemap_6x6 = "astc_mipmap_ldr_cubemap_6x6.ktx2"; // Supercompressed textures (ZLIB) @@ -83,7 +83,7 @@ public static class Astc public const string Rgb32_Unorm_4x4_Zstd9 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZSTD_9.ktx2"; // Cubemap textures - public const string Ldr_Cubemap_6x6 = "astc_ldr_cubemap_6x6.ktx2"; + public const string Rgb32_Srgb_6x6_Cube = "astc_ldr_cubemap_6x6.ktx2"; } } } diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negx.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_negX.png similarity index 100% rename from tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negx.png rename to tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_negX.png diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negy.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_negY.png similarity index 100% rename from tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negy.png rename to tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_negY.png diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negz.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_negZ.png similarity index 100% rename from tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_negz.png rename to tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_negZ.png diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posx.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_posX.png similarity index 100% rename from tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posx.png rename to tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_posX.png diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posy.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_posY.png similarity index 100% rename from tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posy.png rename to tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_posY.png diff --git a/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posz.png b/tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_posZ.png similarity index 100% rename from tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_Astc_6x6_posz.png rename to tests/Images/ReferenceOutput/Ktx2AstcDecoder_CanDecode_All_Faces_posZ.png From 1946b6c1aa064da9ae0030271748160f9dbef9a6 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:14:40 +0100 Subject: [PATCH 22/37] Tidy up unused test files --- tests/ImageSharp.Textures.Tests/TestImages.cs | 1 - tests/Images/Expected/A.png | 3 --- tests/Images/Expected/FlightHelmet_baseColor.png | 3 --- .../Images/Expected/GoldenGateBridge3/filelist.txt | 6 ------ tests/Images/Expected/GoldenGateBridge3/negx.jpg | 3 --- tests/Images/Expected/GoldenGateBridge3/negy.jpg | 3 --- tests/Images/Expected/GoldenGateBridge3/negz.jpg | 3 --- tests/Images/Expected/GoldenGateBridge3/posx.jpg | 3 --- tests/Images/Expected/GoldenGateBridge3/posy.jpg | 3 --- tests/Images/Expected/GoldenGateBridge3/posz.jpg | 3 --- tests/Images/Expected/GoldenGateBridge3/readme.txt | 13 ------------- .../Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg | 3 --- .../Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg | 3 --- .../Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg | 3 --- .../Expected/Iron_Bars/Iron_Bars_001_height.png | 3 --- .../Expected/Iron_Bars/Iron_Bars_001_normal.jpg | 3 --- .../Iron_Bars/Iron_Bars_001_normal_unnormalized.png | 3 --- .../Expected/Iron_Bars/Iron_Bars_001_roughness.jpg | 3 --- tests/Images/Expected/Iron_Bars/readme.txt | 11 ----------- tests/Images/Expected/LA.png | 3 --- tests/Images/Expected/R.png | 3 --- tests/Images/Expected/RG.png | 3 --- .../Ktx2/astc_ldr_12x10_FlightHelmet_baseColor.ktx2 | 3 --- .../Ktx2/astc_ldr_12x12_FlightHelmet_baseColor.ktx2 | 3 --- .../Ktx2/astc_ldr_4x4_FlightHelmet_baseColor.ktx2 | 3 --- .../Ktx2/astc_ldr_5x4_Iron_Bars_001_normal.ktx2 | 3 --- .../Ktx2/astc_ldr_6x5_FlightHelmet_baseColor.ktx2 | 3 --- tests/Images/Input/Ktx2/astc_ldr_6x6_3dtex_7.ktx2 | 3 --- .../Ktx2/astc_ldr_6x6_Iron_Bars_001_normal.ktx2 | 3 --- .../Images/Input/Ktx2/astc_ldr_6x6_arraytex_7.ktx2 | 3 --- tests/Images/Input/Ktx2/astc_ldr_6x6_posx.ktx2 | 3 --- .../Ktx2/astc_ldr_8x6_FlightHelmet_baseColor.ktx2 | 3 --- .../Ktx2/astc_ldr_8x8_FlightHelmet_baseColor.ktx2 | 3 --- .../Input/Ktx2/astc_mipmap_ldr_10x5_posx.ktx2 | 3 --- .../Input/Ktx2/astc_mipmap_ldr_12x10_posx.ktx2 | 3 --- .../Input/Ktx2/astc_mipmap_ldr_12x12_posx.ktx2 | 3 --- .../Images/Input/Ktx2/astc_mipmap_ldr_4x4_posx.ktx2 | 3 --- .../Images/Input/Ktx2/astc_mipmap_ldr_6x5_posx.ktx2 | 3 --- .../Ktx2/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 | 3 --- .../Ktx2/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 | 3 --- .../Ktx2/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 | 3 --- .../Images/Input/Ktx2/astc_mipmap_ldr_6x6_posx.ktx2 | 3 --- .../Images/Input/Ktx2/astc_mipmap_ldr_6x6_posy.ktx2 | 3 --- .../Images/Input/Ktx2/astc_mipmap_ldr_6x6_posz.ktx2 | 3 --- .../Images/Input/Ktx2/astc_mipmap_ldr_8x6_posx.ktx2 | 3 --- .../Images/Input/Ktx2/astc_mipmap_ldr_8x8_posx.ktx2 | 3 --- .../Input/Ktx2/astc_mipmap_ldr_cubemap_6x6.ktx2 | 3 --- 47 files changed, 160 deletions(-) delete mode 100644 tests/Images/Expected/A.png delete mode 100644 tests/Images/Expected/FlightHelmet_baseColor.png delete mode 100644 tests/Images/Expected/GoldenGateBridge3/filelist.txt delete mode 100644 tests/Images/Expected/GoldenGateBridge3/negx.jpg delete mode 100644 tests/Images/Expected/GoldenGateBridge3/negy.jpg delete mode 100644 tests/Images/Expected/GoldenGateBridge3/negz.jpg delete mode 100644 tests/Images/Expected/GoldenGateBridge3/posx.jpg delete mode 100644 tests/Images/Expected/GoldenGateBridge3/posy.jpg delete mode 100644 tests/Images/Expected/GoldenGateBridge3/posz.jpg delete mode 100644 tests/Images/Expected/GoldenGateBridge3/readme.txt delete mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg delete mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg delete mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg delete mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_height.png delete mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal.jpg delete mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png delete mode 100644 tests/Images/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg delete mode 100644 tests/Images/Expected/Iron_Bars/readme.txt delete mode 100644 tests/Images/Expected/LA.png delete mode 100644 tests/Images/Expected/R.png delete mode 100644 tests/Images/Expected/RG.png delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_12x10_FlightHelmet_baseColor.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_12x12_FlightHelmet_baseColor.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_4x4_FlightHelmet_baseColor.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_5x4_Iron_Bars_001_normal.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x5_FlightHelmet_baseColor.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_3dtex_7.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_Iron_Bars_001_normal.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_arraytex_7.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_6x6_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_8x6_FlightHelmet_baseColor.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_ldr_8x8_FlightHelmet_baseColor.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_10x5_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_12x10_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_12x12_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_4x4_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x5_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posy.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posz.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_8x6_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_8x8_posx.ktx2 delete mode 100644 tests/Images/Input/Ktx2/astc_mipmap_ldr_cubemap_6x6.ktx2 diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index 42294299..c405b47a 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -72,7 +72,6 @@ public static class Astc // Textures with several levels of MipMaps public const string Rgb32_Srgb_6x6_MipMap = "astc_ldr_6x6_arraytex_7_mipmap.ktx2"; - public const string Mipmap_Ldr_Cubemap_6x6 = "astc_mipmap_ldr_cubemap_6x6.ktx2"; // Supercompressed textures (ZLIB) public const string Rgb32_Unorm_4x4_Zlib1 = "valid_ASTC_4x4_UNORM_BLOCK_2D_ZLIB_1.ktx2"; diff --git a/tests/Images/Expected/A.png b/tests/Images/Expected/A.png deleted file mode 100644 index 7d1a3678..00000000 --- a/tests/Images/Expected/A.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a31a312bc20c9696a17e2857526b42b0e9b7d024d624fe91a417b12151a9915b -size 53774 diff --git a/tests/Images/Expected/FlightHelmet_baseColor.png b/tests/Images/Expected/FlightHelmet_baseColor.png deleted file mode 100644 index 121fa21d..00000000 --- a/tests/Images/Expected/FlightHelmet_baseColor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e5567daf7a3ac83f6ebc04605f3605ac17c19a153b6b8aadd2596690209c34c -size 2778518 diff --git a/tests/Images/Expected/GoldenGateBridge3/filelist.txt b/tests/Images/Expected/GoldenGateBridge3/filelist.txt deleted file mode 100644 index 56558836..00000000 --- a/tests/Images/Expected/GoldenGateBridge3/filelist.txt +++ /dev/null @@ -1,6 +0,0 @@ -posx.jpg -negx.jpg -posy.jpg -negy.jpg -posz.jpg -negz.jpg diff --git a/tests/Images/Expected/GoldenGateBridge3/negx.jpg b/tests/Images/Expected/GoldenGateBridge3/negx.jpg deleted file mode 100644 index 4b420e53..00000000 --- a/tests/Images/Expected/GoldenGateBridge3/negx.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:681d4598e743ff5ada3e8679c7241b0a338afedee094289fbc88818c44c37c97 -size 743985 diff --git a/tests/Images/Expected/GoldenGateBridge3/negy.jpg b/tests/Images/Expected/GoldenGateBridge3/negy.jpg deleted file mode 100644 index ce1c8ac5..00000000 --- a/tests/Images/Expected/GoldenGateBridge3/negy.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0fe09207f833fd7c9939ebaa28921da832f48e5b502135ff4e5f76e98bd2fd3 -size 586742 diff --git a/tests/Images/Expected/GoldenGateBridge3/negz.jpg b/tests/Images/Expected/GoldenGateBridge3/negz.jpg deleted file mode 100644 index 361149b8..00000000 --- a/tests/Images/Expected/GoldenGateBridge3/negz.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1eb32c8055b52ddf230bfe4846f78fa4ce03600541e7d3569d893dda0c14d3d7 -size 646123 diff --git a/tests/Images/Expected/GoldenGateBridge3/posx.jpg b/tests/Images/Expected/GoldenGateBridge3/posx.jpg deleted file mode 100644 index 3ae03904..00000000 --- a/tests/Images/Expected/GoldenGateBridge3/posx.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1a90ffea9ac8bf9bbfab93aa25ff23222a84decdc7d96c58bc6edbb1e9c9cce7 -size 721206 diff --git a/tests/Images/Expected/GoldenGateBridge3/posy.jpg b/tests/Images/Expected/GoldenGateBridge3/posy.jpg deleted file mode 100644 index 49fd8974..00000000 --- a/tests/Images/Expected/GoldenGateBridge3/posy.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a8e77108e821e7f11e16937152a700b6995ee996b7fedb50a4d6e9ee7d3f04db -size 674410 diff --git a/tests/Images/Expected/GoldenGateBridge3/posz.jpg b/tests/Images/Expected/GoldenGateBridge3/posz.jpg deleted file mode 100644 index 227469be..00000000 --- a/tests/Images/Expected/GoldenGateBridge3/posz.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d6f08f424f2c93f45c1525f756b1365c0a89651ca107d5fc9649b4370e25bb4b -size 616391 diff --git a/tests/Images/Expected/GoldenGateBridge3/readme.txt b/tests/Images/Expected/GoldenGateBridge3/readme.txt deleted file mode 100644 index 8b404c27..00000000 --- a/tests/Images/Expected/GoldenGateBridge3/readme.txt +++ /dev/null @@ -1,13 +0,0 @@ -Author -====== - -This is the work of Emil Persson, aka Humus. -http://www.humus.name - - - -License -======= - -This work is licensed under a Creative Commons Attribution 3.0 Unported License. -http://creativecommons.org/licenses/by/3.0/ diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg deleted file mode 100644 index 56f50812..00000000 --- a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7824d03409aa2059cf00de8c319b56df3a6805d9f76ea7a7c6242da0ff101ed -size 189930 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg deleted file mode 100644 index 88b5d484..00000000 --- a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fedb7076a37938b044428ac5ac608ff6f665358773c51c5412a8d22e9d1175e0 -size 195262 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg deleted file mode 100644 index 1a2950f1..00000000 --- a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35a10620acf7e73ffb55132b9e814ddbbaa45df1c445eefcef06b7458cb29967 -size 108801 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_height.png b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_height.png deleted file mode 100644 index a0821106..00000000 --- a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2d24186ce5a5b9c718ad7b6365a7e22680ce85abfdbed8a37ad9e4205d4d012 -size 456564 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal.jpg deleted file mode 100644 index 27c64184..00000000 --- a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be3169943652ad478fe1f38569feb4aa1da0ff6db37a4cd22fec063fe8aea50f -size 329643 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png deleted file mode 100644 index 82ffa417..00000000 --- a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d525152f126ca682ac2ed54ed01a28300c88072582bc005d0846f556d99fbe24 -size 2143908 diff --git a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg b/tests/Images/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg deleted file mode 100644 index a48d7d33..00000000 --- a/tests/Images/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:808018bf8dde084ac3e23493c3e26ea7c85335551cab20681454f6702d29eafc -size 118946 diff --git a/tests/Images/Expected/Iron_Bars/readme.txt b/tests/Images/Expected/Iron_Bars/readme.txt deleted file mode 100644 index 649d4903..00000000 --- a/tests/Images/Expected/Iron_Bars/readme.txt +++ /dev/null @@ -1,11 +0,0 @@ -Author -====== - -This is the work of Katsuagi from https://3dtextures.me. - - -License -======= - -This work is licensed under a Creative Commons Zero 1.0 -http://creativecommons.org/publicdomain/zero/1.0/legalcode diff --git a/tests/Images/Expected/LA.png b/tests/Images/Expected/LA.png deleted file mode 100644 index 85750bea..00000000 --- a/tests/Images/Expected/LA.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c8c1a3ec4c9dce1a7082229f58b596fc4030b6e92a184399f2c2e0abdd0a6ce -size 6996 diff --git a/tests/Images/Expected/R.png b/tests/Images/Expected/R.png deleted file mode 100644 index 0293e900..00000000 --- a/tests/Images/Expected/R.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8247b0f7fe68e3e79383be1e54a10e8c2ac9227c0d938f628604de123799211e -size 3857 diff --git a/tests/Images/Expected/RG.png b/tests/Images/Expected/RG.png deleted file mode 100644 index 5b70f533..00000000 --- a/tests/Images/Expected/RG.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d62d83441c65a4e060fa5574ae485d71bd50ddad08ae201aa3bd286ae58fb230 -size 5630 diff --git a/tests/Images/Input/Ktx2/astc_ldr_12x10_FlightHelmet_baseColor.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_12x10_FlightHelmet_baseColor.ktx2 deleted file mode 100644 index d4fd37b5..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_12x10_FlightHelmet_baseColor.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e0140fb124e0e448e6141f4b845678cd40bb92825755987916330f55e54cd94 -size 561184 diff --git a/tests/Images/Input/Ktx2/astc_ldr_12x12_FlightHelmet_baseColor.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_12x12_FlightHelmet_baseColor.ktx2 deleted file mode 100644 index 8369532b..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_12x12_FlightHelmet_baseColor.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb1c4d8c290426a36482b9cda5d138999e5abbb30a13a1f91a16fd764a0c58f9 -size 468160 diff --git a/tests/Images/Input/Ktx2/astc_ldr_4x4_FlightHelmet_baseColor.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_4x4_FlightHelmet_baseColor.ktx2 deleted file mode 100644 index d51c2070..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_4x4_FlightHelmet_baseColor.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:870ad9f7107e6dadf1a6848b4b18a21868119012564a91bca03d1a4f9ef53fb8 -size 4194608 diff --git a/tests/Images/Input/Ktx2/astc_ldr_5x4_Iron_Bars_001_normal.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_5x4_Iron_Bars_001_normal.ktx2 deleted file mode 100644 index cbcdb611..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_5x4_Iron_Bars_001_normal.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5a48c09a8d1776dd582ef12bb4e95e75a1d22811c25d498c69909487f851a55f -size 840000 diff --git a/tests/Images/Input/Ktx2/astc_ldr_6x5_FlightHelmet_baseColor.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_6x5_FlightHelmet_baseColor.ktx2 deleted file mode 100644 index 9d1b6594..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_6x5_FlightHelmet_baseColor.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8196161a8673c350e8d1e2ffbaa6aa6bbeb8b91ae72d6d8a951543cf06ca384d -size 2243824 diff --git a/tests/Images/Input/Ktx2/astc_ldr_6x6_3dtex_7.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_6x6_3dtex_7.ktx2 deleted file mode 100644 index e8bddd3c..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_6x6_3dtex_7.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a41d12a094f89aa6e89ca77e734fcd99654a81a83422d478ae5f1ffa5b9ace8 -size 1312 diff --git a/tests/Images/Input/Ktx2/astc_ldr_6x6_Iron_Bars_001_normal.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_6x6_Iron_Bars_001_normal.ktx2 deleted file mode 100644 index 74ecd517..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_6x6_Iron_Bars_001_normal.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bec2ac5dd82714d609f4db3e1079e1637053416bb719ee6bb324f9489c7c87d4 -size 468176 diff --git a/tests/Images/Input/Ktx2/astc_ldr_6x6_arraytex_7.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_6x6_arraytex_7.ktx2 deleted file mode 100644 index df276b35..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_6x6_arraytex_7.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f2ea122a565f7e59ec3ed3be9610eb2da35d3708f678799b16ef524ab31155b -size 1312 diff --git a/tests/Images/Input/Ktx2/astc_ldr_6x6_posx.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_6x6_posx.ktx2 deleted file mode 100644 index 7c440d21..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_6x6_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:790d937978f62dfd5199a7675ddc5d52ad4e08a9a42d24f3cef9a79499c74f65 -size 1871728 diff --git a/tests/Images/Input/Ktx2/astc_ldr_8x6_FlightHelmet_baseColor.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_8x6_FlightHelmet_baseColor.ktx2 deleted file mode 100644 index 07b2f5f2..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_8x6_FlightHelmet_baseColor.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cae54ca9353a6cd3be90a7effc4b6607323a15bd47a91f65fc2a2bcfbc5b7a40 -size 1401136 diff --git a/tests/Images/Input/Ktx2/astc_ldr_8x8_FlightHelmet_baseColor.ktx2 b/tests/Images/Input/Ktx2/astc_ldr_8x8_FlightHelmet_baseColor.ktx2 deleted file mode 100644 index 5e44ba3e..00000000 --- a/tests/Images/Input/Ktx2/astc_ldr_8x8_FlightHelmet_baseColor.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:522f927f8f989a720655c7e3c34b1ea7bae33c014b3c1272bd8bc9011beaa2e6 -size 1048880 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_10x5_posx.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_10x5_posx.ktx2 deleted file mode 100644 index b729b4e9..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_10x5_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:143d48563071171481096baa2b7d1d007b5e810d46952f14faa40bc5519d2b90 -size 1798048 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_12x10_posx.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_12x10_posx.ktx2 deleted file mode 100644 index c5366919..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_12x10_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad63079b017f24018212f7579ccb2442fca9ceedfb48e3618948713bddf7e79d -size 751376 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_12x12_posx.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_12x12_posx.ktx2 deleted file mode 100644 index 030e7a6c..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_12x12_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d3ab56b0855b438fc5a3c0be7e436f45cf01c2f25d9869e5c03073a96fc294bd -size 626864 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_4x4_posx.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_4x4_posx.ktx2 deleted file mode 100644 index cc93d3ed..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_4x4_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa4e0f71cc2162d24f4a0809c41001254b18a0a06c584550f1f610a92c75b9b8 -size 5592992 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x5_posx.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x5_posx.ktx2 deleted file mode 100644 index 59bb728f..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x5_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8e95bfe11bf839c02cb671687281e33b015e24f5ef531a5c838185819d39a93 -size 2994880 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 deleted file mode 100644 index d04718f3..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eda2e0b07974dda81341b1d84d689266480496e1df4f987e961040eedc453a63 -size 235840 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 deleted file mode 100644 index ece5a0f6..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:919300e81ef2f12f0b8f8f199fc15a5f900a5694ea7645d8f4029935bf9c7668 -size 235840 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 deleted file mode 100644 index 76df6240..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44c8e6edb1201fff79abe03e900ba317b4a9ec13c3e34cdddce9c9320f3f2654 -size 235840 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posx.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posx.ktx2 deleted file mode 100644 index 89a9b9dc..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b497a76ec2b4f96aee01e547eeea67aaac3fd3ff0dd8dd2507847911b2b9c8b -size 2498272 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posy.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posy.ktx2 deleted file mode 100644 index 670c0034..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posy.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4e1fef0f274a41e9448f8cd559b4a9c230189d537bc63b7e7d32ee5d6d60fba -size 2498272 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posz.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posz.ktx2 deleted file mode 100644 index fc942d7f..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_6x6_posz.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d33b1d4ed0e3c4ebfdd51c26fc469ab86bfa8e36bbc49a5b8fc843cc9d0db39 -size 2498272 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_8x6_posx.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_8x6_posx.ktx2 deleted file mode 100644 index 16d0fbfc..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_8x6_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04dec629f927de026dfc2da896bb0ae319f38648d421a7f538a308ef8ae98b1a -size 1869280 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_8x8_posx.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_8x8_posx.ktx2 deleted file mode 100644 index 3674d888..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_8x8_posx.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad923ad380af22fbcb04a9a9b191207bcad40b41abb047663f18580b9a09da9c -size 1398704 diff --git a/tests/Images/Input/Ktx2/astc_mipmap_ldr_cubemap_6x6.ktx2 b/tests/Images/Input/Ktx2/astc_mipmap_ldr_cubemap_6x6.ktx2 deleted file mode 100644 index c3a384f0..00000000 --- a/tests/Images/Input/Ktx2/astc_mipmap_ldr_cubemap_6x6.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63845d6debe557f23af9530e275a193c7c70e672c96b176a42673d03b528e2df -size 14986832 From fc7387bbab80ce45661e6ae1405a89f76ad5f8b4 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:19:15 +0100 Subject: [PATCH 23/37] Restore existing test images --- tests/Images/Expected/A.png | 3 +++ tests/Images/Expected/LA.png | 3 +++ tests/Images/Expected/R.png | 3 +++ tests/Images/Expected/RG.png | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 tests/Images/Expected/A.png create mode 100644 tests/Images/Expected/LA.png create mode 100644 tests/Images/Expected/R.png create mode 100644 tests/Images/Expected/RG.png diff --git a/tests/Images/Expected/A.png b/tests/Images/Expected/A.png new file mode 100644 index 00000000..7d1a3678 --- /dev/null +++ b/tests/Images/Expected/A.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a31a312bc20c9696a17e2857526b42b0e9b7d024d624fe91a417b12151a9915b +size 53774 diff --git a/tests/Images/Expected/LA.png b/tests/Images/Expected/LA.png new file mode 100644 index 00000000..85750bea --- /dev/null +++ b/tests/Images/Expected/LA.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c8c1a3ec4c9dce1a7082229f58b596fc4030b6e92a184399f2c2e0abdd0a6ce +size 6996 diff --git a/tests/Images/Expected/R.png b/tests/Images/Expected/R.png new file mode 100644 index 00000000..0293e900 --- /dev/null +++ b/tests/Images/Expected/R.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8247b0f7fe68e3e79383be1e54a10e8c2ac9227c0d938f628604de123799211e +size 3857 diff --git a/tests/Images/Expected/RG.png b/tests/Images/Expected/RG.png new file mode 100644 index 00000000..5b70f533 --- /dev/null +++ b/tests/Images/Expected/RG.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d62d83441c65a4e060fa5574ae485d71bd50ddad08ae201aa3bd286ae58fb230 +size 5630 From 1c6be5705596a69699c89437ab41006cca94422f Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:38:40 +0100 Subject: [PATCH 24/37] Update ASTC types to follow style recommendations --- .../Formats/Ktx/KtxProcessor.cs | 56 ++++++++--------- .../Formats/Ktx2/Ktx2Processor.cs | 56 ++++++++--------- .../TextureFormats/Decoding/RgbaAstc10x10.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc10x5.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc10x6.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc10x8.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc12x10.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc12x12.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc4x4.cs | 61 +++++++++---------- .../TextureFormats/Decoding/RgbaAstc5x4.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc5x5.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc6x5.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc6x6.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc8x5.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc8x6.cs | 53 ++++++++-------- .../TextureFormats/Decoding/RgbaAstc8x8.cs | 53 ++++++++-------- 16 files changed, 410 insertions(+), 452 deletions(-) diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs index 1f101694..8dbafc62 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs @@ -87,33 +87,33 @@ public MipMap[] DecodeMipMaps(Stream stream, int width, int height, uint count) case GlInternalPixelFormat.CompressedRgb8Etc2: return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc4x4Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc5x4Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc5x5Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc6x5Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc6x6Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc8x5Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc8x6Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc8x8Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc10x5Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc10x6Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc10x8Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc10x10Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc12x10Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc12x12Khr: - return this.AllocateMipMaps(stream, width, height, count); + return this.AllocateMipMaps(stream, width, height, count); } break; @@ -195,33 +195,33 @@ public CubemapTexture DecodeCubeMap(Stream stream, int width, int height) case GlInternalPixelFormat.CompressedRgb8Etc2: return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc4x4Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc5x4Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc5x5Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc6x5Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc6x6Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc8x5Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc8x6Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc8x8Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc10x5Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc10x6Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc10x8Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc10x10Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc12x10Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); case GlInternalPixelFormat.CompressedRgbaAstc12x12Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateCubeMap(stream, width, height); } break; diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs index 3d3b1118..ce516cda 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs @@ -159,46 +159,46 @@ public MipMap[] DecodeMipMaps(Stream stream, int width, int height, LevelIndex[] return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_4x4_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_5x4_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_5x4_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_5x5_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_5x5_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_6x5_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_6x5_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_6x6_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_6x6_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_8x5_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_8x5_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_8x6_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_8x6_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_8x8_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_10x5_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_10x5_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_10x6_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_10x6_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_10x8_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_10x8_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_10x10_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_10x10_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_12x10_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_12x10_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_12x12_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_12x12_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); + return AllocateMipMaps(memoryStream, width, height, levelIndices); } throw new NotSupportedException("The pixel format is not supported"); @@ -330,46 +330,46 @@ public CubemapTexture DecodeCubeMap(Stream stream, int width, int height, LevelI return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_4x4_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_5x4_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_5x4_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_5x5_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_5x5_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_6x5_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_6x5_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_6x6_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_6x6_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_8x5_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_8x5_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_8x6_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_8x6_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_8x8_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_10x5_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_10x5_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_10x6_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_10x6_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_10x8_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_10x8_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_10x10_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_10x10_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_12x10_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_12x10_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); case VkFormat.VK_FORMAT_ASTC_12x12_UNORM_BLOCK: case VkFormat.VK_FORMAT_ASTC_12x12_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); + return AllocateCubeMap(stream, width, height, levelIndices); } throw new NotSupportedException("The pixel format is not supported"); diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs index 6d523a8b..7f13e992 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x10.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc10x10. +/// +internal readonly struct RgbaAstc10X10 : IBlock { - /// - /// Texture compressed with RgbaAstc10x10. - /// - internal readonly struct RgbaAstc10x10 : IBlock - { - public static Size BlockSize => new(10, 10); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(10, 10); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 10; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 10; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs index 624a011a..d75b58a9 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x5.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc10x5. +/// +internal readonly struct RgbaAstc10X5 : IBlock { - /// - /// Texture compressed with RgbaAstc10x5. - /// - internal readonly struct RgbaAstc10x5 : IBlock - { - public static Size BlockSize => new(10, 5); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(10, 5); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 10; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 10; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs index 5103a308..1f041af9 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x6.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc10x6. +/// +internal readonly struct RgbaAstc10X6 : IBlock { - /// - /// Texture compressed with RgbaAstc10x6. - /// - internal readonly struct RgbaAstc10x6 : IBlock - { - public static Size BlockSize => new(10, 6); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(10, 6); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 10; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 10; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs index d7f4e3e7..d2883ded 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc10x8.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc10x8. +/// +internal readonly struct RgbaAstc10X8 : IBlock { - /// - /// Texture compressed with RgbaAstc10x8. - /// - internal readonly struct RgbaAstc10x8 : IBlock - { - public static Size BlockSize => new(10, 8); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(10, 8); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 10; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 10; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs index 1034e9f7..38a4e1aa 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x10.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc12x10. +/// +internal readonly struct RgbaAstc12X10 : IBlock { - /// - /// Texture compressed with RgbaAstc12x10. - /// - internal readonly struct RgbaAstc12x10 : IBlock - { - public static Size BlockSize => new(12, 10); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(12, 10); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 12; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 12; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs index b49889b5..0c0d9347 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc12x12.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc12x12. +/// +internal readonly struct RgbaAstc12X12 : IBlock { - /// - /// Texture compressed with RgbaAstc12x12. - /// - internal readonly struct RgbaAstc12x12 : IBlock - { - public static Size BlockSize => new(12, 12); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(12, 12); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 12; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 12; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs index 30498f55..c1068b05 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc4x4.cs @@ -1,45 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc4x4. +/// +internal readonly struct RgbaAstc4X4 : IBlock { - /// - /// Texture compressed with RgbaAstc4x4. - /// - internal readonly struct RgbaAstc4x4 : IBlock - { - // See https://developer.nvidia.com/astc-texture-compression-for-game-assets - // https://chromium.googlesource.com/external/github.com/ARM-software/astc-encoder/+/HEAD/Docs/FormatOverview.md - public static Size BlockSize => new(4, 4); - - /// - // The 2D block footprints in ASTC range from 4x4 texels up to 12x12 texels, which all compress into 128-bit output blocks. - // By dividing 128 bits by the number of texels in the footprint, we derive the format bit rates which range from 8 bpt(128/(4*4)) down to 0.89 bpt(128/(12*12)). - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + // See https://developer.nvidia.com/astc-texture-compression-for-game-assets + // https://chromium.googlesource.com/external/github.com/ARM-software/astc-encoder/+/HEAD/Docs/FormatOverview.md + public static Size BlockSize => new(4, 4); - /// - public byte PixelDepthBytes => 4; + /// + // The 2D block footprints in ASTC range from 4x4 texels up to 12x12 texels, which all compress into 128-bit output blocks. + // By dividing 128 bits by the number of texels in the footprint, we derive the format bit rates which range from 8 bpt(128/(4*4)) down to 0.89 bpt(128/(12*12)). + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 4; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 4; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs index 00d8ed35..f08b8494 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x4.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc5x4. +/// +internal readonly struct RgbaAstc5X4 : IBlock { - /// - /// Texture compressed with RgbaAstc5x4. - /// - internal readonly struct RgbaAstc5x4 : IBlock - { - public static Size BlockSize => new(5, 4); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(5, 4); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 5; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 5; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs index 22b1956f..dd7efeb7 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc5x5.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc5x5. +/// +internal readonly struct RgbaAstc5X5 : IBlock { - /// - /// Texture compressed with RgbaAstc5x5. - /// - internal readonly struct RgbaAstc5x5 : IBlock - { - public static Size BlockSize => new(5, 5); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(5, 5); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 5; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 5; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs index 5b7f058e..f28368a6 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x5.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc6x5. +/// +internal readonly struct RgbaAstc6X5 : IBlock { - /// - /// Texture compressed with RgbaAstc6x5. - /// - internal readonly struct RgbaAstc6x5 : IBlock - { - public static Size BlockSize => new(6, 5); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(6, 5); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 6; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 6; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs index da16d0e9..ce71e216 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc6x6.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc6x6. +/// +internal readonly struct RgbaAstc6X6 : IBlock { - /// - /// Texture compressed with RgbaAstc6x6. - /// - internal readonly struct RgbaAstc6x6 : IBlock - { - public static Size BlockSize => new(6, 6); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(6, 6); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 6; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 6; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs index 3ef70590..5f9b80b7 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x5.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc8x5. +/// +internal readonly struct RgbaAstc8X5 : IBlock { - /// - /// Texture compressed with RgbaAstc8x5. - /// - internal readonly struct RgbaAstc8x5 : IBlock - { - public static Size BlockSize => new(8, 5); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(8, 5); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 8; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 8; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs index 48233a7a..984859ea 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x6.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc8x6. +/// +internal readonly struct RgbaAstc8X6 : IBlock { - /// - /// Texture compressed with RgbaAstc8x6. - /// - internal readonly struct RgbaAstc8x6 : IBlock - { - public static Size BlockSize => new(8, 6); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(8, 6); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 8; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 8; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs index 603b2ffd..da93a202 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/RgbaAstc8x8.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with RgbaAstc8x8. +/// +internal readonly struct RgbaAstc8X8 : IBlock { - /// - /// Texture compressed with RgbaAstc8x8. - /// - internal readonly struct RgbaAstc8x8 : IBlock - { - public static Size BlockSize => new(8, 8); - - /// - public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); + public static Size BlockSize => new(8, 8); - /// - public byte PixelDepthBytes => 4; + /// + public int BitsPerPixel => 128 / (BlockSize.Width * BlockSize.Height); - /// - public byte DivSize => 8; + /// + public byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public byte DivSize => 8; - /// - public bool Compressed => true; + /// + public byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public bool Compressed => true; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => - AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) => + AstcDecoder.DecompressImage(blockData, width, height, BlockSize.Width, BlockSize.Height, this.CompressedBytesPerBlock); } From d8535e5f2fef55a4a92289105b70f7231a4e14f7 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:02:21 +0100 Subject: [PATCH 25/37] Remove unsupported test images --- tests/Images/Input/Ktx2/invalid_color_model_for_basislz.ktx2 | 3 --- tests/Images/Input/Ktx2/invalid_face_count_and_padding.ktx2 | 3 --- tests/Images/Input/Ktx2/invalid_mip_layout_and_padding.ktx2 | 3 --- tests/Images/Input/Ktx2/invalid_nul_on_kvd_val.ktx2 | 3 --- tests/Images/Input/Ktx2/invalid_typesize.ktx2 | 3 --- 5 files changed, 15 deletions(-) delete mode 100644 tests/Images/Input/Ktx2/invalid_color_model_for_basislz.ktx2 delete mode 100644 tests/Images/Input/Ktx2/invalid_face_count_and_padding.ktx2 delete mode 100644 tests/Images/Input/Ktx2/invalid_mip_layout_and_padding.ktx2 delete mode 100644 tests/Images/Input/Ktx2/invalid_nul_on_kvd_val.ktx2 delete mode 100644 tests/Images/Input/Ktx2/invalid_typesize.ktx2 diff --git a/tests/Images/Input/Ktx2/invalid_color_model_for_basislz.ktx2 b/tests/Images/Input/Ktx2/invalid_color_model_for_basislz.ktx2 deleted file mode 100644 index a80b8492..00000000 --- a/tests/Images/Input/Ktx2/invalid_color_model_for_basislz.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7977d20a82094ed8fb27ac0add62e6cc1e670f02908c0f8cbb70577490e49be -size 295771 diff --git a/tests/Images/Input/Ktx2/invalid_face_count_and_padding.ktx2 b/tests/Images/Input/Ktx2/invalid_face_count_and_padding.ktx2 deleted file mode 100644 index 2153f268..00000000 --- a/tests/Images/Input/Ktx2/invalid_face_count_and_padding.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89cd3d9c39221fd5b4035aafe8b0cc6187ae51959a848fd518311d9501df9aa0 -size 1398600 diff --git a/tests/Images/Input/Ktx2/invalid_mip_layout_and_padding.ktx2 b/tests/Images/Input/Ktx2/invalid_mip_layout_and_padding.ktx2 deleted file mode 100644 index 0dcdafed..00000000 --- a/tests/Images/Input/Ktx2/invalid_mip_layout_and_padding.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81db38a0a8bd4790df757ae4acb0c9275b6c03d25d101f016cf0ea9058beac1d -size 467641 diff --git a/tests/Images/Input/Ktx2/invalid_nul_on_kvd_val.ktx2 b/tests/Images/Input/Ktx2/invalid_nul_on_kvd_val.ktx2 deleted file mode 100644 index 8e98275f..00000000 --- a/tests/Images/Input/Ktx2/invalid_nul_on_kvd_val.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b88d98104588cd3fecea79499237cc971dd462ba56efb52aa25b0892d117671 -size 14514 diff --git a/tests/Images/Input/Ktx2/invalid_typesize.ktx2 b/tests/Images/Input/Ktx2/invalid_typesize.ktx2 deleted file mode 100644 index acdcd8ab..00000000 --- a/tests/Images/Input/Ktx2/invalid_typesize.ktx2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adb00d0e814c9a5bfc2515420bac7c29f91630142e672bd015d1be1d9f97863d -size 15981074 From bba744ec3628b60cef54959cb855718ac817326c Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:08:20 +0100 Subject: [PATCH 26/37] Tidy up AstcDecoder and add more guards --- .../TextureFormats/Decoding/AstcDecoder.cs | 42 ++- .../Decoding/AstcDecoderTests.cs | 333 ++++++++++++++++++ 2 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs index 6b2d39f8..548a4062 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs @@ -10,6 +10,8 @@ namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; ///

v%fcTaL{NYj%p<=}R-K{2h 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 diff --git a/tests/Images/Input/Astc/rgb_5x4.astc b/tests/Images/Input/Astc/rgb_5x4.astc deleted file mode 100644 index b9e09236a22b9790a3b31b2268aaf5f0c9df1ff8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51855 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�f&Uft**^6@ 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 Date: Wed, 4 Feb 2026 15:33:11 +0100 Subject: [PATCH 06/37] Reduce code duplication for ASTC block sizes --- .../TextureFormats/Decoding/AstcDecoder.cs | 62 +++++++++++++++++++ .../TextureFormats/Decoding/RgbaAstc10x10.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc10x5.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc10x6.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc10x8.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc12x10.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc12x12.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc4x4.cs | 49 +-------------- .../TextureFormats/Decoding/RgbaAstc5x4.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc5x5.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc6x5.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc6x6.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc8x5.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc8x6.cs | 47 +------------- .../TextureFormats/Decoding/RgbaAstc8x8.cs | 47 +------------- 15 files changed, 90 insertions(+), 632 deletions(-) diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs index f2025298..6b2d39f8 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs @@ -26,6 +26,68 @@ public static void DecodeBlock(ReadOnlySpan blockData, int blockWidth, int AstcSharp.AstcDecoder.DecompressBlock(blockData, footprint, decodedPixels); } + ///

internal static class AstcDecoder { + internal const int AstcBlockSize = 16; + /// /// Decodes an ASTC block into RGBA pixels. /// @@ -17,10 +19,21 @@ internal static class AstcDecoder /// The width of the block footprint (4-12). /// The height of the block footprint (4-12). /// The output span for decoded RGBA pixels. - /// Optional flag to indicate if the output should be in sRGB color space. + /// Thrown if blockData is not 16 bytes or decodedPixels is the wrong size. /// Thrown if the block dimensions are invalid. - public static void DecodeBlock(ReadOnlySpan blockData, int blockWidth, int blockHeight, Span decodedPixels, bool isSrgb = false) + public static void DecodeBlock(ReadOnlySpan blockData, int blockWidth, int blockHeight, Span decodedPixels) { + if (blockData.Length != AstcBlockSize) + { + throw new ArgumentException($"ASTC block data must be exactly {AstcBlockSize} bytes. Received {blockData.Length} bytes.", nameof(blockData)); + } + + int expectedDecodedSize = blockWidth * blockHeight * 4; + if (decodedPixels.Length < expectedDecodedSize) + { + throw new ArgumentException($"Output buffer must be at least {expectedDecodedSize} bytes for {blockWidth}x{blockHeight} block. Received {decodedPixels.Length} bytes.", nameof(decodedPixels)); + } + Footprint footprint = Footprint.FromFootprintType(FootprintFromDimensions(blockWidth, blockHeight)); AstcSharp.AstcDecoder.DecompressBlock(blockData, footprint, decodedPixels); @@ -36,6 +49,9 @@ public static void DecodeBlock(ReadOnlySpan blockData, int blockWidth, int /// The height of the block footprint. /// The number of compressed bytes per block. /// The decompressed RGBA pixel data. + /// Thrown if blockData is null. + /// Thrown if dimensions or block parameters are invalid. + /// Thrown if blockData length is invalid. public static byte[] DecompressImage( byte[] blockData, int width, @@ -44,8 +60,28 @@ public static byte[] DecompressImage( int blockHeight, byte compressedBytesPerBlock) { + ArgumentNullException.ThrowIfNull(blockData); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(0, width); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(0, height); + + if (compressedBytesPerBlock != AstcBlockSize) + { + throw new ArgumentOutOfRangeException(nameof(compressedBytesPerBlock), compressedBytesPerBlock, $"ASTC blocks must be {AstcBlockSize} bytes."); + } + + // Validate block dimensions (will throw if invalid) + _ = FootprintFromDimensions(blockWidth, blockHeight); + int blocksWide = (width + blockWidth - 1) / blockWidth; int blocksHigh = (height + blockHeight - 1) / blockHeight; + int totalBlocks = blocksWide * blocksHigh; + int expectedDataLength = totalBlocks * compressedBytesPerBlock; + + if (blockData.Length < expectedDataLength) + { + throw new ArgumentException($"Block data is too small. Expected at least {expectedDataLength} bytes for {width}x{height} texture with {blockWidth}x{blockHeight} blocks, but received {blockData.Length} bytes.", nameof(blockData)); + } + byte[] decompressedData = new byte[width * height * 4]; byte[] decodedBlock = new byte[blockWidth * blockHeight * 4]; @@ -105,6 +141,6 @@ private static FootprintType FootprintFromDimensions(int width, int height) (10, 10) => FootprintType.Footprint10x10, (12, 10) => FootprintType.Footprint12x10, (12, 12) => FootprintType.Footprint12x12, - _ => throw new ArgumentOutOfRangeException(nameof(width), "Invalid footprint type."), + _ => throw new ArgumentOutOfRangeException(nameof(width), $"Invalid ASTC block dimensions: {width}x{height}. Valid sizes are 4x4, 5x4, 5x5, 6x5, 6x6, 8x5, 8x6, 8x8, 10x5, 10x6, 10x8, 10x10, 12x10, 12x12."), }; } diff --git a/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs new file mode 100644 index 00000000..7145ae6c --- /dev/null +++ b/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs @@ -0,0 +1,333 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +namespace SixLabors.ImageSharp.Textures.Tests.TextureFormats.Decoding; + +[Trait("Format", "Astc")] +public class AstcDecoderTests +{ + [Theory] + [InlineData(4, 4)] + [InlineData(5, 4)] + [InlineData(5, 5)] + [InlineData(6, 5)] + [InlineData(6, 6)] + [InlineData(8, 5)] + [InlineData(8, 6)] + [InlineData(8, 8)] + [InlineData(10, 5)] + [InlineData(10, 6)] + [InlineData(10, 8)] + [InlineData(10, 10)] + [InlineData(12, 10)] + [InlineData(12, 12)] + public void DecodeBlock_WithValidBlockData_DoesNotThrow(int blockWidth, int blockHeight) + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; // ASTC blocks are always 16 bytes + byte[] decodedPixels = new byte[blockWidth * blockHeight * 4]; + + AstcDecoder.DecodeBlock(blockData, blockWidth, blockHeight, decodedPixels); + + Assert.Equal(blockWidth * blockHeight * 4, decodedPixels.Length); + } + + [Fact] + public void DecodeBlock_WithTooSmallBlockData_ThrowsArgumentException() + { + byte[] blockData = new byte[15]; // Too small + byte[] decodedPixels = new byte[4 * 4 * 4]; + + ArgumentException ex = Assert.Throws(() => + AstcDecoder.DecodeBlock(blockData, 4, 4, decodedPixels)); + + Assert.Contains("16 bytes", ex.Message); + Assert.Contains("blockData", ex.ParamName); + } + + [Fact] + public void DecodeBlock_WithTooLargeBlockData_ThrowsArgumentException() + { + byte[] blockData = new byte[17]; // Too large + byte[] decodedPixels = new byte[4 * 4 * 4]; + + ArgumentException ex = Assert.Throws(() => + AstcDecoder.DecodeBlock(blockData, 4, 4, decodedPixels)); + + Assert.Contains("16 bytes", ex.Message); + Assert.Contains("blockData", ex.ParamName); + } + + [Fact] + public void DecodeBlock_WithEmptyBlockData_ThrowsArgumentException() + { + byte[] blockData = []; + byte[] decodedPixels = new byte[4 * 4 * 4]; + + ArgumentException ex = Assert.Throws(() => + AstcDecoder.DecodeBlock(blockData, 4, 4, decodedPixels)); + + Assert.Contains("blockData", ex.ParamName); + } + + [Fact] + public void DecodeBlock_WithTooSmallOutputBuffer_ThrowsArgumentException() + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + byte[] decodedPixels = new byte[10]; // Too small for 4x4 block (needs 64 bytes) + + ArgumentException ex = Assert.Throws(() => + AstcDecoder.DecodeBlock(blockData, 4, 4, decodedPixels)); + + Assert.Contains("Output buffer", ex.Message); + Assert.Contains("decodedPixels", ex.ParamName); + } + + [Theory] + [InlineData(3, 3)] + [InlineData(4, 3)] + [InlineData(3, 4)] + [InlineData(7, 7)] + [InlineData(11, 11)] + [InlineData(13, 13)] + [InlineData(16, 16)] + public void DecodeBlock_WithInvalidBlockDimensions_ThrowsArgumentOutOfRangeException(int blockWidth, int blockHeight) + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + byte[] decodedPixels = new byte[blockWidth * blockHeight * 4]; + + ArgumentOutOfRangeException ex = Assert.Throws(() => + AstcDecoder.DecodeBlock(blockData, blockWidth, blockHeight, decodedPixels)); + + Assert.Contains("Invalid ASTC block dimensions", ex.Message); + } + + [Fact] + public void DecodeBlock_WithZeroBlockWidth_ThrowsArgumentOutOfRangeException() + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + byte[] decodedPixels = new byte[64]; + + Assert.Throws(() => + AstcDecoder.DecodeBlock(blockData, 0, 4, decodedPixels)); + } + + [Fact] + public void DecodeBlock_WithNegativeBlockWidth_ThrowsArgumentOutOfRangeException() + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + byte[] decodedPixels = new byte[64]; + + Assert.Throws(() => + AstcDecoder.DecodeBlock(blockData, -1, 4, decodedPixels)); + } + + [Fact] + public void DecodeBlock_WithNegativeBlockHeight_ThrowsArgumentOutOfRangeException() + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + byte[] decodedPixels = new byte[64]; + + Assert.Throws(() => + AstcDecoder.DecodeBlock(blockData, 4, -1, decodedPixels)); + } + + [Theory] + [InlineData(256, 256, 4, 4)] + [InlineData(512, 512, 8, 8)] + [InlineData(128, 128, 6, 6)] + [InlineData(200, 200, 10, 10)] + public void DecompressImage_WithValidParameters_ReturnsCorrectSizedArray(int width, int height, int blockWidth, int blockHeight) + { + int blocksWide = (width + blockWidth - 1) / blockWidth; + int blocksHigh = (height + blockHeight - 1) / blockHeight; + int totalBlocks = blocksWide * blocksHigh; + byte[] blockData = new byte[totalBlocks * AstcDecoder.AstcBlockSize]; + + byte[] result = AstcDecoder.DecompressImage(blockData, width, height, blockWidth, blockHeight, AstcDecoder.AstcBlockSize); + + Assert.Equal(width * height * 4, result.Length); + } + + [Fact] + public void DecompressImage_WithNullBlockData_ThrowsArgumentNullException() + { + ArgumentNullException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(null, 256, 256, 4, 4, 16)); + + Assert.Equal("blockData", ex.ParamName); + } + + [Fact] + public void DecompressImage_WithZeroWidth_ThrowsArgumentOutOfRangeException() + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + + ArgumentOutOfRangeException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(blockData, 0, 256, 4, 4, 16)); + + Assert.Equal("width", ex.ParamName); + Assert.Contains("greater than 0", ex.Message); + } + + [Fact] + public void DecompressImage_WithNegativeWidth_ThrowsArgumentOutOfRangeException() + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + + ArgumentOutOfRangeException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(blockData, -256, 256, 4, 4, 16)); + + Assert.Equal("width", ex.ParamName); + } + + [Fact] + public void DecompressImage_WithZeroHeight_ThrowsArgumentOutOfRangeException() + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + + ArgumentOutOfRangeException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(blockData, 256, 0, 4, 4, 16)); + + Assert.Equal("height", ex.ParamName); + Assert.Contains("greater than 0", ex.Message); + } + + [Fact] + public void DecompressImage_WithNegativeHeight_ThrowsArgumentOutOfRangeException() + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + + ArgumentOutOfRangeException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(blockData, 256, -256, 4, 4, 16)); + + Assert.Equal("height", ex.ParamName); + } + + [Theory] + [InlineData(15)] + [InlineData(17)] + [InlineData(32)] + [InlineData(8)] + public void DecompressImage_WithInvalidCompressedBytesPerBlock_ThrowsArgumentOutOfRangeException(byte invalidBytes) + { + byte[] blockData = new byte[invalidBytes * 64]; // 8x8 blocks for 256x256 + + ArgumentOutOfRangeException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(blockData, 256, 256, 4, 4, invalidBytes)); + + Assert.Equal("compressedBytesPerBlock", ex.ParamName); + Assert.Contains("16 bytes", ex.Message); + } + + [Theory] + [InlineData(3, 3)] + [InlineData(4, 3)] + [InlineData(7, 7)] + [InlineData(16, 16)] + public void DecompressImage_WithInvalidBlockDimensions_ThrowsArgumentOutOfRangeException(int blockWidth, int blockHeight) + { + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + + ArgumentOutOfRangeException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(blockData, 256, 256, blockWidth, blockHeight, 16)); + + Assert.Contains("Invalid ASTC block dimensions", ex.Message); + } + + [Fact] + public void DecompressImage_WithTooSmallBlockData_ThrowsArgumentException() + { + // For 256x256 with 4x4 blocks, we need 64x64 = 4096 blocks * 16 bytes = 65536 bytes + byte[] blockData = new byte[1000]; // Too small + + ArgumentException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(blockData, 256, 256, 4, 4, 16)); + + Assert.Equal("blockData", ex.ParamName); + Assert.Contains("too small", ex.Message); + } + + [Fact] + public void DecompressImage_WithEmptyBlockData_ThrowsArgumentException() + { + byte[] blockData = []; + + ArgumentException ex = Assert.Throws(() => + AstcDecoder.DecompressImage(blockData, 256, 256, 4, 4, 16)); + + Assert.Equal("blockData", ex.ParamName); + } + + [Theory] + [InlineData(257, 256)] // Width not multiple of block size + [InlineData(256, 257)] // Height not multiple of block size + [InlineData(255, 255)] // Both not multiples + [InlineData(100, 100)] // Different non-multiples + public void DecompressImage_WithNonMultipleImageSizes_ReturnExpectedSize(int width, int height) + { + int blockWidth = 4; + int blockHeight = 4; + int blocksWide = (width + blockWidth - 1) / blockWidth; + int blocksHigh = (height + blockHeight - 1) / blockHeight; + int totalBlocks = blocksWide * blocksHigh; + byte[] blockData = new byte[totalBlocks * 16]; + + byte[] result = AstcDecoder.DecompressImage(blockData, width, height, blockWidth, blockHeight, 16); + + Assert.Equal(width * height * 4, result.Length); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(2, 2)] + [InlineData(3, 3)] + public void DecompressImage_WithVerySmallImages_ReturnExpectedSize(int width, int height) + { + // Even tiny images need at least one block + byte[] blockData = new byte[AstcDecoder.AstcBlockSize]; + + byte[] result = AstcDecoder.DecompressImage(blockData, width, height, 4, 4, 16); + + Assert.Equal(width * height * 4, result.Length); + } + + [Theory] + [InlineData(4096, 4096, 4, 4)] + [InlineData(2048, 2048, 8, 8)] + public void DecompressImage_WithLargeImages_ReturnExpectedSize(int width, int height, int blockWidth, int blockHeight) + { + int blocksWide = (width + blockWidth - 1) / blockWidth; + int blocksHigh = (height + blockHeight - 1) / blockHeight; + int totalBlocks = blocksWide * blocksHigh; + byte[] blockData = new byte[totalBlocks * 16]; + + byte[] result = AstcDecoder.DecompressImage(blockData, width, height, blockWidth, blockHeight, 16); + + Assert.Equal(width * height * 4, result.Length); + } + + [Fact] + public void DecompressImage_WithExactBlockDataSize_ReturnExpectedSize() + { + // 256x256 with 4x4 blocks = 64x64 blocks = 4096 blocks * 16 bytes = 65536 bytes + byte[] blockData = new byte[65536]; + + byte[] result = AstcDecoder.DecompressImage(blockData, 256, 256, 4, 4, 16); + + Assert.Equal(256 * 256 * 4, result.Length); + } + + [Fact] + public void DecompressImage_WithExtraBlockData_ReturnExpectedSize() + { + // More data than needed should work (extra data ignored) + byte[] blockData = new byte[100000]; + + byte[] result = AstcDecoder.DecompressImage(blockData, 256, 256, 4, 4, 16); + + Assert.Equal(256 * 256 * 4, result.Length); + } + +} From 7bc09d0b3a681ee127ba59ffd4202f057fadb7cd Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 13 Feb 2026 21:49:11 +0100 Subject: [PATCH 27/37] Correctly guard image dimensions --- .../TextureFormats/Decoding/AstcDecoder.cs | 4 ++-- .../TextureFormats/Decoding/AstcDecoderTests.cs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs index 548a4062..99259857 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs @@ -61,8 +61,8 @@ public static byte[] DecompressImage( byte compressedBytesPerBlock) { ArgumentNullException.ThrowIfNull(blockData); - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(0, width); - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(0, height); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(width, 0); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(height, 0); if (compressedBytesPerBlock != AstcBlockSize) { diff --git a/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs index 7145ae6c..f98efec7 100644 --- a/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs @@ -168,7 +168,6 @@ public void DecompressImage_WithZeroWidth_ThrowsArgumentOutOfRangeException() AstcDecoder.DecompressImage(blockData, 0, 256, 4, 4, 16)); Assert.Equal("width", ex.ParamName); - Assert.Contains("greater than 0", ex.Message); } [Fact] @@ -191,7 +190,6 @@ public void DecompressImage_WithZeroHeight_ThrowsArgumentOutOfRangeException() AstcDecoder.DecompressImage(blockData, 256, 0, 4, 4, 16)); Assert.Equal("height", ex.ParamName); - Assert.Contains("greater than 0", ex.Message); } [Fact] From 4ca0d00c90e0c2ad27aa6d1bef1f0826f34e0c04 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Sat, 14 Feb 2026 11:03:18 +0100 Subject: [PATCH 28/37] Update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5be8e659..6fb3d82e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ SixLabors.ImageSharp.Textures [![Build Status](https://img.shields.io/github/actions/workflow/status/SixLabors/ImageSharp.Textures/build-and-test.yml?branch=main)](https://github.com/SixLabors/ImageSharp.Textures/actions) [![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp.Textures/branch/main/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp) -[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![License: Six Labors Split](https://img.shields.io/badge/license-Six%20Labors%20Split-%23e30183)](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=flat&logo=twitter)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) @@ -33,6 +33,7 @@ with the following compressions: - BC5 - BC6H - BC7 +- ASTC Encoding textures is **not** yet supported. PR are of course very welcome. From 1ae2ca33fa9947ce6422939b1bc0e1278594a74c Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:20:25 +0100 Subject: [PATCH 29/37] Update AstcSharp --- src/ImageSharp.Textures/ImageSharp.Textures.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Textures/ImageSharp.Textures.csproj b/src/ImageSharp.Textures/ImageSharp.Textures.csproj index bad97213..a04dd9a2 100644 --- a/src/ImageSharp.Textures/ImageSharp.Textures.csproj +++ b/src/ImageSharp.Textures/ImageSharp.Textures.csproj @@ -39,7 +39,7 @@ - + From 2b635e25b74b9b1234b45ce5ea171980e206723c Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:40:48 +0100 Subject: [PATCH 30/37] Bring AstcSharp inside ImageSharp.Textures --- ImageSharp.Textures.sln | 95 ++- src/Directory.Build.props | 2 + src/ImageSharp.Textures.Astc/AstcDecoder.cs | 394 +++++++++++ .../BiseEncoding/BiseEncodingMode.cs | 18 + .../BoundedIntegerSequenceCodec.cs | 256 +++++++ .../BoundedIntegerSequenceDecoder.cs | 138 ++++ .../BoundedIntegerSequenceEncoder.cs | 132 ++++ .../Quantize/BitQuantizationMap.cs | 49 ++ .../BiseEncoding/Quantize/Quantization.cs | 211 ++++++ .../BiseEncoding/Quantize/QuantizationMap.cs | 60 ++ .../Quantize/QuintQuantizationMap.cs | 58 ++ .../Quantize/TritQuantizationMap.cs | 68 ++ .../BlockDecoder/FusedBlockDecoder.cs | 173 +++++ .../BlockDecoder/FusedHdrBlockDecoder.cs | 215 ++++++ .../BlockDecoder/FusedLdrBlockDecoder.cs | 152 +++++ .../ColorEncoding/ColorEndpointMode.cs | 38 ++ .../ColorEndpointModeExtensions.cs | 31 + .../ColorEncoding/EndpointCodec.cs | 228 +++++++ .../ColorEncoding/EndpointEncoder.cs | 498 ++++++++++++++ .../ColorEncoding/EndpointEncodingMode.cs | 14 + .../EndpointEncodingModeExtensions.cs | 15 + .../ColorEncoding/HdrEndpointDecoder.cs | 359 ++++++++++ .../ColorEncoding/IColorEndpointPair.cs | 32 + .../ColorEncoding/Partition.cs | 165 +++++ .../ColorEncoding/RgbaColorExtensions.cs | 54 ++ .../Core/BitOperations.cs | 97 +++ .../Core/DecimationTable.cs | 145 ++++ .../Core/Footprint.cs | 70 ++ .../Core/FootprintType.cs | 39 ++ src/ImageSharp.Textures.Astc/Core/RgbColor.cs | 61 ++ .../Core/RgbaColor.cs | 58 ++ .../Core/RgbaHdrColor.cs | 51 ++ .../Core/SimdHelpers.cs | 156 +++++ .../Core/UInt128Extensions.cs | 69 ++ src/ImageSharp.Textures.Astc/IO/AstcFile.cs | 65 ++ .../IO/AstcFileHeader.cs | 41 ++ src/ImageSharp.Textures.Astc/IO/BitStream.cs | 172 +++++ .../ImageSharp.Textures.Astc.csproj | 33 + .../TexelBlock/BlockInfo.cs | 321 +++++++++ .../TexelBlock/IntermediateBlock.cs | 239 +++++++ .../TexelBlock/IntermediateBlockPacker.cs | 344 ++++++++++ .../TexelBlock/LogicalBlock.cs | 644 ++++++++++++++++++ .../TexelBlock/PhysicalBlock.cs | 176 +++++ .../TexelBlock/PhysicalBlockMode.cs | 23 + .../ImageSharp.Textures.csproj | 2 +- .../TextureFormats/Decoding/AstcDecoder.cs | 4 +- ...Sharp.Textures.Astc.Reference.Tests.csproj | 45 ++ .../ReferenceDecoderHdrTests.cs | 246 +++++++ .../ReferenceDecoderTests.cs | 266 ++++++++ .../Utils/ReferenceDecoder.cs | 230 +++++++ .../BitOperationsTests.cs | 123 ++++ .../BitStreamTests.cs | 193 ++++++ .../CodecTests.cs | 132 ++++ .../EndpointCodecTests.cs | 366 ++++++++++ .../FootprintTests.cs | 83 +++ .../HDR/HdrComparisonTests.cs | 210 ++++++ .../HDR/HdrDecoderTests.cs | 94 +++ .../HDR/HdrImageTests.cs | 146 ++++ .../HDR/RgbaHdrColorTests.cs | 116 ++++ .../ImageSharp.Textures.Astc.Tests.csproj | 30 + .../IntegerSequenceCodecTests.cs | 320 +++++++++ .../IntegrationTests.cs | 55 ++ .../IntermediateBlockTests.cs | 456 +++++++++++++ .../LogicalAstcBlockTests.cs | 392 +++++++++++ .../PartitionTests.cs | 228 +++++++ .../PhysicalAstcBlockTests.cs | 616 +++++++++++++++++ .../QuantizationTests.cs | 390 +++++++++++ .../TestData/Expected/A.png | 3 + .../Expected/FlightHelmet_baseColor.png | 3 + .../Expected/GoldenGateBridge3/filelist.txt | 6 + .../Expected/GoldenGateBridge3/negx.jpg | 3 + .../Expected/GoldenGateBridge3/negy.jpg | 3 + .../Expected/GoldenGateBridge3/negz.jpg | 3 + .../Expected/GoldenGateBridge3/posx.jpg | 3 + .../Expected/GoldenGateBridge3/posy.jpg | 3 + .../Expected/GoldenGateBridge3/posz.jpg | 3 + .../Expected/GoldenGateBridge3/readme.txt | 13 + .../Iron_Bars/Iron_Bars_001_Opacity.jpg | 3 + .../Iron_Bars_001_ambientOcclusion.jpg | 3 + .../Iron_Bars/Iron_Bars_001_basecolor.jpg | 3 + .../Iron_Bars/Iron_Bars_001_height.png | 3 + .../Iron_Bars/Iron_Bars_001_normal.jpg | 3 + .../Iron_Bars_001_normal_unnormalized.png | 3 + .../Iron_Bars/Iron_Bars_001_roughness.jpg | 3 + .../TestData/Expected/Iron_Bars/readme.txt | 11 + .../TestData/Expected/LA.png | 3 + .../TestData/Expected/R.png | 3 + .../TestData/Expected/RG.png | 3 + .../TestData/Expected/Yokohama3/filelist.txt | 6 + .../TestData/Expected/Yokohama3/negx.jpg | 3 + .../TestData/Expected/Yokohama3/negy.jpg | 3 + .../TestData/Expected/Yokohama3/negz.jpg | 3 + .../TestData/Expected/Yokohama3/posx.jpg | 3 + .../TestData/Expected/Yokohama3/posy.jpg | 3 + .../TestData/Expected/Yokohama3/posz.jpg | 3 + .../TestData/Expected/Yokohama3/readme.txt | 13 + .../TestData/Expected/atlas_small_4x4.bmp | 3 + .../TestData/Expected/atlas_small_5x5.bmp | 3 + .../TestData/Expected/atlas_small_6x6.bmp | 3 + .../TestData/Expected/atlas_small_8x8.bmp | 3 + .../TestData/Expected/footprint_10x10.bmp | 3 + .../TestData/Expected/footprint_10x5.bmp | 3 + .../TestData/Expected/footprint_10x6.bmp | 3 + .../TestData/Expected/footprint_10x8.bmp | 3 + .../TestData/Expected/footprint_12x10.bmp | 3 + .../TestData/Expected/footprint_12x12.bmp | 3 + .../TestData/Expected/footprint_4x4.bmp | 3 + .../TestData/Expected/footprint_5x4.bmp | 3 + .../TestData/Expected/footprint_5x5.bmp | 3 + .../TestData/Expected/footprint_6x5.bmp | 3 + .../TestData/Expected/footprint_6x6.bmp | 3 + .../TestData/Expected/footprint_8x5.bmp | 3 + .../TestData/Expected/footprint_8x6.bmp | 3 + .../TestData/Expected/footprint_8x8.bmp | 3 + .../TestData/Expected/rgb_12x12.bmp | 3 + .../TestData/Expected/rgb_4x4.bmp | 3 + .../TestData/Expected/rgb_5x4.bmp | 3 + .../TestData/Expected/rgb_6x6.bmp | 3 + .../TestData/Expected/rgb_8x8.bmp | 3 + .../TestData/HDR/HDR-A-1x1.astc | Bin 0 -> 32 bytes .../TestData/HDR/HDR-A-1x1.exr | 3 + .../TestData/HDR/LDR-A-1x1.astc | Bin 0 -> 32 bytes .../TestData/HDR/LDR-A-1x1.png | 3 + .../TestData/HDR/hdr-complex.exr | 3 + .../TestData/HDR/hdr-tile.astc | Bin 0 -> 80 bytes .../TestData/HDR/hdr.exr | 3 + .../TestData/HDR/hdr.hdr | Bin 0 -> 217 bytes .../TestData/HDR/ldr-tile.astc | Bin 0 -> 80 bytes .../TestData/Input/atlas_small_4x4.astc | Bin 0 -> 65551 bytes .../TestData/Input/atlas_small_5x5.astc | Bin 0 -> 43280 bytes .../TestData/Input/atlas_small_6x6.astc | Bin 0 -> 29599 bytes .../TestData/Input/atlas_small_8x8.astc | Bin 0 -> 16400 bytes .../TestData/Input/checkerboard.astc | Bin 0 -> 80 bytes .../TestData/Input/checkered_10.astc | Bin 0 -> 1616 bytes .../TestData/Input/checkered_11.astc | Bin 0 -> 1952 bytes .../TestData/Input/checkered_12.astc | Bin 0 -> 2320 bytes .../TestData/Input/checkered_4.astc | Bin 0 -> 272 bytes .../TestData/Input/checkered_5.astc | Bin 0 -> 416 bytes .../TestData/Input/checkered_6.astc | Bin 0 -> 592 bytes .../TestData/Input/checkered_7.astc | Bin 0 -> 800 bytes .../TestData/Input/checkered_8.astc | Bin 0 -> 1040 bytes .../TestData/Input/checkered_9.astc | Bin 0 -> 1312 bytes .../TestData/Input/footprint_10x10.astc | Bin 0 -> 272 bytes .../TestData/Input/footprint_10x5.astc | Bin 0 -> 464 bytes .../TestData/Input/footprint_10x6.astc | Bin 0 -> 400 bytes .../TestData/Input/footprint_10x8.astc | Bin 0 -> 272 bytes .../TestData/Input/footprint_12x10.astc | Bin 0 -> 208 bytes .../TestData/Input/footprint_12x12.astc | Bin 0 -> 160 bytes .../TestData/Input/footprint_4x4.astc | Bin 0 -> 1040 bytes .../TestData/Input/footprint_5x4.astc | Bin 0 -> 912 bytes .../TestData/Input/footprint_5x5.astc | Bin 0 -> 800 bytes .../TestData/Input/footprint_6x5.astc | Bin 0 -> 688 bytes .../TestData/Input/footprint_6x6.astc | Bin 0 -> 592 bytes .../TestData/Input/footprint_8x5.astc | Bin 0 -> 464 bytes .../TestData/Input/footprint_8x6.astc | Bin 0 -> 400 bytes .../TestData/Input/footprint_8x8.astc | Bin 0 -> 272 bytes .../TestData/Input/rgb_12x12.astc | Bin 0 -> 7312 bytes .../TestData/Input/rgb_4x4.astc | Bin 0 -> 64528 bytes .../TestData/Input/rgb_5x4.astc | Bin 0 -> 51855 bytes .../TestData/Input/rgb_6x6.astc | Bin 0 -> 29200 bytes .../TestData/Input/rgb_8x8.astc | Bin 0 -> 16144 bytes .../Utils/FileBasedHelpers.cs | 74 ++ .../Utils/ImageBuffer.cs | 84 +++ .../Utils/ImageUtils.cs | 32 + .../WeightInfillTests.cs | 48 ++ .../AstcFullImageDecodeBenchmark.cs | 38 ++ .../BenchmarkTestDataLocator.cs | 32 + .../DecodingBenchmark.cs | 68 ++ .../ImageSharp.Textures.Benchmarks.csproj | 4 + 169 files changed, 11574 insertions(+), 10 deletions(-) create mode 100644 src/ImageSharp.Textures.Astc/AstcDecoder.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/BiseEncodingMode.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/BitQuantizationMap.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuantizationMap.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs create mode 100644 src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs create mode 100644 src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs create mode 100644 src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs create mode 100644 src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointMode.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointModeExtensions.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncoder.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingMode.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingModeExtensions.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/IColorEndpointPair.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs create mode 100644 src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/BitOperations.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/DecimationTable.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/Footprint.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/FootprintType.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/RgbColor.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/RgbaColor.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/RgbaHdrColor.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs create mode 100644 src/ImageSharp.Textures.Astc/Core/UInt128Extensions.cs create mode 100644 src/ImageSharp.Textures.Astc/IO/AstcFile.cs create mode 100644 src/ImageSharp.Textures.Astc/IO/AstcFileHeader.cs create mode 100644 src/ImageSharp.Textures.Astc/IO/BitStream.cs create mode 100644 src/ImageSharp.Textures.Astc/ImageSharp.Textures.Astc.csproj create mode 100644 src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs create mode 100644 src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs create mode 100644 src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs create mode 100644 src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs create mode 100644 src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs create mode 100644 src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlockMode.cs create mode 100644 tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj create mode 100644 tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Reference.Tests/Utils/ReferenceDecoder.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/BitOperationsTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/BitStreamTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/CodecTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/EndpointCodecTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/FootprintTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/HDR/HdrComparisonTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/HDR/HdrDecoderTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/HDR/HdrImageTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/HDR/RgbaHdrColorTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/ImageSharp.Textures.Astc.Tests.csproj create mode 100644 tests/ImageSharp.Textures.Astc.Tests/IntegerSequenceCodecTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/IntegrationTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/IntermediateBlockTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/LogicalAstcBlockTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/PartitionTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/PhysicalAstcBlockTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/QuantizationTests.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/A.png create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/FlightHelmet_baseColor.png create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/filelist.txt create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negx.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negy.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negz.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posx.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posy.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posz.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/readme.txt create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_height.png create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/readme.txt create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/LA.png create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/R.png create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/RG.png create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/filelist.txt create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negx.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negy.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negz.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posx.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posy.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posz.jpg create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/readme.txt create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_4x4.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_5x5.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_6x6.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_8x8.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x10.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x5.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x6.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x8.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x10.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x12.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_4x4.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x4.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x5.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x5.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x6.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x5.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x6.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x8.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_12x12.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_4x4.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_5x4.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_6x6.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_8x8.bmp create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.exr create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.png create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-complex.exr create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-tile.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr.exr create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr.hdr create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/ldr-tile.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_4x4.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_5x5.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_6x6.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_8x8.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkerboard.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_10.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_11.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_12.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_4.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_5.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_6.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_7.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_8.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_9.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x10.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x5.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x6.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x8.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_12x10.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_12x12.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_4x4.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_5x4.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_5x5.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x5.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x6.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x5.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x6.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x8.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_12x12.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_4x4.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_5x4.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_6x6.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_8x8.astc create mode 100644 tests/ImageSharp.Textures.Astc.Tests/Utils/FileBasedHelpers.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/Utils/ImageBuffer.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/Utils/ImageUtils.cs create mode 100644 tests/ImageSharp.Textures.Astc.Tests/WeightInfillTests.cs create mode 100644 tests/ImageSharp.Textures.Benchmarks/AstcFullImageDecodeBenchmark.cs create mode 100644 tests/ImageSharp.Textures.Benchmarks/BenchmarkTestDataLocator.cs create mode 100644 tests/ImageSharp.Textures.Benchmarks/DecodingBenchmark.cs diff --git a/ImageSharp.Textures.sln b/ImageSharp.Textures.sln index 636514f8..b57b5731 100644 --- a/ImageSharp.Textures.sln +++ b/ImageSharp.Textures.sln @@ -49,35 +49,106 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Textures.Astc", "src\ImageSharp.Textures.Astc\ImageSharp.Textures.Astc.csproj", "{AE37301B-3635-4C61-A026-DEB2E1328DD1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Textures.Astc.Tests", "tests\ImageSharp.Textures.Astc.Tests\ImageSharp.Textures.Astc.Tests.csproj", "{BE98A221-C320-49AA-A7E9-3A67CC6830D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Textures.Astc.Reference.Tests", "tests\ImageSharp.Textures.Astc.Reference.Tests\ImageSharp.Textures.Astc.Reference.Tests.csproj", "{E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{1588f6c4-2186-4a35-9693-e9f296791393}*SharedItemsImports = 5 - tests\Images\Images.projitems*{17fcbd4d-d232-45e8-876f-dfbc2fad52cf}*SharedItemsImports = 5 - tests\Images\Images.projitems*{18be79b6-6b95-4ed7-a963-ad75f6cb9f3c}*SharedItemsImports = 5 - tests\Images\Images.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13 - tests\Images\Images.projitems*{b159ffd1-e646-42d0-892c-4abf69103712}*SharedItemsImports = 5 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {1588F6C4-2186-4A35-9693-E9F296791393}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1588F6C4-2186-4A35-9693-E9F296791393}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1588F6C4-2186-4A35-9693-E9F296791393}.Debug|x64.ActiveCfg = Debug|Any CPU + {1588F6C4-2186-4A35-9693-E9F296791393}.Debug|x64.Build.0 = Debug|Any CPU + {1588F6C4-2186-4A35-9693-E9F296791393}.Debug|x86.ActiveCfg = Debug|Any CPU + {1588F6C4-2186-4A35-9693-E9F296791393}.Debug|x86.Build.0 = Debug|Any CPU {1588F6C4-2186-4A35-9693-E9F296791393}.Release|Any CPU.ActiveCfg = Release|Any CPU {1588F6C4-2186-4A35-9693-E9F296791393}.Release|Any CPU.Build.0 = Release|Any CPU + {1588F6C4-2186-4A35-9693-E9F296791393}.Release|x64.ActiveCfg = Release|Any CPU + {1588F6C4-2186-4A35-9693-E9F296791393}.Release|x64.Build.0 = Release|Any CPU + {1588F6C4-2186-4A35-9693-E9F296791393}.Release|x86.ActiveCfg = Release|Any CPU + {1588F6C4-2186-4A35-9693-E9F296791393}.Release|x86.Build.0 = Release|Any CPU {B159FFD1-E646-42D0-892C-4ABF69103712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B159FFD1-E646-42D0-892C-4ABF69103712}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B159FFD1-E646-42D0-892C-4ABF69103712}.Debug|x64.ActiveCfg = Debug|Any CPU + {B159FFD1-E646-42D0-892C-4ABF69103712}.Debug|x64.Build.0 = Debug|Any CPU + {B159FFD1-E646-42D0-892C-4ABF69103712}.Debug|x86.ActiveCfg = Debug|Any CPU + {B159FFD1-E646-42D0-892C-4ABF69103712}.Debug|x86.Build.0 = Debug|Any CPU {B159FFD1-E646-42D0-892C-4ABF69103712}.Release|Any CPU.ActiveCfg = Release|Any CPU {B159FFD1-E646-42D0-892C-4ABF69103712}.Release|Any CPU.Build.0 = Release|Any CPU + {B159FFD1-E646-42D0-892C-4ABF69103712}.Release|x64.ActiveCfg = Release|Any CPU + {B159FFD1-E646-42D0-892C-4ABF69103712}.Release|x64.Build.0 = Release|Any CPU + {B159FFD1-E646-42D0-892C-4ABF69103712}.Release|x86.ActiveCfg = Release|Any CPU + {B159FFD1-E646-42D0-892C-4ABF69103712}.Release|x86.Build.0 = Release|Any CPU {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Debug|x64.ActiveCfg = Debug|Any CPU + {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Debug|x64.Build.0 = Debug|Any CPU + {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Debug|x86.ActiveCfg = Debug|Any CPU + {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Debug|x86.Build.0 = Debug|Any CPU {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Release|Any CPU.Build.0 = Release|Any CPU + {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Release|x64.ActiveCfg = Release|Any CPU + {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Release|x64.Build.0 = Release|Any CPU + {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Release|x86.ActiveCfg = Release|Any CPU + {18BE79B6-6B95-4ED7-A963-AD75F6CB9F3C}.Release|x86.Build.0 = Release|Any CPU {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Debug|x64.ActiveCfg = Debug|Any CPU + {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Debug|x64.Build.0 = Debug|Any CPU + {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Debug|x86.ActiveCfg = Debug|Any CPU + {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Debug|x86.Build.0 = Debug|Any CPU {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Release|Any CPU.Build.0 = Release|Any CPU + {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Release|x64.ActiveCfg = Release|Any CPU + {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Release|x64.Build.0 = Release|Any CPU + {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Release|x86.ActiveCfg = Release|Any CPU + {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF}.Release|x86.Build.0 = Release|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Debug|x64.ActiveCfg = Debug|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Debug|x64.Build.0 = Debug|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Debug|x86.ActiveCfg = Debug|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Debug|x86.Build.0 = Debug|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|Any CPU.Build.0 = Release|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|x64.ActiveCfg = Release|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|x64.Build.0 = Release|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|x86.ActiveCfg = Release|Any CPU + {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|x86.Build.0 = Release|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|x64.ActiveCfg = Debug|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|x64.Build.0 = Debug|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|x86.ActiveCfg = Debug|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|x86.Build.0 = Debug|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|Any CPU.Build.0 = Release|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|x64.ActiveCfg = Release|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|x64.Build.0 = Release|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|x86.ActiveCfg = Release|Any CPU + {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|x86.Build.0 = Release|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|x64.ActiveCfg = Debug|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|x64.Build.0 = Debug|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|x86.ActiveCfg = Debug|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|x86.Build.0 = Debug|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Release|Any CPU.Build.0 = Release|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Release|x64.ActiveCfg = Release|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Release|x64.Build.0 = Release|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Release|x86.ActiveCfg = Release|Any CPU + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -90,8 +161,18 @@ Global {17FCBD4D-D232-45E8-876F-DFBC2FAD52CF} = {6DF92068-B792-4038-8E3F-5FDF2E026BE7} {E6224AB7-6987-4BA1-B2A6-ECFB7DA281DE} = {9F1F0B0F-704F-4B71-89EF-EE36042A27C9} {9F19EBB4-32DB-4AFE-A5E4-722EDFAAE04B} = {E6224AB7-6987-4BA1-B2A6-ECFB7DA281DE} + {AE37301B-3635-4C61-A026-DEB2E1328DD1} = {5E6840D2-9CBB-4FDE-8378-33086CF5A8D8} + {BE98A221-C320-49AA-A7E9-3A67CC6830D0} = {6DF92068-B792-4038-8E3F-5FDF2E026BE7} + {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720} = {6DF92068-B792-4038-8E3F-5FDF2E026BE7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F1762A0D-74C4-454A-BCB7-C010BB067E58} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{1588f6c4-2186-4a35-9693-e9f296791393}*SharedItemsImports = 5 + tests\Images\Images.projitems*{17fcbd4d-d232-45e8-876f-dfbc2fad52cf}*SharedItemsImports = 5 + tests\Images\Images.projitems*{18be79b6-6b95-4ed7-a963-ad75f6cb9f3c}*SharedItemsImports = 5 + tests\Images\Images.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13 + tests\Images\Images.projitems*{b159ffd1-e646-42d0-892c-4abf69103712}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e40e9b78..5736a886 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -25,6 +25,8 @@ + + diff --git a/src/ImageSharp.Textures.Astc/AstcDecoder.cs b/src/ImageSharp.Textures.Astc/AstcDecoder.cs new file mode 100644 index 00000000..50018aaf --- /dev/null +++ b/src/ImageSharp.Textures.Astc/AstcDecoder.cs @@ -0,0 +1,394 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Buffers.Binary; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Astc.BlockDecoder; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +namespace SixLabors.ImageSharp.Textures.Astc; + +/// +/// Provides methods to decode ASTC-compressed texture data into uncompressed pixel formats. +/// +public static class AstcDecoder +{ + private static readonly ArrayPool _arrayPool = ArrayPool.Shared; + private const int BytesPerPixelUnorm8 = 4; + + /// + /// Decompresses ASTC-compressed data to uncompressed RGBA8 format (4 bytes per pixel). + /// + /// The ASTC-compressed texture data + /// Image width in pixels + /// Image height in pixels + /// The ASTC block footprint (e.g., 4x4, 5x5) + /// Array of bytes in RGBA8 format (width * height * 4 bytes total) + /// If decompression fails for any block + public static Span DecompressImage(ReadOnlySpan astcData, int width, int height, Footprint footprint) + { + var imageBuffer = new byte[width * height * BytesPerPixelUnorm8]; + + return DecompressImage(astcData, width, height, footprint, imageBuffer) + ? imageBuffer + : []; + } + + /// + /// Decompresses ASTC-compressed data to uncompressed RGBA8 format into a caller-provided buffer. + /// + /// The ASTC-compressed texture data + /// Image width in pixels + /// Image height in pixels + /// The ASTC block footprint (e.g., 4x4, 5x5) + /// Output buffer. Must be at least width * height * 4 bytes. + /// True if decompression succeeded, false if input was invalid. + /// If decompression fails for any block + public static bool DecompressImage(ReadOnlySpan astcData, int width, int height, Footprint footprint, Span imageBuffer) + { + if (!TryGetBlockLayout(astcData, width, height, footprint, out int blocksWide, out int blocksHigh)) + return false; + + var decodedBlock = Array.Empty(); + + try + { + // Create a buffer once for fallback blocks; fast path writes directly to image + decodedBlock = _arrayPool.Rent(footprint.Width * footprint.Height * BytesPerPixelUnorm8); + var decodedPixels = decodedBlock.AsSpan(); + int blockIndex = 0; + int footprintWidth = footprint.Width; + int footprintHeight = footprint.Height; + + for (int blockY = 0; blockY < blocksHigh; blockY++) + { + for (int blockX = 0; blockX < blocksWide; blockX++) + { + int blockDataOffset = blockIndex++ * PhysicalBlock.SizeInBytes; + if (blockDataOffset + PhysicalBlock.SizeInBytes > astcData.Length) + continue; + + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset)); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset + 8)); + var blockBits = new UInt128(high, low); + + int dstBaseX = blockX * footprintWidth; + int dstBaseY = blockY * footprintHeight; + int copyWidth = Math.Min(footprintWidth, width - dstBaseX); + int copyHeight = Math.Min(footprintHeight, height - dstBaseY); + + var info = BlockInfo.Decode(blockBits); + if (!info.IsValid) continue; + + // Fast path: fuse decode directly into image buffer for interior full blocks + if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane + && !info.EndpointMode0.IsHdr() + && copyWidth == footprintWidth && copyHeight == footprintHeight) + { + FusedLdrBlockDecoder.DecompressBlockFusedLdrToImage( + blockBits, in info, footprint, + dstBaseX, dstBaseY, width, imageBuffer); + continue; + } + + // Fallback: decode to temp buffer, then copy + if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane + && !info.EndpointMode0.IsHdr()) + { + FusedLdrBlockDecoder.DecompressBlockFusedLdr(blockBits, in info, footprint, decodedPixels); + } + else + { + var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); + if (logicalBlock is null) continue; + logicalBlock.WriteAllPixelsLdr(footprint, decodedPixels); + } + + int copyBytes = copyWidth * BytesPerPixelUnorm8; + for (int pixelY = 0; pixelY < copyHeight; pixelY++) + { + int srcOffset = pixelY * footprintWidth * BytesPerPixelUnorm8; + int dstOffset = ((dstBaseY + pixelY) * width + dstBaseX) * BytesPerPixelUnorm8; + decodedPixels.Slice(srcOffset, copyBytes) + .CopyTo(imageBuffer.Slice(dstOffset, copyBytes)); + } + } + } + } + finally + { + _arrayPool.Return(decodedBlock); + } + + return true; + } + + /// + /// Decompress a single ASTC block to RGBA8 pixel data + /// + /// The data to decode + /// The type of ASTC block footprint e.g. 4x4, 5x5, etc. + /// The decoded block of pixels as RGBA values + public static Span DecompressBlock(ReadOnlySpan blockData, Footprint footprint) + { + var decodedPixels = Array.Empty(); + try + { + decodedPixels = _arrayPool.Rent(footprint.Width * footprint.Height * BytesPerPixelUnorm8); + var decodedPixelBuffer = decodedPixels.AsSpan(); + + DecompressBlock(blockData, footprint, decodedPixelBuffer); + } + finally + { + _arrayPool.Return(decodedPixels); + } + + return decodedPixels; + } + + /// + /// Decompresses a single ASTC block to RGBA8 pixel data + /// + /// The data to decode + /// The type of ASTC block footprint e.g. 4x4, 5x5, etc. + /// The buffer to write the decoded pixels into + /// The decoded block of pixels as RGBA values + public static void DecompressBlock(ReadOnlySpan blockData, Footprint footprint, Span buffer) + { + // Read the 16 bytes that make up the ASTC block as a 128-bit value + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(blockData); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(blockData.Slice(8)); + var blockBits = new UInt128(high, low); + + var info = BlockInfo.Decode(blockBits); + if (!info.IsValid) return; + + // Fully fused fast path for single-partition, non-dual-plane, LDR blocks + if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane + && !info.EndpointMode0.IsHdr()) + { + FusedLdrBlockDecoder.DecompressBlockFusedLdr(blockBits, in info, footprint, buffer); + return; + } + + // Fallback for void extent, multi-partition, dual plane, HDR + var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); + if (logicalBlock is null) return; + logicalBlock.WriteAllPixelsLdr(footprint, buffer); + } + + /// + /// Decompresses ASTC-compressed data to RGBA values. + /// + /// The ASTC-compressed texture data + /// Image width in pixels + /// Image height in pixels + /// The ASTC block footprint (e.g., 4x4, 5x5) + /// + /// Values in RGBA order. For HDR content, values may exceed 1.0. + /// + public static Span DecompressHdrImage(ReadOnlySpan astcData, int width, int height, Footprint footprint) + { + const int channelsPerPixel = 4; + var imageBuffer = new float[width * height * channelsPerPixel]; + if (!DecompressHdrImage(astcData, width, height, footprint, imageBuffer)) + return []; + return imageBuffer; + } + + /// + /// Decompresses ASTC-compressed data to RGBA float values into a caller-provided buffer. + /// + /// The ASTC-compressed texture data + /// Image width in pixels + /// Image height in pixels + /// The ASTC block footprint (e.g., 4x4, 5x5) + /// Output buffer. Must be at least width * height * 4 floats. + /// True if decompression succeeded, false if input was invalid. + /// If decompression fails for any block + public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, int height, Footprint footprint, Span imageBuffer) + { + if (!TryGetBlockLayout(astcData, width, height, footprint, out int blocksWide, out int blocksHigh)) + return false; + + const int channelsPerPixel = 4; + var decodedBlock = Array.Empty(); + + try + { + // Create a buffer once for fallback blocks; fast path writes directly to image + decodedBlock = ArrayPool.Shared.Rent(footprint.Width * footprint.Height * channelsPerPixel); + var decodedPixels = decodedBlock.AsSpan(); + int blockIndex = 0; + int footprintWidth = footprint.Width; + int footprintHeight = footprint.Height; + + for (int blockY = 0; blockY < blocksHigh; blockY++) + { + for (int blockX = 0; blockX < blocksWide; blockX++) + { + int blockDataOffset = blockIndex++ * PhysicalBlock.SizeInBytes; + if (blockDataOffset + PhysicalBlock.SizeInBytes > astcData.Length) + continue; + + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset)); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset + 8)); + var blockBits = new UInt128(high, low); + + int dstBaseX = blockX * footprintWidth; + int dstBaseY = blockY * footprintHeight; + int copyWidth = Math.Min(footprintWidth, width - dstBaseX); + int copyHeight = Math.Min(footprintHeight, height - dstBaseY); + + var info = BlockInfo.Decode(blockBits); + if (!info.IsValid) continue; + + // Fast path: fuse decode directly into image buffer for interior full blocks + if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane + && copyWidth == footprintWidth && copyHeight == footprintHeight) + { + FusedHdrBlockDecoder.DecompressBlockFusedHdrToImage( + blockBits, in info, footprint, + dstBaseX, dstBaseY, width, imageBuffer); + continue; + } + + // Fused decode to temp buffer for single-partition non-dual-plane + if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane) + { + FusedHdrBlockDecoder.DecompressBlockFusedHdr(blockBits, in info, footprint, decodedPixels); + } + else + { + // Fallback: LogicalBlock path for void extent, multi-partition, dual plane + var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); + if (logicalBlock is null) continue; + for (int row = 0; row < footprintHeight; row++) + { + for (int column = 0; column < footprintWidth; ++column) + { + var pixelOffset = (footprintWidth * row * channelsPerPixel) + (column * channelsPerPixel); + logicalBlock.WriteHdrPixel(column, row, decodedPixels.Slice(pixelOffset, channelsPerPixel)); + } + } + } + + int copyFloats = copyWidth * channelsPerPixel; + for (int pixelY = 0; pixelY < copyHeight; pixelY++) + { + int srcOffset = pixelY * footprintWidth * channelsPerPixel; + int dstOffset = ((dstBaseY + pixelY) * width + dstBaseX) * channelsPerPixel; + decodedPixels.Slice(srcOffset, copyFloats) + .CopyTo(imageBuffer.Slice(dstOffset, copyFloats)); + } + } + } + } + finally + { + ArrayPool.Shared.Return(decodedBlock); + } + + return true; + } + + /// + /// Decompresses ASTC-compressed data to RGBA values. + /// + /// The ASTC-compressed texture data + /// Image width in pixels + /// Image height in pixels + /// The ASTC block footprint type + /// + /// Values in RGBA order. For HDR content, values may exceed 1.0. + /// + public static Span DecompressHdrImage(ReadOnlySpan astcData, int width, int height, FootprintType footprint) + { + var footPrint = Footprint.FromFootprintType(footprint); + return DecompressHdrImage(astcData, width, height, footPrint); + } + + /// + /// Decompresses a single ASTC block to float RGBA values. + /// + /// The 16-byte ASTC block to decode + /// The ASTC block footprint + /// The buffer to write decoded values into (must be at least footprint.Width * footprint.Height * 4 elements) + public static void DecompressHdrBlock(ReadOnlySpan blockData, Footprint footprint, Span buffer) + { + // Read the 16 bytes that make up the ASTC block as a 128-bit value + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(blockData); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(blockData.Slice(8)); + var blockBits = new UInt128(high, low); + + var info = BlockInfo.Decode(blockBits); + if (!info.IsValid) return; + + // Fused fast path for single-partition, non-dual-plane blocks + if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane) + { + FusedHdrBlockDecoder.DecompressBlockFusedHdr(blockBits, in info, footprint, buffer); + return; + } + + // Fallback for void extent, multi-partition, dual plane + var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); + if (logicalBlock is null) return; + + const int channelsPerPixel = 4; + for (int row = 0; row < footprint.Height; row++) + { + for (int column = 0; column < footprint.Width; ++column) + { + var pixelOffset = (footprint.Width * row * channelsPerPixel) + (column * channelsPerPixel); + logicalBlock.WriteHdrPixel(column, row, buffer.Slice(pixelOffset, channelsPerPixel)); + } + } + } + + internal static Span DecompressImage(AstcFile file) + { + ArgumentNullException.ThrowIfNull(file); + + return DecompressImage(file.Blocks, file.Width, file.Height, file.Footprint); + } + + internal static Span DecompressImage(ReadOnlySpan astcData, int width, int height, FootprintType footprint) + { + var footPrint = Footprint.FromFootprintType(footprint); + + return DecompressImage(astcData, width, height, footPrint); + } + + private static bool TryGetBlockLayout( + ReadOnlySpan astcData, + int width, + int height, + Footprint footprint, + out int blocksWide, + out int blocksHigh) + { + int blockWidth = footprint.Width; + int blockHeight = footprint.Height; + blocksWide = 0; + blocksHigh = 0; + + if (blockWidth == 0 || blockHeight == 0 || width == 0 || height == 0) + return false; + + blocksWide = (width + blockWidth - 1) / blockWidth; + if (blocksWide == 0) + return false; + + blocksHigh = (height + blockHeight - 1) / blockHeight; + int expectedBlockCount = blocksWide * blocksHigh; + if (astcData.Length % PhysicalBlock.SizeInBytes != 0 || astcData.Length / PhysicalBlock.SizeInBytes != expectedBlockCount) + return false; + + return true; + } +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BiseEncodingMode.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BiseEncodingMode.cs new file mode 100644 index 00000000..ae168b4b --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BiseEncodingMode.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding; + +/// +/// The encoding modes supported by BISE. +/// +/// +/// Note that the values correspond to the number of symbols in each alphabet. +/// +internal enum BiseEncodingMode +{ + Unknown = 0, + BitEncoding = 1, + TritEncoding = 3, + QuintEncoding = 5, +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs new file mode 100644 index 00000000..b20a8639 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs @@ -0,0 +1,256 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding; + +/// +/// The Bounded Integer Sequence Encoding (BISE) allows storage of character sequences using +/// arbitrary alphabets of up to 256 symbols. Each alphabet size is encoded in the most +/// space-efficient choice of bits, trits, and quints. +/// +internal partial class BoundedIntegerSequenceCodec +{ + + /// + /// The maximum number of bits needed to encode an ISE value. + /// + /// + /// The ASTC specification does not give a maximum number, however unquantized color + /// values have a maximum range of 255, meaning that we can't feasibly have more + /// than eight bits per value. + /// + private const int Log2MaxRangeForBits = 8; + + /// + /// The number of bits used after each value to store the interleaved quint block. + /// + protected static readonly int[] InterleavedQuintBits = [3, 2, 2]; + + /// + /// The number of bits used after each value to store the interleaved trit block. + /// + protected static readonly int[] InterleavedTritBits = [2, 2, 1, 2, 1]; + + /// + /// Trit encodings for BISE blocks. + /// + /// + /// + /// These tables are used to decode the blocks of values encoded using the ASTC + /// integer sequence encoding. The theory is that five trits (values that can + /// take any number in the range [0, 2]) can take on a total of 3^5 = 243 total + /// values, which can be stored in eight bits. These eight bits are used to + /// decode the five trits based on the ASTC specification in Section C.2.12. + /// + /// + /// For simplicity, we have stored a look-up table here so that we don't need + /// to implement the decoding logic. Similarly, seven bits are used to decode + /// three quints. + /// + /// + protected static readonly int[][] TritEncodings = + [ + [0,0,0,0,0], [1,0,0,0,0], [2,0,0,0,0], [0,0,2,0,0], [0,1,0,0,0], [1,1,0,0,0], [2,1,0,0,0], [1,0,2,0,0], [0,2,0,0,0], + [1,2,0,0,0], [2,2,0,0,0], [2,0,2,0,0], [0,2,2,0,0], [1,2,2,0,0], [2,2,2,0,0], [2,0,2,0,0], [0,0,1,0,0], [1,0,1,0,0], + [2,0,1,0,0], [0,1,2,0,0], [0,1,1,0,0], [1,1,1,0,0], [2,1,1,0,0], [1,1,2,0,0], [0,2,1,0,0], [1,2,1,0,0], [2,2,1,0,0], + [2,1,2,0,0], [0,0,0,2,2], [1,0,0,2,2], [2,0,0,2,2], [0,0,2,2,2], [0,0,0,1,0], [1,0,0,1,0], [2,0,0,1,0], [0,0,2,1,0], + [0,1,0,1,0], [1,1,0,1,0], [2,1,0,1,0], [1,0,2,1,0], [0,2,0,1,0], [1,2,0,1,0], [2,2,0,1,0], [2,0,2,1,0], [0,2,2,1,0], + [1,2,2,1,0], [2,2,2,1,0], [2,0,2,1,0], [0,0,1,1,0], [1,0,1,1,0], [2,0,1,1,0], [0,1,2,1,0], [0,1,1,1,0], [1,1,1,1,0], + [2,1,1,1,0], [1,1,2,1,0], [0,2,1,1,0], [1,2,1,1,0], [2,2,1,1,0], [2,1,2,1,0], [0,1,0,2,2], [1,1,0,2,2], [2,1,0,2,2], + [1,0,2,2,2], [0,0,0,2,0], [1,0,0,2,0], [2,0,0,2,0], [0,0,2,2,0], [0,1,0,2,0], [1,1,0,2,0], [2,1,0,2,0], [1,0,2,2,0], + [0,2,0,2,0], [1,2,0,2,0], [2,2,0,2,0], [2,0,2,2,0], [0,2,2,2,0], [1,2,2,2,0], [2,2,2,2,0], [2,0,2,2,0], [0,0,1,2,0], + [1,0,1,2,0], [2,0,1,2,0], [0,1,2,2,0], [0,1,1,2,0], [1,1,1,2,0], [2,1,1,2,0], [1,1,2,2,0], [0,2,1,2,0], [1,2,1,2,0], + [2,2,1,2,0], [2,1,2,2,0], [0,2,0,2,2], [1,2,0,2,2], [2,2,0,2,2], [2,0,2,2,2], [0,0,0,0,2], [1,0,0,0,2], [2,0,0,0,2], + [0,0,2,0,2], [0,1,0,0,2], [1,1,0,0,2], [2,1,0,0,2], [1,0,2,0,2], [0,2,0,0,2], [1,2,0,0,2], [2,2,0,0,2], [2,0,2,0,2], + [0,2,2,0,2], [1,2,2,0,2], [2,2,2,0,2], [2,0,2,0,2], [0,0,1,0,2], [1,0,1,0,2], [2,0,1,0,2], [0,1,2,0,2], [0,1,1,0,2], + [1,1,1,0,2], [2,1,1,0,2], [1,1,2,0,2], [0,2,1,0,2], [1,2,1,0,2], [2,2,1,0,2], [2,1,2,0,2], [0,2,2,2,2], [1,2,2,2,2], + [2,2,2,2,2], [2,0,2,2,2], [0,0,0,0,1], [1,0,0,0,1], [2,0,0,0,1], [0,0,2,0,1], [0,1,0,0,1], [1,1,0,0,1], [2,1,0,0,1], + [1,0,2,0,1], [0,2,0,0,1], [1,2,0,0,1], [2,2,0,0,1], [2,0,2,0,1], [0,2,2,0,1], [1,2,2,0,1], [2,2,2,0,1], [2,0,2,0,1], + [0,0,1,0,1], [1,0,1,0,1], [2,0,1,0,1], [0,1,2,0,1], [0,1,1,0,1], [1,1,1,0,1], [2,1,1,0,1], [1,1,2,0,1], [0,2,1,0,1], + [1,2,1,0,1], [2,2,1,0,1], [2,1,2,0,1], [0,0,1,2,2], [1,0,1,2,2], [2,0,1,2,2], [0,1,2,2,2], [0,0,0,1,1], [1,0,0,1,1], + [2,0,0,1,1], [0,0,2,1,1], [0,1,0,1,1], [1,1,0,1,1], [2,1,0,1,1], [1,0,2,1,1], [0,2,0,1,1], [1,2,0,1,1], [2,2,0,1,1], + [2,0,2,1,1], [0,2,2,1,1], [1,2,2,1,1], [2,2,2,1,1], [2,0,2,1,1], [0,0,1,1,1], [1,0,1,1,1], [2,0,1,1,1], [0,1,2,1,1], + [0,1,1,1,1], [1,1,1,1,1], [2,1,1,1,1], [1,1,2,1,1], [0,2,1,1,1], [1,2,1,1,1], [2,2,1,1,1], [2,1,2,1,1], [0,1,1,2,2], + [1,1,1,2,2], [2,1,1,2,2], [1,1,2,2,2], [0,0,0,2,1], [1,0,0,2,1], [2,0,0,2,1], [0,0,2,2,1], [0,1,0,2,1], [1,1,0,2,1], + [2,1,0,2,1], [1,0,2,2,1], [0,2,0,2,1], [1,2,0,2,1], [2,2,0,2,1], [2,0,2,2,1], [0,2,2,2,1], [1,2,2,2,1], [2,2,2,2,1], + [2,0,2,2,1], [0,0,1,2,1], [1,0,1,2,1], [2,0,1,2,1], [0,1,2,2,1], [0,1,1,2,1], [1,1,1,2,1], [2,1,1,2,1], [1,1,2,2,1], + [0,2,1,2,1], [1,2,1,2,1], [2,2,1,2,1], [2,1,2,2,1], [0,2,1,2,2], [1,2,1,2,2], [2,2,1,2,2], [2,1,2,2,2], [0,0,0,1,2], + [1,0,0,1,2], [2,0,0,1,2], [0,0,2,1,2], [0,1,0,1,2], [1,1,0,1,2], [2,1,0,1,2], [1,0,2,1,2], [0,2,0,1,2], [1,2,0,1,2], + [2,2,0,1,2], [2,0,2,1,2], [0,2,2,1,2], [1,2,2,1,2], [2,2,2,1,2], [2,0,2,1,2], [0,0,1,1,2], [1,0,1,1,2], [2,0,1,1,2], + [0,1,2,1,2], [0,1,1,1,2], [1,1,1,1,2], [2,1,1,1,2], [1,1,2,1,2], [0,2,1,1,2], [1,2,1,1,2], [2,2,1,1,2], [2,1,2,1,2], + [0,2,2,2,2], [1,2,2,2,2], [2,2,2,2,2], [2,1,2,2,2] + ]; + + /// + /// Quint encodings for BISE blocks. + /// + /// + /// See for more details. + /// + protected static readonly int[][] QuintEncodings = + [ + [0,0,0], [1,0,0], [2,0,0], [3,0,0], [4,0,0], [0,4,0], [4,4,0], [4,4,4], [0,1,0], [1,1,0], [2,1,0], [3,1,0], [4,1,0], + [1,4,0], [4,4,1], [4,4,4], [0,2,0], [1,2,0], [2,2,0], [3,2,0], [4,2,0], [2,4,0], [4,4,2], [4,4,4], [0,3,0], [1,3,0], + [2,3,0], [3,3,0], [4,3,0], [3,4,0], [4,4,3], [4,4,4], [0,0,1], [1,0,1], [2,0,1], [3,0,1], [4,0,1], [0,4,1], [4,0,4], + [0,4,4], [0,1,1], [1,1,1], [2,1,1], [3,1,1], [4,1,1], [1,4,1], [4,1,4], [1,4,4], [0,2,1], [1,2,1], [2,2,1], [3,2,1], + [4,2,1], [2,4,1], [4,2,4], [2,4,4], [0,3,1], [1,3,1], [2,3,1], [3,3,1], [4,3,1], [3,4,1], [4,3,4], [3,4,4], [0,0,2], + [1,0,2], [2,0,2], [3,0,2], [4,0,2], [0,4,2], [2,0,4], [3,0,4], [0,1,2], [1,1,2], [2,1,2], [3,1,2], [4,1,2], [1,4,2], + [2,1,4], [3,1,4], [0,2,2], [1,2,2], [2,2,2], [3,2,2], [4,2,2], [2,4,2], [2,2,4], [3,2,4], [0,3,2], [1,3,2], [2,3,2], + [3,3,2], [4,3,2], [3,4,2], [2,3,4], [3,3,4], [0,0,3], [1,0,3], [2,0,3], [3,0,3], [4,0,3], [0,4,3], [0,0,4], [1,0,4], + [0,1,3], [1,1,3], [2,1,3], [3,1,3], [4,1,3], [1,4,3], [0,1,4], [1,1,4], [0,2,3], [1,2,3], [2,2,3], [3,2,3], [4,2,3], + [2,4,3], [0,2,4], [1,2,4], [0,3,3], [1,3,3], [2,3,3], [3,3,3], [4,3,3], [3,4,3], [0,3,4], [1,3,4] + ]; + + /// + /// The maximum ranges for BISE encoding. + /// + /// + /// These are the numbers between 1 and + /// that can be represented exactly as a number in the ranges + /// [0, 2^k), [0, 3 * 2^k), and [0, 5 * 2^k). + /// + internal static readonly int[] MaxRanges = [1, 2, 3, 4, 5, 7, 9, 11, 15, 19, 23, 31, 39, 47, 63, 79, 95, 127, 159, 191, 255]; + + // Flat encoding tables: eliminates jagged array indirection + protected static readonly int[] FlatTritEncodings = FlattenEncodings(TritEncodings, 5); + protected static readonly int[] FlatQuintEncodings = FlattenEncodings(QuintEncodings, 3); + + private static readonly (BiseEncodingMode Mode, int BitCount)[] _packingModeCache = InitPackingModeCache(); + + protected BiseEncodingMode _encoding; + protected int _bitCount; + + + /// + /// Base class for ASTC integer sequence encoders and decoders. These codecs + /// operate on sequences of integers and produce bit patterns that pack the + /// integers based on the encoding scheme specified in the ASTC specification + /// Section C.2.12. + /// + /// + /// + /// The resulting bit pattern is a sequence of encoded blocks. + /// All blocks in a sequence are one of the following encodings: + /// + /// + /// Bit encoding: one encoded value of the form 2^k + /// Trit encoding: five encoded values of the form 3*2^k + /// Quint encoding: three encoded values of the form 5*2^k + /// + /// The layouts of each block are designed such that the blocks can be truncated + /// during encoding in order to support variable length input sequences (i.e. a + /// sequence of values that are encoded using trit encoded blocks does not + /// need to have a multiple-of-five length). + /// + /// Creates a decoder that decodes values within [0, ] (inclusive). + protected BoundedIntegerSequenceCodec(int range) + { + var (encodingMode, bitCount) = GetPackingModeBitCount(range); + _encoding = encodingMode; + _bitCount = bitCount; + } + + + /// + /// The number of bits needed to encode the given number of values with respect to the + /// number of trits, quints, and bits specified by . + /// + public static (BiseEncodingMode Mode, int BitCount) GetPackingModeBitCount(int range) + { + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(range, 0); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(range, 1 << Log2MaxRangeForBits); + + return _packingModeCache[range]; + } + + /// + /// Returns the overall bit count for a range of values encoded + /// + public static int GetBitCount(BiseEncodingMode encodingMode, int valuesCount, int bitCount) + { + var encodingBitCount = encodingMode switch + { + BiseEncodingMode.TritEncoding => ((valuesCount * 8) + 4) / 5, + BiseEncodingMode.QuintEncoding => ((valuesCount * 7) + 2) / 3, + BiseEncodingMode.BitEncoding => 0, + _ => throw new ArgumentOutOfRangeException(nameof(encodingMode), "Invalid encoding mode"), + }; + var baseBitCount = valuesCount * bitCount; + + return encodingBitCount + baseBitCount; + } + + /// + /// The number of bits needed to encode a given number of values within the range [0, ] (inclusive). + /// + public static int GetBitCountForRange(int valuesCount, int range) + { + var (mode, bitCount) = GetPackingModeBitCount(range); + + return GetBitCount(mode, valuesCount, bitCount); + } + + + /// + /// The size of a single ISE block in bits + /// + protected int GetEncodedBlockSize() + { + var (blockSize, extraBlockSize) = _encoding switch + { + BiseEncodingMode.TritEncoding => (5, 8), + BiseEncodingMode.QuintEncoding => (3, 7), + BiseEncodingMode.BitEncoding => (1, 0), + _ => (0, 0), + }; + + return extraBlockSize + blockSize * _bitCount; + } + + + private static int[] FlattenEncodings(int[][] jagged, int stride) + { + var flat = new int[jagged.Length * stride]; + for (int i = 0; i < jagged.Length; i++) + { + for (int j = 0; j < stride; j++) + flat[i * stride + j] = jagged[i][j]; + } + return flat; + } + + private static (BiseEncodingMode, int)[] InitPackingModeCache() + { + var cache = new (BiseEncodingMode, int)[1 << Log2MaxRangeForBits]; + // Precompute for all valid ranges [1, 255] + for (int range = 1; range < cache.Length; range++) + { + int index = -1; + for (int i = 0; i < MaxRanges.Length; i++) + { + if (MaxRanges[i] >= range) { index = i; break; } + } + int maxValue = index < 0 + ? MaxRanges[MaxRanges.Length - 1] + 1 + : MaxRanges[index] + 1; + + // Check QuintEncoding (5), TritEncoding (3), BitEncoding (1) in descending order + BiseEncodingMode encodingMode = BiseEncodingMode.Unknown; + ReadOnlySpan modes = [BiseEncodingMode.QuintEncoding, BiseEncodingMode.TritEncoding, BiseEncodingMode.BitEncoding]; + foreach (var em in modes) + { + if (maxValue % (int)em == 0 && int.IsPow2(maxValue / (int)em)) + { + encodingMode = em; + break; + } + } + + if (encodingMode == BiseEncodingMode.Unknown) + throw new InvalidOperationException($"Invalid range for BISE encoding: {range}"); + + cache[range] = (encodingMode, int.Log2(maxValue / (int)encodingMode)); + } + return cache; + } +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs new file mode 100644 index 00000000..aac0547f --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs @@ -0,0 +1,138 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.IO; + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding; + +internal sealed class BoundedIntegerSequenceDecoder : BoundedIntegerSequenceCodec +{ + + private static readonly BoundedIntegerSequenceDecoder?[] _cache = new BoundedIntegerSequenceDecoder?[256]; + + + public BoundedIntegerSequenceDecoder(int range) : base(range) { } + + + public static BoundedIntegerSequenceDecoder GetCached(int range) + { + var decoder = _cache[range]; + if (decoder is null) + { + decoder = new BoundedIntegerSequenceDecoder(range); + _cache[range] = decoder; + } + return decoder; + } + + /// + /// Decode a sequence of bounded integers into a caller-provided span. + /// + /// The number of values to decode. + /// The source of values to decode from. + /// The span to write decoded values into. + /// + /// + public void Decode(int valuesCount, ref BitStream bitSource, Span result) + { + int totalBitCount = GetBitCount(_encoding, valuesCount, _bitCount); + int bitsPerBlock = GetEncodedBlockSize(); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(bitsPerBlock, 64); + + Span blockResult = stackalloc int[5]; + int resultIndex = 0; + int bitsRemaining = totalBitCount; + + while (bitsRemaining > 0) + { + int bitsToRead = Math.Min(bitsRemaining, bitsPerBlock); + if (!bitSource.TryGetBits(bitsToRead, out ulong blockBits)) + throw new InvalidOperationException("Not enough bits in BitStream to decode BISE block"); + + if (_encoding == BiseEncodingMode.BitEncoding) + { + if (resultIndex < valuesCount) + result[resultIndex++] = (int)blockBits; + } + else + { + int decoded = DecodeISEBlock(_encoding, blockBits, _bitCount, blockResult); + for (int i = 0; i < decoded && resultIndex < valuesCount; ++i) + result[resultIndex++] = blockResult[i]; + } + + bitsRemaining -= bitsPerBlock; + } + + if (resultIndex < valuesCount) + throw new InvalidOperationException("Decoded fewer values than expected from BISE block"); + } + + /// + /// Decode a sequence of bounded integers. The number of bits read is dependent on the number + /// of bits required to encode based on the calculation provided + /// in Section C.2.22 of the ASTC specification. + /// + /// The number of values to decode. + /// The source of values to decode from. + /// The decoded values. The collection always contains exactly elements. + /// + /// + public int[] Decode(int valuesCount, ref BitStream bitSource) + { + var result = new int[valuesCount]; + Decode(valuesCount, ref bitSource, result); + return result; + } + + /// + /// Decode a trit/quint block into a caller-provided span. + /// Returns the number of values written. + /// Uses direct bit extraction (no BitStream) and flat encoding tables. + /// + public static int DecodeISEBlock(BiseEncodingMode mode, ulong encodedBlock, int encodedBitCount, Span result) + { + ulong mantissaMask = (1UL << encodedBitCount) - 1; + + if (mode == BiseEncodingMode.TritEncoding) + { + // 5 values, interleaved bits = [2, 2, 1, 2, 1] = 8 bits total + int bitPosition = 0; + int mantissa0 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; + ulong encodedTrits = (encodedBlock >> bitPosition) & 0x3; bitPosition += 2; + int mantissa1 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; + encodedTrits |= ((encodedBlock >> bitPosition) & 0x3) << 2; bitPosition += 2; + int mantissa2 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; + encodedTrits |= ((encodedBlock >> bitPosition) & 0x1) << 4; bitPosition += 1; + int mantissa3 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; + encodedTrits |= ((encodedBlock >> bitPosition) & 0x3) << 5; bitPosition += 2; + int mantissa4 = (int)((encodedBlock >> bitPosition) & mantissaMask); + encodedTrits |= ((encodedBlock >> (bitPosition + encodedBitCount)) & 0x1) << 7; + + int tritTableBase = (int)encodedTrits * 5; + result[0] = (FlatTritEncodings[tritTableBase] << encodedBitCount) | mantissa0; + result[1] = (FlatTritEncodings[tritTableBase + 1] << encodedBitCount) | mantissa1; + result[2] = (FlatTritEncodings[tritTableBase + 2] << encodedBitCount) | mantissa2; + result[3] = (FlatTritEncodings[tritTableBase + 3] << encodedBitCount) | mantissa3; + result[4] = (FlatTritEncodings[tritTableBase + 4] << encodedBitCount) | mantissa4; + return 5; + } + else + { + // 3 values, interleaved bits = [3, 2, 2] = 7 bits total + int bitPosition = 0; + int mantissa0 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; + ulong encodedQuints = (encodedBlock >> bitPosition) & 0x7; bitPosition += 3; + int mantissa1 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; + encodedQuints |= ((encodedBlock >> bitPosition) & 0x3) << 3; bitPosition += 2; + int mantissa2 = (int)((encodedBlock >> bitPosition) & mantissaMask); + encodedQuints |= ((encodedBlock >> (bitPosition + encodedBitCount)) & 0x3) << 5; + + int quintTableBase = (int)encodedQuints * 3; + result[0] = (FlatQuintEncodings[quintTableBase] << encodedBitCount) | mantissa0; + result[1] = (FlatQuintEncodings[quintTableBase + 1] << encodedBitCount) | mantissa1; + result[2] = (FlatQuintEncodings[quintTableBase + 2] << encodedBitCount) | mantissa2; + return 3; + } + } +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs new file mode 100644 index 00000000..70f4ff3d --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs @@ -0,0 +1,132 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.IO; + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding; + +internal sealed class BoundedIntegerSequenceEncoder : BoundedIntegerSequenceCodec +{ + private readonly List _values = []; + + public BoundedIntegerSequenceEncoder(int range) : base(range) { } + + /// + /// Adds a value to the encoding sequence. + /// + public void AddValue(int val) => _values.Add(val); + + /// + /// Encodes and writes the stored values encoding to the sink. Repeated calls will produce the same result. + /// + public void Encode(ref BitStream bitSink) + { + int totalBitCount = GetBitCount(_encoding, _values.Count, _bitCount); + + int index = 0; + int bitsWrittenCount = 0; + while (index < _values.Count) + { + switch (_encoding) + { + case BiseEncodingMode.TritEncoding: + var trits = new List(); + for (int i = 0; i < 5; ++i) + { + if (index < _values.Count) trits.Add(_values[index++]); + else trits.Add(0); + } + EncodeISEBlock(trits, _bitCount, ref bitSink, ref bitsWrittenCount, totalBitCount); + break; + case BiseEncodingMode.QuintEncoding: + var quints = new List(); + for (int i = 0; i < 3; ++i) + { + var value = index < _values.Count + ? _values[index++] + : 0; + quints.Add(value); + } + EncodeISEBlock(quints, _bitCount, ref bitSink, ref bitsWrittenCount, totalBitCount); + break; + case BiseEncodingMode.BitEncoding: + bitSink.PutBits((uint)_values[index++], GetEncodedBlockSize()); + break; + } + } + } + + /// + /// Clear the stored values. + /// + public void Reset() => _values.Clear(); + + private static void EncodeISEBlock(List values, int bitsPerValue, ref BitStream bitSink, ref int bitsWritten, int totalBitCount) where T : unmanaged + { + int valueCount = values.Count; + int valueRange = (valueCount == 3) ? 5 : 3; + int bitsPerBlock = (valueRange == 5) ? 7 : 8; + int[] interleavedBits = (valueRange == 5) + ? InterleavedQuintBits + : InterleavedTritBits; + + var nonBitComponents = new int[valueCount]; + var bitComponents = new int[valueCount]; + for (int i = 0; i < valueCount; ++i) + { + bitComponents[i] = values[i] & ((1 << bitsPerValue) - 1); + nonBitComponents[i] = values[i] >> bitsPerValue; + } + + // Determine how many interleaved bits for this block given the global + // totalBitCount and how many bits have already been written. + int tempBitsAdded = bitsWritten; + int encodedBitCount = 0; + for (int i = 0; i < valueCount; ++i) + { + tempBitsAdded += bitsPerValue; + if (tempBitsAdded >= totalBitCount) break; + encodedBitCount += interleavedBits[i]; + tempBitsAdded += interleavedBits[i]; + if (tempBitsAdded >= totalBitCount) break; + } + + int nonBitEncoding = -1; + for (int j = (1 << encodedBitCount) - 1; j >= 0; --j) + { + bool matches = true; + for (int i = 0; i < valueCount; ++i) + { + if (valueRange == 5) + { + if (QuintEncodings[j][i] != nonBitComponents[i]) { matches = false; break; } + } + else + { + if (TritEncodings[j][i] != nonBitComponents[i]) { matches = false; break; } + } + } + if (matches) { nonBitEncoding = j; break; } + } + + if (nonBitEncoding < 0) throw new InvalidOperationException(); + + int nonBitEncodingCopy = nonBitEncoding; + for (int i = 0; i < valueCount; ++i) + { + if (bitsWritten + bitsPerValue <= totalBitCount) + { + bitSink.PutBits((uint)bitComponents[i], bitsPerValue); + bitsWritten += bitsPerValue; + } + int interleavedBitCount = interleavedBits[i]; + int interleavedBitsValue = nonBitEncodingCopy & ((1 << interleavedBitCount) - 1); + if (bitsWritten + interleavedBitCount <= totalBitCount) + { + bitSink.PutBits((uint)interleavedBitsValue, interleavedBitCount); + bitsWritten += interleavedBitCount; + nonBitEncodingCopy >>= interleavedBitCount; + } + } + } +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/BitQuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/BitQuantizationMap.cs new file mode 100644 index 00000000..cc43474e --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/BitQuantizationMap.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; + +internal sealed class BitQuantizationMap : QuantizationMap +{ + // TotalUnquantizedBits is 8 for endpoint values and 6 for weights + public BitQuantizationMap(int range, int totalUnquantizedBits) + { + // ensure range+1 is power of two + ArgumentOutOfRangeException.ThrowIfNotEqual(CountOnes(range + 1), 1); + + int bitCount = Log2Floor(range + 1); + + for (int bits = 0; bits <= range; bits++) + { + int unquantized = bits; + int unquantizedBitCount = bitCount; + while (unquantizedBitCount < totalUnquantizedBits) + { + int destinationShiftUp = Math.Min(bitCount, totalUnquantizedBits - unquantizedBitCount); + int sourceShiftDown = bitCount - destinationShiftUp; + unquantized <<= destinationShiftUp; + unquantized |= bits >> sourceShiftDown; + unquantizedBitCount += destinationShiftUp; + } + if (unquantizedBitCount != totalUnquantizedBits) throw new InvalidOperationException(); + _unquantizationMapBuilder.Add(unquantized); + + if (bits > 0) + { + int previousUnquantized = _unquantizationMapBuilder[bits - 1]; + while (_quantizationMapBuilder.Count <= (previousUnquantized + unquantized) / 2) + _quantizationMapBuilder.Add(bits - 1); + } + while (_quantizationMapBuilder.Count <= unquantized) _quantizationMapBuilder.Add(bits); + } + + Freeze(); + } + + private static int CountOnes(int value) + { + int count = 0; + while (value != 0) { count += value & 1; value >>= 1; } + return count; + } +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs new file mode 100644 index 00000000..d82e1919 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs @@ -0,0 +1,211 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; + +internal static class Quantization +{ + public const int EndpointRangeMinValue = 5; + public const int WeightRangeMaxValue = 31; + + private static readonly SortedDictionary _endpointMaps = InitEndpointMaps(); + private static readonly SortedDictionary _weightMaps = InitWeightMaps(); + + // Flat lookup tables indexed by range value for O(1) access. + // Each slot maps to the QuantizationMap for the greatest supported range <= that index. + private static readonly QuantizationMap?[] _endpointMapByRange = InitEndpointMapFlat(); + private static readonly QuantizationMap?[] _weightMapByRange = InitWeightMapFlat(); + + // Pre-computed flat tables for weight unquantization: entry[quantizedValue] = final unquantized weight. + // Includes the dq > 32 -> dq + 1 adjustment. Indexed by weight range. + // Valid ranges: 1, 2, 3, 4, 5, 7, 9, 11, 15, 19, 23, 31 + private static readonly int[]?[] _unquantizeWeightsFlat = InitializeUnquantizeWeightsFlat(); + + // Pre-computed flat tables for endpoint unquantization. + // Indexed by range value. Valid ranges: 5, 7, 9, 11, 15, 19, 23, 31, 39, 47, 63, 79, 95, 127, 159, 191, 255 + private static readonly int[]?[] _unquantizeEndpointsFlat = InitializeUnquantizeEndpointsFlat(); + + public static int QuantizeCEValueToRange(int value, int rangeMaxValue) + { + ArgumentOutOfRangeException.ThrowIfLessThan(rangeMaxValue, EndpointRangeMinValue); + ArgumentOutOfRangeException.ThrowIfGreaterThan(rangeMaxValue, byte.MaxValue); + ArgumentOutOfRangeException.ThrowIfLessThan(value, 0); + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, byte.MaxValue); + + var map = GetQuantMapForValueRange(rangeMaxValue); + return map != null ? map.Quantize(value) : 0; + } + + public static int UnquantizeCEValueFromRange(int value, int rangeMaxValue) + { + ArgumentOutOfRangeException.ThrowIfLessThan(rangeMaxValue, EndpointRangeMinValue); + ArgumentOutOfRangeException.ThrowIfGreaterThan(rangeMaxValue, byte.MaxValue); + ArgumentOutOfRangeException.ThrowIfLessThan(value, 0); + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, rangeMaxValue); + + var map = GetQuantMapForValueRange(rangeMaxValue); + return map != null ? map.Unquantize(value) : 0; + } + + public static int QuantizeWeightToRange(int weight, int rangeMaxValue) + { + ArgumentOutOfRangeException.ThrowIfLessThan(rangeMaxValue, 1); + ArgumentOutOfRangeException.ThrowIfGreaterThan(rangeMaxValue, WeightRangeMaxValue); + ArgumentOutOfRangeException.ThrowIfLessThan(weight, 0); + ArgumentOutOfRangeException.ThrowIfGreaterThan(weight, 64); + + if (weight > 33) weight -= 1; + var map = GetQuantMapForWeightRange(rangeMaxValue); + return map != null ? map.Quantize(weight) : 0; + } + + public static int UnquantizeWeightFromRange(int weight, int rangeMaxValue) + { + ArgumentOutOfRangeException.ThrowIfLessThan(rangeMaxValue, 1); + ArgumentOutOfRangeException.ThrowIfGreaterThan(rangeMaxValue, WeightRangeMaxValue); + ArgumentOutOfRangeException.ThrowIfLessThan(weight, 0); + ArgumentOutOfRangeException.ThrowIfGreaterThan(weight, rangeMaxValue); + + var map = GetQuantMapForWeightRange(rangeMaxValue); + int dequantized = map != null ? map.Unquantize(weight) : 0; + if (dequantized > 32) dequantized += 1; + return dequantized; + } + + /// + /// Batch unquantize: uses pre-computed flat table for O(1) lookup per value. + /// No per-call validation, no conditional branch per weight. + /// + internal static void UnquantizeWeightsBatch(Span weights, int count, int range) + { + var table = _unquantizeWeightsFlat[range]; + if (table == null) return; + for (int i = 0; i < count; i++) + { + weights[i] = table[weights[i]]; + } + } + + /// + /// Batch unquantize color endpoint values: uses pre-computed flat table. + /// No per-call validation, single array lookup per value. + /// + internal static void UnquantizeCEValuesBatch(Span values, int count, int rangeMaxValue) + { + var table = _unquantizeEndpointsFlat[rangeMaxValue]; + if (table == null) return; + for (int i = 0; i < count; i++) + { + values[i] = table[values[i]]; + } + } + + private static SortedDictionary InitEndpointMaps() + { + var d = new SortedDictionary + { + { 5, new TritQuantizationMap(5, TritQuantizationMap.GetUnquantizedValue) }, + { 7, new BitQuantizationMap(7, 8) }, + { 9, new QuintQuantizationMap(9, QuintQuantizationMap.GetUnquantizedValue) }, + { 11, new TritQuantizationMap(11, TritQuantizationMap.GetUnquantizedValue) }, + { 15, new BitQuantizationMap(15, 8) }, + { 19, new QuintQuantizationMap(19, QuintQuantizationMap.GetUnquantizedValue) }, + { 23, new TritQuantizationMap(23, TritQuantizationMap.GetUnquantizedValue) }, + { 31, new BitQuantizationMap(31, 8) }, + { 39, new QuintQuantizationMap(39, QuintQuantizationMap.GetUnquantizedValue) }, + { 47, new TritQuantizationMap(47, TritQuantizationMap.GetUnquantizedValue) }, + { 63, new BitQuantizationMap(63, 8) }, + { 79, new QuintQuantizationMap(79, QuintQuantizationMap.GetUnquantizedValue) }, + { 95, new TritQuantizationMap(95, TritQuantizationMap.GetUnquantizedValue) }, + { 127, new BitQuantizationMap(127, 8) }, + { 159, new QuintQuantizationMap(159, QuintQuantizationMap.GetUnquantizedValue) }, + { 191, new TritQuantizationMap(191, TritQuantizationMap.GetUnquantizedValue) }, + { 255, new BitQuantizationMap(255, 8) } + }; + return d; + } + + private static SortedDictionary InitWeightMaps() + { + var d = new SortedDictionary + { + { 1, new BitQuantizationMap(1, 6) }, + { 2, new TritQuantizationMap(2, TritQuantizationMap.GetUnquantizedWeight) }, + { 3, new BitQuantizationMap(3, 6) }, + { 4, new QuintQuantizationMap(4, QuintQuantizationMap.GetUnquantizedWeight) }, + { 5, new TritQuantizationMap(5, TritQuantizationMap.GetUnquantizedWeight) }, + { 7, new BitQuantizationMap(7, 6) }, + { 9, new QuintQuantizationMap(9, QuintQuantizationMap.GetUnquantizedWeight) }, + { 11, new TritQuantizationMap(11, TritQuantizationMap.GetUnquantizedWeight) }, + { 15, new BitQuantizationMap(15, 6) }, + { 19, new QuintQuantizationMap(19, QuintQuantizationMap.GetUnquantizedWeight) }, + { 23, new TritQuantizationMap(23, TritQuantizationMap.GetUnquantizedWeight) }, + { 31, new BitQuantizationMap(31, 6) } + }; + return d; + } + + private static QuantizationMap?[] BuildFlatLookup(SortedDictionary maps, int size) + { + var flat = new QuantizationMap?[size]; + QuantizationMap? current = null; + for (int i = 0; i < size; i++) + { + if (maps.TryGetValue(i, out var map)) + current = map; + flat[i] = current; + } + return flat; + } + + private static QuantizationMap?[] InitEndpointMapFlat() + => BuildFlatLookup(InitEndpointMaps(), 256); + + private static QuantizationMap?[] InitWeightMapFlat() + => BuildFlatLookup(InitWeightMaps(), 32); + + private static QuantizationMap? GetQuantMapForValueRange(int r) + { + if ((uint)r >= (uint)_endpointMapByRange.Length) return null; + return _endpointMapByRange[r]; + } + + private static QuantizationMap? GetQuantMapForWeightRange(int r) + { + if ((uint)r >= (uint)_weightMapByRange.Length) return null; + return _weightMapByRange[r]; + } + + private static int[]?[] InitializeUnquantizeWeightsFlat() + { + var tables = new int[]?[WeightRangeMaxValue + 1]; + foreach (var kvp in _weightMaps) + { + int range = kvp.Key; + var map = kvp.Value; + var table = new int[range + 1]; + for (int i = 0; i <= range; i++) + { + int dequantized = map.Unquantize(i); + table[i] = dequantized > 32 ? dequantized + 1 : dequantized; + } + tables[range] = table; + } + return tables; + } + + private static int[]?[] InitializeUnquantizeEndpointsFlat() + { + var tables = new int[]?[256]; + foreach (var kvp in _endpointMaps) + { + int range = kvp.Key; + var map = kvp.Value; + var table = new int[range + 1]; + for (int i = 0; i <= range; i++) + table[i] = map.Unquantize(i); + tables[range] = table; + } + return tables; + } +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuantizationMap.cs new file mode 100644 index 00000000..ac39fad5 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuantizationMap.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; + +internal class QuantizationMap +{ + protected List _quantizationMapBuilder = []; + protected List _unquantizationMapBuilder = []; + + // Flat arrays for O(1) lookup on the hot path (set by Freeze) + private int[] _quantizationMap = []; + private int[] _unquantizationMap = []; + + public int Quantize(int x) + => (uint)x < (uint)_quantizationMap.Length + ? _quantizationMap[x] + : 0; + + public int Unquantize(int x) + => (uint)x < (uint)_unquantizationMap.Length + ? _unquantizationMap[x] + : 0; + + internal static int Log2Floor(int value) + { + int result = 0; + while ((1 << (result + 1)) <= value) result++; + return result; + } + + /// + /// Converts builder lists to flat arrays. Called after construction is complete. + /// + protected void Freeze() + { + _unquantizationMap = [.. _unquantizationMapBuilder]; + _quantizationMap = [.. _quantizationMapBuilder]; + _unquantizationMapBuilder = []; + _quantizationMapBuilder = []; + } + + protected void GenerateQuantizationMap() + { + if (_unquantizationMapBuilder.Count <= 1) return; + _quantizationMapBuilder.Clear(); + for (int i = 0; i < 256; ++i) + { + int bestIndex = 0; + int bestScore = int.MaxValue; + for (int index = 0; index < _unquantizationMapBuilder.Count; ++index) + { + int diff = i - _unquantizationMapBuilder[index]; + int score = diff * diff; + if (score < bestScore) { bestIndex = index; bestScore = score; } + } + _quantizationMapBuilder.Add(bestIndex); + } + } +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs new file mode 100644 index 00000000..8a89c692 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; + +internal sealed class QuintQuantizationMap : QuantizationMap +{ + public QuintQuantizationMap(int range, Func unquantFunc) + { + ArgumentOutOfRangeException.ThrowIfNotEqual((range + 1) % 5, 0); + + int bitsPowerOfTwo = (range + 1) / 5; + int bitCount = bitsPowerOfTwo == 0 ? 0 : Log2Floor(bitsPowerOfTwo); + + for (int quint = 0; quint < 5; ++quint) + for (int bits = 0; bits < (1 << bitCount); ++bits) + _unquantizationMapBuilder.Add(unquantFunc(quint, bits, range)); + + GenerateQuantizationMap(); + Freeze(); + } + + internal static int GetUnquantizedValue(int quint, int bits, int range) + { + int a = (bits & 1) != 0 ? 0x1FF : 0; + var (b, c) = range switch + { + 9 => (0, 113), + 19 => ((bits >> 1) & 0x1) is var x ? ((x << 2) | (x << 3) | (x << 8), 54) : default, + 39 => ((bits >> 1) & 0x3) is var x ? ((x >> 1) | (x << 1) | (x << 7), 26) : default, + 79 => ((bits >> 1) & 0x7) is var x ? ((x >> 1) | (x << 6), 13) : default, + 159 => ((bits >> 1) & 0xF) is var x ? ((x >> 3) | (x << 5), 6) : default, + _ => throw new ArgumentException("Illegal quint encoding") + }; + int t = quint * c + b; + t ^= a; + t = (a & 0x80) | (t >> 2); + return t; + } + + internal static int GetUnquantizedWeight(int quint, int bits, int range) + { + if (range == 4) + return new[] { 0, 16, 32, 47, 63 }[quint]; + + int a = (bits & 1) != 0 ? 0x7F : 0; + var (b, c) = range switch + { + 9 => (0, 28), + 19 => ((bits >> 1) & 0x1) is var x ? ((x << 1) | (x << 6), 13) : default, + _ => throw new ArgumentException("Illegal quint encoding") + }; + int t = quint * c + b; + t ^= a; + t = (a & 0x20) | (t >> 2); + return t; + } +} diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs new file mode 100644 index 00000000..74966ade --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; + +internal sealed class TritQuantizationMap : QuantizationMap +{ + public TritQuantizationMap(int range, Func unquantFunc) + { + ArgumentOutOfRangeException.ThrowIfNotEqual((range + 1) % 3, 0); + + int bitsPowerOfTwo = (range + 1) / 3; + int bitCount = bitsPowerOfTwo == 0 ? 0 : Log2Floor(bitsPowerOfTwo); + + for (int trit = 0; trit < 3; ++trit) + for (int bits = 0; bits < (1 << bitCount); ++bits) + _unquantizationMapBuilder.Add(unquantFunc(trit, bits, range)); + + GenerateQuantizationMap(); + Freeze(); + } + + internal static int GetUnquantizedValue(int trit, int bits, int range) + { + int a = (bits & 1) != 0 ? 0x1FF : 0; + var (b, c) = range switch + { + 5 => (0, 204), + 11 => ((bits >> 1) & 0x1) is var x ? ((x << 1) | (x << 2) | (x << 4) | (x << 8), 93) : default, + 23 => ((bits >> 1) & 0x3) is var x ? (x | (x << 2) | (x << 7), 44) : default, + 47 => ((bits >> 1) & 0x7) is var x ? (x | (x << 6), 22) : default, + 95 => ((bits >> 1) & 0xF) is var x ? ((x >> 2) | (x << 5), 11) : default, + 191 => ((bits >> 1) & 0x1F) is var x ? ((x >> 4) | (x << 4), 5) : default, + _ => throw new ArgumentException("Illegal trit encoding") + }; + int t = trit * c + b; + t ^= a; + t = (a & 0x80) | (t >> 2); + return t; + } + + internal static int GetUnquantizedWeight(int trit, int bits, int range) + { + if (range == 2) + return trit switch + { + 0 => 0, + 1 => 32, + _ => 63 + }; + + int a = (bits & 1) != 0 ? 0x7F : 0; + var (b, c) = range switch + { + 5 => (0, 50), + 11 => ((bits >> 1) & 1) is var x + ? (x | (x << 2) | (x << 6), 23) + : default, + 23 => ((bits >> 1) & 0x3) is var x + ? (x | (x << 5), 11) + : default, + _ => throw new ArgumentException("Illegal trit encoding") + }; + int t = trit * c + b; + t ^= a; + return (a & 0x20) | (t >> 2); + } +} diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs new file mode 100644 index 00000000..a12c081f --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +namespace SixLabors.ImageSharp.Textures.Astc.BlockDecoder; + +/// +/// Shared decode core for the fused (zero-allocation) ASTC block decode pipeline. +/// Contains BISE extraction and weight infill used by both LDR and HDR decoders. +/// +internal static class FusedBlockDecoder +{ + /// + /// Shared decode core: BISE decode, unquantize, and infill. + /// Populates and returns the decoded endpoint pair. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static ColorEndpointPair DecodeFusedCore( + UInt128 bits, in BlockInfo info, Footprint footprint, Span texelWeights) + { + // 1. BISE decode color endpoint values + int colorCount = info.EndpointMode0.GetColorValuesCount(); + Span colors = stackalloc int[colorCount]; + DecodeBiseValues(bits, info.ColorStartBit, info.ColorBitCount, info.ColorValuesRange, colorCount, colors); + + // 2. Batch unquantize color values, then decode endpoint pair + Quantization.UnquantizeCEValuesBatch(colors, colorCount, info.ColorValuesRange); + var endpointPair = EndpointCodec.DecodeColorsForModePolymorphicUnquantized(colors, info.EndpointMode0); + + // 3. BISE decode weights + int gridSize = info.GridWidth * info.GridHeight; + Span gridWeights = stackalloc int[gridSize]; + DecodeBiseWeights(bits, info.WeightBitCount, info.WeightRange, gridSize, gridWeights); + + // 4. Batch unquantize weights + Quantization.UnquantizeWeightsBatch(gridWeights, gridSize, info.WeightRange); + + // 5. Infill weights from grid to texels (or pass through if identity mapping) + if (info.GridWidth == footprint.Width && info.GridHeight == footprint.Height) + { + gridWeights[..footprint.PixelCount].CopyTo(texelWeights); + } + else + { + var decimationInfo = DecimationTable.Get(footprint, info.GridWidth, info.GridHeight); + DecimationTable.InfillWeights(gridWeights, decimationInfo, texelWeights); + } + + return endpointPair; + } + + /// + /// Decodes BISE-encoded values from the specified bit region of the block. + /// For bit-only encoding with small total bit count, extracts directly from ulong + /// without creating a BitStream (avoids per-value ShiftBuffer overhead). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void DecodeBiseValues(UInt128 bits, int startBit, int bitCount, int range, int valuesCount, Span result) + { + var (encMode, bitsPerValue) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(range); + + if (encMode == BiseEncodingMode.BitEncoding) + { + // Fast path: extract N-bit values directly via shifts + int totalBits = valuesCount * bitsPerValue; + ulong mask = (1UL << bitsPerValue) - 1; + + if (startBit + totalBits <= 64) + { + // All color data fits in the low 64 bits + ulong data = bits.Low() >> startBit; + for (int i = 0; i < valuesCount; i++) + { + result[i] = (int)(data & mask); + data >>= bitsPerValue; + } + } + else + { + // Spans both halves — use UInt128 shift then extract from low + var shifted = (bits >> startBit) & UInt128Extensions.OnesMask(totalBits); + ulong lowBits = shifted.Low(); + ulong highBits = shifted.High(); + int bitPos = 0; + for (int i = 0; i < valuesCount; i++) + { + if (bitPos < 64) + { + ulong val = (lowBits >> bitPos) & mask; + if (bitPos + bitsPerValue > 64) + val |= (highBits << (64 - bitPos)) & mask; + result[i] = (int)val; + } + else + { + result[i] = (int)((highBits >> (bitPos - 64)) & mask); + } + bitPos += bitsPerValue; + } + } + return; + } + + // Trit/quint encoding: fall back to full BISE decoder + var colorBitMask = UInt128Extensions.OnesMask(bitCount); + var colorBits = (bits >> startBit) & colorBitMask; + var colorBitStream = new BitStream(colorBits, 128); + var decoder = BoundedIntegerSequenceDecoder.GetCached(range); + decoder.Decode(valuesCount, ref colorBitStream, result); + } + + /// + /// Decodes BISE-encoded weight values from the reversed high-end of the block. + /// For bit-only encoding, extracts directly from the reversed bits without BitStream. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void DecodeBiseWeights(UInt128 bits, int weightBitCount, int weightRange, int count, Span result) + { + var (encMode, bitsPerValue) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(weightRange); + var weightBits = UInt128Extensions.ReverseBits(bits) & UInt128Extensions.OnesMask(weightBitCount); + + if (encMode == BiseEncodingMode.BitEncoding) + { + // Fast path: extract N-bit values directly via shifts + int totalBits = count * bitsPerValue; + ulong mask = (1UL << bitsPerValue) - 1; + + if (totalBits <= 64) + { + ulong data = weightBits.Low(); + for (int i = 0; i < count; i++) + { + result[i] = (int)(data & mask); + data >>= bitsPerValue; + } + } + else + { + ulong lowBits = weightBits.Low(); + ulong highBits = weightBits.High(); + int bitPos = 0; + for (int i = 0; i < count; i++) + { + if (bitPos < 64) + { + ulong val = (lowBits >> bitPos) & mask; + if (bitPos + bitsPerValue > 64) + val |= (highBits << (64 - bitPos)) & mask; + result[i] = (int)val; + } + else + { + result[i] = (int)((highBits >> (bitPos - 64)) & mask); + } + bitPos += bitsPerValue; + } + } + return; + } + + // Trit/quint encoding: fall back to full BISE decoder + var weightBitStream = new BitStream(weightBits, 128); + var decoder = BoundedIntegerSequenceDecoder.GetCached(weightRange); + decoder.Decode(count, ref weightBitStream, result); + } +} diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs new file mode 100644 index 00000000..b75bca3c --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs @@ -0,0 +1,215 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +namespace SixLabors.ImageSharp.Textures.Astc.BlockDecoder; + +/// +/// HDR pixel writers and entry points for the fused decode pipeline. +/// All methods handle single-partition, non-dual-plane blocks. +/// +internal static class FusedHdrBlockDecoder +{ + /// + /// Fused HDR decode to contiguous float buffer. + /// Handles single-partition, non-dual-plane blocks with both LDR and HDR endpoints. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static void DecompressBlockFusedHdr(UInt128 bits, in BlockInfo info, Footprint footprint, Span buffer) + { + Span texelWeights = stackalloc int[footprint.PixelCount]; + var endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); + WriteHdrOutputPixels(buffer, footprint.PixelCount, in endpointPair, texelWeights); + } + + /// + /// Fused HDR decode writing directly to image buffer at strided positions. + /// Handles single-partition, non-dual-plane blocks with both LDR and HDR endpoints. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static void DecompressBlockFusedHdrToImage( + UInt128 bits, + in BlockInfo info, + Footprint footprint, + int dstBaseX, + int dstBaseY, + int imageWidth, + Span imageBuffer) + { + Span texelWeights = stackalloc int[footprint.PixelCount]; + var endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); + WriteHdrOutputPixelsToImage(imageBuffer, footprint, dstBaseX, dstBaseY, imageWidth, in endpointPair, texelWeights); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteHdrOutputPixels( + Span buffer, int pixelCount, in ColorEndpointPair endpointPair, Span texelWeights) + { + if (endpointPair.IsHdr) + WriteHdrPixels(buffer, pixelCount, in endpointPair, texelWeights); + else + WriteLdrAsHdrPixels(buffer, pixelCount, in endpointPair, texelWeights); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteHdrOutputPixelsToImage( + Span imageBuffer, + Footprint footprint, + int dstBaseX, + int dstBaseY, + int imageWidth, + in ColorEndpointPair endpointPair, + Span texelWeights) + { + if (endpointPair.IsHdr) + WriteHdrPixelsToImage(imageBuffer, footprint, dstBaseX, dstBaseY, imageWidth, in endpointPair, texelWeights); + else + WriteLdrAsHdrPixelsToImage(imageBuffer, footprint, dstBaseX, dstBaseY, imageWidth, in endpointPair, texelWeights); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLdrAsHdrPixels(Span buffer, int pixelCount, in ColorEndpointPair endpointPair, Span texelWeights) + { + int lowR = endpointPair.LdrLow.R, lowG = endpointPair.LdrLow.G, lowB = endpointPair.LdrLow.B, lowA = endpointPair.LdrLow.A; + int highR = endpointPair.LdrHigh.R, highG = endpointPair.LdrHigh.G, highB = endpointPair.LdrHigh.B, highA = endpointPair.LdrHigh.A; + + for (int i = 0; i < pixelCount; i++) + { + int weight = texelWeights[i]; + int offset = i * 4; + buffer[offset + 0] = InterpolateLdrAsFloat(lowR, highR, weight); + buffer[offset + 1] = InterpolateLdrAsFloat(lowG, highG, weight); + buffer[offset + 2] = InterpolateLdrAsFloat(lowB, highB, weight); + buffer[offset + 3] = InterpolateLdrAsFloat(lowA, highA, weight); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLdrAsHdrPixelsToImage( + Span imageBuffer, + Footprint footprint, + int dstBaseX, + int dstBaseY, + int imageWidth, + in ColorEndpointPair endpointPair, + Span texelWeights) + { + int lowR = endpointPair.LdrLow.R, lowG = endpointPair.LdrLow.G, lowB = endpointPair.LdrLow.B, lowA = endpointPair.LdrLow.A; + int highR = endpointPair.LdrHigh.R, highG = endpointPair.LdrHigh.G, highB = endpointPair.LdrHigh.B, highA = endpointPair.LdrHigh.A; + + const int channelsPerPixel = 4; + int footprintWidth = footprint.Width; + int footprintHeight = footprint.Height; + int rowStride = imageWidth * channelsPerPixel; + + for (int pixelY = 0; pixelY < footprintHeight; pixelY++) + { + int dstRowOffset = (dstBaseY + pixelY) * rowStride + dstBaseX * channelsPerPixel; + int srcRowBase = pixelY * footprintWidth; + + for (int pixelX = 0; pixelX < footprintWidth; pixelX++) + { + int weight = texelWeights[srcRowBase + pixelX]; + int dstOffset = dstRowOffset + pixelX * channelsPerPixel; + imageBuffer[dstOffset + 0] = InterpolateLdrAsFloat(lowR, highR, weight); + imageBuffer[dstOffset + 1] = InterpolateLdrAsFloat(lowG, highG, weight); + imageBuffer[dstOffset + 2] = InterpolateLdrAsFloat(lowB, highB, weight); + imageBuffer[dstOffset + 3] = InterpolateLdrAsFloat(lowA, highA, weight); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteHdrPixels(Span buffer, int pixelCount, in ColorEndpointPair endpointPair, Span texelWeights) + { + bool alphaIsLdr = endpointPair.AlphaIsLdr; + int lowR = endpointPair.HdrLow.R, lowG = endpointPair.HdrLow.G, lowB = endpointPair.HdrLow.B, lowA = endpointPair.HdrLow.A; + int highR = endpointPair.HdrHigh.R, highG = endpointPair.HdrHigh.G, highB = endpointPair.HdrHigh.B, highA = endpointPair.HdrHigh.A; + + for (int i = 0; i < pixelCount; i++) + { + int weight = texelWeights[i]; + int offset = i * 4; + buffer[offset + 0] = InterpolateHdrAsFloat(lowR, highR, weight); + buffer[offset + 1] = InterpolateHdrAsFloat(lowG, highG, weight); + buffer[offset + 2] = InterpolateHdrAsFloat(lowB, highB, weight); + + if (alphaIsLdr) + { + int interpolated = (lowA * (64 - weight) + highA * weight + 32) / 64; + buffer[offset + 3] = (ushort)Math.Clamp(interpolated, 0, 0xFFFF) / 65535.0f; + } + else + { + buffer[offset + 3] = InterpolateHdrAsFloat(lowA, highA, weight); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteHdrPixelsToImage( + Span imageBuffer, + Footprint footprint, + int dstBaseX, + int dstBaseY, + int imageWidth, + in ColorEndpointPair endpointPair, + Span texelWeights) + { + bool alphaIsLdr = endpointPair.AlphaIsLdr; + int lowR = endpointPair.HdrLow.R, lowG = endpointPair.HdrLow.G, lowB = endpointPair.HdrLow.B, lowA = endpointPair.HdrLow.A; + int highR = endpointPair.HdrHigh.R, highG = endpointPair.HdrHigh.G, highB = endpointPair.HdrHigh.B, highA = endpointPair.HdrHigh.A; + + const int channelsPerPixel = 4; + int footprintWidth = footprint.Width; + int footprintHeight = footprint.Height; + int rowStride = imageWidth * channelsPerPixel; + + for (int pixelY = 0; pixelY < footprintHeight; pixelY++) + { + int dstRowOffset = (dstBaseY + pixelY) * rowStride + dstBaseX * channelsPerPixel; + int srcRowBase = pixelY * footprintWidth; + + for (int pixelX = 0; pixelX < footprintWidth; pixelX++) + { + int weight = texelWeights[srcRowBase + pixelX]; + int dstOffset = dstRowOffset + pixelX * channelsPerPixel; + imageBuffer[dstOffset + 0] = InterpolateHdrAsFloat(lowR, highR, weight); + imageBuffer[dstOffset + 1] = InterpolateHdrAsFloat(lowG, highG, weight); + imageBuffer[dstOffset + 2] = InterpolateHdrAsFloat(lowB, highB, weight); + + if (alphaIsLdr) + { + int interpolated = (lowA * (64 - weight) + highA * weight + 32) / 64; + imageBuffer[dstOffset + 3] = (ushort)Math.Clamp(interpolated, 0, 0xFFFF) / 65535.0f; + } + else + { + imageBuffer[dstOffset + 3] = InterpolateHdrAsFloat(lowA, highA, weight); + } + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float InterpolateLdrAsFloat(int p0, int p1, int weight) + { + int c0 = (p0 << 8) | p0; + int c1 = (p1 << 8) | p1; + int interpolated = (c0 * (64 - weight) + c1 * weight + 32) / 64; + return Math.Clamp(interpolated, 0, 0xFFFF) / 65535.0f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float InterpolateHdrAsFloat(int p0, int p1, int weight) + { + int interpolated = (p0 * (64 - weight) + p1 * weight + 32) / 64; + ushort clamped = (ushort)Math.Clamp(interpolated, 0, 0xFFFF); + ushort halfFloatBits = LogicalBlock.LnsToSf16(clamped); + return (float)BitConverter.UInt16BitsToHalf(halfFloatBits); + } +} diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs new file mode 100644 index 00000000..b57209d9 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs @@ -0,0 +1,152 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +namespace SixLabors.ImageSharp.Textures.Astc.BlockDecoder; + +/// +/// LDR pixel writers and entry points for the fused decode pipeline. +/// All methods handle single-partition, non-dual-plane blocks. +/// +internal static class FusedLdrBlockDecoder +{ + private const int BytesPerPixelUnorm8 = 4; + + /// + /// Fused LDR decode to contiguous buffer. + /// Only handles single-partition, non-dual-plane, LDR blocks. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static void DecompressBlockFusedLdr(UInt128 bits, in BlockInfo info, Footprint footprint, Span buffer) + { + Span texelWeights = stackalloc int[footprint.PixelCount]; + var endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); + WriteLdrPixels(buffer, footprint.PixelCount, in endpointPair, texelWeights); + } + + /// + /// Fused LDR decode writing directly to image buffer at strided positions. + /// Only handles single-partition, non-dual-plane, LDR blocks. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static void DecompressBlockFusedLdrToImage( + UInt128 bits, + in BlockInfo info, + Footprint footprint, + int dstBaseX, + int dstBaseY, + int imageWidth, + Span imageBuffer) + { + Span texelWeights = stackalloc int[footprint.PixelCount]; + var endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); + WriteLdrPixelsToImage(imageBuffer, footprint, dstBaseX, dstBaseY, imageWidth, in endpointPair, texelWeights); + } + + /// + /// Writes all pixels for a single-partition LDR block using SIMD where possible. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLdrPixels(Span buffer, int pixelCount, in ColorEndpointPair endpointPair, Span texelWeights) + { + int lowR = endpointPair.LdrLow.R, lowG = endpointPair.LdrLow.G, lowB = endpointPair.LdrLow.B, lowA = endpointPair.LdrLow.A; + int highR = endpointPair.LdrHigh.R, highG = endpointPair.LdrHigh.G, highB = endpointPair.LdrHigh.B, highA = endpointPair.LdrHigh.A; + + int i = 0; + if (Vector128.IsHardwareAccelerated) + { + int limit = pixelCount - 3; + for (; i < limit; i += 4) + { + var weights = Vector128.Create( + texelWeights[i], texelWeights[i + 1], + texelWeights[i + 2], texelWeights[i + 3]); + SimdHelpers.Write4PixelLdr( + buffer, + i * 4, + lowR, + lowG, + lowB, + lowA, + highR, + highG, + highB, + highA, + weights); + } + } + + for (; i < pixelCount; i++) + { + SimdHelpers.WriteSinglePixelLdr( + buffer, + i * 4, + lowR, + lowG, + lowB, + lowA, + highR, + highG, + highB, + highA, + texelWeights[i]); + } + } + + /// + /// Writes LDR pixels directly to image buffer at strided positions. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLdrPixelsToImage( + Span imageBuffer, + Footprint footprint, + int dstBaseX, + int dstBaseY, + int imageWidth, + in ColorEndpointPair endpointPair, + Span texelWeights) + { + int lowR = endpointPair.LdrLow.R, lowG = endpointPair.LdrLow.G, lowB = endpointPair.LdrLow.B, lowA = endpointPair.LdrLow.A; + int highR = endpointPair.LdrHigh.R, highG = endpointPair.LdrHigh.G, highB = endpointPair.LdrHigh.B, highA = endpointPair.LdrHigh.A; + + int footprintWidth = footprint.Width; + int footprintHeight = footprint.Height; + int rowStride = imageWidth * BytesPerPixelUnorm8; + + for (int pixelY = 0; pixelY < footprintHeight; pixelY++) + { + int dstRowOffset = (dstBaseY + pixelY) * rowStride + dstBaseX * BytesPerPixelUnorm8; + int srcRowBase = pixelY * footprintWidth; + int pixelX = 0; + + if (Vector128.IsHardwareAccelerated) + { + int limit = footprintWidth - 3; + for (; pixelX < limit; pixelX += 4) + { + int texelIndex = srcRowBase + pixelX; + var weights = Vector128.Create( + texelWeights[texelIndex], texelWeights[texelIndex + 1], + texelWeights[texelIndex + 2], texelWeights[texelIndex + 3]); + SimdHelpers.Write4PixelLdr( + imageBuffer, dstRowOffset + pixelX * BytesPerPixelUnorm8, + lowR, lowG, lowB, lowA, highR, highG, highB, highA, + weights); + } + } + + for (; pixelX < footprintWidth; pixelX++) + { + SimdHelpers.WriteSinglePixelLdr( + imageBuffer, dstRowOffset + pixelX * BytesPerPixelUnorm8, + lowR, lowG, lowB, lowA, highR, highG, highB, highA, + texelWeights[srcRowBase + pixelX]); + } + } + } +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointMode.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointMode.cs new file mode 100644 index 00000000..92950ee5 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointMode.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +/// +/// ASTC supports 16 color endpoint encoding schemes, known as endpoint modes +/// +/// +/// The options for endpoint modes let you vary the following: +/// +/// The number of color channels. For example, luminance, luminance+alpha, rgb, or rgba +/// The encoding method. For example, direct, base+offset, base+scale, or quantization level +/// The data range. For example, low dynamic range or High Dynamic Range +/// +/// +internal enum ColorEndpointMode +{ + LdrLumaDirect = 0, + LdrLumaBaseOffset, + HdrLumaLargeRange, + HdrLumaSmallRange, + LdrLumaAlphaDirect, + LdrLumaAlphaBaseOffset, + LdrRgbBaseScale, + HdrRgbBaseScale, + LdrRgbDirect, + LdrRgbBaseOffset, + LdrRgbBaseScaleTwoA, + HdrRgbDirect, + LdrRgbaDirect, + LdrRgbaBaseOffset, + HdrRgbDirectLdrAlpha, + HdrRgbDirectHdrAlpha, + + // Number of endpoint modes defined by the ASTC specification. + ColorEndpointModeCount +} \ No newline at end of file diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointModeExtensions.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointModeExtensions.cs new file mode 100644 index 00000000..b37e6727 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointModeExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +internal static class ColorEndpointModeExtensions +{ + public static int GetEndpointModeClass(this ColorEndpointMode mode) + => (int)mode / 4; + + public static int GetColorValuesCount(this ColorEndpointMode mode) + => (mode.GetEndpointModeClass() + 1) * 2; + + /// + /// Determines whether the specified endpoint mode uses HDR (High Dynamic Range) encoding. + /// + /// + /// True if the mode is one of the 6 HDR modes (2, 3, 7, 11, 14, 15), false otherwise. + /// + public static bool IsHdr(this ColorEndpointMode mode) + => mode switch + { + ColorEndpointMode.HdrLumaLargeRange => true, // Mode 2 + ColorEndpointMode.HdrLumaSmallRange => true, // Mode 3 + ColorEndpointMode.HdrRgbBaseScale => true, // Mode 7 + ColorEndpointMode.HdrRgbDirect => true, // Mode 11 + ColorEndpointMode.HdrRgbDirectLdrAlpha => true, // Mode 14 + ColorEndpointMode.HdrRgbDirectHdrAlpha => true, // Mode 15 + _ => false + }; +} \ No newline at end of file diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs new file mode 100644 index 00000000..4829e8ea --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +internal static class EndpointCodec +{ + /// + /// Decodes color endpoints for the specified mode, returning a polymorphic endpoint pair + /// that supports both LDR and HDR modes. + /// + /// Quantized integer values from the ASTC block + /// Maximum quantization value + /// The color endpoint mode + /// A ColorEndpointPair representing either LDR or HDR endpoints + public static ColorEndpointPair DecodeColorsForModePolymorphic(ReadOnlySpan values, int maxValue, ColorEndpointMode mode) + { + if (mode.IsHdr()) + { + var (low, high) = HdrEndpointDecoder.DecodeHdrMode(values, maxValue, mode); + bool alphaIsLdr = mode == ColorEndpointMode.HdrRgbDirectLdrAlpha; + return ColorEndpointPair.Hdr(low, high, alphaIsLdr); + } + else + { + var (low, high) = DecodeColorsForMode(values, maxValue, mode); + return ColorEndpointPair.Ldr(low, high); + } + } + + public static (RgbaColor endpointLowRgba, RgbaColor endpointHighRgba) DecodeColorsForMode(ReadOnlySpan values, int maxValue, ColorEndpointMode mode) + { + int count = mode.GetColorValuesCount(); + Span unquantizedValues = stackalloc int[count]; + int copyLen = Math.Min(count, values.Length); + for (int i = 0; i < copyLen; i++) unquantizedValues[i] = values[i]; + UnquantizeInline(unquantizedValues, maxValue); + var pair = DecodeColorsForModeUnquantized(unquantizedValues, mode); + return (pair.LdrLow, pair.LdrHigh); + } + + /// + /// Decodes color endpoints from already-unquantized values, supporting both LDR and HDR modes. + /// Called from the fused HDR decode path where BISE decode + batch unquantize + /// have already been performed. Returns a ColorEndpointPair (LDR or HDR). + /// + internal static ColorEndpointPair DecodeColorsForModePolymorphicUnquantized(ReadOnlySpan unquantizedValues, ColorEndpointMode mode) + { + if (mode.IsHdr()) + { + var (low, high) = HdrEndpointDecoder.DecodeHdrModeUnquantized(unquantizedValues, mode); + bool alphaIsLdr = mode == ColorEndpointMode.HdrRgbDirectLdrAlpha; + return ColorEndpointPair.Hdr(low, high, alphaIsLdr); + } + + return DecodeColorsForModeUnquantized(unquantizedValues, mode); + } + + /// + /// Decodes color endpoints from already-unquantized values. + /// Called from the fused decode path where BISE decode + batch unquantize + /// have already been performed. Returns an LDR ColorEndpointPair. + /// + internal static ColorEndpointPair DecodeColorsForModeUnquantized(ReadOnlySpan unquantizedValues, ColorEndpointMode mode) + { + RgbaColor endpointLowRgba, endpointHighRgba; + + switch (mode) + { + case ColorEndpointMode.LdrLumaDirect: + endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[0], unquantizedValues[0]); + endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[1], unquantizedValues[1]); + break; + case ColorEndpointMode.LdrLumaBaseOffset: + { + int l0 = (unquantizedValues[0] >> 2) | (unquantizedValues[1] & 0xC0); + int l1 = Math.Min(l0 + (unquantizedValues[1] & 0x3F), 0xFF); + endpointLowRgba = new RgbaColor(l0, l0, l0); + endpointHighRgba = new RgbaColor(l1, l1, l1); + break; + } + case ColorEndpointMode.LdrLumaAlphaDirect: + endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[0], unquantizedValues[0], unquantizedValues[2]); + endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[1], unquantizedValues[1], unquantizedValues[3]); + break; + case ColorEndpointMode.LdrLumaAlphaBaseOffset: + { + var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); + var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); + endpointLowRgba = new RgbaColor(a0, a0, a0, a2); + int highLuma = a0 + b0; + endpointHighRgba = new RgbaColor(highLuma, highLuma, highLuma, a2 + b2); + break; + } + case ColorEndpointMode.LdrRgbBaseScale: + endpointLowRgba = new RgbaColor( + (unquantizedValues[0] * unquantizedValues[3]) >> 8, + (unquantizedValues[1] * unquantizedValues[3]) >> 8, + (unquantizedValues[2] * unquantizedValues[3]) >> 8); + endpointHighRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2]); + break; + case ColorEndpointMode.LdrRgbDirect: + { + int sum0 = unquantizedValues[0] + unquantizedValues[2] + unquantizedValues[4]; + int sum1 = unquantizedValues[1] + unquantizedValues[3] + unquantizedValues[5]; + if (sum1 < sum0) + { + endpointLowRgba = new RgbaColor( + r: (unquantizedValues[1] + unquantizedValues[5]) >> 1, + g: (unquantizedValues[3] + unquantizedValues[5]) >> 1, + b: unquantizedValues[5]); + endpointHighRgba = new RgbaColor( + r: (unquantizedValues[0] + unquantizedValues[4]) >> 1, + g: (unquantizedValues[2] + unquantizedValues[4]) >> 1, + b: unquantizedValues[4]); + } + else + { + endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[2], unquantizedValues[4]); + endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[3], unquantizedValues[5]); + } + break; + } + case ColorEndpointMode.LdrRgbBaseOffset: + { + var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); + var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); + var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); + if (b0 + b1 + b2 < 0) + { + endpointLowRgba = new RgbaColor( + r: (a0 + b0 + a2 + b2) >> 1, + g: (a1 + b1 + a2 + b2) >> 1, + b: a2 + b2); + endpointHighRgba = new RgbaColor( + r: (a0 + a2) >> 1, + g: (a1 + a2) >> 1, + b: a2); + } + else + { + endpointLowRgba = new RgbaColor(a0, a1, a2); + endpointHighRgba = new RgbaColor(a0 + b0, a1 + b1, a2 + b2); + } + break; + } + case ColorEndpointMode.LdrRgbBaseScaleTwoA: + endpointLowRgba = new RgbaColor( + r: (unquantizedValues[0] * unquantizedValues[3]) >> 8, + g: (unquantizedValues[1] * unquantizedValues[3]) >> 8, + b: (unquantizedValues[2] * unquantizedValues[3]) >> 8, + a: unquantizedValues[4]); + endpointHighRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[5]); + break; + case ColorEndpointMode.LdrRgbaDirect: + { + int sum0 = unquantizedValues[0] + unquantizedValues[2] + unquantizedValues[4]; + int sum1 = unquantizedValues[1] + unquantizedValues[3] + unquantizedValues[5]; + if (sum1 >= sum0) + { + endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[2], unquantizedValues[4], unquantizedValues[6]); + endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[3], unquantizedValues[5], unquantizedValues[7]); + } + else + { + endpointLowRgba = new RgbaColor( + r: (unquantizedValues[1] + unquantizedValues[5]) >> 1, + g: (unquantizedValues[3] + unquantizedValues[5]) >> 1, + b: unquantizedValues[5], + a: unquantizedValues[7]); + endpointHighRgba = new RgbaColor( + r: (unquantizedValues[0] + unquantizedValues[4]) >> 1, + g: (unquantizedValues[2] + unquantizedValues[4]) >> 1, + b: unquantizedValues[4], + a: unquantizedValues[6]); + } + break; + } + case ColorEndpointMode.LdrRgbaBaseOffset: + { + var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); + var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); + var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); + var (b3, a3) = BitOperations.TransferPrecision(unquantizedValues[7], unquantizedValues[6]); + if (b0 + b1 + b2 < 0) + { + endpointLowRgba = new RgbaColor( + r: (a0 + b0 + a2 + b2) >> 1, + g: (a1 + b1 + a2 + b2) >> 1, + b: a2 + b2, + a: a3 + b3); + endpointHighRgba = new RgbaColor( + r: (a0 + a2) >> 1, + g: (a1 + a2) >> 1, + b: a2, + a: a3); + } + else + { + endpointLowRgba = new RgbaColor(a0, a1, a2, a3); + endpointHighRgba = new RgbaColor(a0 + b0, a1 + b1, a2 + b2, a3 + b3); + } + break; + } + default: + endpointLowRgba = RgbaColor.Empty; + endpointHighRgba = RgbaColor.Empty; + break; + } + + return ColorEndpointPair.Ldr(endpointLowRgba, endpointHighRgba); + } + + internal static int[] UnquantizeArray(int[] values, int maxValue) + { + var result = new int[values.Length]; + for (int i = 0; i < values.Length; ++i) result[i] = Quantization.UnquantizeCEValueFromRange(values[i], maxValue); + return result; + } + + private static void UnquantizeInline(Span values, int maxValue) + { + for (int i = 0; i < values.Length; ++i) values[i] = Quantization.UnquantizeCEValueFromRange(values[i], maxValue); + } +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncoder.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncoder.cs new file mode 100644 index 00000000..97757292 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncoder.cs @@ -0,0 +1,498 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +internal static class EndpointEncoder +{ + public static bool UsesBlueContract(int maxValue, ColorEndpointMode mode, List values) + { + int valueCount = mode.GetColorValuesCount(); + ArgumentOutOfRangeException.ThrowIfLessThan(values.Count, valueCount); + + switch (mode) + { + case ColorEndpointMode.LdrRgbDirect: + case ColorEndpointMode.LdrRgbaDirect: + { + int maxValueCount = Math.Max(ColorEndpointMode.LdrRgbDirect.GetColorValuesCount(), ColorEndpointMode.LdrRgbaDirect.GetColorValuesCount()); + var v = new int[maxValueCount]; + for (int i = 0; i < maxValueCount; ++i) v[i] = i < values.Count ? values[i] : 0; + var unquantizedValues = EndpointCodec.UnquantizeArray(v, maxValue); + int s0 = unquantizedValues[0] + unquantizedValues[2] + unquantizedValues[4]; + int s1 = unquantizedValues[1] + unquantizedValues[3] + unquantizedValues[5]; + return s0 > s1; + } + case ColorEndpointMode.LdrRgbBaseOffset: + case ColorEndpointMode.LdrRgbaBaseOffset: + { + int maxValueCount = Math.Max(ColorEndpointMode.LdrRgbBaseOffset.GetColorValuesCount(), ColorEndpointMode.LdrRgbaBaseOffset.GetColorValuesCount()); + var v = new int[maxValueCount]; + for (int i = 0; i < maxValueCount; ++i) v[i] = i < values.Count ? values[i] : 0; + var unquantizedValues = EndpointCodec.UnquantizeArray(v, maxValue); + var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); + var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); + var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); + return (b0 + b1 + b2) < 0; + } + default: + return false; + } + } + + // TODO: Extract an interface and implement instances for each encoding mode + public static bool EncodeColorsForMode(RgbaColor endpointLowRgba, RgbaColor endpointHighRgba, int maxValue, EndpointEncodingMode encodingMode, out ColorEndpointMode astcMode, List values) + { + bool needsWeightSwap = false; + astcMode = ColorEndpointMode.LdrLumaDirect; + int valueCount = encodingMode.GetValuesCount(); + for (int i = values.Count; i < valueCount; ++i) values.Add(0); + + switch (encodingMode) + { + case EndpointEncodingMode.DirectLuma: + return EncodeColorsLuma(endpointLowRgba, endpointHighRgba, maxValue, out astcMode, values); + case EndpointEncodingMode.DirectLumaAlpha: + { + int avg1 = endpointLowRgba.Average; + int avg2 = endpointHighRgba.Average; + values[0] = Quantization.QuantizeCEValueToRange(avg1, maxValue); + values[1] = Quantization.QuantizeCEValueToRange(avg2, maxValue); + values[2] = Quantization.QuantizeCEValueToRange(endpointLowRgba[3], maxValue); + values[3] = Quantization.QuantizeCEValueToRange(endpointHighRgba[3], maxValue); + astcMode = ColorEndpointMode.LdrLumaAlphaDirect; + } + break; + case EndpointEncodingMode.BaseScaleRgb: + case EndpointEncodingMode.BaseScaleRgba: + { + var baseColor = endpointHighRgba; + var scaled = endpointLowRgba; + + int numChannelsGe = 0; + for (int i = 0; i < 3; ++i) numChannelsGe += endpointHighRgba[i] >= endpointLowRgba[i] ? 1 : 0; + + if (numChannelsGe < 2) + { + needsWeightSwap = true; + var temp = baseColor; baseColor = scaled; scaled = temp; + } + + var quantizedBase = QuantizeColorArray(baseColor, maxValue); + var unquantizedBase = EndpointCodec.UnquantizeArray(quantizedBase, maxValue); + + int numSamples = 0; + int scaleSum = 0; + for (int i = 0; i < 3; ++i) + { + int x = unquantizedBase[i]; + if (x != 0) + { + ++numSamples; + scaleSum += (scaled[i] * 256) / x; + } + } + + values[0] = quantizedBase[0]; + values[1] = quantizedBase[1]; + values[2] = quantizedBase[2]; + if (numSamples > 0) + { + int avgScale = Math.Clamp(scaleSum / numSamples, 0, 255); + values[3] = Quantization.QuantizeCEValueToRange(avgScale, maxValue); + } + else + { + values[3] = maxValue; + } + astcMode = ColorEndpointMode.LdrRgbBaseScale; + + if (encodingMode == EndpointEncodingMode.BaseScaleRgba) + { + values[4] = Quantization.QuantizeCEValueToRange(scaled[3], maxValue); + values[5] = Quantization.QuantizeCEValueToRange(baseColor[3], maxValue); + astcMode = ColorEndpointMode.LdrRgbBaseScaleTwoA; + } + } + break; + case EndpointEncodingMode.DirectRbg: + case EndpointEncodingMode.DirectRgba: + return EncodeColorsRGBA(endpointLowRgba, endpointHighRgba, maxValue, encodingMode == EndpointEncodingMode.DirectRgba, out astcMode, values); + default: + throw new InvalidOperationException("Unimplemented color encoding."); + } + + return needsWeightSwap; + } + + private static int[] QuantizeColorArray(RgbaColor c, int maxValue) + { + var array = new int[RgbaColor.BytesPerPixel]; + for (int i = 0; i < RgbaColor.BytesPerPixel; ++i) array[i] = Quantization.QuantizeCEValueToRange(c[i], maxValue); + return array; + } + + private static bool EncodeColorsLuma(RgbaColor endpointLow, RgbaColor endpointHigh, int maxValue, out ColorEndpointMode astcMode, List values) + { + astcMode = ColorEndpointMode.LdrLumaDirect; + ArgumentOutOfRangeException.ThrowIfLessThan(values.Count, 2); + + int avg1 = endpointLow.Average; + int avg2 = endpointHigh.Average; + + bool needsWeightSwap = false; + if (avg1 > avg2) { needsWeightSwap = true; var temp = avg1; avg1 = avg2; avg2 = temp; } + + int offset = Math.Min(avg2 - avg1, 0x3F); + int quantOffLow = Quantization.QuantizeCEValueToRange((avg1 & 0x3F) << 2, maxValue); + int quantOffHigh = Quantization.QuantizeCEValueToRange((avg1 & 0xC0) | offset, maxValue); + + int quantLow = Quantization.QuantizeCEValueToRange(avg1, maxValue); + int quantHigh = Quantization.QuantizeCEValueToRange(avg2, maxValue); + + values[0] = quantOffLow; + values[1] = quantOffHigh; + var (decLowOff, decHighOff) = EndpointCodec.DecodeColorsForMode(values.ToArray(), maxValue, ColorEndpointMode.LdrLumaBaseOffset); + + values[0] = quantLow; + values[1] = quantHigh; + var (decLowDir, decHighDir) = EndpointCodec.DecodeColorsForMode(values.ToArray(), maxValue, ColorEndpointMode.LdrLumaDirect); + + int calculateErrorOff = 0; + int calculateErrorDir = 0; + if (needsWeightSwap) + { + calculateErrorDir = RgbaColor.SquaredError(decLowDir, endpointHigh) + RgbaColor.SquaredError(decHighDir, endpointLow); + calculateErrorOff = RgbaColor.SquaredError(decLowOff, endpointHigh) + RgbaColor.SquaredError(decHighOff, endpointLow); + } + else + { + calculateErrorDir = RgbaColor.SquaredError(decLowDir, endpointLow) + RgbaColor.SquaredError(decHighDir, endpointHigh); + calculateErrorOff = RgbaColor.SquaredError(decLowOff, endpointLow) + RgbaColor.SquaredError(decHighOff, endpointHigh); + } + + if (calculateErrorDir <= calculateErrorOff) + { + values[0] = quantLow; + values[1] = quantHigh; + astcMode = ColorEndpointMode.LdrLumaDirect; + } + else + { + values[0] = quantOffLow; + values[1] = quantOffHigh; + astcMode = ColorEndpointMode.LdrLumaBaseOffset; + } + + return needsWeightSwap; + } + + private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpointHighRgba, int maxValue, bool withAlpha, out ColorEndpointMode astcMode, List values) + { + astcMode = ColorEndpointMode.LdrRgbDirect; + int numChannels = withAlpha ? 4 : 3; + + var invertedBlueContractLow = endpointLowRgba.WithInvertedBlueContract(); + var invertedBlueContractHigh = endpointHighRgba.WithInvertedBlueContract(); + + var directBase = new int[4]; + var directOffset = new int[4]; + for (int i = 0; i < 4; ++i) + { + directBase[i] = endpointLowRgba[i]; + directOffset[i] = Math.Clamp(endpointHighRgba[i] - endpointLowRgba[i], -32, 31); + (directOffset[i], directBase[i]) = BitOperations.TransferPrecisionInverse(directOffset[i], directBase[i]); + } + + var invertedBlueContractBase = new int[4]; + var invertedBlueContractOffset = new int[4]; + for (int i = 0; i < 4; ++i) + { + invertedBlueContractBase[i] = invertedBlueContractHigh[i]; + invertedBlueContractOffset[i] = Math.Clamp(invertedBlueContractLow[i] - invertedBlueContractHigh[i], -32, 31); + (invertedBlueContractOffset[i], invertedBlueContractBase[i]) = BitOperations.TransferPrecisionInverse(invertedBlueContractOffset[i], invertedBlueContractBase[i]); + } + + var directBaseSwapped = new int[4]; + var directOffsetSwapped = new int[4]; + for (int i = 0; i < 4; ++i) + { + directBaseSwapped[i] = endpointHighRgba[i]; + directOffsetSwapped[i] = Math.Clamp(endpointLowRgba[i] - endpointHighRgba[i], -32, 31); + (directOffsetSwapped[i], directBaseSwapped[i]) = BitOperations.TransferPrecisionInverse(directOffsetSwapped[i], directBaseSwapped[i]); + } + + var invertedBlueContractBaseSwapped = new int[4]; + var invertedBlueContractOffsetSwapped = new int[4]; + for (int i = 0; i < 4; ++i) + { + invertedBlueContractBaseSwapped[i] = invertedBlueContractLow[i]; + invertedBlueContractOffsetSwapped[i] = Math.Clamp(invertedBlueContractHigh[i] - invertedBlueContractLow[i], -32, 31); + (invertedBlueContractOffsetSwapped[i], invertedBlueContractBaseSwapped[i]) = BitOperations.TransferPrecisionInverse(invertedBlueContractOffsetSwapped[i], invertedBlueContractBaseSwapped[i]); + } + + var directQuantized = new QuantizedEndpointPair(endpointLowRgba, endpointHighRgba, maxValue); + var bcQuantized = new QuantizedEndpointPair(invertedBlueContractLow, invertedBlueContractHigh, maxValue); + + var offsetQuantized = new QuantizedEndpointPair(new RgbaColor(directBase[0], directBase[1], directBase[2], directBase[3]), new RgbaColor(directOffset[0], directOffset[1], directOffset[2], directOffset[3]), maxValue); + var bcOffsetQuantized = new QuantizedEndpointPair(new RgbaColor(invertedBlueContractBase[0], invertedBlueContractBase[1], invertedBlueContractBase[2], invertedBlueContractBase[3]), new RgbaColor(invertedBlueContractOffset[0], invertedBlueContractOffset[1], invertedBlueContractOffset[2], invertedBlueContractOffset[3]), maxValue); + + var offsetSwappedQuantized = new QuantizedEndpointPair(new RgbaColor(directBaseSwapped[0], directBaseSwapped[1], directBaseSwapped[2], directBaseSwapped[3]), new RgbaColor(directOffsetSwapped[0], directOffsetSwapped[1], directOffsetSwapped[2], directOffsetSwapped[3]), maxValue); + var bcOffsetSwappedQuantized = new QuantizedEndpointPair(new RgbaColor(invertedBlueContractBaseSwapped[0], invertedBlueContractBaseSwapped[1], invertedBlueContractBaseSwapped[2], invertedBlueContractBaseSwapped[3]), new RgbaColor(invertedBlueContractOffsetSwapped[0], invertedBlueContractOffsetSwapped[1], invertedBlueContractOffsetSwapped[2], invertedBlueContractOffsetSwapped[3]), maxValue); + + var errors = new List(6); + + // 3.1 regular unquantized error + { + var rgbaLow = directQuantized.UnquantizedLow(); + var rgbaHigh = directQuantized.UnquantizedHigh(); + var lowColor = new RgbaColor(rgbaLow[0], rgbaLow[1], rgbaLow[2], rgbaLow[3]); + var highColor = new RgbaColor(rgbaHigh[0], rgbaHigh[1], rgbaHigh[2], rgbaHigh[3]); + var squaredRgbError = withAlpha + ? RgbaColor.SquaredError(lowColor, endpointLowRgba) + RgbaColor.SquaredError(highColor, endpointHighRgba) + : RgbColor.SquaredError(lowColor, endpointLowRgba) + RgbColor.SquaredError(highColor, endpointHighRgba); + errors.Add(new CEEncodingOption(squaredRgbError, directQuantized, false, false, false)); + } + + // 3.2 blue-contract + { + var blueContractUnquantizedLow = bcQuantized.UnquantizedLow(); + var blueContractUnquantizedHigh = bcQuantized.UnquantizedHigh(); + var blueContractLow = RgbaColorExtensions.WithBlueContract(blueContractUnquantizedLow[0], blueContractUnquantizedLow[1], blueContractUnquantizedLow[2], blueContractUnquantizedLow[3]); + var blueContractHigh = RgbaColorExtensions.WithBlueContract(blueContractUnquantizedHigh[0], blueContractUnquantizedHigh[1], blueContractUnquantizedHigh[2], blueContractUnquantizedHigh[3]); + // TODO: How to handle alpha for this entire functions?? + var blueContractSquaredError = withAlpha + ? RgbaColor.SquaredError(blueContractLow, endpointLowRgba) + RgbaColor.SquaredError(blueContractHigh, endpointHighRgba) + : RgbColor.SquaredError(blueContractLow, endpointLowRgba) + RgbColor.SquaredError(blueContractHigh, endpointHighRgba); + + errors.Add(new CEEncodingOption(blueContractSquaredError, bcQuantized, swapEndpoints: false, blueContract: true, useOffsetMode: false)); + } + + // 3.3 base/offset + Action computeBaseOffsetError = (pair, swapped) => + { + var baseArr = pair.UnquantizedLow(); + var offsetArr = pair.UnquantizedHigh(); + + var baseColor = new RgbaColor(baseArr[0], baseArr[1], baseArr[2], baseArr[3]); + var offsetColor = new RgbaColor(offsetArr[0], offsetArr[1], offsetArr[2], offsetArr[3]).AsOffsetFrom(baseColor); + + int baseOffsetError = 0; + if (swapped) + { + baseOffsetError = withAlpha + ? RgbaColor.SquaredError(baseColor, endpointHighRgba) + RgbaColor.SquaredError(offsetColor, endpointLowRgba) + : RgbColor.SquaredError(baseColor, endpointHighRgba) + RgbColor.SquaredError(offsetColor, endpointLowRgba); + } + else + { + baseOffsetError = withAlpha + ? RgbaColor.SquaredError(baseColor, endpointLowRgba) + RgbaColor.SquaredError(offsetColor, endpointHighRgba) + : RgbColor.SquaredError(baseColor, endpointLowRgba) + RgbColor.SquaredError(offsetColor, endpointHighRgba); + } + + errors.Add(new CEEncodingOption(baseOffsetError, pair, swapped, false, true)); + }; + + computeBaseOffsetError(offsetQuantized, false); + + Action computeBaseOffsetBlueContractError = (pair, swapped) => + { + var baseArr = pair.UnquantizedLow(); + var offsetArr = pair.UnquantizedHigh(); + + var baseColor = new RgbaColor(baseArr[0], baseArr[1], baseArr[2], baseArr[3]); + var offsetColor = new RgbaColor(offsetArr[0], offsetArr[1], offsetArr[2], offsetArr[3]).AsOffsetFrom(baseColor); + + baseColor = baseColor.WithBlueContract(); + offsetColor = offsetColor.WithBlueContract(); + + int squaredBlueContractError = 0; + if (swapped) + { + squaredBlueContractError = withAlpha + ? RgbaColor.SquaredError(baseColor, endpointLowRgba) + RgbaColor.SquaredError(offsetColor, endpointHighRgba) + : RgbColor.SquaredError(baseColor, endpointLowRgba) + RgbColor.SquaredError(offsetColor, endpointHighRgba); + } + else + { + squaredBlueContractError = withAlpha + ? RgbaColor.SquaredError(baseColor, endpointHighRgba) + RgbaColor.SquaredError(offsetColor, endpointLowRgba) + : RgbColor.SquaredError(baseColor, endpointHighRgba) + RgbColor.SquaredError(offsetColor, endpointLowRgba); + } + + errors.Add(new CEEncodingOption(squaredBlueContractError, pair, swapped, true, true)); + }; + + computeBaseOffsetBlueContractError(bcOffsetQuantized, false); + computeBaseOffsetError(offsetSwappedQuantized, true); + computeBaseOffsetBlueContractError(bcOffsetSwappedQuantized, true); + + errors.Sort((a, b) => a.Error().CompareTo(b.Error())); + + foreach (var measurement in errors) + { + bool needsWeightSwap = false; + ColorEndpointMode modeUnused; + if (measurement.Pack(withAlpha, out modeUnused, values, ref needsWeightSwap)) + { + return needsWeightSwap; + } + } + + throw new InvalidOperationException("Shouldn't have reached this point"); + } + + private class QuantizedEndpointPair + { + private readonly RgbaColor _originalLow; + private readonly RgbaColor _originalHigh; + private readonly int[] _quantizedLow; + private readonly int[] _quantizedHigh; + private readonly int[] _unquantizedLow; + private readonly int[] _unquantizedHigh; + + public QuantizedEndpointPair(RgbaColor low, RgbaColor high, int maxValue) + { + _originalLow = low; + _originalHigh = high; + _quantizedLow = QuantizeColorArray(low, maxValue); + _quantizedHigh = QuantizeColorArray(high, maxValue); + _unquantizedLow = EndpointCodec.UnquantizeArray(_quantizedLow, maxValue); + _unquantizedHigh = EndpointCodec.UnquantizeArray(_quantizedHigh, maxValue); + } + + public int[] QuantizedLow() => _quantizedLow; + public int[] QuantizedHigh() => _quantizedHigh; + public int[] UnquantizedLow() => _unquantizedLow; + public int[] UnquantizedHigh() => _unquantizedHigh; + public RgbaColor OriginalLow() => _originalLow; + public RgbaColor OriginalHigh() => _originalHigh; + } + + private class CEEncodingOption + { + private readonly int _squaredError; + private readonly QuantizedEndpointPair _quantizedEndpoints; + private readonly bool _swapEndpoints; + private readonly bool _blueContract; + private readonly bool _useOffsetMode; + + public CEEncodingOption( + int squaredError, + QuantizedEndpointPair quantizedEndpoints, + bool swapEndpoints, + bool blueContract, + bool useOffsetMode) + { + _squaredError = squaredError; + _quantizedEndpoints = quantizedEndpoints; + _swapEndpoints = swapEndpoints; + _blueContract = blueContract; + _useOffsetMode = useOffsetMode; + } + + public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List values, ref bool needsWeightSwap) + { + endpointMode = ColorEndpointMode.LdrLumaDirect; + var unquantizedLowOriginal = _quantizedEndpoints.UnquantizedLow(); + var unquantizedHighOriginal = _quantizedEndpoints.UnquantizedHigh(); + + var unquantizedLow = (int[])unquantizedLowOriginal.Clone(); + var unquantizedHigh = (int[])unquantizedHighOriginal.Clone(); + + if (_useOffsetMode) + { + for (int i = 0; i < 4; ++i) + { + (unquantizedHigh[i], unquantizedLow[i]) = BitOperations.TransferPrecision(unquantizedHigh[i], unquantizedLow[i]); + } + } + + int sum0 = 0, sum1 = 0; + for (int i = 0; i < 3; ++i) + { + sum0 += unquantizedLow[i]; + sum1 += unquantizedHigh[i]; + } + + bool swapVals = false; + if (_useOffsetMode) + { + if (_blueContract) + { + swapVals = sum1 >= 0; + } + else + { + swapVals = sum1 < 0; + } + + if (swapVals) return false; + } + else + { + if (_blueContract) + { + if (sum1 == sum0) return false; + swapVals = sum1 > sum0; + needsWeightSwap = !needsWeightSwap; + } + else + { + swapVals = sum1 < sum0; + } + } + + var quantizedLowOriginal = _quantizedEndpoints.QuantizedLow(); + var quantizedHighOriginal = _quantizedEndpoints.QuantizedHigh(); + + var quantizedLow = (int[])quantizedLowOriginal.Clone(); + var quantizedHigh = (int[])quantizedHighOriginal.Clone(); + + if (swapVals) + { + if (_useOffsetMode) throw new InvalidOperationException(); + var tmp = quantizedLow; quantizedLow = quantizedHigh; quantizedHigh = tmp; + needsWeightSwap = !needsWeightSwap; + } + + values[0] = quantizedLow[0]; + values[1] = quantizedHigh[0]; + values[2] = quantizedLow[1]; + values[3] = quantizedHigh[1]; + values[4] = quantizedLow[2]; + values[5] = quantizedHigh[2]; + + if (_useOffsetMode) + { + endpointMode = ColorEndpointMode.LdrRgbBaseOffset; + } + else + { + endpointMode = ColorEndpointMode.LdrRgbDirect; + } + + if (hasAlpha) + { + values[6] = quantizedLow[3]; + values[7] = quantizedHigh[3]; + if (_useOffsetMode) endpointMode = ColorEndpointMode.LdrRgbaBaseOffset; + else endpointMode = ColorEndpointMode.LdrRgbaDirect; + } + + if (_swapEndpoints) + { + needsWeightSwap = !needsWeightSwap; + } + + return true; + } + + public bool BlueContract() => _blueContract; + public int Error() => _squaredError; + } +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingMode.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingMode.cs new file mode 100644 index 00000000..08f2f58e --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingMode.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +internal enum EndpointEncodingMode +{ + DirectLuma, + DirectLumaAlpha, + BaseScaleRgb, + BaseScaleRgba, + DirectRbg, + DirectRgba +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingModeExtensions.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingModeExtensions.cs new file mode 100644 index 00000000..3000d37d --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingModeExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +internal static class EndpointEncodingModeExtensions +{ + public static int GetValuesCount(this EndpointEncodingMode mode) => mode switch + { + EndpointEncodingMode.DirectLuma => 2, + EndpointEncodingMode.DirectLumaAlpha or EndpointEncodingMode.BaseScaleRgb => 4, + EndpointEncodingMode.DirectRbg or EndpointEncodingMode.BaseScaleRgba => 6, + _ => 8 + }; +} \ No newline at end of file diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs new file mode 100644 index 00000000..7ed98612 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs @@ -0,0 +1,359 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +/// +/// Decodes HDR (High Dynamic Range) color endpoints for ASTC texture compression. +/// +/// +/// HDR modes produce 12-bit intermediate values (0-4095) which are shifted left by 4 +/// to produce the final 16-bit values (0-65520) stored as FP16 bit patterns. +/// +internal static class HdrEndpointDecoder +{ + public static (RgbaHdrColor low, RgbaHdrColor high) DecodeHdrMode(ReadOnlySpan values, int maxValue, ColorEndpointMode mode) + { + int count = mode.GetColorValuesCount(); + Span unquantizedValues = stackalloc int[count]; + int copyLength = Math.Min(count, values.Length); + for (int i = 0; i < copyLength; i++) + unquantizedValues[i] = Quantization.UnquantizeCEValueFromRange(values[i], maxValue); + return DecodeHdrModeUnquantized(unquantizedValues, mode); + } + + /// + /// Decodes HDR endpoints from already-unquantized values. + /// Called from the fused decode path where BISE decode + batch unquantize + /// have already been performed. + /// + public static (RgbaHdrColor low, RgbaHdrColor high) DecodeHdrModeUnquantized(ReadOnlySpan value, ColorEndpointMode mode) + { + return mode switch + { + ColorEndpointMode.HdrLumaLargeRange => UnpackHdrLuminanceLargeRangeCore(value[0], value[1]), + ColorEndpointMode.HdrLumaSmallRange => UnpackHdrLuminanceSmallRangeCore(value[0], value[1]), + ColorEndpointMode.HdrRgbBaseScale => UnpackHdrRgbBaseScaleCore(value[0], value[1], value[2], value[3]), + ColorEndpointMode.HdrRgbDirect => UnpackHdrRgbDirectCore(value[0], value[1], value[2], value[3], value[4], value[5]), + ColorEndpointMode.HdrRgbDirectLdrAlpha => UnpackHdrRgbDirectLdrAlphaCore(value), + ColorEndpointMode.HdrRgbDirectHdrAlpha => UnpackHdrRgbDirectHdrAlphaCore(value), + _ => throw new InvalidOperationException($"Mode {mode} is not an HDR mode") + }; + } + + /// + /// Performs an unsigned left shift of a signed value, avoiding undefined behavior + /// that would occur with signed left shift of negative values. + /// + private static int SafeSignedLeftShift(int value, int shift) => (int)((uint)value << shift); + + private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrLuminanceLargeRangeCore(int v0, int v1) + { + int y0, y1; + if (v1 >= v0) + { + y0 = v0 << 4; + y1 = v1 << 4; + } + else + { + y0 = (v1 << 4) + 8; + y1 = (v0 << 4) - 8; + } + + var low = new RgbaHdrColor((ushort)(y0 << 4), (ushort)(y0 << 4), (ushort)(y0 << 4), 0x7800); + var high = new RgbaHdrColor((ushort)(y1 << 4), (ushort)(y1 << 4), (ushort)(y1 << 4), 0x7800); + return (low, high); + } + + private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrLuminanceSmallRangeCore(int v0, int v1) + { + int y0, y1; + if ((v0 & 0x80) != 0) + { + y0 = ((v1 & 0xE0) << 4) | ((v0 & 0x7F) << 2); + y1 = (v1 & 0x1F) << 2; + } + else + { + y0 = ((v1 & 0xF0) << 4) | ((v0 & 0x7F) << 1); + y1 = (v1 & 0x0F) << 1; + } + + y1 += y0; + if (y1 > 0xFFF) + y1 = 0xFFF; + + var low = new RgbaHdrColor((ushort)(y0 << 4), (ushort)(y0 << 4), (ushort)(y0 << 4), 0x7800); + var high = new RgbaHdrColor((ushort)(y1 << 4), (ushort)(y1 << 4), (ushort)(y1 << 4), 0x7800); + return (low, high); + } + + private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbBaseScaleCore(int v0, int v1, int v2, int v3) + { + int modeValue = ((v0 & 0xC0) >> 6) | (((v1 & 0x80) >> 7) << 2) | (((v2 & 0x80) >> 7) << 3); + + int majorComponent; + int mode; + + (majorComponent, mode) = modeValue switch + { + _ when (modeValue & 0xC) != 0xC => (modeValue >> 2, modeValue & 3), + not 0xF => (modeValue & 3, 4), + _ => (0, 5) + }; + + int red = v0 & 0x3F; + int green = v1 & 0x1F; + int blue = v2 & 0x1F; + int scale = v3 & 0x1F; + + int bit0 = (v1 >> 6) & 1; + int bit1 = (v1 >> 5) & 1; + int bit2 = (v2 >> 6) & 1; + int bit3 = (v2 >> 5) & 1; + int bit4 = (v3 >> 7) & 1; + int bit5 = (v3 >> 6) & 1; + int bit6 = (v3 >> 5) & 1; + + int oneHotMode = 1 << mode; + + if ((oneHotMode & 0x30) != 0) green |= bit0 << 6; + if ((oneHotMode & 0x3A) != 0) green |= bit1 << 5; + if ((oneHotMode & 0x30) != 0) blue |= bit2 << 6; + if ((oneHotMode & 0x3A) != 0) blue |= bit3 << 5; + + if ((oneHotMode & 0x3D) != 0) scale |= bit6 << 5; + if ((oneHotMode & 0x2D) != 0) scale |= bit5 << 6; + if ((oneHotMode & 0x04) != 0) scale |= bit4 << 7; + + if ((oneHotMode & 0x3B) != 0) red |= bit4 << 6; + if ((oneHotMode & 0x04) != 0) red |= bit3 << 6; + + if ((oneHotMode & 0x10) != 0) red |= bit5 << 7; + if ((oneHotMode & 0x0F) != 0) red |= bit2 << 7; + + if ((oneHotMode & 0x05) != 0) red |= bit1 << 8; + if ((oneHotMode & 0x0A) != 0) red |= bit0 << 8; + + if ((oneHotMode & 0x05) != 0) red |= bit0 << 9; + if ((oneHotMode & 0x02) != 0) red |= bit6 << 9; + + if ((oneHotMode & 0x01) != 0) red |= bit3 << 10; + if ((oneHotMode & 0x02) != 0) red |= bit5 << 10; + + // Shift amounts per mode (from ARM reference) + ReadOnlySpan shiftAmounts = [1, 1, 2, 3, 4, 5]; + int shiftAmount = shiftAmounts[mode]; + + red <<= shiftAmount; + green <<= shiftAmount; + blue <<= shiftAmount; + scale <<= shiftAmount; + + if (mode != 5) + { + green = red - green; + blue = red - blue; + } + + // Swap components based on major component + (red, green, blue) = majorComponent switch + { + 1 => (green, red, blue), + 2 => (blue, green, red), + _ => (red, green, blue) + }; + + // Low endpoint is base minus scale offset + int red0 = red - scale; + int green0 = green - scale; + int blue0 = blue - scale; + + // Clamp to [0, 0xFFF] + red = Math.Max(red, 0); + green = Math.Max(green, 0); + blue = Math.Max(blue, 0); + red0 = Math.Max(red0, 0); + green0 = Math.Max(green0, 0); + blue0 = Math.Max(blue0, 0); + + var low = new RgbaHdrColor((ushort)(red0 << 4), (ushort)(green0 << 4), (ushort)(blue0 << 4), 0x7800); + var high = new RgbaHdrColor((ushort)(red << 4), (ushort)(green << 4), (ushort)(blue << 4), 0x7800); + return (low, high); + } + + private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectCore(int v0, int v1, int v2, int v3, int v4, int v5) + { + int modeValue = ((v1 & 0x80) >> 7) | (((v2 & 0x80) >> 7) << 1) | (((v3 & 0x80) >> 7) << 2); + int majorComponent = ((v4 & 0x80) >> 7) | (((v5 & 0x80) >> 7) << 1); + + // Special case: majorComponent == 3 (direct passthrough) + if (majorComponent == 3) + { + var low = new RgbaHdrColor( + (ushort)(v0 << 8), + (ushort)(v2 << 8), + (ushort)((v4 & 0x7F) << 9), + 0x7800); + var high = new RgbaHdrColor( + (ushort)(v1 << 8), + (ushort)(v3 << 8), + (ushort)((v5 & 0x7F) << 9), + 0x7800); + return (low, high); + } + + int a = v0 | ((v1 & 0x40) << 2); + int b0 = v2 & 0x3F; + int b1 = v3 & 0x3F; + int c = v1 & 0x3F; + int d0 = v4 & 0x7F; + int d1 = v5 & 0x7F; + + // Data bits table from ARM reference + ReadOnlySpan dataBitsTable = [7, 6, 7, 6, 5, 6, 5, 6]; + int dataBits = dataBitsTable[modeValue]; + + int bit0 = (v2 >> 6) & 1; + int bit1 = (v3 >> 6) & 1; + int bit2 = (v4 >> 6) & 1; + int bit3 = (v5 >> 6) & 1; + int bit4 = (v4 >> 5) & 1; + int bit5 = (v5 >> 5) & 1; + + int oneHotModeValue = 1 << modeValue; + + // Bit placement for 'a' + if ((oneHotModeValue & 0xA4) != 0) a |= bit0 << 9; + if ((oneHotModeValue & 0x8) != 0) a |= bit2 << 9; + if ((oneHotModeValue & 0x50) != 0) a |= bit4 << 9; + if ((oneHotModeValue & 0x50) != 0) a |= bit5 << 10; + if ((oneHotModeValue & 0xA0) != 0) a |= bit1 << 10; + if ((oneHotModeValue & 0xC0) != 0) a |= bit2 << 11; + + // Bit placement for 'c' + if ((oneHotModeValue & 0x4) != 0) c |= bit1 << 6; + if ((oneHotModeValue & 0xE8) != 0) c |= bit3 << 6; + if ((oneHotModeValue & 0x20) != 0) c |= bit2 << 7; + + // Bit placement for 'b0' and 'b1' + if ((oneHotModeValue & 0x5B) != 0) { b0 |= bit0 << 6; b1 |= bit1 << 6; } + if ((oneHotModeValue & 0x12) != 0) { b0 |= bit2 << 7; b1 |= bit3 << 7; } + + // Bit placement for 'd0' and 'd1' + if ((oneHotModeValue & 0xAF) != 0) { d0 |= bit4 << 5; d1 |= bit5 << 5; } + if ((oneHotModeValue & 0x5) != 0) { d0 |= bit2 << 6; d1 |= bit3 << 6; } + + // Sign-extend d0 and d1 based on dataBits + int signExtendShift = 32 - dataBits; + d0 = (d0 << signExtendShift) >> signExtendShift; + d1 = (d1 << signExtendShift) >> signExtendShift; + + // Expand to 12 bits + int valueShift = (modeValue >> 1) ^ 3; + a = SafeSignedLeftShift(a, valueShift); + b0 = SafeSignedLeftShift(b0, valueShift); + b1 = SafeSignedLeftShift(b1, valueShift); + c = SafeSignedLeftShift(c, valueShift); + d0 = SafeSignedLeftShift(d0, valueShift); + d1 = SafeSignedLeftShift(d1, valueShift); + + // Compute color values per ARM reference + int red1 = a; + int green1 = a - b0; + int blue1 = a - b1; + int red0 = a - c; + int green0 = a - b0 - c - d0; + int blue0 = a - b1 - c - d1; + + // Clamp to [0, 4095] + red0 = Math.Clamp(red0, 0, 0xFFF); + green0 = Math.Clamp(green0, 0, 0xFFF); + blue0 = Math.Clamp(blue0, 0, 0xFFF); + red1 = Math.Clamp(red1, 0, 0xFFF); + green1 = Math.Clamp(green1, 0, 0xFFF); + blue1 = Math.Clamp(blue1, 0, 0xFFF); + + // Swap components based on major component + (red0, green0, blue0, red1, green1, blue1) = majorComponent switch + { + 1 => (green0, red0, blue0, green1, red1, blue1), + 2 => (blue0, green0, red0, blue1, green1, red1), + _ => (red0, green0, blue0, red1, green1, blue1) + }; + + var lowResult = new RgbaHdrColor((ushort)(red0 << 4), (ushort)(green0 << 4), (ushort)(blue0 << 4), 0x7800); + var highResult = new RgbaHdrColor((ushort)(red1 << 4), (ushort)(green1 << 4), (ushort)(blue1 << 4), 0x7800); + return (lowResult, highResult); + } + + private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectLdrAlphaCore(ReadOnlySpan unquantizedValues) + { + var (rgbLow, rgbHigh) = UnpackHdrRgbDirectCore(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[3], unquantizedValues[4], unquantizedValues[5]); + + ushort alpha0 = (ushort)(unquantizedValues[6] * 257); + ushort alpha1 = (ushort)(unquantizedValues[7] * 257); + + var low = new RgbaHdrColor(rgbLow.R, rgbLow.G, rgbLow.B, alpha0); + var high = new RgbaHdrColor(rgbHigh.R, rgbHigh.G, rgbHigh.B, alpha1); + return (low, high); + } + + private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectHdrAlphaCore(ReadOnlySpan unquantizedValues) + { + var (rgbLow, rgbHigh) = UnpackHdrRgbDirectCore(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[3], unquantizedValues[4], unquantizedValues[5]); + + var (alpha0, alpha1) = UnpackHdrAlpha(unquantizedValues[6], unquantizedValues[7]); + + var low = new RgbaHdrColor(rgbLow.R, rgbLow.G, rgbLow.B, alpha0); + var high = new RgbaHdrColor(rgbHigh.R, rgbHigh.G, rgbHigh.B, alpha1); + return (low, high); + } + + /// + /// Decodes HDR alpha values + /// + private static (ushort low, ushort high) UnpackHdrAlpha(int v6, int v7) + { + int selector = ((v6 >> 7) & 1) | ((v7 >> 6) & 2); + v6 &= 0x7F; + v7 &= 0x7F; + + int a0, a1; + + if (selector == 3) + { + // Simple mode: direct 7-bit values shifted to 12-bit + a0 = v6 << 5; + a1 = v7 << 5; + } + else + { + // Complex mode: base + sign-extended offset + v6 |= (v7 << (selector + 1)) & 0x780; + v7 &= (0x3F >> selector); + v7 ^= 32 >> selector; + v7 -= 32 >> selector; + v6 <<= (4 - selector); + v7 <<= (4 - selector); + v7 += v6; + + if (v7 < 0) + v7 = 0; + else if (v7 > 0xFFF) + v7 = 0xFFF; + + a0 = v6; + a1 = v7; + } + + a0 = Math.Clamp(a0, 0, 0xFFF); + a1 = Math.Clamp(a1, 0, 0xFFF); + + return ((ushort)(a0 << 4), (ushort)(a1 << 4)); + } +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/IColorEndpointPair.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/IColorEndpointPair.cs new file mode 100644 index 00000000..45cae3a1 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/IColorEndpointPair.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +/// +/// A value-type discriminated union representing either an LDR or HDR color endpoint pair. +/// +[StructLayout(LayoutKind.Auto)] +internal struct ColorEndpointPair +{ + public bool IsHdr; + + // LDR fields (used when IsHdr == false) + public RgbaColor LdrLow; + public RgbaColor LdrHigh; + + // HDR fields (used when IsHdr == true) + public RgbaHdrColor HdrLow; + public RgbaHdrColor HdrHigh; + public bool AlphaIsLdr; + public bool ValuesAreLns; + + public static ColorEndpointPair Ldr(RgbaColor low, RgbaColor high) + => new() { IsHdr = false, LdrLow = low, LdrHigh = high }; + + public static ColorEndpointPair Hdr(RgbaHdrColor low, RgbaHdrColor high, bool alphaIsLdr = false, bool valuesAreLns = true) + => new() { IsHdr = true, HdrLow = low, HdrHigh = high, AlphaIsLdr = alphaIsLdr, ValuesAreLns = valuesAreLns }; +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs new file mode 100644 index 00000000..ad8de230 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs @@ -0,0 +1,165 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +internal sealed class Partition +{ + + private static readonly System.Collections.Concurrent.ConcurrentDictionary<(Footprint, int, int), Partition> _partitionCache = new(); + + public Footprint Footprint; + public int PartitionCount; + public int? PartitionId; + public int[] Assignment; + + + public Partition(Footprint footprint, int partitionCount, int? id = null) + { + Footprint = footprint; PartitionCount = partitionCount; PartitionId = id; Assignment = []; + } + + + public override bool Equals(object? obj) + { + if (obj is not Partition other) return false; + return PartitionMetric(this, other) == 0; + } + + public override int GetHashCode() => HashCode.Combine(Footprint, PartitionCount, PartitionId); + + public static int PartitionMetric(Partition a, Partition b) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(a.Footprint, b.Footprint); + + const int MaxNumSubsets = 4; + int width = a.Footprint.Width; + int height = a.Footprint.Height; + + var pairCounts = new List<(int a, int b, int count)>(); + for (int y = 0; y < 4; ++y) for (int x = 0; x < 4; ++x) pairCounts.Add((x, y, 0)); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int idx = y * width + x; + int aVal = a.Assignment[idx]; + int bVal = b.Assignment[idx]; + pairCounts[bVal * 4 + aVal] = (aVal, bVal, pairCounts[bVal * 4 + aVal].count + 1); + } + } + + var sorted = pairCounts.OrderByDescending(p => p.count).ToList(); + var assigned = new bool[MaxNumSubsets, MaxNumSubsets]; + int pixelsMatched = 0; + foreach (var pairCount in sorted) + { + bool isAssigned = false; + for (int i = 0; i < MaxNumSubsets; ++i) + { + if (assigned[pairCount.a, i] || assigned[i, pairCount.b]) { isAssigned = true; break; } + } + if (!isAssigned) + { + assigned[pairCount.a, pairCount.b] = true; + pixelsMatched += pairCount.count; + } + } + + return width * height - pixelsMatched; + } + + // Basic GetASTCPartition implementation using selection function from C++ + public static Partition GetASTCPartition(Footprint footprint, int partitionCount, int partitionId) + { + var key = (footprint, partitionCount, partitionId); + if (_partitionCache.TryGetValue(key, out var cached)) + return cached; + + var part = new Partition(footprint, partitionCount, partitionId); + int w = footprint.Width; + int h = footprint.Height; + var assignment = new int[w * h]; + int idx = 0; + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) + assignment[idx++] = SelectASTCPartition(partitionId, x, y, 0, partitionCount, footprint.PixelCount); + part.Assignment = assignment; + _partitionCache.TryAdd(key, part); + return part; + } + + // For now, implement a naive FindClosestASTCPartition that just checks a + // small set of candidate partitions generated by enum footprints. We'll + // return the same footprint size partitions with ID=0 for simplicity. + public static Partition FindClosestASTCPartition(Partition candidate) + { + // Search a few partitions and pick the one with minimal PartitionMetric + var best = GetASTCPartition(candidate.Footprint, Math.Max(1, candidate.PartitionCount), 0); + int bestDist = PartitionMetric(best, candidate); + return best; + } + + + // Very small port of selection function; behavior taken from C++ file. + private static int SelectASTCPartition(int seed, int x, int y, int z, int partitionCount, int pixelCount) + { + if (partitionCount <= 1) return 0; + if (pixelCount < 31) { x <<= 1; y <<= 1; z <<= 1; } + seed += (partitionCount - 1) * 1024; + uint randomNumber = (uint)seed; + randomNumber ^= randomNumber >> 15; + randomNumber -= randomNumber << 17; + randomNumber += randomNumber << 7; + randomNumber += randomNumber << 4; + randomNumber ^= randomNumber >> 5; + randomNumber += randomNumber << 16; + randomNumber ^= randomNumber >> 7; + randomNumber ^= randomNumber >> 3; + randomNumber ^= randomNumber << 6; + randomNumber ^= randomNumber >> 17; + + uint seed1 = randomNumber & 0xF; + uint seed2 = (randomNumber >> 4) & 0xF; + uint seed3 = (randomNumber >> 8) & 0xF; + uint seed4 = (randomNumber >> 12) & 0xF; + uint seed5 = (randomNumber >> 16) & 0xF; + uint seed6 = (randomNumber >> 20) & 0xF; + uint seed7 = (randomNumber >> 24) & 0xF; + uint seed8 = (randomNumber >> 28) & 0xF; + uint seed9 = (randomNumber >> 18) & 0xF; + uint seed10 = (randomNumber >> 22) & 0xF; + uint seed11 = (randomNumber >> 26) & 0xF; + uint seed12 = ((randomNumber >> 30) | (randomNumber << 2)) & 0xF; + + seed1 *= seed1; seed2 *= seed2; seed3 *= seed3; seed4 *= seed4; + seed5 *= seed5; seed6 *= seed6; seed7 *= seed7; seed8 *= seed8; + seed9 *= seed9; seed10 *= seed10; seed11 *= seed11; seed12 *= seed12; + + int sh1, sh2, sh3; + if ((seed & 1) != 0) { sh1 = (seed & 2) != 0 ? 4 : 5; sh2 = (partitionCount == 3) ? 6 : 5; } + else { sh1 = (partitionCount == 3) ? 6 : 5; sh2 = (seed & 2) != 0 ? 4 : 5; } + sh3 = (seed & 0x10) != 0 ? sh1 : sh2; + + seed1 >>= sh1; seed2 >>= sh2; seed3 >>= sh1; seed4 >>= sh2; + seed5 >>= sh1; seed6 >>= sh2; seed7 >>= sh1; seed8 >>= sh2; + seed9 >>= sh3; seed10 >>= sh3; seed11 >>= sh3; seed12 >>= sh3; + + int a = (int)(seed1 * x + seed2 * y + seed11 * z + (randomNumber >> 14)); + int b = (int)(seed3 * x + seed4 * y + seed12 * z + (randomNumber >> 10)); + int c = (int)(seed5 * x + seed6 * y + seed9 * z + (randomNumber >> 6)); + int d = (int)(seed7 * x + seed8 * y + seed10 * z + (randomNumber >> 2)); + + a &= 0x3F; b &= 0x3F; c &= 0x3F; d &= 0x3F; + if (partitionCount <= 3) d = 0; + if (partitionCount <= 2) c = 0; + + if (a >= b && a >= c && a >= d) return 0; + else if (b >= c && b >= d) return 1; + else if (c >= d) return 2; + else return 3; + } +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs new file mode 100644 index 00000000..0d59b21d --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; + +internal static class RgbaColorExtensions +{ + /// + /// Uses the value in the blue channel to tint the red and green + /// + /// + /// Applies the 'blue_contract' function defined in Section C.2.14 of the ASTC specification. + /// + public static RgbaColor WithBlueContract(int red, int green, int blue, int alpha) + => new( + r: (red + blue) >> 1, + g: (green + blue) >> 1, + b: blue, + a: alpha); + + /// + /// Uses the value in the blue channel to tint the red and green + /// + /// + /// Applies the 'blue_contract' function defined in Section C.2.14 of the ASTC specification. + /// + public static RgbaColor WithBlueContract(this RgbaColor color) + => WithBlueContract(color.R, color.G, color.B, color.A); + + /// + /// The inverse of + /// + public static RgbaColor WithInvertedBlueContract(this RgbaColor color) + => new( + r: 2 * color.R - color.B, + g: 2 * color.G - color.B, + b: color.B, + a: color.A); + + public static RgbaColor AsOffsetFrom(this RgbaColor color, RgbaColor baseColor) + { + int[] offset = [color.R, color.G, color.B, color.A]; + + for (int i = 0; i < RgbaColor.BytesPerPixel; ++i) + { + var (a, b) = BitOperations.TransferPrecision(offset[i], baseColor[i]); + offset[i] = Math.Clamp(baseColor[i] + a, byte.MinValue, byte.MaxValue); + } + + return new RgbaColor(offset[0], offset[1], offset[2], offset[3]); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Textures.Astc/Core/BitOperations.cs b/src/ImageSharp.Textures.Astc/Core/BitOperations.cs new file mode 100644 index 00000000..dc5a9db9 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/BitOperations.cs @@ -0,0 +1,97 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +internal static class BitOperations +{ + /// + /// Return the specified range as a (low bits in lower 64 bits) + /// + public static UInt128 GetBits(UInt128 value, int start, int length) + { + if (length <= 0) + return UInt128.Zero; + + var shifted = value >> start; + if (length >= 128) + return shifted; + + if (length >= 64) + { + ulong lowMask = ~0UL; + int highBits = length - 64; + ulong highMask = (highBits == 64) + ? ~0UL + : ((1UL << highBits) - 1UL); + + return new UInt128(shifted.High() & highMask, shifted.Low() & lowMask); + } + else + { + ulong mask = (length == 64) + ? ~0UL + : ((1UL << length) - 1UL); + + return new UInt128(0, shifted.Low() & mask); + } + } + + /// + /// Return the specified range as a ulong + /// + public static ulong GetBits(ulong value, int start, int length) + { + if (length <= 0) + return 0UL; + + int totalBits = sizeof(ulong) * 8; + ulong mask = length == totalBits + ? ~0UL + : ~0UL >> (totalBits - length); + + return (value >> start) & mask; + } + + /// + /// Transfers a few bits of precision from one value to another. + /// + /// + /// The 'bit_transfer_signed' function defined in Section C.2.14 of the ASTC specification + /// + public static (int a, int b) TransferPrecision(int a, int b) + { + b >>= 1; + b |= a & 0x80; + a >>= 1; + a &= 0x3F; + + if ((a & 0x20) != 0) + a -= 0x40; + + return (a, b); + } + + /// + /// Takes two values, |a| in the range [-32, 31], and |b| in the range [0, 255], + /// and returns the two values in [0, 255] that will reconstruct |a| and |b| when + /// passed to the function. + /// + public static (int a, int b) TransferPrecisionInverse(int a, int b) + { + ArgumentOutOfRangeException.ThrowIfLessThan(a, -32); + ArgumentOutOfRangeException.ThrowIfGreaterThan(a, 31); + ArgumentOutOfRangeException.ThrowIfLessThan(b, byte.MinValue); + ArgumentOutOfRangeException.ThrowIfGreaterThan(b, byte.MaxValue); + + if (a < 0) + a += 0x40; + + a <<= 1; + a |= b & 0x80; + b <<= 1; + b &= 0xff; + + return (a, b); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs b/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs new file mode 100644 index 00000000..020181d3 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs @@ -0,0 +1,145 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +/// +/// Pre-computed weight infill data for a specific (footprint, weightGridX, weightGridY) combination. +/// Stores bilinear interpolation indices and factors in a transposed layout. +/// +internal sealed class DecimationInfo +{ + public readonly int TexelCount; + + // Transposed layout: [contribution * TexelCount + texel] + // 4 contributions per texel (bilinear interpolation from weight grid). + // For edge texels where some grid points are out of bounds, factor is 0 and index is 0. + public readonly int[] WeightIndices; // size: 4 * TexelCount + public readonly int[] WeightFactors; // size: 4 * TexelCount + + public DecimationInfo(int texelCount, int[] weightIndices, int[] weightFactors) + { + TexelCount = texelCount; + WeightIndices = weightIndices; + WeightFactors = weightFactors; + } +} + +/// +/// Caches pre-computed DecimationInfo tables and provides weight infill. +/// For each unique (footprint, gridX, gridY) combination, the bilinear interpolation +/// indices and factors are computed once and reused for every block with that configuration. +/// Uses a flat array indexed by (footprintType, gridX, gridY) for O(1) lookup. +/// +internal static class DecimationTable +{ + // Grid dimensions range from 2 to 12 inclusive + private const int GridMin = 2; + private const int GridRange = 11; // 12 - 2 + 1 + private const int FootprintCount = 14; + private static readonly DecimationInfo?[] _table = new DecimationInfo?[FootprintCount * GridRange * GridRange]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DecimationInfo Get(Footprint footprint, int gridX, int gridY) + { + int index = (int)footprint.Type * GridRange * GridRange + (gridX - GridMin) * GridRange + (gridY - GridMin); + var decimationInfo = _table[index]; + if (decimationInfo is null) + { + decimationInfo = Compute(footprint.Width, footprint.Height, gridX, gridY); + _table[index] = decimationInfo; + } + return decimationInfo; + } + + /// + /// Performs weight infill using pre-computed tables. + /// Maps unquantized grid weights to per-texel weights via bilinear interpolation + /// with pre-computed indices and factors. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void InfillWeights(ReadOnlySpan gridWeights, DecimationInfo decimationInfo, Span result) + { + int texelCount = decimationInfo.TexelCount; + int[] weightIndices = decimationInfo.WeightIndices; + int[] weightFactors = decimationInfo.WeightFactors; + int offset1 = texelCount, offset2 = texelCount * 2, offset3 = texelCount * 3; + + for (int i = 0; i < texelCount; i++) + { + result[i] = (8 + + gridWeights[weightIndices[i]] * weightFactors[i] + + gridWeights[weightIndices[offset1 + i]] * weightFactors[offset1 + i] + + gridWeights[weightIndices[offset2 + i]] * weightFactors[offset2 + i] + + gridWeights[weightIndices[offset3 + i]] * weightFactors[offset3 + i]) >> 4; + } + } + + private static int GetScaleFactorD(int blockDimensions) + { + return (int)((1024f + (blockDimensions >> 1)) / (blockDimensions - 1)); + } + + private static DecimationInfo Compute(int footprintWidth, int footprintHeight, int gridWidth, int gridHeight) + { + int texelCount = footprintWidth * footprintHeight; + + var indices = new int[4 * texelCount]; + var factors = new int[4 * texelCount]; + + int scaleHorizontal = GetScaleFactorD(footprintWidth); + int scaleVertical = GetScaleFactorD(footprintHeight); + int gridLimit = gridWidth * gridHeight; + int maxGridX = gridWidth - 1; + int maxGridY = gridHeight - 1; + + int texelIndex = 0; + for (int texelY = 0; texelY < footprintHeight; ++texelY) + { + int scaledY = scaleVertical * texelY; + int gridY = (scaledY * maxGridY + 32) >> 6; + int gridRowIndex = gridY >> 4; + int fractionY = gridY & 0xF; + + for (int texelX = 0; texelX < footprintWidth; ++texelX) + { + int scaledX = scaleHorizontal * texelX; + int gridX = (scaledX * maxGridX + 32) >> 6; + int gridColIndex = gridX >> 4; + int fractionX = gridX & 0xF; + + int gridPoint0 = gridColIndex + gridWidth * gridRowIndex; + int gridPoint1 = gridPoint0 + 1; + int gridPoint2 = gridColIndex + gridWidth * (gridRowIndex + 1); + int gridPoint3 = gridPoint2 + 1; + + int factor3 = (fractionX * fractionY + 8) >> 4; + int factor2 = fractionY - factor3; + int factor1 = fractionX - factor3; + int factor0 = 16 - fractionX - fractionY + factor3; + + // For out-of-bounds grid points, zero the factor and use index 0 (safe dummy) + if (gridPoint3 >= gridLimit) { factor3 = 0; gridPoint3 = 0; } + if (gridPoint2 >= gridLimit) { factor2 = 0; gridPoint2 = 0; } + if (gridPoint1 >= gridLimit) { factor1 = 0; gridPoint1 = 0; } + if (gridPoint0 >= gridLimit) { factor0 = 0; gridPoint0 = 0; } + + indices[0 * texelCount + texelIndex] = gridPoint0; + indices[1 * texelCount + texelIndex] = gridPoint1; + indices[2 * texelCount + texelIndex] = gridPoint2; + indices[3 * texelCount + texelIndex] = gridPoint3; + + factors[0 * texelCount + texelIndex] = factor0; + factors[1 * texelCount + texelIndex] = factor1; + factors[2 * texelCount + texelIndex] = factor2; + factors[3 * texelCount + texelIndex] = factor3; + + texelIndex++; + } + } + + return new DecimationInfo(texelCount, indices, factors); + } +} diff --git a/src/ImageSharp.Textures.Astc/Core/Footprint.cs b/src/ImageSharp.Textures.Astc/Core/Footprint.cs new file mode 100644 index 00000000..215f4d4a --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/Footprint.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +/// +/// Represents the dimensions of an ASTC block footprint. +/// +public readonly record struct Footprint +{ + /// Gets the block width in texels. + public int Width { get; } + + /// Gets the block height in texels. + public int Height { get; } + + /// Gets the footprint type enum value. + public FootprintType Type { get; } + + /// Gets the total number of texels in the block (Width * Height). + public int PixelCount { get; } + + private Footprint(FootprintType type, int width, int height) + { + ArgumentOutOfRangeException.ThrowIfNegative(width); + ArgumentOutOfRangeException.ThrowIfNegative(height); + + this.Type = type; + this.Width = width; + this.Height = height; + this.PixelCount = width * height; + } + + /// + /// Creates a from the specified . + /// + public static Footprint FromFootprintType(FootprintType type) => type switch + { + FootprintType.Footprint4x4 => Get4x4(), + FootprintType.Footprint5x4 => Get5x4(), + FootprintType.Footprint5x5 => Get5x5(), + FootprintType.Footprint6x5 => Get6x5(), + FootprintType.Footprint6x6 => Get6x6(), + FootprintType.Footprint8x5 => Get8x5(), + FootprintType.Footprint8x6 => Get8x6(), + FootprintType.Footprint8x8 => Get8x8(), + FootprintType.Footprint10x5 => Get10x5(), + FootprintType.Footprint10x6 => Get10x6(), + FootprintType.Footprint10x8 => Get10x8(), + FootprintType.Footprint10x10 => Get10x10(), + FootprintType.Footprint12x10 => Get12x10(), + FootprintType.Footprint12x12 => Get12x12(), + _ => throw new ArgumentOutOfRangeException($"Invalid FootprintType: {type}"), + }; + + internal static Footprint Get4x4() => new(FootprintType.Footprint4x4, 4, 4); + internal static Footprint Get5x4() => new(FootprintType.Footprint5x4, 5, 4); + internal static Footprint Get5x5() => new(FootprintType.Footprint5x5, 5, 5); + internal static Footprint Get6x5() => new(FootprintType.Footprint6x5, 6, 5); + internal static Footprint Get6x6() => new(FootprintType.Footprint6x6, 6, 6); + internal static Footprint Get8x5() => new(FootprintType.Footprint8x5, 8, 5); + internal static Footprint Get8x6() => new(FootprintType.Footprint8x6, 8, 6); + internal static Footprint Get8x8() => new(FootprintType.Footprint8x8, 8, 8); + internal static Footprint Get10x5() => new(FootprintType.Footprint10x5, 10, 5); + internal static Footprint Get10x6() => new(FootprintType.Footprint10x6, 10, 6); + internal static Footprint Get10x8() => new(FootprintType.Footprint10x8, 10, 8); + internal static Footprint Get10x10() => new(FootprintType.Footprint10x10, 10, 10); + internal static Footprint Get12x10() => new(FootprintType.Footprint12x10, 12, 10); + internal static Footprint Get12x12() => new(FootprintType.Footprint12x12, 12, 12); +} diff --git a/src/ImageSharp.Textures.Astc/Core/FootprintType.cs b/src/ImageSharp.Textures.Astc/Core/FootprintType.cs new file mode 100644 index 00000000..d7ab3204 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/FootprintType.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +/// +/// The supported ASTC block footprint sizes. +/// +public enum FootprintType +{ + /// 4x4 texel block. + Footprint4x4, + /// 5x4 texel block. + Footprint5x4, + /// 5x5 texel block. + Footprint5x5, + /// 6x5 texel block. + Footprint6x5, + /// 6x6 texel block. + Footprint6x6, + /// 8x5 texel block. + Footprint8x5, + /// 8x6 texel block. + Footprint8x6, + /// 8x8 texel block. + Footprint8x8, + /// 10x5 texel block. + Footprint10x5, + /// 10x6 texel block. + Footprint10x6, + /// 10x8 texel block. + Footprint10x8, + /// 10x10 texel block. + Footprint10x10, + /// 12x10 texel block. + Footprint12x10, + /// 12x12 texel block. + Footprint12x12, +} diff --git a/src/ImageSharp.Textures.Astc/Core/RgbColor.cs b/src/ImageSharp.Textures.Astc/Core/RgbColor.cs new file mode 100644 index 00000000..17518b28 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/RgbColor.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +internal readonly record struct RgbColor(byte R, byte G, byte B) +{ + public static int BytesPerPixel => 3; + + public static RgbColor Empty => default; + + /// + /// The rounded arithmetic mean of the R, G, and B channels + /// + public byte Average + { + get + { + var sum = R + G + B; + return (byte)((sum * 256 + 384) / 768); + } + } + + public RgbColor(int r, int g, int b) : this( + (byte)Math.Clamp(r, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(g, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(b, byte.MinValue, byte.MaxValue)) + { + } + + public int this[int i] + => i switch + { + 0 => R, + 1 => G, + 2 => B, + _ => throw new ArgumentOutOfRangeException(nameof(i), $"Index must be between 0 and {BytesPerPixel - 1}. Actual value: {i}.") + }; + + public static int SquaredError(RgbColor a, RgbColor b) + { + int result = 0; + for (int i = 0; i < BytesPerPixel; i++) + { + int diff = a[i] - b[i]; + result += diff * diff; + } + return result; + } + + /// + /// Computes the squared error comparing only the RGB channels of two RgbaColors. + /// + public static int SquaredError(RgbaColor a, RgbaColor b) + { + int dr = a.R - b.R; + int dg = a.G - b.G; + int db = a.B - b.B; + return dr * dr + dg * dg + db * db; + } +} diff --git a/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs b/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs new file mode 100644 index 00000000..e073c55e --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +internal readonly record struct RgbaColor(byte R, byte G, byte B, byte A) +{ + public static int BytesPerPixel => 4; + + public static RgbaColor Empty => default; + + /// + /// The rounded arithmetic mean of the R, G, and B channels + /// + public byte Average + { + get + { + var sum = R + G + B; + return (byte)((sum * 256 + 384) / 768); + } + } + + public RgbaColor(int r, int g, int b, int a = byte.MaxValue) : this( + (byte)Math.Clamp(r, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(g, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(b, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(a, byte.MinValue, byte.MaxValue)) + { + } + + public int this[int i] + => i switch + { + 0 => R, + 1 => G, + 2 => B, + 3 => A, + _ => throw new ArgumentOutOfRangeException(nameof(i), $"Index must be between 0 and {BytesPerPixel - 1}. Actual value: {i}.") + }; + + public static int SquaredError(RgbaColor a, RgbaColor b) + { + int result = 0; + for (int i = 0; i < BytesPerPixel; i++) + { + int diff = a[i] - b[i]; + result += diff * diff; + } + return result; + } + + public bool IsCloseTo(RgbaColor other, int tolerance) + => Math.Abs(R - other.R) <= tolerance && + Math.Abs(G - other.G) <= tolerance && + Math.Abs(B - other.B) <= tolerance && + Math.Abs(A - other.A) <= tolerance; +} diff --git a/src/ImageSharp.Textures.Astc/Core/RgbaHdrColor.cs b/src/ImageSharp.Textures.Astc/Core/RgbaHdrColor.cs new file mode 100644 index 00000000..209b205a --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/RgbaHdrColor.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +/// +/// Represents an HDR (High Dynamic Range) color with 16-bit per-channel precision. +/// +/// +/// HDR colors use ushort values (0-65535) for each channel, allowing representation +/// of values beyond the standard 0-255 LDR range. This enables encoding of High Dynamic +/// Range content that can represent brightness values exceeding the typical white point. +/// +internal readonly record struct RgbaHdrColor(ushort R, ushort G, ushort B, ushort A) +{ + public static RgbaHdrColor Empty => default; + + /// + /// Indexer to access channels by index: 0=R, 1=G, 2=B, 3=A + /// + public ushort this[int i] => i switch + { + 0 => R, + 1 => G, + 2 => B, + 3 => A, + _ => throw new ArgumentOutOfRangeException(nameof(i), $"Index must be between 0 and 3. Actual value: {i}.") + }; + + /// + /// Converts an LDR color (0-255) to HDR range (0-65535). + /// + public static RgbaHdrColor FromRgba(RgbaColor ldr) + => new((ushort)(ldr.R * 257), (ushort)(ldr.G * 257), (ushort)(ldr.B * 257), (ushort)(ldr.A * 257)); + + /// + /// Converts an HDR color (0-65535) to LDR range (0-255). + /// + /// + /// Values are clamped to 0-255 range, so HDR values exceeding + /// the standard white point will be clipped. + /// + public RgbaColor ToLowDynamicRange() + => new((byte)(R >> 8), (byte)(G >> 8), (byte)(B >> 8), (byte)(A >> 8)); + + public bool IsCloseTo(RgbaHdrColor other, int tolerance) + => Math.Abs(R - other.R) <= tolerance && + Math.Abs(G - other.G) <= tolerance && + Math.Abs(B - other.B) <= tolerance && + Math.Abs(A - other.A) <= tolerance; +} diff --git a/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs b/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs new file mode 100644 index 00000000..83cc6e22 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs @@ -0,0 +1,156 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +internal static class SimdHelpers +{ + private static readonly Vector128 Vec32 = Vector128.Create(32); + private static readonly Vector128 Vec64 = Vector128.Create(64); + private static readonly Vector128 Vec255 = Vector128.Create(255); + private static readonly Vector128 Vec32767 = Vector128.Create(32767); + + /// + /// Interpolates one channel for 4 pixels simultaneously. + /// All 4 pixels share the same endpoint values but have different weights. + /// Returns 4 byte results packed into the lower bytes of a . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Interpolate4ChannelPixels(int p0, int p1, Vector128 weights) + { + // Bit-replicate endpoint bytes to 16-bit + var c0 = Vector128.Create((p0 << 8) | p0); + var c1 = Vector128.Create((p1 << 8) | p1); + + // c = (c0 * (64 - w) + c1 * w + 32) >> 6 + // NOTE: Using >> 6 instead of / 64 because Vector128 division + // has no hardware support and decomposes to scalar operations. + var w64 = Vec64 - weights; + var c = (c0 * w64 + c1 * weights + Vec32) >> 6; + + // Quantize: (c * 255 + 32767) >> 16, clamped to [0, 255] + var result = (c * Vec255 + Vec32767) >>> 16; + return Vector128.Min(Vector128.Max(result, Vector128.Zero), Vec255); + } + + /// + /// Writes 4 LDR pixels directly to output buffer using SIMD. + /// Processes each channel across 4 pixels in parallel, then interleaves to RGBA output. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write4PixelLdr( + Span output, + int offset, + int lowR, + int lowG, + int lowB, + int lowA, + int highR, + int highG, + int highB, + int highA, + Vector128 weights) + { + var r = Interpolate4ChannelPixels(lowR, highR, weights); + var g = Interpolate4ChannelPixels(lowG, highG, weights); + var b = Interpolate4ChannelPixels(lowB, highB, weights); + var a = Interpolate4ChannelPixels(lowA, highA, weights); + + // Pack 4 RGBA pixels into 16 bytes via vector OR+shift. + // Each int element has its channel value in bits [0:7]. + // Combine: element[i] = R[i] | (G[i] << 8) | (B[i] << 16) | (A[i] << 24) + // On little-endian, storing this int32 writes bytes [R, G, B, A]. + var rgba = r | (g << 8) | (b << 16) | (a << 24); + rgba.AsByte().CopyTo(output.Slice(offset, 16)); + } + + /// + /// Scalar single-pixel LDR interpolation, writing directly to buffer. + /// No RgbaColor allocation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteSinglePixelLdr( + Span output, + int offset, + int lowR, + int lowG, + int lowB, + int lowA, + int highR, + int highG, + int highB, + int highA, + int weight) + { + output[offset + 0] = (byte)InterpolateChannelScalar(lowR, highR, weight); + output[offset + 1] = (byte)InterpolateChannelScalar(lowG, highG, weight); + output[offset + 2] = (byte)InterpolateChannelScalar(lowB, highB, weight); + output[offset + 3] = (byte)InterpolateChannelScalar(lowA, highA, weight); + } + + /// + /// Scalar single-pixel dual-plane LDR interpolation, writing directly to buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteSinglePixelLdrDualPlane( + Span output, + int offset, + int lowR, + int lowG, + int lowB, + int lowA, + int highR, + int highG, + int highB, + int highA, + int weight, + int dpChannel, + int dpWeight) + { + output[offset + 0] = (byte)InterpolateChannelScalar(lowR, highR, + dpChannel == 0 ? dpWeight : weight); + output[offset + 1] = (byte)InterpolateChannelScalar(lowG, highG, + dpChannel == 1 ? dpWeight : weight); + output[offset + 2] = (byte)InterpolateChannelScalar(lowB, highB, + dpChannel == 2 ? dpWeight : weight); + output[offset + 3] = (byte)InterpolateChannelScalar(lowA, highA, + dpChannel == 3 ? dpWeight : weight); + } + + // Keep the old API for ColorAt() (used by tests and non-hot paths) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor InterpolateColorLdr(RgbaColor low, RgbaColor high, int weight) + => new( + r: InterpolateChannelScalar(low.R, high.R, weight), + g: InterpolateChannelScalar(low.G, high.G, weight), + b: InterpolateChannelScalar(low.B, high.B, weight), + a: InterpolateChannelScalar(low.A, high.A, weight)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor InterpolateColorLdrDualPlane( + RgbaColor low, RgbaColor high, + int weight, int dualPlaneChannel, int dualPlaneWeight) + => new( + r: InterpolateChannelScalar(low.R, high.R, + dualPlaneChannel == 0 ? dualPlaneWeight : weight), + g: InterpolateChannelScalar(low.G, high.G, + dualPlaneChannel == 1 ? dualPlaneWeight : weight), + b: InterpolateChannelScalar(low.B, high.B, + dualPlaneChannel == 2 ? dualPlaneWeight : weight), + a: InterpolateChannelScalar(low.A, high.A, + dualPlaneChannel == 3 ? dualPlaneWeight : weight)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int InterpolateChannelScalar(int p0, int p1, int weight) + { + int c0 = (p0 << 8) | p0; + int c1 = (p1 << 8) | p1; + int c = (c0 * (64 - weight) + c1 * weight + 32) / 64; + int quantized = ((c * 255) + 32767) / 65536; + + return Math.Clamp(quantized, 0, 255); + } +} diff --git a/src/ImageSharp.Textures.Astc/Core/UInt128Extensions.cs b/src/ImageSharp.Textures.Astc/Core/UInt128Extensions.cs new file mode 100644 index 00000000..4e6f1083 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/UInt128Extensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +internal static class UInt128Extensions +{ + /// + /// The lower 64 bits of the value + /// + public static ulong Low(this UInt128 value) + => (ulong)(value & 0xFFFFFFFFFFFFFFFFUL); + + /// + /// The upper 64 bits of the value + /// + public static ulong High(this UInt128 value) + => (ulong)(value >> 64); + + /// + /// A mask with the lowest n bits set to 1 + /// + public static UInt128 OnesMask(int n) + { + if (n <= 0) return UInt128.Zero; + if (n >= 128) return new UInt128(~0UL, ~0UL); + if (n <= 64) + { + ulong low = (n == 64) + ? ~0UL + : ((1UL << n) - 1UL); + + return new UInt128(0UL, low); + } + else + { + int highBits = n - 64; + ulong low = ~0UL; + ulong high = (highBits == 64) + ? ~0UL + : ((1UL << highBits) - 1UL); + + return new UInt128(high, low); + } + } + + /// + /// Reverse bits across the full 128-bit value + /// + public static UInt128 ReverseBits(this UInt128 value) + { + ulong revLow = ReverseBits(value.Low()); + ulong revHigh = ReverseBits(value.High()); + + return new UInt128(revLow, revHigh); + } + + private static ulong ReverseBits(ulong x) + { + x = ((x >> 1) & 0x5555555555555555UL) | ((x & 0x5555555555555555UL) << 1); + x = ((x >> 2) & 0x3333333333333333UL) | ((x & 0x3333333333333333UL) << 2); + x = ((x >> 4) & 0x0F0F0F0F0F0F0F0FUL) | ((x & 0x0F0F0F0F0F0F0F0FUL) << 4); + x = ((x >> 8) & 0x00FF00FF00FF00FFUL) | ((x & 0x00FF00FF00FF00FFUL) << 8); + x = ((x >> 16) & 0x0000FFFF0000FFFFUL) | ((x & 0x0000FFFF0000FFFFUL) << 16); + x = (x >> 32) | (x << 32); + + return x; + } +} \ No newline at end of file diff --git a/src/ImageSharp.Textures.Astc/IO/AstcFile.cs b/src/ImageSharp.Textures.Astc/IO/AstcFile.cs new file mode 100644 index 00000000..59754a9a --- /dev/null +++ b/src/ImageSharp.Textures.Astc/IO/AstcFile.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.IO; + +/// +/// A very simple format consisting of a small header followed immediately +/// by the binary payload for a single image surface. +/// +/// +/// See https://github.com/ARM-software/astc-encoder/blob/main/Docs/FileFormat.md +/// +internal record AstcFile +{ + private readonly AstcFileHeader _header; + private readonly byte[] _blocks; + + public ReadOnlySpan Blocks => _blocks; + public Footprint Footprint { get; } + public int Width => _header.ImageWidth; + public int Height => _header.ImageHeight; + public int Depth => _header.ImageDepth; + + internal AstcFile(AstcFileHeader header, byte[] blocks) + { + _header = header; + _blocks = blocks; + Footprint = GetFootprint(); + } + + public static AstcFile FromMemory(byte[] data) + { + var header = AstcFileHeader.FromMemory(data.AsSpan(0, AstcFileHeader.SizeInBytes)); + + // Remaining bytes are blocks; C++ reference keeps them as string; here we keep as byte[] + var blocks = new byte[data.Length - AstcFileHeader.SizeInBytes]; + Array.Copy(data, AstcFileHeader.SizeInBytes, blocks, 0, blocks.Length); + + return new AstcFile(header, blocks); + } + + /// + /// Map the block dimensions in the header to a Footprint, if possible. + /// + private Footprint GetFootprint() => (_header.BlockWidth, _header.BlockHeight) switch + { + (4, 4) => Footprint.FromFootprintType(FootprintType.Footprint4x4), + (5, 4) => Footprint.FromFootprintType(FootprintType.Footprint5x4), + (5, 5) => Footprint.FromFootprintType(FootprintType.Footprint5x5), + (6, 5) => Footprint.FromFootprintType(FootprintType.Footprint6x5), + (6, 6) => Footprint.FromFootprintType(FootprintType.Footprint6x6), + (8, 5) => Footprint.FromFootprintType(FootprintType.Footprint8x5), + (8, 6) => Footprint.FromFootprintType(FootprintType.Footprint8x6), + (8, 8) => Footprint.FromFootprintType(FootprintType.Footprint8x8), + (10, 5) => Footprint.FromFootprintType(FootprintType.Footprint10x5), + (10, 6) => Footprint.FromFootprintType(FootprintType.Footprint10x6), + (10, 8) => Footprint.FromFootprintType(FootprintType.Footprint10x8), + (10, 10) => Footprint.FromFootprintType(FootprintType.Footprint10x10), + (12, 10) => Footprint.FromFootprintType(FootprintType.Footprint12x10), + (12, 12) => Footprint.FromFootprintType(FootprintType.Footprint12x12), + _ => throw new ArgumentOutOfRangeException($"Unsupported block dimensions: {_header.BlockWidth}x{_header.BlockHeight}"), + }; +} diff --git a/src/ImageSharp.Textures.Astc/IO/AstcFileHeader.cs b/src/ImageSharp.Textures.Astc/IO/AstcFileHeader.cs new file mode 100644 index 00000000..2922203d --- /dev/null +++ b/src/ImageSharp.Textures.Astc/IO/AstcFileHeader.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.IO; + +/// +/// The 16 byte ASTC file header +/// +/// +/// ASTC block and decoded image dimensions in texels. +/// +/// For 2D images the Z dimension must be set to 1. +/// +/// Note that the image is not required to be an exact multiple of the compressed block +/// size; the compressed data may include padding that is discarded during decompression. +/// +internal readonly record struct AstcFileHeader(byte BlockWidth, byte BlockHeight, byte BlockDepth, int ImageWidth, int ImageHeight, int ImageDepth) +{ + public const uint Magic = 0x5CA1AB13; + public const int SizeInBytes = 16; + + public static AstcFileHeader FromMemory(Span data) + { + ArgumentOutOfRangeException.ThrowIfLessThan(data.Length, SizeInBytes); + + // ASTC header is 16 bytes: + // - magic (4), + // - blockdim (3), + // - xsize,y,z (each 3 little-endian bytes) + uint magic = BitConverter.ToUInt32(data); + ArgumentOutOfRangeException.ThrowIfNotEqual(magic, Magic); + + return new AstcFileHeader( + BlockWidth: data[4], + BlockHeight: data[5], + BlockDepth: data[6], + ImageWidth: data[7] | (data[8] << 8) | (data[9] << 16), + ImageHeight: data[10] | (data[11] << 8) | (data[12] << 16), + ImageDepth: data[13] | (data[14] << 8) | (data[15] << 16)); + } +} diff --git a/src/ImageSharp.Textures.Astc/IO/BitStream.cs b/src/ImageSharp.Textures.Astc/IO/BitStream.cs new file mode 100644 index 00000000..71bd157e --- /dev/null +++ b/src/ImageSharp.Textures.Astc/IO/BitStream.cs @@ -0,0 +1,172 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + + +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.IO; + +/// +/// A simple bit stream used for reading/writing arbitrary-sized chunks. +/// +internal struct BitStream +{ + private ulong _low; + private ulong _high; + private uint _dataSize; // number of valid bits in the 128-bit buffer + + public uint Bits => _dataSize; + + public BitStream(ulong data = 0, uint dataSize = 0) + { + _low = data; + _high = 0; + _dataSize = dataSize; + } + + public BitStream(UInt128 data, uint dataSize) + { + _low = data.Low(); + _high = data.High(); + _dataSize = dataSize; + } + + public void PutBits(T x, int size) where T : unmanaged + { + ulong value = x switch + { + uint ui => ui, + ulong ul => ul, + ushort us => us, + byte b => b, + _ => Convert.ToUInt64(x) + }; + + if (_dataSize + (uint)size > 128) + throw new InvalidOperationException("Not enough space in BitStream"); + + if (_dataSize < 64) + { + int lowFree = (int)(64 - _dataSize); + if (size <= lowFree) + { + _low |= (value & MaskFor(size)) << (int)_dataSize; + } + else + { + _low |= (value & MaskFor(lowFree)) << (int)_dataSize; + _high |= (value >> lowFree) & MaskFor(size - lowFree); + } + } + else + { + int shift = (int)(_dataSize - 64); + _high |= (value & MaskFor(size)) << shift; + } + + _dataSize += (uint)size; + } + + /// + /// Attempt to retrieve the specified number of bits from the buffer. + /// The buffer is shifted accordingly if successful. + /// + public bool TryGetBits(int count, out T bits) where T : unmanaged + { + T? result = null; + + if (typeof(T) == typeof(UInt128)) + { + result = (T?)(object?)GetBitsUInt128(count); + } + else if (count <= _dataSize) + { + ulong value = count switch + { + 0 => 0, + <= 64 => _low & MaskFor(count), + _ => _low + }; + + ShiftBuffer(count); + object boxed = Convert.ChangeType(value, typeof(T)); + result = (T)boxed; + } + + bits = result ?? default; + + return result is not null; + } + + public bool TryGetBits(int count, out ulong bits) + { + if (count > _dataSize) { bits = 0; return false; } + bits = count switch + { + 0 => 0, + <= 64 => _low & MaskFor(count), + _ => _low + }; + ShiftBuffer(count); + return true; + } + + public bool TryGetBits(int count, out uint bits) + { + if (count > _dataSize) { bits = 0; return false; } + bits = (uint)(count switch + { + 0 => 0UL, + <= 64 => _low & MaskFor(count), + _ => _low + }); + ShiftBuffer(count); + return true; + } + + private static ulong MaskFor(int bits) + => bits == 64 + ? ~0UL + : ((1UL << bits) - 1UL); + + private UInt128? GetBitsUInt128(int count) + { + if (count > _dataSize) + return null; + + UInt128 result = count switch + { + 0 => UInt128.Zero, + <= 64 => (UInt128)(_low & MaskFor(count)), + 128 => new UInt128(_high, _low), + _ => new UInt128( + (count - 64 == 64) ? _high : (_high & MaskFor(count - 64)), + _low) + }; + + ShiftBuffer(count); + + return result; + } + + private void ShiftBuffer(int count) + { + if (count < 64) + { + _low = (_low >> count) | (_high << (64 - count)); + _high = _high >> count; + } + else if (count == 64) + { + _low = _high; + _high = 0; + } + else + { + _low = _high >> (count - 64); + _high = 0; + } + + _dataSize -= (uint)count; + } +} diff --git a/src/ImageSharp.Textures.Astc/ImageSharp.Textures.Astc.csproj b/src/ImageSharp.Textures.Astc/ImageSharp.Textures.Astc.csproj new file mode 100644 index 00000000..a4d3f596 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/ImageSharp.Textures.Astc.csproj @@ -0,0 +1,33 @@ + + + + + SixLabors.ImageSharp.Textures.Astc + SixLabors.ImageSharp.Textures.Astc + + + + + enable + Nullable + + + + + + net8.0;net9.0 + + + + + net8.0 + + + + + + + + + + diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs b/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs new file mode 100644 index 00000000..22608623 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs @@ -0,0 +1,321 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +/// +/// Fused block info computed in a single pass from raw ASTC block bits +/// +internal struct BlockInfo +{ + private static readonly int[] _weightRanges = + [-1, -1, 1, 2, 3, 4, 5, 7, -1, -1, 9, 11, 15, 19, 23, 31]; + + private static readonly int[] _extraCemBitsForPartition = [0, 2, 5, 8]; + + // Valid BISE endpoint ranges in descending order (only these produce valid encodings) + private static readonly int[] _validEndpointRanges = + [255, 191, 159, 127, 95, 79, 63, 47, 39, 31, 23, 19, 15, 11, 9, 7, 5]; + + public bool IsValid; + public bool IsVoidExtent; + + // Weight grid + public int GridWidth; + public int GridHeight; + public int WeightRange; + public int WeightBitCount; + + // Partitions + public int PartitionCount; + + // Dual plane + public bool IsDualPlane; + public int DualPlaneChannel; // only valid if IsDualPlane + + // Color endpoints + public int ColorStartBit; + public int ColorBitCount; + public int ColorValuesRange; + public int ColorValuesCount; + + // Endpoint modes (up to 4 partitions) + public ColorEndpointMode EndpointMode0; + public ColorEndpointMode EndpointMode1; + public ColorEndpointMode EndpointMode2; + public ColorEndpointMode EndpointMode3; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ColorEndpointMode GetEndpointMode(int partition) => partition switch + { + 0 => EndpointMode0, + 1 => EndpointMode1, + 2 => EndpointMode2, + 3 => EndpointMode3, + _ => EndpointMode0 + }; + + /// + /// Decode all block info from raw 128-bit ASTC block data in a single pass. + /// Returns a BlockInfo with IsValid=false if the block is illegal or reserved. + /// Returns a BlockInfo with IsVoidExtent=true for void extent blocks. + /// + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static BlockInfo Decode(UInt128 bits) + { + ulong lowBits = bits.Low(); + + // ---- Step 1: Check void extent ---- + // Void extent: bits[0:9] == 0x1FC (9 bits) + if ((lowBits & 0x1FF) == 0x1FC) + { + return new BlockInfo + { + IsVoidExtent = true, + IsValid = !CheckVoidExtentIsIllegal(bits, lowBits) + }; + } + + // ---- Step 2: Decode block mode, grid dims, weight range in ONE pass ---- + // This inlines DecodeBlockMode + DecodeWeightProperties + int gridWidth, gridHeight; + bool isWidthA6HeightB6 = false; + uint rBits; // 3-bit range index component + + if ((lowBits & 0x3) != 0) // bits[0:2] != 0 + { + ulong modeBits = (lowBits >> 2) & 0x3; // bits[2:4] + int a = (int)((lowBits >> 5) & 0x3); // bits[5:7] + + (gridWidth, gridHeight) = modeBits switch + { + 0 => ((int)((lowBits >> 7) & 0x3) + 4, a + 2), + 1 => ((int)((lowBits >> 7) & 0x3) + 8, a + 2), + 2 => (a + 2, (int)((lowBits >> 7) & 0x3) + 8), + 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), + 3 => (a + 2, (int)((lowBits >> 7) & 0x1) + 6), + _ => default // unreachable + }; + + // Range r[2:0] = {bit4, bit1, bit0} for these modes + rBits = (uint)(((lowBits >> 4) & 1) | (((lowBits >> 0) & 0x3) << 1)); + } + else // bits[0:2] == 0 + { + ulong modeBits = (lowBits >> 5) & 0xF; // bits[5:9] + int a = (int)((lowBits >> 5) & 0x3); // bits[5:7] + + switch (modeBits) + { + case var _ when (modeBits & 0xC) == 0x0: + if ((lowBits & 0xF) == 0) + return default; // reserved block mode + gridWidth = 12; + gridHeight = a + 2; + break; + case var _ when (modeBits & 0xC) == 0x4: + gridWidth = a + 2; + gridHeight = 12; + break; + case 0xC: + gridWidth = 6; + gridHeight = 10; + break; + case 0xD: + gridWidth = 10; + gridHeight = 6; + break; + case var _ when (modeBits & 0xC) == 0x8: + gridWidth = a + 6; + gridHeight = (int)((lowBits >> 9) & 0x3) + 6; + isWidthA6HeightB6 = true; + break; + default: + return default; // reserved + } + + // Range r[2:0] = {bit4, bit3, bit2} for these modes + rBits = (uint)(((lowBits >> 4) & 1) | (((lowBits >> 2) & 0x3) << 1)); + } + + // ---- Step 3: Compute weight range from r and h bits ---- + uint hBit = isWidthA6HeightB6 + ? 0u + : (uint)((lowBits >> 9) & 1); + int rangeIdx = (int)((hBit << 3) | rBits); + if ((uint)rangeIdx >= (uint)_weightRanges.Length) + return default; + int weightRange = _weightRanges[rangeIdx]; + if (weightRange < 0) + return default; + + // ---- Step 4: Dual plane ---- + // WidthA6HeightB6 mode never has dual plane; otherwise check bit 10 + bool isDualPlane = !isWidthA6HeightB6 && ((lowBits >> 10) & 1) != 0; + + // ---- Step 5: Partition count ---- + int partitionCount = 1 + (int)((lowBits >> 11) & 0x3); + + // ---- Step 6: Validate weight count ---- + int numWeights = gridWidth * gridHeight; + if (isDualPlane) numWeights *= 2; + if (numWeights > 64) + return default; + + // 4 partitions + dual plane is illegal + if (partitionCount == 4 && isDualPlane) + return default; + + // ---- Step 7: Weight bit count ---- + int weightBitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(numWeights, weightRange); + if (weightBitCount < 24 || weightBitCount > 96) + return default; + + // ---- Step 8: Endpoint modes + extra CEM bits ---- + ColorEndpointMode cem0 = default, cem1 = default, cem2 = default, cem3 = default; + int colorValuesCount = 0; + int numExtraCEMBits = 0; + + if (partitionCount == 1) + { + cem0 = (ColorEndpointMode)((lowBits >> 13) & 0xF); + colorValuesCount = (((int)cem0 / 4) + 1) * 2; + } + else + { + // Multi-partition CEM decode + ulong sharedCemMarker = (lowBits >> 23) & 0x3; + + if (sharedCemMarker == 0) + { + // Shared CEM: all partitions use the same mode + var sharedCem = (ColorEndpointMode)((lowBits >> 25) & 0xF); + cem0 = cem1 = cem2 = cem3 = sharedCem; + for (int i = 0; i < partitionCount; i++) + colorValuesCount += sharedCem.GetColorValuesCount(); + } + else + { + // Non-shared CEM: per-partition modes + numExtraCEMBits = _extraCemBitsForPartition[partitionCount - 1]; + + int extraCemStartPos = 128 - numExtraCEMBits - weightBitCount; + var extraCem = BitOperations.GetBits(bits, extraCemStartPos, numExtraCEMBits); + + ulong cemval = (lowBits >> 23) & 0x3F; // 6 bits starting at bit 23 + int baseCem = (int)(((cemval & 0x3) - 1) * 4); + cemval >>= 2; + + ulong combined = cemval | (extraCem.Low() << 4); + ulong cembits = combined; + + // Extract c bits (1 bit per partition) + Span c = stackalloc int[4]; + for (int i = 0; i < partitionCount; i++) + { + c[i] = (int)(cembits & 0x1); + cembits >>= 1; + } + // Extract m bits (2 bits per partition) + for (int i = 0; i < partitionCount; i++) + { + int m = (int)(cembits & 0x3); + cembits >>= 2; + var mode = (ColorEndpointMode)(baseCem + 4 * c[i] + m); + switch (i) + { + case 0: cem0 = mode; break; + case 1: cem1 = mode; break; + case 2: cem2 = mode; break; + case 3: cem3 = mode; break; + } + colorValuesCount += mode.GetColorValuesCount(); + } + } + } + + if (colorValuesCount > 18) + return default; + + // ---- Step 9: Dual plane start position and channel ---- + int dualPlaneBitStartPos = 128 - weightBitCount - numExtraCEMBits; + if (isDualPlane) dualPlaneBitStartPos -= 2; + + int dualPlaneChannel = isDualPlane + ? (int)BitOperations.GetBits(bits, dualPlaneBitStartPos, 2).Low() + : -1; + + // ---- Step 10: Color values info ---- + int colorStartBit = (partitionCount == 1) ? 17 : 29; + int maxColorBits = dualPlaneBitStartPos - colorStartBit; + + // Minimum bits needed check + int requiredColorBits = ((13 * colorValuesCount) + 4) / 5; + if (maxColorBits < requiredColorBits) + return default; + + // Find max color range that fits (only check valid BISE ranges: 17 vs up to 255) + int colorValuesRange = 0, colorBitCount = 0; + foreach (int rv in _validEndpointRanges) + { + int bitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(colorValuesCount, rv); + if (bitCount <= maxColorBits) + { + colorValuesRange = rv; + colorBitCount = bitCount; + break; + } + } + + if (colorValuesRange == 0) + return default; + + // ---- Step 11: Validate endpoint modes are not HDR for batchable checks ---- + // (HDR blocks are still valid, just flagged for downstream use) + + return new BlockInfo + { + IsValid = true, + IsVoidExtent = false, + GridWidth = gridWidth, + GridHeight = gridHeight, + WeightRange = weightRange, + WeightBitCount = weightBitCount, + PartitionCount = partitionCount, + IsDualPlane = isDualPlane, + DualPlaneChannel = dualPlaneChannel, + ColorStartBit = colorStartBit, + ColorBitCount = colorBitCount, + ColorValuesRange = colorValuesRange, + ColorValuesCount = colorValuesCount, + EndpointMode0 = cem0, + EndpointMode1 = cem1, + EndpointMode2 = cem2, + EndpointMode3 = cem3, + }; + } + + /// + /// Inline void extent validation (replaces PhysicalBlock.CheckVoidExtentIsIllegal). + /// + private static bool CheckVoidExtentIsIllegal(UInt128 bits, ulong lowBits) + { + if (BitOperations.GetBits(bits, 10, 2).Low() != 0x3UL) + return true; + + int c0 = (int)BitOperations.GetBits(lowBits, 12, 13); + int c1 = (int)BitOperations.GetBits(lowBits, 25, 13); + int c2 = (int)BitOperations.GetBits(lowBits, 38, 13); + int c3 = (int)BitOperations.GetBits(lowBits, 51, 13); + + const int all1s = (1 << 13) - 1; + bool coordsAll1s = c0 == all1s && c1 == all1s && c2 == all1s && c3 == all1s; + + return !coordsAll1s && (c0 >= c1 || c2 >= c3); + } +} diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs new file mode 100644 index 00000000..661a8fba --- /dev/null +++ b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs @@ -0,0 +1,239 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; + +namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +internal static class IntermediateBlock +{ + // From Table C.2.7 -- valid weight ranges + public static readonly int[] ValidWeightRanges = [1, 2, 3, 4, 5, 7, 9, 11, 15, 19, 23, 31]; + + // Returns the maximum endpoint value range or negative on error + private const int EndpointRangeInvalidWeightDimensions = -1; + private const int EndpointRangeNotEnoughColorBits = -2; + + public static IntermediateBlockData? UnpackIntermediateBlock(PhysicalBlock physicalBlock) + { + if (physicalBlock.IsIllegalEncoding || physicalBlock.IsVoidExtent) + return null; + + var info = BlockInfo.Decode(physicalBlock.BlockBits); + if (!info.IsValid || info.IsVoidExtent) + return null; + + return UnpackIntermediateBlock(physicalBlock.BlockBits, in info); + } + + /// + /// Fast overload that uses pre-computed BlockInfo instead of calling PhysicalBlock getters. + /// + public static IntermediateBlockData? UnpackIntermediateBlock(UInt128 bits, in BlockInfo info) + { + if (!info.IsValid || info.IsVoidExtent) return null; + + var data = new IntermediateBlockData(); + + // Use cached values from BlockInfo instead of PhysicalBlock getters + var colorBitMask = UInt128Extensions.OnesMask(info.ColorBitCount); + var colorBits = (bits >> info.ColorStartBit) & colorBitMask; + var colorBitStream = new BitStream(colorBits, 128); + + var colorDecoder = BoundedIntegerSequenceDecoder.GetCached(info.ColorValuesRange); + Span colors = stackalloc int[info.ColorValuesCount]; + colorDecoder.Decode(info.ColorValuesCount, ref colorBitStream, colors); + + data.WeightGridX = info.GridWidth; + data.WeightGridY = info.GridHeight; + data.WeightRange = info.WeightRange; + + data.PartitionId = info.PartitionCount > 1 + ? (int)BitOperations.GetBits(bits.Low(), 13, 10) + : null; + + data.DualPlaneChannel = info.IsDualPlane + ? info.DualPlaneChannel + : null; + + int colorIndex = 0; + data.EndpointCount = info.PartitionCount; + for (int i = 0; i < info.PartitionCount; ++i) + { + var mode = info.GetEndpointMode(i); + int colorCount = mode.GetColorValuesCount(); + var ep = new IntermediateEndpointData { Mode = mode, ColorCount = colorCount }; + for (int j = 0; j < colorCount; ++j) + { + ep.Colors[j] = colors[colorIndex++]; + } + data.Endpoints[i] = ep; + } + + data.EndpointRange = info.ColorValuesRange; + + var weightBits = UInt128Extensions.ReverseBits(bits) & UInt128Extensions.OnesMask(info.WeightBitCount); + var weightBitStream = new BitStream(weightBits, 128); + + var weightDecoder = BoundedIntegerSequenceDecoder.GetCached(data.WeightRange); + int weightsCount = data.WeightGridX * data.WeightGridY; + if (info.IsDualPlane) weightsCount *= 2; + data.Weights = new int[weightsCount]; + data.WeightsCount = weightsCount; + weightDecoder.Decode(weightsCount, ref weightBitStream, data.Weights); + + return data; + } + + public static int EndpointRangeForBlock(in IntermediateBlockData data) + { + int dualPlaneMultiplier = data.DualPlaneChannel.HasValue + ? 2 + : 1; + if (BoundedIntegerSequenceCodec.GetBitCountForRange(data.WeightGridX * data.WeightGridY * dualPlaneMultiplier, data.WeightRange) > 96) + return EndpointRangeInvalidWeightDimensions; + + int partitionCount = data.EndpointCount; + int bitsWrittenCount = 11 + 2 + + ((partitionCount > 1) ? 10 : 0) + + ((partitionCount == 1) ? 4 : 6); + int availableColorBitsCount = ExtraConfigBitPosition(data) - bitsWrittenCount; + + int colorValuesCount = 0; + for (int i = 0; i < data.EndpointCount; i++) colorValuesCount += data.Endpoints[i].Mode.GetColorValuesCount(); + + int bitsNeededCount = (13 * colorValuesCount + 4) / 5; + if (availableColorBitsCount < bitsNeededCount) return EndpointRangeNotEnoughColorBits; + + int colorValueRange = byte.MaxValue; + for (; colorValueRange > 1; --colorValueRange) + { + int bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(colorValuesCount, colorValueRange); + if (bitCountForRange <= availableColorBitsCount) break; + } + return colorValueRange; + } + + public static VoidExtentData? UnpackVoidExtent(PhysicalBlock physicalBlock) + { + var colorStartBit = physicalBlock.GetColorStartBit(); + var colorBitCount = physicalBlock.GetColorBitCount(); + if (physicalBlock.IsIllegalEncoding || !physicalBlock.IsVoidExtent || colorStartBit is null || colorBitCount is null) + return null; + + var colorBits = (physicalBlock.BlockBits >> colorStartBit.Value) & UInt128Extensions.OnesMask(colorBitCount.Value); + // We expect low 64 bits contain the 4x16-bit channels + var low = colorBits.Low(); + + var data = new VoidExtentData(); + // Bit 9 of the block mode indicates HDR (1) vs LDR (0) void extent + data.IsHdr = (physicalBlock.BlockBits.Low() & (1UL << 9)) != 0; + data.R = (ushort)((low >> 0) & 0xFFFF); + data.G = (ushort)((low >> 16) & 0xFFFF); + data.B = (ushort)((low >> 32) & 0xFFFF); + data.A = (ushort)((low >> 48) & 0xFFFF); + + var coords = physicalBlock.GetVoidExtentCoordinates(); + data.Coords = new ushort[4]; + if (coords != null) + { + data.Coords[0] = (ushort)coords[0]; + data.Coords[1] = (ushort)coords[1]; + data.Coords[2] = (ushort)coords[2]; + data.Coords[3] = (ushort)coords[3]; + } + else + { + ushort allOnes = (ushort)((1 << 13) - 1); + for (int i = 0; i < 4; ++i) data.Coords[i] = allOnes; + } + + return data; + } + + /// + /// Determines if all endpoint modes in the intermediate block data are the same + /// + internal static bool SharedEndpointModes(in IntermediateBlockData data) + { + if (data.EndpointCount == 0) return true; + var first = data.Endpoints[0].Mode; + for (int i = 1; i < data.EndpointCount; i++) + if (data.Endpoints[i].Mode != first) return false; + return true; + } + + internal static int ExtraConfigBitPosition(in IntermediateBlockData data) + { + bool hasDualChannel = data.DualPlaneChannel.HasValue; + int weightCount = data.WeightGridX * data.WeightGridY * (hasDualChannel ? 2 : 1); + int weightBitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(weightCount, data.WeightRange); + + int extraConfigBitCount = 0; + if (!SharedEndpointModes(data)) + { + int encodedCemBitCount = 2 + data.EndpointCount * 3; + extraConfigBitCount = encodedCemBitCount - 6; + } + + if (hasDualChannel) extraConfigBitCount += 2; + + return 128 - weightBitCount - extraConfigBitCount; + } + + internal struct VoidExtentData + { + public bool IsHdr; + public ushort R; + public ushort G; + public ushort B; + public ushort A; + public ushort[] Coords; // length 4 + } + + [System.Runtime.CompilerServices.InlineArray(MaxColorValues)] + internal struct EndpointColorValues + { + public const int MaxColorValues = 8; +#pragma warning disable CS0169, S1144 // Accessed by runtime via [InlineArray] + private int _element0; +#pragma warning restore CS0169, S1144 + } + + internal struct IntermediateBlockData + { + public int WeightGridX; + public int WeightGridY; + public int WeightRange; + + public int[] Weights; + public int WeightsCount; + + public int? PartitionId; + public int? DualPlaneChannel; + + public IntermediateEndpointBuffer Endpoints; + public int EndpointCount; + + public int? EndpointRange; + } + + internal struct IntermediateEndpointData + { + public ColorEndpointMode Mode; + public EndpointColorValues Colors; + public int ColorCount; + } + + [System.Runtime.CompilerServices.InlineArray(MaxPartitions)] + internal struct IntermediateEndpointBuffer + { + public const int MaxPartitions = 4; +#pragma warning disable CS0169, S1144 // Accessed by runtime via [InlineArray] + private IntermediateEndpointData _element0; +#pragma warning restore CS0169, S1144 + } +} diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs new file mode 100644 index 00000000..b6dbd6d6 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs @@ -0,0 +1,344 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; + +namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +internal static class IntermediateBlockPacker +{ + private static readonly BlockModeInfo[] _blockModeInfoTable = [ + new BlockModeInfo{ MinWeightGridDimX=4, MaxWeightGridDimX=7, MinWeightGridDimY=2, MaxWeightGridDimY=5, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=7, WeightGridYOffsetBitPos=5, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=8, MaxWeightGridDimX=11, MinWeightGridDimY=2, MaxWeightGridDimY=5, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=7, WeightGridYOffsetBitPos=5, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=2, MaxWeightGridDimX=5, MinWeightGridDimY=8, MaxWeightGridDimY=11, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=5, WeightGridYOffsetBitPos=7, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=2, MaxWeightGridDimX=5, MinWeightGridDimY=6, MaxWeightGridDimY=7, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=5, WeightGridYOffsetBitPos=7, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=2, MaxWeightGridDimX=3, MinWeightGridDimY=2, MaxWeightGridDimY=5, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=7, WeightGridYOffsetBitPos=5, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=12, MaxWeightGridDimX=12, MinWeightGridDimY=2, MaxWeightGridDimY=5, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=-1, WeightGridYOffsetBitPos=5, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=2, MaxWeightGridDimX=5, MinWeightGridDimY=12, MaxWeightGridDimY=12, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=5, WeightGridYOffsetBitPos=-1, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=6, MaxWeightGridDimX=6, MinWeightGridDimY=10, MaxWeightGridDimY=10, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=-1, WeightGridYOffsetBitPos=-1, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=10, MaxWeightGridDimX=10, MinWeightGridDimY=6, MaxWeightGridDimY=6, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=-1, WeightGridYOffsetBitPos=-1, RequireSinglePlaneLowPrec=false }, + new BlockModeInfo{ MinWeightGridDimX=6, MaxWeightGridDimX=9, MinWeightGridDimY=6, MaxWeightGridDimY=9, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=5, WeightGridYOffsetBitPos=9, RequireSinglePlaneLowPrec=true } + ]; + + private static readonly uint[] _blockModeMasks = [0x0u, 0x4u, 0x8u, 0xCu, 0x10Cu, 0x0u, 0x80u, 0x180u, 0x1A0u, 0x100u]; + + public static (string? error, UInt128 physicalBlockBits) Pack(in IntermediateBlock.IntermediateBlockData data) + { + UInt128 physicalBlockBits = 0; + int expectedWeightsCount = data.WeightGridX * data.WeightGridY + * (data.DualPlaneChannel.HasValue ? 2 : 1); + int actualWeightsCount = data.WeightsCount > 0 + ? data.WeightsCount + : (data.Weights?.Length ?? 0); + if (actualWeightsCount != expectedWeightsCount) + { + return ("Incorrect number of weights!", 0); + } + + var bitSink = new BitStream(0UL, 0); + + // First we need to encode the block mode. + var errorMessage = PackBlockMode(data.WeightGridX, data.WeightGridY, data.WeightRange, data.DualPlaneChannel.HasValue, ref bitSink); + if (errorMessage != null) { return (errorMessage, 0); } + + // number of partitions minus one + int partitionCount = data.EndpointCount; + bitSink.PutBits((uint)(partitionCount - 1), 2); + + if (partitionCount > 1) + { + int id = data.PartitionId ?? 0; + ArgumentOutOfRangeException.ThrowIfLessThan(id, 0); + bitSink.PutBits((uint)id, 10); + } + + var (weightSink, weightBitsCount) = EncodeWeights(data); + + var (error, extraConfig) = EncodeColorEndpointModes(data, partitionCount, ref bitSink); + if (error != null) return (error, 0); + + int colorValueRange = data.EndpointRange.HasValue + ? data.EndpointRange.Value + : IntermediateBlock.EndpointRangeForBlock(data); + if (colorValueRange == -1) + throw new InvalidOperationException($"{nameof(colorValueRange)} must not be EndpointRangeInvalidWeightDimensions"); + if (colorValueRange == -2) + { + return ("Intermediate block emits illegal color range", 0); + } + + var colorEncoder = new BoundedIntegerSequenceEncoder(colorValueRange); + for (int i = 0; i < data.EndpointCount; i++) + { + var ep = data.Endpoints[i]; + for (int j = 0; j < ep.ColorCount; j++) + { + int color = ep.Colors[j]; + if (color > colorValueRange) return ("Color outside available color range!", 0); + colorEncoder.AddValue(color); + } + } + colorEncoder.Encode(ref bitSink); + + int extraConfigBitPosition = IntermediateBlock.ExtraConfigBitPosition(data); + int extraConfigBits = 128 - weightBitsCount - extraConfigBitPosition; + + ArgumentOutOfRangeException.ThrowIfNegative(extraConfigBits); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(extraConfig, 1 << extraConfigBits); + + int bitsToSkip = extraConfigBitPosition - (int)bitSink.Bits; + ArgumentOutOfRangeException.ThrowIfNegative(bitsToSkip); + while (bitsToSkip > 0) + { + int skipping = Math.Min(32, bitsToSkip); + bitSink.PutBits(0u, skipping); + bitsToSkip -= skipping; + } + + if (extraConfigBits > 0) + { + bitSink.PutBits((uint)extraConfig, extraConfigBits); + } + + ArgumentOutOfRangeException.ThrowIfNotEqual(bitSink.Bits, (uint)128 - weightBitsCount); + + // Flush out the bit writer + if (!bitSink.TryGetBits(128 - weightBitsCount, out var astcBits)) + throw new InvalidOperationException(); + if (!weightSink.TryGetBits(weightBitsCount, out var revWeightBits)) + throw new InvalidOperationException(); + + var combined = astcBits | UInt128Extensions.ReverseBits(revWeightBits); + physicalBlockBits = combined; + + var block = PhysicalBlock.Create(physicalBlockBits); + var illegal = block.IdentifyInvalidEncodingIssues(); + + return (illegal, physicalBlockBits); + } + + public static (string? error, UInt128 physicalBlockBits) Pack(IntermediateBlock.VoidExtentData data) + { + // Pack void extent + // Assemble the 128-bit value explicitly: low 64 bits = RGBA (4x16) + // high 64 bits = 12-bit header (0xDFC) followed by four 13-bit coords. + ulong high64 = ((ulong)data.A << 48) | ((ulong)data.B << 32) | ((ulong)data.G << 16) | (ulong)data.R; + ulong low64 = 0UL; + // Header occupies lowest 12 bits of the high word + low64 |= 0xDFCu; + for (int i = 0; i < 4; ++i) + { + low64 |= ((ulong)(data.Coords[i] & 0x1FFF)) << (12 + 13 * i); + } + + UInt128 physicalBlockBits; + // Decide representation: if the RGBA low word is zero we emit the + // compact single-ulong representation (low word = header+coords, + // high word = 0) to match the reference tests. Otherwise the + // low word holds RGBA and the high word holds header+coords. + if (high64 == 0UL) + { + physicalBlockBits = (UInt128)low64; + // using compact void extent representation + } + else + { + physicalBlockBits = new UInt128(high64, low64); + // using full void extent representation + } + + var block = PhysicalBlock.Create(physicalBlockBits); + var illegal = block.IdentifyInvalidEncodingIssues(); + if (illegal is not null) + { + throw new InvalidOperationException($"{nameof(Pack)}(void extent) produced illegal encoding"); + } + return (illegal, physicalBlockBits); + } + + private static (string? error, int[] range) GetEncodedWeightRange(int range) + { + int[][] validRangeEncodings = [ + [0,1,0], [1,1,0], [0,0,1], [1,0,1], [0,1,1], [1,1,1], + [0,1,0], [1,1,0], [0,0,1], [1,0,1], [0,1,1], [1,1,1] + ]; + + int smallestRange = IntermediateBlock.ValidWeightRanges.First(); + int largestRange = IntermediateBlock.ValidWeightRanges.Last(); + if (range < smallestRange || largestRange < range) + { + return ($"Could not find block mode. Invalid weight range: {range} not in [{smallestRange}, {largestRange}]", new int[3]); + } + + int index = Array.FindIndex(IntermediateBlock.ValidWeightRanges, v => v >= range); + if (index < 0) index = IntermediateBlock.ValidWeightRanges.Length - 1; + var encoding = validRangeEncodings[index]; + return (null, [encoding[0], encoding[1], encoding[2]]); + } + + private static string? PackBlockMode(int dimX, int dimY, int range, bool dualPlane, ref BitStream bitSink) + { + bool highPrec = range > 7; + var (maybeErr, rangeValues) = GetEncodedWeightRange(range); + if (maybeErr != null) return maybeErr; + + // Ensure top two bits of r1 and r2 not both zero per reference + if ((rangeValues[1] | rangeValues[2]) <= 0) + throw new InvalidOperationException($"{nameof(rangeValues)}[1] | {nameof(rangeValues)}[2] must be > 0"); + + for (int mode = 0; mode < _blockModeInfoTable.Length; ++mode) + { + var blockMode = _blockModeInfoTable[mode]; + bool isValidMode = true; + isValidMode &= blockMode.MinWeightGridDimX <= dimX; + isValidMode &= dimX <= blockMode.MaxWeightGridDimX; + isValidMode &= blockMode.MinWeightGridDimY <= dimY; + isValidMode &= dimY <= blockMode.MaxWeightGridDimY; + isValidMode &= !(blockMode.RequireSinglePlaneLowPrec && dualPlane); + isValidMode &= !(blockMode.RequireSinglePlaneLowPrec && highPrec); + + if (!isValidMode) continue; + + uint encodedMode = _blockModeMasks[mode]; + void setBit(uint value, int offset) + { + if (offset < 0) return; + encodedMode = (encodedMode & ~(1u << offset)) | ((value & 1u) << offset); + } + + setBit((uint)rangeValues[0], blockMode.R0BitPos); + setBit((uint)rangeValues[1], blockMode.R1BitPos); + setBit((uint)rangeValues[2], blockMode.R2BitPos); + + int offsetX = dimX - blockMode.MinWeightGridDimX; + int offsetY = dimY - blockMode.MinWeightGridDimY; + + if (blockMode.WeightGridXOffsetBitPos >= 0) + { + encodedMode |= (uint)(offsetX << blockMode.WeightGridXOffsetBitPos); + } + else + { + ArgumentOutOfRangeException.ThrowIfNotEqual(offsetX, 0); + } + + if (blockMode.WeightGridYOffsetBitPos >= 0) + { + encodedMode |= (uint)(offsetY << blockMode.WeightGridYOffsetBitPos); + } + else + { + ArgumentOutOfRangeException.ThrowIfNotEqual(offsetY, 0); + } + + if (!blockMode.RequireSinglePlaneLowPrec) + { + setBit((uint)(highPrec ? 1u : 0u), 9); + setBit((uint)(dualPlane ? 1u : 0u), 10); + } + + if (bitSink.Bits != 0) + throw new InvalidOperationException($"{nameof(bitSink)}.{nameof(bitSink.Bits)} must be 0"); + bitSink.PutBits(encodedMode, 11); + return null; + } + + return "Could not find viable block mode"; + } + + private static (BitStream weightSink, int weightBitsCount) EncodeWeights(in IntermediateBlock.IntermediateBlockData data) + { + var weightSink = new BitStream(0UL, 0); + var weightsEncoder = new BoundedIntegerSequenceEncoder(data.WeightRange); + int weightCount = data.WeightsCount > 0 + ? data.WeightsCount + : (data.Weights?.Length ?? 0); + if (data.Weights is null) + throw new InvalidOperationException($"{nameof(data.Weights)} is null in {nameof(EncodeWeights)}"); + for (var i = 0; i < weightCount; i++) weightsEncoder.AddValue(data.Weights[i]); + weightsEncoder.Encode(ref weightSink); + + int weightBitsCount = (int)weightSink.Bits; + if ((int)weightSink.Bits != BoundedIntegerSequenceCodec.GetBitCountForRange(weightCount, data.WeightRange)) + throw new InvalidOperationException($"{nameof(weightSink)}.{nameof(weightSink.Bits)} does not match expected bit count"); + + return (weightSink, weightBitsCount); + } + + private static (string? error, int extraConfig) EncodeColorEndpointModes(in IntermediateBlock.IntermediateBlockData data, int partitionCount, ref BitStream bitSink) + { + int extraConfig = 0; + bool sharedEndpointMode = IntermediateBlock.SharedEndpointModes(data); + + if (sharedEndpointMode) + { + if (partitionCount > 1) bitSink.PutBits(0u, 2); + bitSink.PutBits((uint)data.Endpoints[0].Mode, 4); + } + else + { + // compute min_class, max_class + int minClass = 2; int maxClass = 0; + for (int i = 0; i < data.EndpointCount; i++) + { + int endpointModeClass = ((int)data.Endpoints[i].Mode) >> 2; + minClass = Math.Min(minClass, endpointModeClass); + maxClass = Math.Max(maxClass, endpointModeClass); + } + + if (maxClass - minClass > 1) return ("Endpoint modes are invalid", 0); + + var cemEncoder = new BitStream(0UL, 0); + cemEncoder.PutBits((uint)(minClass + 1), 2); + + for (int i = 0; i < data.EndpointCount; i++) + { + int endpointModeClass = ((int)data.Endpoints[i].Mode) >> 2; + int classSelectorBit = endpointModeClass - minClass; + cemEncoder.PutBits(classSelectorBit, 1); + } + + for (int i = 0; i < data.EndpointCount; i++) + { + int epMode = ((int)data.Endpoints[i].Mode) & 3; + cemEncoder.PutBits(epMode, 2); + } + + int cemBits = 2 + partitionCount * 3; + if (!cemEncoder.TryGetBits(cemBits, out uint encodedCem)) + throw new InvalidOperationException(); + + extraConfig = (int)(encodedCem >> 6); + + bitSink.PutBits(encodedCem, Math.Min(6, cemBits)); + } + + // dual plane channel + if (data.DualPlaneChannel.HasValue) + { + int channel = data.DualPlaneChannel.Value; + ArgumentOutOfRangeException.ThrowIfLessThan(channel, 0); + ArgumentOutOfRangeException.ThrowIfGreaterThan(channel, 3); + extraConfig = (extraConfig << 2) | channel; + } + + return (null, extraConfig); + } + + private struct BlockModeInfo + { + public int MinWeightGridDimX; + public int MaxWeightGridDimX; + public int MinWeightGridDimY; + public int MaxWeightGridDimY; + public int R0BitPos; + public int R1BitPos; + public int R2BitPos; + public int WeightGridXOffsetBitPos; + public int WeightGridYOffsetBitPos; + public bool RequireSinglePlaneLowPrec; + } +} diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs new file mode 100644 index 00000000..98514355 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs @@ -0,0 +1,644 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; +using SixLabors.ImageSharp.Textures.Astc.BlockDecoder; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +internal sealed class LogicalBlock +{ + private ColorEndpointPair[] _endpoints; + private int _endpointCount; + private int[] _weights; + private Partition _partition; + private DualPlaneData? _dualPlane; + + public LogicalBlock(Footprint footprint) + { + _endpoints = [ColorEndpointPair.Ldr(RgbaColor.Empty, RgbaColor.Empty)]; + _endpointCount = 1; + _weights = new int[footprint.PixelCount]; + _partition = new Partition(footprint, 1, 0) + { + Assignment = new int[footprint.PixelCount] + }; + } + + public LogicalBlock(Footprint footprint, in IntermediateBlock.IntermediateBlockData block) + { + _endpoints = new ColorEndpointPair[block.EndpointCount]; + _endpointCount = DecodeEndpoints(in block, _endpoints); + _partition = ComputePartition(footprint, in block); + _weights = new int[footprint.PixelCount]; + CalculateWeights(footprint, in block); + } + + public LogicalBlock(Footprint footprint, IntermediateBlock.VoidExtentData block) + { + _endpoints = new ColorEndpointPair[1]; + _endpointCount = DecodeEndpoints(block, _endpoints); + _partition = ComputePartition(footprint, block); + _weights = new int[footprint.PixelCount]; + } + + /// + /// Direct-decode constructor: decodes directly from raw bits + BlockInfo, + /// bypassing IntermediateBlock and using batch unquantize operations. + /// + private LogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) + { + // --- BISE decode + batch unquantize color endpoint values --- + Span colors = stackalloc int[info.ColorValuesCount]; + FusedBlockDecoder.DecodeBiseValues(bits, info.ColorStartBit, info.ColorBitCount, + info.ColorValuesRange, info.ColorValuesCount, colors); + Quantization.UnquantizeCEValuesBatch(colors, info.ColorValuesCount, info.ColorValuesRange); + + // --- Decode endpoints per partition --- + _endpointCount = info.PartitionCount; + _endpoints = new ColorEndpointPair[_endpointCount]; + int colorIndex = 0; + for (int i = 0; i < _endpointCount; i++) + { + var mode = info.GetEndpointMode(i); + int colorCount = mode.GetColorValuesCount(); + ReadOnlySpan slice = colors.Slice(colorIndex, colorCount); + _endpoints[i] = EndpointCodec.DecodeColorsForModePolymorphicUnquantized(slice, mode); + colorIndex += colorCount; + } + + // --- Set up partition --- + _partition = info.PartitionCount > 1 + ? Partition.GetASTCPartition(footprint, info.PartitionCount, + (int)BitOperations.GetBits(bits.Low(), 13, 10)) + : GenerateSinglePartition(footprint); + + // --- BISE decode + unquantize + infill weights --- + int gridSize = info.GridWidth * info.GridHeight; + bool isDualPlane = info.IsDualPlane; + int totalWeights = isDualPlane ? gridSize * 2 : gridSize; + + Span rawWeights = stackalloc int[totalWeights]; + FusedBlockDecoder.DecodeBiseWeights(bits, info.WeightBitCount, info.WeightRange, + totalWeights, rawWeights); + + var decimationInfo = DecimationTable.Get(footprint, info.GridWidth, info.GridHeight); + _weights = new int[footprint.PixelCount]; + + if (!isDualPlane) + { + Quantization.UnquantizeWeightsBatch(rawWeights, gridSize, info.WeightRange); + DecimationTable.InfillWeights(rawWeights[..gridSize], decimationInfo, _weights); + } + else + { + // De-interleave: even indices -> plane0, odd indices -> plane1 + Span plane0 = stackalloc int[gridSize]; + Span plane1 = stackalloc int[gridSize]; + for (int i = 0; i < gridSize; i++) + { + plane0[i] = rawWeights[i * 2]; + plane1[i] = rawWeights[i * 2 + 1]; + } + + Quantization.UnquantizeWeightsBatch(plane0, gridSize, info.WeightRange); + Quantization.UnquantizeWeightsBatch(plane1, gridSize, info.WeightRange); + + DecimationTable.InfillWeights(plane0, decimationInfo, _weights); + + _dualPlane = new DualPlaneData + { + Channel = info.DualPlaneChannel, + Weights = new int[footprint.PixelCount] + }; + DecimationTable.InfillWeights(plane1, decimationInfo, _dualPlane.Weights); + } + } + + public Footprint GetFootprint() => _partition.Footprint; + + public void SetWeightAt(int x, int y, int weight) + { + if (weight < 0 || weight > 64) + throw new ArgumentOutOfRangeException(nameof(weight)); + + _weights[y * GetFootprint().Width + x] = weight; + } + + public int WeightAt(int x, int y) => _weights[y * GetFootprint().Width + x]; + + public void SetDualPlaneWeightAt(int channel, int x, int y, int weight) + { + ArgumentOutOfRangeException.ThrowIfNegative(channel); + ArgumentOutOfRangeException.ThrowIfGreaterThan(weight, 64); + + if (!IsDualPlane()) + throw new InvalidOperationException("Not a dual plane block"); + + if (_dualPlane is not null && _dualPlane.Channel == channel) + _dualPlane.Weights[y * GetFootprint().Width + x] = weight; + else + SetWeightAt(x, y, weight); + } + + public int DualPlaneWeightAt(int channel, int x, int y) + { + if (!IsDualPlane()) + return WeightAt(x, y); + + return _dualPlane is not null && _dualPlane.Channel == channel + ? _dualPlane.Weights[y * GetFootprint().Width + x] + : WeightAt(x, y); + } + + public RgbaColor ColorAt(int x, int y) + { + var footprint = GetFootprint(); + + ArgumentOutOfRangeException.ThrowIfNegative(x); + ArgumentOutOfRangeException.ThrowIfNegative(y); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(x, footprint.Width); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(y, footprint.Height); + + int index = y * footprint.Width + x; + int part = _partition.Assignment[index]; + ref var endpoint = ref _endpoints[part]; + + int weight = _weights[index]; + if (!endpoint.IsHdr) + { + if (_dualPlane is not null) + return SimdHelpers.InterpolateColorLdrDualPlane( + endpoint.LdrLow, endpoint.LdrHigh, weight, _dualPlane.Channel, _dualPlane.Weights[index]); + return SimdHelpers.InterpolateColorLdr(endpoint.LdrLow, endpoint.LdrHigh, weight); + } + else + { + if (_dualPlane is not null) + { + int dualPlaneChannel = _dualPlane.Channel; + int dualPlaneWeight = _dualPlane.Weights[index]; + return new RgbaColor( + r: InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], + dualPlaneChannel == 0 ? dualPlaneWeight : weight) >> 8, + g: InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], + dualPlaneChannel == 1 ? dualPlaneWeight : weight) >> 8, + b: InterpolateChannelHdr(endpoint.HdrLow[2], endpoint.HdrHigh[2], + dualPlaneChannel == 2 ? dualPlaneWeight : weight) >> 8, + a: InterpolateChannelHdr(endpoint.HdrLow[3], endpoint.HdrHigh[3], + dualPlaneChannel == 3 ? dualPlaneWeight : weight) >> 8); + } + return new RgbaColor( + r: InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], weight) >> 8, + g: InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], weight) >> 8, + b: InterpolateChannelHdr(endpoint.HdrLow[2], endpoint.HdrHigh[2], weight) >> 8, + a: InterpolateChannelHdr(endpoint.HdrLow[3], endpoint.HdrHigh[3], weight) >> 8); + } + } + + /// + /// Returns the HDR color at the specified pixel position. + /// + /// + /// For HDR endpoints, returns full 16-bit precision (0-65535) per channel. + /// For LDR endpoints, upscales to HDR range. + /// + public RgbaHdrColor ColorAtHdr(int x, int y) + { + var footprint = GetFootprint(); + + ArgumentOutOfRangeException.ThrowIfNegative(x); + ArgumentOutOfRangeException.ThrowIfNegative(y); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(x, footprint.Width); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(y, footprint.Height); + + int index = y * footprint.Width + x; + int part = _partition.Assignment[index]; + ref var endpoint = ref _endpoints[part]; + + int weight = _weights[index]; + if (endpoint.IsHdr) + { + if (_dualPlane != null) + { + int dualPlaneChannel = _dualPlane.Channel; + int dualPlaneWeight = _dualPlane.Weights[index]; + return new RgbaHdrColor( + InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], + dualPlaneChannel == 0 ? dualPlaneWeight : weight), + InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], + dualPlaneChannel == 1 ? dualPlaneWeight : weight), + InterpolateChannelHdr(endpoint.HdrLow[2], endpoint.HdrHigh[2], + dualPlaneChannel == 2 ? dualPlaneWeight : weight), + InterpolateChannelHdr(endpoint.HdrLow[3], endpoint.HdrHigh[3], + dualPlaneChannel == 3 ? dualPlaneWeight : weight)); + } + return new RgbaHdrColor( + InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], weight), + InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], weight), + InterpolateChannelHdr(endpoint.HdrLow[2], endpoint.HdrHigh[2], weight), + InterpolateChannelHdr(endpoint.HdrLow[3], endpoint.HdrHigh[3], weight)); + } + else + { + if (_dualPlane != null) + { + int dualPlaneChannel = _dualPlane.Channel; + int dualPlaneWeight = _dualPlane.Weights[index]; + return new RgbaHdrColor( + (ushort)(InterpolateChannel(endpoint.LdrLow.R, endpoint.LdrHigh.R, + dualPlaneChannel == 0 ? dualPlaneWeight : weight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.G, endpoint.LdrHigh.G, + dualPlaneChannel == 1 ? dualPlaneWeight : weight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.B, endpoint.LdrHigh.B, + dualPlaneChannel == 2 ? dualPlaneWeight : weight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.A, endpoint.LdrHigh.A, + dualPlaneChannel == 3 ? dualPlaneWeight : weight) * 257)); + } + return new RgbaHdrColor( + (ushort)(InterpolateChannel(endpoint.LdrLow.R, endpoint.LdrHigh.R, weight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.G, endpoint.LdrHigh.G, weight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.B, endpoint.LdrHigh.B, weight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.A, endpoint.LdrHigh.A, weight) * 257)); + } + } + + /// + /// Writes the HDR float values for the pixel at (x, y) into the output span. + /// + /// + /// For HDR endpoints, values are in LNS (Log-Normalized Space). After interpolation + /// in LNS, the result is converted to FP16 via then widened to float. + /// For Mode 14 (HDR RGB + LDR Alpha), the alpha channel is UNORM16 instead of LNS. + /// For LDR endpoints, the interpolated UNORM16 value is normalized to 0.0-1.0. + /// + public void WriteHdrPixel(int x, int y, Span output) + { + var footprint = GetFootprint(); + + ArgumentOutOfRangeException.ThrowIfNegative(x); + ArgumentOutOfRangeException.ThrowIfNegative(y); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(x, footprint.Width); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(y, footprint.Height); + + int index = y * footprint.Width + x; + int part = _partition.Assignment[index]; + ref var endpoint = ref _endpoints[part]; + + int weight = _weights[index]; + int dualPlaneChannel = _dualPlane?.Channel ?? -1; + int dualPlaneWeight = _dualPlane?.Weights[index] ?? weight; + + if (endpoint.IsHdr) + { + for (int channel = 0; channel < RgbaColor.BytesPerPixel; ++channel) + { + int channelWeight = (channel == dualPlaneChannel) + ? dualPlaneWeight + : weight; + ushort interpolated = InterpolateChannelHdr(endpoint.HdrLow[channel], endpoint.HdrHigh[channel], channelWeight); + + if (channel == 3 && endpoint.AlphaIsLdr) + { + // Mode 14: alpha is UNORM16, normalize directly + output[channel] = interpolated / 65535.0f; + } + else if (endpoint.ValuesAreLns) + { + // Normal HDR block: convert from LNS to FP16, then to float + ushort halfFloatBits = LnsToSf16(interpolated); + output[channel] = (float)BitConverter.UInt16BitsToHalf(halfFloatBits); + } + else + { + // Void extent HDR: values are already FP16 bit patterns + output[channel] = (float)BitConverter.UInt16BitsToHalf(interpolated); + } + } + } + else + { + for (int channel = 0; channel < RgbaColor.BytesPerPixel; ++channel) + { + int channelWeight = (channel == dualPlaneChannel) + ? dualPlaneWeight + : weight; + int p0 = channel switch { 0 => endpoint.LdrLow.R, 1 => endpoint.LdrLow.G, 2 => endpoint.LdrLow.B, _ => endpoint.LdrLow.A }; + int p1 = channel switch { 0 => endpoint.LdrHigh.R, 1 => endpoint.LdrHigh.G, 2 => endpoint.LdrHigh.B, _ => endpoint.LdrHigh.A }; + ushort unorm16 = InterpolateLdrAsUnorm16(p0, p1, channelWeight); + output[channel] = unorm16 / 65535.0f; + } + } + } + + /// + /// Writes all pixels in the block directly to the output buffer in RGBA byte format. + /// Avoids per-pixel method call overhead, type dispatch, and RgbaColor allocation. + /// + public void WriteAllPixelsLdr(Footprint footprint, Span buffer) + { + ref var endpoint0 = ref _endpoints[0]; + + if (!endpoint0.IsHdr && _partition.PartitionCount == 1) + { + // Fast path: single-partition LDR block (most common case) + int lowR = endpoint0.LdrLow.R, lowG = endpoint0.LdrLow.G, lowB = endpoint0.LdrLow.B, lowA = endpoint0.LdrLow.A; + int highR = endpoint0.LdrHigh.R, highG = endpoint0.LdrHigh.G, highB = endpoint0.LdrHigh.B, highA = endpoint0.LdrHigh.A; + + if (_dualPlane == null) + { + WriteLdrSinglePartition(buffer, footprint, lowR, lowG, lowB, lowA, highR, highG, highB, highA); + } + else + { + int dualPlaneChannel = _dualPlane.Channel; + var dpWeights = _dualPlane.Weights; + int pixelCount = footprint.PixelCount; + for (int i = 0; i < pixelCount; i++) + { + SimdHelpers.WriteSinglePixelLdrDualPlane( + buffer, i * 4, + lowR, lowG, lowB, lowA, highR, highG, highB, highA, + _weights[i], dualPlaneChannel, dpWeights[i]); + } + } + } + else + { + // General path: multi-partition or HDR blocks + WriteAllPixelsGeneral(footprint, buffer); + } + } + + public void SetPartition(Partition p) + { + if (!p.Footprint.Equals(_partition.Footprint)) + throw new InvalidOperationException("New partitions may not be for a different footprint"); + _partition = p; + if (_endpointCount < p.PartitionCount) + { + var newEndpoints = new ColorEndpointPair[p.PartitionCount]; + Array.Copy(_endpoints, newEndpoints, _endpointCount); + for (int i = _endpointCount; i < p.PartitionCount; i++) + newEndpoints[i] = ColorEndpointPair.Ldr(RgbaColor.Empty, RgbaColor.Empty); + _endpoints = newEndpoints; + } + _endpointCount = p.PartitionCount; + } + + public void SetEndpoints(RgbaColor firstEndpoint, RgbaColor secondEndpoint, int subset) + { + ArgumentOutOfRangeException.ThrowIfNegative(subset); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(subset, _partition.PartitionCount); + + _endpoints[subset] = ColorEndpointPair.Ldr(firstEndpoint, secondEndpoint); + } + + public void SetDualPlaneChannel(int channel) + { + if (channel < 0) { _dualPlane = null; } + else if (_dualPlane != null) { _dualPlane.Channel = channel; } + else { _dualPlane = new DualPlaneData { Channel = channel, Weights = (int[])_weights.Clone() }; } + } + + public bool IsDualPlane() => _dualPlane is not null; + + public static LogicalBlock? UnpackLogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) + { + if (!info.IsValid) return null; + + if (info.IsVoidExtent) + { + // Void extent blocks are rare; fall back to existing PhysicalBlock path + var pb = PhysicalBlock.Create(bits); + var voidExtentData = IntermediateBlock.UnpackVoidExtent(pb); + if (voidExtentData is null) return null; + + return new LogicalBlock(footprint, voidExtentData.Value); + } + else + { + return new LogicalBlock(footprint, bits, in info); + } + } + + /// + /// Converts a 16-bit LNS (Log-Normalized Space) value to a 16-bit SF16 (FP16) bit pattern. + /// + /// + /// The LNS value encodes a 5-bit exponent in the upper bits and an 11-bit mantissa + /// in the lower bits. The mantissa is transformed using a piecewise linear function + /// before being combined with the exponent to form the FP16 result. + /// + internal static ushort LnsToSf16(int lns) + { + int mantissaComponent = lns & 0x7FF; // Lower 11 bits: mantissa component + int exponentComponent = (lns >> 11) & 0x1F; // Upper 5 bits: exponent component + + int mantissaTransformed; + if (mantissaComponent < 512) + mantissaTransformed = mantissaComponent * 3; + else if (mantissaComponent < 1536) + mantissaTransformed = mantissaComponent * 4 - 512; + else + mantissaTransformed = mantissaComponent * 5 - 2048; + + int result = (exponentComponent << 10) | (mantissaTransformed >> 3); + return (ushort)Math.Min(result, 0x7BFF); // Clamp to max finite FP16 + } + + private static int DecodeEndpoints(in IntermediateBlock.IntermediateBlockData block, ColorEndpointPair[] endpointPair) + { + int endpointRange = block.EndpointRange ?? IntermediateBlock.EndpointRangeForBlock(block); + if (endpointRange <= 0) throw new InvalidOperationException("Invalid endpoint range"); + for (int i = 0; i < block.EndpointCount; i++) + { + var ed = block.Endpoints[i]; + ReadOnlySpan colorSpan = ((ReadOnlySpan)ed.Colors)[..ed.ColorCount]; + endpointPair[i] = EndpointCodec.DecodeColorsForModePolymorphic(colorSpan, endpointRange, ed.Mode); + } + return block.EndpointCount; + } + + private static int DecodeEndpoints(IntermediateBlock.VoidExtentData block, ColorEndpointPair[] endpointPair) + { + if (block.IsHdr) + { + // HDR void extent: ushort values are FP16 bit patterns (not LNS) + var hdrColor = new RgbaHdrColor(block.R, block.G, block.B, block.A); + endpointPair[0] = ColorEndpointPair.Hdr(hdrColor, hdrColor, valuesAreLns: false); + } + else + { + // LDR void extent: ushort values are UNORM16, convert to byte range + var ldrColor = new RgbaColor( + (byte)(block.R >> 8), + (byte)(block.G >> 8), + (byte)(block.B >> 8), + (byte)(block.A >> 8)); + endpointPair[0] = ColorEndpointPair.Ldr(ldrColor, ldrColor); + } + return 1; + } + + private static Partition GenerateSinglePartition(Footprint footprint) + { + return new Partition(footprint, 1, 0) + { + Assignment = new int[footprint.PixelCount] + }; + } + + private static Partition ComputePartition(Footprint footprint, in IntermediateBlock.IntermediateBlockData block) + => block.PartitionId.HasValue + ? Partition.GetASTCPartition(footprint, block.EndpointCount, block.PartitionId.Value) + : GenerateSinglePartition(footprint); + + private static Partition ComputePartition(Footprint footprint, IntermediateBlock.VoidExtentData block) + => GenerateSinglePartition(footprint); + + private void CalculateWeights(Footprint footprint, in IntermediateBlock.IntermediateBlockData block) + { + int gridSize = block.WeightGridX * block.WeightGridY; + int weightFrequency = block.DualPlaneChannel.HasValue ? 2 : 1; + + // Get decimation info once for both planes + var decimationInfo = DecimationTable.Get(footprint, block.WeightGridX, block.WeightGridY); + + // stackalloc avoids per-block heap allocation (max 12×12 = 144 ints = 576 bytes) + Span unquantized = stackalloc int[gridSize]; + for (int i = 0; i < gridSize; ++i) + { + unquantized[i] = Quantization.UnquantizeWeightFromRange( + block.Weights[i * weightFrequency], block.WeightRange); + } + DecimationTable.InfillWeights(unquantized, decimationInfo, _weights); + + if (block.DualPlaneChannel.HasValue) + { + var dualPlane = new DualPlaneData(); + dualPlane.Channel = block.DualPlaneChannel.Value; + dualPlane.Weights = new int[footprint.PixelCount]; + _dualPlane = dualPlane; + for (int i = 0; i < gridSize; ++i) + { + unquantized[i] = Quantization.UnquantizeWeightFromRange( + block.Weights[i * weightFrequency + 1], block.WeightRange); + } + DecimationTable.InfillWeights(unquantized, decimationInfo, _dualPlane.Weights); + } + } + + private static int InterpolateChannel(int p0, int p1, int weight) + { + int c0 = (p0 << 8) | p0; + int c1 = (p1 << 8) | p1; + int c = (c0 * (64 - weight) + c1 * weight + 32) / 64; + int quantized = ((c * byte.MaxValue) + short.MaxValue) / (ushort.MaxValue + 1); + return Math.Clamp(quantized, 0, byte.MaxValue); + } + + /// + /// Interpolates an LDR channel value and returns the full 16-bit UNORM result + /// (before reduction to byte). Used by the HDR output path for LDR endpoints. + /// + private static ushort InterpolateLdrAsUnorm16(int p0, int p1, int weight) + { + int c0 = (p0 << 8) | p0; + int c1 = (p1 << 8) | p1; + int c = (c0 * (64 - weight) + c1 * weight + 32) / 64; + return (ushort)Math.Clamp(c, 0, 0xFFFF); + } + + /// + /// Interpolates an HDR channel value between two endpoints using the specified weight. + /// + /// + /// HDR endpoints are already 16-bit values (FP16 bit patterns). Unlike LDR interpolation + /// which expands 8-bit to 16-bit before interpolating, HDR interpolation operates directly + /// on the 16-bit values + /// + private static ushort InterpolateChannelHdr(int p0, int p1, int weight) + { + int c = (p0 * (64 - weight) + p1 * weight + 32) / 64; + return (ushort)Math.Clamp(c, 0, 0xFFFF); + } + + private void WriteLdrSinglePartition( + Span buffer, + Footprint footprint, + int lowR, + int lowG, + int lowB, + int lowA, + int highR, + int highG, + int highB, + int highA) + { + int pixelCount = footprint.PixelCount; + for (int i = 0; i < pixelCount; i++) + { + SimdHelpers.WriteSinglePixelLdr( + buffer, i * 4, + lowR, lowG, lowB, lowA, highR, highG, highB, highA, + _weights[i]); + } + } + + private void WriteAllPixelsGeneral(Footprint footprint, Span buffer) + { + int pixelCount = footprint.PixelCount; + for (int i = 0; i < pixelCount; i++) + { + int part = _partition.Assignment[i]; + ref var endpoint = ref _endpoints[part]; + + int weight = _weights[i]; + if (!endpoint.IsHdr) + { + if (_dualPlane is not null) + { + SimdHelpers.WriteSinglePixelLdrDualPlane( + buffer, i * 4, + endpoint.LdrLow.R, endpoint.LdrLow.G, endpoint.LdrLow.B, endpoint.LdrLow.A, + endpoint.LdrHigh.R, endpoint.LdrHigh.G, endpoint.LdrHigh.B, endpoint.LdrHigh.A, + weight, _dualPlane.Channel, _dualPlane.Weights[i]); + } + else + { + SimdHelpers.WriteSinglePixelLdr( + buffer, i * 4, + endpoint.LdrLow.R, endpoint.LdrLow.G, endpoint.LdrLow.B, endpoint.LdrLow.A, + endpoint.LdrHigh.R, endpoint.LdrHigh.G, endpoint.LdrHigh.B, endpoint.LdrHigh.A, + weight); + } + } + else + { + int dualPlaneChannel = _dualPlane?.Channel ?? -1; + int dualPlaneWeight = _dualPlane?.Weights[i] ?? weight; + buffer[i * 4 + 0] = (byte)(InterpolateChannelHdr( + endpoint.HdrLow[0], endpoint.HdrHigh[0], + dualPlaneChannel == 0 ? dualPlaneWeight : weight) >> 8); + buffer[i * 4 + 1] = (byte)(InterpolateChannelHdr( + endpoint.HdrLow[1], endpoint.HdrHigh[1], + dualPlaneChannel == 1 ? dualPlaneWeight : weight) >> 8); + buffer[i * 4 + 2] = (byte)(InterpolateChannelHdr( + endpoint.HdrLow[2], endpoint.HdrHigh[2], + dualPlaneChannel == 2 ? dualPlaneWeight : weight) >> 8); + buffer[i * 4 + 3] = (byte)(InterpolateChannelHdr( + endpoint.HdrLow[3], endpoint.HdrHigh[3], + dualPlaneChannel == 3 ? dualPlaneWeight : weight) >> 8); + } + } + } + + private class DualPlaneData + { + public int Channel; + public int[] Weights = []; + } +} diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs new file mode 100644 index 00000000..bd269adc --- /dev/null +++ b/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs @@ -0,0 +1,176 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +/// +/// A physical ASTC texel block (128 bits). +/// Delegates all block mode decoding to . +/// +internal readonly struct PhysicalBlock +{ + public const int SizeInBytes = 16; + private readonly BlockInfo _info; + + public UInt128 BlockBits { get; } + + public bool IsVoidExtent => _info.IsVoidExtent; + + public bool IsIllegalEncoding => !_info.IsValid; + + public bool IsDualPlane + => _info.IsValid && !_info.IsVoidExtent && _info.IsDualPlane; + + private PhysicalBlock(UInt128 bits, BlockInfo info) + { + BlockBits = bits; + _info = info; + } + + /// + /// Factory method to create a PhysicalBlock from raw bits + /// + public static PhysicalBlock Create(UInt128 bits) + => new(bits, BlockInfo.Decode(bits)); + + public static PhysicalBlock Create(ulong low) => Create((UInt128)low); + + public static PhysicalBlock Create(ulong low, ulong high) => Create(new UInt128(high, low)); + + internal (int Width, int Height)? GetWeightGridDimensions() + => _info.IsValid && !_info.IsVoidExtent + ? (_info.GridWidth, _info.GridHeight) + : null; + + internal int? GetWeightRange() + => _info.IsValid && !_info.IsVoidExtent + ? _info.WeightRange + : null; + + internal int[]? GetVoidExtentCoordinates() + { + if (!_info.IsVoidExtent) return null; + + // If void extent coords are all 1's then these are not valid void extent coords + ulong voidExtentMask = 0xFFFFFFFFFFFFFDFFUL; + ulong constBlockMode = 0xFFFFFFFFFFFFFDFCUL; + + return _info.IsValid && (voidExtentMask & BlockBits.Low()) != constBlockMode + ? DecodeVoidExtentCoordinates(BlockBits) + : null; + } + + /// + /// Get the dual plane channel if dual plane is enabled + /// + /// The dual plane channel if enabled, otherwise null. + internal int? GetDualPlaneChannel() + => _info.IsValid && _info.IsDualPlane + ? _info.DualPlaneChannel + : null; + + internal string? IdentifyInvalidEncodingIssues() + { + if (_info.IsValid) return null; + return _info.IsVoidExtent + ? IdentifyVoidExtentIssues(BlockBits) + : "Invalid block encoding"; + } + + internal int? GetWeightBitCount() + => _info.IsValid && !_info.IsVoidExtent + ? _info.WeightBitCount + : null; + + internal int? GetWeightStartBit() + => _info.IsValid && !_info.IsVoidExtent + ? 128 - _info.WeightBitCount + : null; + + internal int? GetPartitionsCount() + => _info.IsValid && !_info.IsVoidExtent + ? _info.PartitionCount + : null; + + internal int? GetPartitionId() + { + if (!_info.IsValid || _info.IsVoidExtent || _info.PartitionCount == 1) return null; + return (int)BitOperations.GetBits(BlockBits.Low(), 13, 10); + } + + internal ColorEndpointMode? GetEndpointMode(int partition) + { + if (!_info.IsValid || _info.IsVoidExtent) return null; + if (partition < 0 || partition >= _info.PartitionCount) return null; + return _info.GetEndpointMode(partition); + } + + internal int? GetColorStartBit() + { + if (_info.IsVoidExtent) return 64; + return _info.IsValid + ? _info.ColorStartBit + : null; + } + + internal int? GetColorValuesCount() + { + if (_info.IsVoidExtent) return 4; + return _info.IsValid + ? _info.ColorValuesCount + : null; + } + + internal int? GetColorBitCount() + { + if (_info.IsVoidExtent) return 64; + return _info.IsValid + ? _info.ColorBitCount + : null; + } + + internal int? GetColorValuesRange() + { + if (_info.IsVoidExtent) return (1 << 16) - 1; + return _info.IsValid + ? _info.ColorValuesRange + : null; + } + + internal static int[] DecodeVoidExtentCoordinates(UInt128 astcBits) + { + ulong lowBits = astcBits.Low(); + var coords = new int[4]; + for (int i = 0; i < 4; ++i) + { + coords[i] = (int)BitOperations.GetBits(lowBits, 12 + 13 * i, 13); + } + return coords; + } + + /// + /// Full error-string version for void extent issues (used for error reporting) + /// + private static string? IdentifyVoidExtentIssues(UInt128 bits) + { + if (BitOperations.GetBits(bits, 10, 2).Low() != 0x3UL) + return "Reserved bits set for void extent block"; + + ulong lowBits = bits.Low(); + int c0 = (int)BitOperations.GetBits(lowBits, 12, 13); + int c1 = (int)BitOperations.GetBits(lowBits, 25, 13); + int c2 = (int)BitOperations.GetBits(lowBits, 38, 13); + int c3 = (int)BitOperations.GetBits(lowBits, 51, 13); + + const int all1s = (1 << 13) - 1; + bool coordsAll1s = c0 == all1s && c1 == all1s && c2 == all1s && c3 == all1s; + + if (!coordsAll1s && (c0 >= c1 || c2 >= c3)) + return "Void extent texture coordinates are invalid"; + + return null; + } +} diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlockMode.cs b/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlockMode.cs new file mode 100644 index 00000000..dc414388 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlockMode.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +/// +/// The overall block modes defined in table C.2.8. There are 10 +/// weight grid encoding schemes + void extent. +/// +internal enum PhysicalBlockMode +{ + WidthB4HeightA2, + WidthB8HeightA2, + WidthA2HeightB8, + WidthA2HeightB6, + WidthB2HeightA2, + Width12HeightA2, + WidthA2Height12, + Width6Height10, + Width10Height6, + WidthA6HeightB6, + VoidExtent, +} diff --git a/src/ImageSharp.Textures/ImageSharp.Textures.csproj b/src/ImageSharp.Textures/ImageSharp.Textures.csproj index a04dd9a2..c9948470 100644 --- a/src/ImageSharp.Textures/ImageSharp.Textures.csproj +++ b/src/ImageSharp.Textures/ImageSharp.Textures.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs index 99259857..f0d42dd2 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using AstcSharp.Core; +using SixLabors.ImageSharp.Textures.Astc.Core; namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; @@ -36,7 +36,7 @@ public static void DecodeBlock(ReadOnlySpan blockData, int blockWidth, int Footprint footprint = Footprint.FromFootprintType(FootprintFromDimensions(blockWidth, blockHeight)); - AstcSharp.AstcDecoder.DecompressBlock(blockData, footprint, decodedPixels); + Astc.AstcDecoder.DecompressBlock(blockData, footprint, decodedPixels); } /// public static class AstcDecoder { - private static readonly ArrayPool _arrayPool = ArrayPool.Shared; + private static readonly ArrayPool ArrayPool = ArrayPool.Shared; private const int BytesPerPixelUnorm8 = 4; /// @@ -50,14 +50,16 @@ public static Span DecompressImage(ReadOnlySpan astcData, int width, public static bool DecompressImage(ReadOnlySpan astcData, int width, int height, Footprint footprint, Span imageBuffer) { if (!TryGetBlockLayout(astcData, width, height, footprint, out int blocksWide, out int blocksHigh)) + { return false; + } var decodedBlock = Array.Empty(); try { // Create a buffer once for fallback blocks; fast path writes directly to image - decodedBlock = _arrayPool.Rent(footprint.Width * footprint.Height * BytesPerPixelUnorm8); + decodedBlock = ArrayPool.Rent(footprint.Width * footprint.Height * BytesPerPixelUnorm8); var decodedPixels = decodedBlock.AsSpan(); int blockIndex = 0; int footprintWidth = footprint.Width; @@ -69,7 +71,9 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h { int blockDataOffset = blockIndex++ * PhysicalBlock.SizeInBytes; if (blockDataOffset + PhysicalBlock.SizeInBytes > astcData.Length) + { continue; + } ulong low = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset)); ulong high = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset + 8)); @@ -81,7 +85,10 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h int copyHeight = Math.Min(footprintHeight, height - dstBaseY); var info = BlockInfo.Decode(blockBits); - if (!info.IsValid) continue; + if (!info.IsValid) + { + continue; + } // Fast path: fuse decode directly into image buffer for interior full blocks if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane @@ -89,8 +96,13 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h && copyWidth == footprintWidth && copyHeight == footprintHeight) { FusedLdrBlockDecoder.DecompressBlockFusedLdrToImage( - blockBits, in info, footprint, - dstBaseX, dstBaseY, width, imageBuffer); + blockBits, + in info, + footprint, + dstBaseX, + dstBaseY, + width, + imageBuffer); continue; } @@ -103,7 +115,11 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h else { var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); - if (logicalBlock is null) continue; + if (logicalBlock is null) + { + continue; + } + logicalBlock.WriteAllPixelsLdr(footprint, decodedPixels); } @@ -111,7 +127,7 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h for (int pixelY = 0; pixelY < copyHeight; pixelY++) { int srcOffset = pixelY * footprintWidth * BytesPerPixelUnorm8; - int dstOffset = ((dstBaseY + pixelY) * width + dstBaseX) * BytesPerPixelUnorm8; + int dstOffset = (((dstBaseY + pixelY) * width) + dstBaseX) * BytesPerPixelUnorm8; decodedPixels.Slice(srcOffset, copyBytes) .CopyTo(imageBuffer.Slice(dstOffset, copyBytes)); } @@ -120,7 +136,7 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h } finally { - _arrayPool.Return(decodedBlock); + ArrayPool.Return(decodedBlock); } return true; @@ -137,14 +153,14 @@ public static Span DecompressBlock(ReadOnlySpan blockData, Footprint var decodedPixels = Array.Empty(); try { - decodedPixels = _arrayPool.Rent(footprint.Width * footprint.Height * BytesPerPixelUnorm8); + decodedPixels = ArrayPool.Rent(footprint.Width * footprint.Height * BytesPerPixelUnorm8); var decodedPixelBuffer = decodedPixels.AsSpan(); DecompressBlock(blockData, footprint, decodedPixelBuffer); } finally { - _arrayPool.Return(decodedPixels); + ArrayPool.Return(decodedPixels); } return decodedPixels; @@ -156,7 +172,6 @@ public static Span DecompressBlock(ReadOnlySpan blockData, Footprint /// The data to decode /// The type of ASTC block footprint e.g. 4x4, 5x5, etc. /// The buffer to write the decoded pixels into - /// The decoded block of pixels as RGBA values public static void DecompressBlock(ReadOnlySpan blockData, Footprint footprint, Span buffer) { // Read the 16 bytes that make up the ASTC block as a 128-bit value @@ -165,7 +180,10 @@ public static void DecompressBlock(ReadOnlySpan blockData, Footprint footp var blockBits = new UInt128(high, low); var info = BlockInfo.Decode(blockBits); - if (!info.IsValid) return; + if (!info.IsValid) + { + return; + } // Fully fused fast path for single-partition, non-dual-plane, LDR blocks if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane @@ -177,7 +195,11 @@ public static void DecompressBlock(ReadOnlySpan blockData, Footprint footp // Fallback for void extent, multi-partition, dual plane, HDR var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); - if (logicalBlock is null) return; + if (logicalBlock is null) + { + return; + } + logicalBlock.WriteAllPixelsLdr(footprint, buffer); } @@ -196,7 +218,10 @@ public static Span DecompressHdrImage(ReadOnlySpan astcData, int wi const int channelsPerPixel = 4; var imageBuffer = new float[width * height * channelsPerPixel]; if (!DecompressHdrImage(astcData, width, height, footprint, imageBuffer)) + { return []; + } + return imageBuffer; } @@ -213,7 +238,9 @@ public static Span DecompressHdrImage(ReadOnlySpan astcData, int wi public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, int height, Footprint footprint, Span imageBuffer) { if (!TryGetBlockLayout(astcData, width, height, footprint, out int blocksWide, out int blocksHigh)) + { return false; + } const int channelsPerPixel = 4; var decodedBlock = Array.Empty(); @@ -233,7 +260,9 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in { int blockDataOffset = blockIndex++ * PhysicalBlock.SizeInBytes; if (blockDataOffset + PhysicalBlock.SizeInBytes > astcData.Length) + { continue; + } ulong low = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset)); ulong high = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset + 8)); @@ -245,15 +274,23 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in int copyHeight = Math.Min(footprintHeight, height - dstBaseY); var info = BlockInfo.Decode(blockBits); - if (!info.IsValid) continue; + if (!info.IsValid) + { + continue; + } // Fast path: fuse decode directly into image buffer for interior full blocks if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane && copyWidth == footprintWidth && copyHeight == footprintHeight) { FusedHdrBlockDecoder.DecompressBlockFusedHdrToImage( - blockBits, in info, footprint, - dstBaseX, dstBaseY, width, imageBuffer); + blockBits, + in info, + footprint, + dstBaseX, + dstBaseY, + width, + imageBuffer); continue; } @@ -266,7 +303,11 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in { // Fallback: LogicalBlock path for void extent, multi-partition, dual plane var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); - if (logicalBlock is null) continue; + if (logicalBlock is null) + { + continue; + } + for (int row = 0; row < footprintHeight; row++) { for (int column = 0; column < footprintWidth; ++column) @@ -281,7 +322,7 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in for (int pixelY = 0; pixelY < copyHeight; pixelY++) { int srcOffset = pixelY * footprintWidth * channelsPerPixel; - int dstOffset = ((dstBaseY + pixelY) * width + dstBaseX) * channelsPerPixel; + int dstOffset = (((dstBaseY + pixelY) * width) + dstBaseX) * channelsPerPixel; decodedPixels.Slice(srcOffset, copyFloats) .CopyTo(imageBuffer.Slice(dstOffset, copyFloats)); } @@ -326,7 +367,10 @@ public static void DecompressHdrBlock(ReadOnlySpan blockData, Footprint fo var blockBits = new UInt128(high, low); var info = BlockInfo.Decode(blockBits); - if (!info.IsValid) return; + if (!info.IsValid) + { + return; + } // Fused fast path for single-partition, non-dual-plane blocks if (!info.IsVoidExtent && info.PartitionCount == 1 && !info.IsDualPlane) @@ -337,7 +381,10 @@ public static void DecompressHdrBlock(ReadOnlySpan blockData, Footprint fo // Fallback for void extent, multi-partition, dual plane var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); - if (logicalBlock is null) return; + if (logicalBlock is null) + { + return; + } const int channelsPerPixel = 4; for (int row = 0; row < footprint.Height; row++) @@ -378,16 +425,22 @@ private static bool TryGetBlockLayout( blocksHigh = 0; if (blockWidth == 0 || blockHeight == 0 || width == 0 || height == 0) + { return false; + } blocksWide = (width + blockWidth - 1) / blockWidth; if (blocksWide == 0) + { return false; + } blocksHigh = (height + blockHeight - 1) / blockHeight; int expectedBlockCount = blocksWide * blocksHigh; if (astcData.Length % PhysicalBlock.SizeInBytes != 0 || astcData.Length / PhysicalBlock.SizeInBytes != expectedBlockCount) + { return false; + } return true; } diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs index b20a8639..0c244c7a 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs @@ -10,7 +10,6 @@ namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding; /// internal partial class BoundedIntegerSequenceCodec { - /// /// The maximum number of bits needed to encode an ISE value. /// @@ -21,12 +20,12 @@ internal partial class BoundedIntegerSequenceCodec /// private const int Log2MaxRangeForBits = 8; - /// + /// /// The number of bits used after each value to store the interleaved quint block. /// protected static readonly int[] InterleavedQuintBits = [3, 2, 2]; - /// + /// /// The number of bits used after each value to store the interleaved trit block. /// protected static readonly int[] InterleavedTritBits = [2, 2, 1, 2, 1]; @@ -50,35 +49,35 @@ internal partial class BoundedIntegerSequenceCodec /// protected static readonly int[][] TritEncodings = [ - [0,0,0,0,0], [1,0,0,0,0], [2,0,0,0,0], [0,0,2,0,0], [0,1,0,0,0], [1,1,0,0,0], [2,1,0,0,0], [1,0,2,0,0], [0,2,0,0,0], - [1,2,0,0,0], [2,2,0,0,0], [2,0,2,0,0], [0,2,2,0,0], [1,2,2,0,0], [2,2,2,0,0], [2,0,2,0,0], [0,0,1,0,0], [1,0,1,0,0], - [2,0,1,0,0], [0,1,2,0,0], [0,1,1,0,0], [1,1,1,0,0], [2,1,1,0,0], [1,1,2,0,0], [0,2,1,0,0], [1,2,1,0,0], [2,2,1,0,0], - [2,1,2,0,0], [0,0,0,2,2], [1,0,0,2,2], [2,0,0,2,2], [0,0,2,2,2], [0,0,0,1,0], [1,0,0,1,0], [2,0,0,1,0], [0,0,2,1,0], - [0,1,0,1,0], [1,1,0,1,0], [2,1,0,1,0], [1,0,2,1,0], [0,2,0,1,0], [1,2,0,1,0], [2,2,0,1,0], [2,0,2,1,0], [0,2,2,1,0], - [1,2,2,1,0], [2,2,2,1,0], [2,0,2,1,0], [0,0,1,1,0], [1,0,1,1,0], [2,0,1,1,0], [0,1,2,1,0], [0,1,1,1,0], [1,1,1,1,0], - [2,1,1,1,0], [1,1,2,1,0], [0,2,1,1,0], [1,2,1,1,0], [2,2,1,1,0], [2,1,2,1,0], [0,1,0,2,2], [1,1,0,2,2], [2,1,0,2,2], - [1,0,2,2,2], [0,0,0,2,0], [1,0,0,2,0], [2,0,0,2,0], [0,0,2,2,0], [0,1,0,2,0], [1,1,0,2,0], [2,1,0,2,0], [1,0,2,2,0], - [0,2,0,2,0], [1,2,0,2,0], [2,2,0,2,0], [2,0,2,2,0], [0,2,2,2,0], [1,2,2,2,0], [2,2,2,2,0], [2,0,2,2,0], [0,0,1,2,0], - [1,0,1,2,0], [2,0,1,2,0], [0,1,2,2,0], [0,1,1,2,0], [1,1,1,2,0], [2,1,1,2,0], [1,1,2,2,0], [0,2,1,2,0], [1,2,1,2,0], - [2,2,1,2,0], [2,1,2,2,0], [0,2,0,2,2], [1,2,0,2,2], [2,2,0,2,2], [2,0,2,2,2], [0,0,0,0,2], [1,0,0,0,2], [2,0,0,0,2], - [0,0,2,0,2], [0,1,0,0,2], [1,1,0,0,2], [2,1,0,0,2], [1,0,2,0,2], [0,2,0,0,2], [1,2,0,0,2], [2,2,0,0,2], [2,0,2,0,2], - [0,2,2,0,2], [1,2,2,0,2], [2,2,2,0,2], [2,0,2,0,2], [0,0,1,0,2], [1,0,1,0,2], [2,0,1,0,2], [0,1,2,0,2], [0,1,1,0,2], - [1,1,1,0,2], [2,1,1,0,2], [1,1,2,0,2], [0,2,1,0,2], [1,2,1,0,2], [2,2,1,0,2], [2,1,2,0,2], [0,2,2,2,2], [1,2,2,2,2], - [2,2,2,2,2], [2,0,2,2,2], [0,0,0,0,1], [1,0,0,0,1], [2,0,0,0,1], [0,0,2,0,1], [0,1,0,0,1], [1,1,0,0,1], [2,1,0,0,1], - [1,0,2,0,1], [0,2,0,0,1], [1,2,0,0,1], [2,2,0,0,1], [2,0,2,0,1], [0,2,2,0,1], [1,2,2,0,1], [2,2,2,0,1], [2,0,2,0,1], - [0,0,1,0,1], [1,0,1,0,1], [2,0,1,0,1], [0,1,2,0,1], [0,1,1,0,1], [1,1,1,0,1], [2,1,1,0,1], [1,1,2,0,1], [0,2,1,0,1], - [1,2,1,0,1], [2,2,1,0,1], [2,1,2,0,1], [0,0,1,2,2], [1,0,1,2,2], [2,0,1,2,2], [0,1,2,2,2], [0,0,0,1,1], [1,0,0,1,1], - [2,0,0,1,1], [0,0,2,1,1], [0,1,0,1,1], [1,1,0,1,1], [2,1,0,1,1], [1,0,2,1,1], [0,2,0,1,1], [1,2,0,1,1], [2,2,0,1,1], - [2,0,2,1,1], [0,2,2,1,1], [1,2,2,1,1], [2,2,2,1,1], [2,0,2,1,1], [0,0,1,1,1], [1,0,1,1,1], [2,0,1,1,1], [0,1,2,1,1], - [0,1,1,1,1], [1,1,1,1,1], [2,1,1,1,1], [1,1,2,1,1], [0,2,1,1,1], [1,2,1,1,1], [2,2,1,1,1], [2,1,2,1,1], [0,1,1,2,2], - [1,1,1,2,2], [2,1,1,2,2], [1,1,2,2,2], [0,0,0,2,1], [1,0,0,2,1], [2,0,0,2,1], [0,0,2,2,1], [0,1,0,2,1], [1,1,0,2,1], - [2,1,0,2,1], [1,0,2,2,1], [0,2,0,2,1], [1,2,0,2,1], [2,2,0,2,1], [2,0,2,2,1], [0,2,2,2,1], [1,2,2,2,1], [2,2,2,2,1], - [2,0,2,2,1], [0,0,1,2,1], [1,0,1,2,1], [2,0,1,2,1], [0,1,2,2,1], [0,1,1,2,1], [1,1,1,2,1], [2,1,1,2,1], [1,1,2,2,1], - [0,2,1,2,1], [1,2,1,2,1], [2,2,1,2,1], [2,1,2,2,1], [0,2,1,2,2], [1,2,1,2,2], [2,2,1,2,2], [2,1,2,2,2], [0,0,0,1,2], - [1,0,0,1,2], [2,0,0,1,2], [0,0,2,1,2], [0,1,0,1,2], [1,1,0,1,2], [2,1,0,1,2], [1,0,2,1,2], [0,2,0,1,2], [1,2,0,1,2], - [2,2,0,1,2], [2,0,2,1,2], [0,2,2,1,2], [1,2,2,1,2], [2,2,2,1,2], [2,0,2,1,2], [0,0,1,1,2], [1,0,1,1,2], [2,0,1,1,2], - [0,1,2,1,2], [0,1,1,1,2], [1,1,1,1,2], [2,1,1,1,2], [1,1,2,1,2], [0,2,1,1,2], [1,2,1,1,2], [2,2,1,1,2], [2,1,2,1,2], - [0,2,2,2,2], [1,2,2,2,2], [2,2,2,2,2], [2,1,2,2,2] + [0, 0, 0, 0, 0], [1, 0, 0, 0, 0], [2, 0, 0, 0, 0], [0, 0, 2, 0, 0], [0, 1, 0, 0, 0], [1, 1, 0, 0, 0], [2, 1, 0, 0, 0], [1, 0, 2, 0, 0], [0, 2, 0, 0, 0], + [1, 2, 0, 0, 0], [2, 2, 0, 0, 0], [2, 0, 2, 0, 0], [0, 2, 2, 0, 0], [1, 2, 2, 0, 0], [2, 2, 2, 0, 0], [2, 0, 2, 0, 0], [0, 0, 1, 0, 0], [1, 0, 1, 0, 0], + [2, 0, 1, 0, 0], [0, 1, 2, 0, 0], [0, 1, 1, 0, 0], [1, 1, 1, 0, 0], [2, 1, 1, 0, 0], [1, 1, 2, 0, 0], [0, 2, 1, 0, 0], [1, 2, 1, 0, 0], [2, 2, 1, 0, 0], + [2, 1, 2, 0, 0], [0, 0, 0, 2, 2], [1, 0, 0, 2, 2], [2, 0, 0, 2, 2], [0, 0, 2, 2, 2], [0, 0, 0, 1, 0], [1, 0, 0, 1, 0], [2, 0, 0, 1, 0], [0, 0, 2, 1, 0], + [0, 1, 0, 1, 0], [1, 1, 0, 1, 0], [2, 1, 0, 1, 0], [1, 0, 2, 1, 0], [0, 2, 0, 1, 0], [1, 2, 0, 1, 0], [2, 2, 0, 1, 0], [2, 0, 2, 1, 0], [0, 2, 2, 1, 0], + [1, 2, 2, 1, 0], [2, 2, 2, 1, 0], [2, 0, 2, 1, 0], [0, 0, 1, 1, 0], [1, 0, 1, 1, 0], [2, 0, 1, 1, 0], [0, 1, 2, 1, 0], [0, 1, 1, 1, 0], [1, 1, 1, 1, 0], + [2, 1, 1, 1, 0], [1, 1, 2, 1, 0], [0, 2, 1, 1, 0], [1, 2, 1, 1, 0], [2, 2, 1, 1, 0], [2, 1, 2, 1, 0], [0, 1, 0, 2, 2], [1, 1, 0, 2, 2], [2, 1, 0, 2, 2], + [1, 0, 2, 2, 2], [0, 0, 0, 2, 0], [1, 0, 0, 2, 0], [2, 0, 0, 2, 0], [0, 0, 2, 2, 0], [0, 1, 0, 2, 0], [1, 1, 0, 2, 0], [2, 1, 0, 2, 0], [1, 0, 2, 2, 0], + [0, 2, 0, 2, 0], [1, 2, 0, 2, 0], [2, 2, 0, 2, 0], [2, 0, 2, 2, 0], [0, 2, 2, 2, 0], [1, 2, 2, 2, 0], [2, 2, 2, 2, 0], [2, 0, 2, 2, 0], [0, 0, 1, 2, 0], + [1, 0, 1, 2, 0], [2, 0, 1, 2, 0], [0, 1, 2, 2, 0], [0, 1, 1, 2, 0], [1, 1, 1, 2, 0], [2, 1, 1, 2, 0], [1, 1, 2, 2, 0], [0, 2, 1, 2, 0], [1, 2, 1, 2, 0], + [2, 2, 1, 2, 0], [2, 1, 2, 2, 0], [0, 2, 0, 2, 2], [1, 2, 0, 2, 2], [2, 2, 0, 2, 2], [2, 0, 2, 2, 2], [0, 0, 0, 0, 2], [1, 0, 0, 0, 2], [2, 0, 0, 0, 2], + [0, 0, 2, 0, 2], [0, 1, 0, 0, 2], [1, 1, 0, 0, 2], [2, 1, 0, 0, 2], [1, 0, 2, 0, 2], [0, 2, 0, 0, 2], [1, 2, 0, 0, 2], [2, 2, 0, 0, 2], [2, 0, 2, 0, 2], + [0, 2, 2, 0, 2], [1, 2, 2, 0, 2], [2, 2, 2, 0, 2], [2, 0, 2, 0, 2], [0, 0, 1, 0, 2], [1, 0, 1, 0, 2], [2, 0, 1, 0, 2], [0, 1, 2, 0, 2], [0, 1, 1, 0, 2], + [1, 1, 1, 0, 2], [2, 1, 1, 0, 2], [1, 1, 2, 0, 2], [0, 2, 1, 0, 2], [1, 2, 1, 0, 2], [2, 2, 1, 0, 2], [2, 1, 2, 0, 2], [0, 2, 2, 2, 2], [1, 2, 2, 2, 2], + [2, 2, 2, 2, 2], [2, 0, 2, 2, 2], [0, 0, 0, 0, 1], [1, 0, 0, 0, 1], [2, 0, 0, 0, 1], [0, 0, 2, 0, 1], [0, 1, 0, 0, 1], [1, 1, 0, 0, 1], [2, 1, 0, 0, 1], + [1, 0, 2, 0, 1], [0, 2, 0, 0, 1], [1, 2, 0, 0, 1], [2, 2, 0, 0, 1], [2, 0, 2, 0, 1], [0, 2, 2, 0, 1], [1, 2, 2, 0, 1], [2, 2, 2, 0, 1], [2, 0, 2, 0, 1], + [0, 0, 1, 0, 1], [1, 0, 1, 0, 1], [2, 0, 1, 0, 1], [0, 1, 2, 0, 1], [0, 1, 1, 0, 1], [1, 1, 1, 0, 1], [2, 1, 1, 0, 1], [1, 1, 2, 0, 1], [0, 2, 1, 0, 1], + [1, 2, 1, 0, 1], [2, 2, 1, 0, 1], [2, 1, 2, 0, 1], [0, 0, 1, 2, 2], [1, 0, 1, 2, 2], [2, 0, 1, 2, 2], [0, 1, 2, 2, 2], [0, 0, 0, 1, 1], [1, 0, 0, 1, 1], + [2, 0, 0, 1, 1], [0, 0, 2, 1, 1], [0, 1, 0, 1, 1], [1, 1, 0, 1, 1], [2, 1, 0, 1, 1], [1, 0, 2, 1, 1], [0, 2, 0, 1, 1], [1, 2, 0, 1, 1], [2, 2, 0, 1, 1], + [2, 0, 2, 1, 1], [0, 2, 2, 1, 1], [1, 2, 2, 1, 1], [2, 2, 2, 1, 1], [2, 0, 2, 1, 1], [0, 0, 1, 1, 1], [1, 0, 1, 1, 1], [2, 0, 1, 1, 1], [0, 1, 2, 1, 1], + [0, 1, 1, 1, 1], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1], [1, 1, 2, 1, 1], [0, 2, 1, 1, 1], [1, 2, 1, 1, 1], [2, 2, 1, 1, 1], [2, 1, 2, 1, 1], [0, 1, 1, 2, 2], + [1, 1, 1, 2, 2], [2, 1, 1, 2, 2], [1, 1, 2, 2, 2], [0, 0, 0, 2, 1], [1, 0, 0, 2, 1], [2, 0, 0, 2, 1], [0, 0, 2, 2, 1], [0, 1, 0, 2, 1], [1, 1, 0, 2, 1], + [2, 1, 0, 2, 1], [1, 0, 2, 2, 1], [0, 2, 0, 2, 1], [1, 2, 0, 2, 1], [2, 2, 0, 2, 1], [2, 0, 2, 2, 1], [0, 2, 2, 2, 1], [1, 2, 2, 2, 1], [2, 2, 2, 2, 1], + [2, 0, 2, 2, 1], [0, 0, 1, 2, 1], [1, 0, 1, 2, 1], [2, 0, 1, 2, 1], [0, 1, 2, 2, 1], [0, 1, 1, 2, 1], [1, 1, 1, 2, 1], [2, 1, 1, 2, 1], [1, 1, 2, 2, 1], + [0, 2, 1, 2, 1], [1, 2, 1, 2, 1], [2, 2, 1, 2, 1], [2, 1, 2, 2, 1], [0, 2, 1, 2, 2], [1, 2, 1, 2, 2], [2, 2, 1, 2, 2], [2, 1, 2, 2, 2], [0, 0, 0, 1, 2], + [1, 0, 0, 1, 2], [2, 0, 0, 1, 2], [0, 0, 2, 1, 2], [0, 1, 0, 1, 2], [1, 1, 0, 1, 2], [2, 1, 0, 1, 2], [1, 0, 2, 1, 2], [0, 2, 0, 1, 2], [1, 2, 0, 1, 2], + [2, 2, 0, 1, 2], [2, 0, 2, 1, 2], [0, 2, 2, 1, 2], [1, 2, 2, 1, 2], [2, 2, 2, 1, 2], [2, 0, 2, 1, 2], [0, 0, 1, 1, 2], [1, 0, 1, 1, 2], [2, 0, 1, 1, 2], + [0, 1, 2, 1, 2], [0, 1, 1, 1, 2], [1, 1, 1, 1, 2], [2, 1, 1, 1, 2], [1, 1, 2, 1, 2], [0, 2, 1, 1, 2], [1, 2, 1, 1, 2], [2, 2, 1, 1, 2], [2, 1, 2, 1, 2], + [0, 2, 2, 2, 2], [1, 2, 2, 2, 2], [2, 2, 2, 2, 2], [2, 1, 2, 2, 2] ]; /// @@ -89,16 +88,16 @@ internal partial class BoundedIntegerSequenceCodec /// protected static readonly int[][] QuintEncodings = [ - [0,0,0], [1,0,0], [2,0,0], [3,0,0], [4,0,0], [0,4,0], [4,4,0], [4,4,4], [0,1,0], [1,1,0], [2,1,0], [3,1,0], [4,1,0], - [1,4,0], [4,4,1], [4,4,4], [0,2,0], [1,2,0], [2,2,0], [3,2,0], [4,2,0], [2,4,0], [4,4,2], [4,4,4], [0,3,0], [1,3,0], - [2,3,0], [3,3,0], [4,3,0], [3,4,0], [4,4,3], [4,4,4], [0,0,1], [1,0,1], [2,0,1], [3,0,1], [4,0,1], [0,4,1], [4,0,4], - [0,4,4], [0,1,1], [1,1,1], [2,1,1], [3,1,1], [4,1,1], [1,4,1], [4,1,4], [1,4,4], [0,2,1], [1,2,1], [2,2,1], [3,2,1], - [4,2,1], [2,4,1], [4,2,4], [2,4,4], [0,3,1], [1,3,1], [2,3,1], [3,3,1], [4,3,1], [3,4,1], [4,3,4], [3,4,4], [0,0,2], - [1,0,2], [2,0,2], [3,0,2], [4,0,2], [0,4,2], [2,0,4], [3,0,4], [0,1,2], [1,1,2], [2,1,2], [3,1,2], [4,1,2], [1,4,2], - [2,1,4], [3,1,4], [0,2,2], [1,2,2], [2,2,2], [3,2,2], [4,2,2], [2,4,2], [2,2,4], [3,2,4], [0,3,2], [1,3,2], [2,3,2], - [3,3,2], [4,3,2], [3,4,2], [2,3,4], [3,3,4], [0,0,3], [1,0,3], [2,0,3], [3,0,3], [4,0,3], [0,4,3], [0,0,4], [1,0,4], - [0,1,3], [1,1,3], [2,1,3], [3,1,3], [4,1,3], [1,4,3], [0,1,4], [1,1,4], [0,2,3], [1,2,3], [2,2,3], [3,2,3], [4,2,3], - [2,4,3], [0,2,4], [1,2,4], [0,3,3], [1,3,3], [2,3,3], [3,3,3], [4,3,3], [3,4,3], [0,3,4], [1,3,4] + [0, 0, 0], [1, 0, 0], [2, 0, 0], [3, 0, 0], [4, 0, 0], [0, 4, 0], [4, 4, 0], [4, 4, 4], [0, 1, 0], [1, 1, 0], [2, 1, 0], [3, 1, 0], [4, 1, 0], + [1, 4, 0], [4, 4, 1], [4, 4, 4], [0, 2, 0], [1, 2, 0], [2, 2, 0], [3, 2, 0], [4, 2, 0], [2, 4, 0], [4, 4, 2], [4, 4, 4], [0, 3, 0], [1, 3, 0], + [2, 3, 0], [3, 3, 0], [4, 3, 0], [3, 4, 0], [4, 4, 3], [4, 4, 4], [0, 0, 1], [1, 0, 1], [2, 0, 1], [3, 0, 1], [4, 0, 1], [0, 4, 1], [4, 0, 4], + [0, 4, 4], [0, 1, 1], [1, 1, 1], [2, 1, 1], [3, 1, 1], [4, 1, 1], [1, 4, 1], [4, 1, 4], [1, 4, 4], [0, 2, 1], [1, 2, 1], [2, 2, 1], [3, 2, 1], + [4, 2, 1], [2, 4, 1], [4, 2, 4], [2, 4, 4], [0, 3, 1], [1, 3, 1], [2, 3, 1], [3, 3, 1], [4, 3, 1], [3, 4, 1], [4, 3, 4], [3, 4, 4], [0, 0, 2], + [1, 0, 2], [2, 0, 2], [3, 0, 2], [4, 0, 2], [0, 4, 2], [2, 0, 4], [3, 0, 4], [0, 1, 2], [1, 1, 2], [2, 1, 2], [3, 1, 2], [4, 1, 2], [1, 4, 2], + [2, 1, 4], [3, 1, 4], [0, 2, 2], [1, 2, 2], [2, 2, 2], [3, 2, 2], [4, 2, 2], [2, 4, 2], [2, 2, 4], [3, 2, 4], [0, 3, 2], [1, 3, 2], [2, 3, 2], + [3, 3, 2], [4, 3, 2], [3, 4, 2], [2, 3, 4], [3, 3, 4], [0, 0, 3], [1, 0, 3], [2, 0, 3], [3, 0, 3], [4, 0, 3], [0, 4, 3], [0, 0, 4], [1, 0, 4], + [0, 1, 3], [1, 1, 3], [2, 1, 3], [3, 1, 3], [4, 1, 3], [1, 4, 3], [0, 1, 4], [1, 1, 4], [0, 2, 3], [1, 2, 3], [2, 2, 3], [3, 2, 3], [4, 2, 3], + [2, 4, 3], [0, 2, 4], [1, 2, 4], [0, 3, 3], [1, 3, 3], [2, 3, 3], [3, 3, 3], [4, 3, 3], [3, 4, 3], [0, 3, 4], [1, 3, 4] ]; /// @@ -115,14 +114,13 @@ internal partial class BoundedIntegerSequenceCodec protected static readonly int[] FlatTritEncodings = FlattenEncodings(TritEncodings, 5); protected static readonly int[] FlatQuintEncodings = FlattenEncodings(QuintEncodings, 3); - private static readonly (BiseEncodingMode Mode, int BitCount)[] _packingModeCache = InitPackingModeCache(); - - protected BiseEncodingMode _encoding; - protected int _bitCount; + private static readonly (BiseEncodingMode Mode, int BitCount)[] PackingModeCache = InitPackingModeCache(); + private BiseEncodingMode encoding; + private int bitCount; /// - /// Base class for ASTC integer sequence encoders and decoders. These codecs + /// Initializes a new instance of the class. /// operate on sequences of integers and produce bit patterns that pack the /// integers based on the encoding scheme specified in the ASTC specification /// Section C.2.12. @@ -146,10 +144,13 @@ internal partial class BoundedIntegerSequenceCodec protected BoundedIntegerSequenceCodec(int range) { var (encodingMode, bitCount) = GetPackingModeBitCount(range); - _encoding = encodingMode; - _bitCount = bitCount; + this.encoding = encodingMode; + this.bitCount = bitCount; } + protected BiseEncodingMode Encoding => this.encoding; + + protected int BitCount => this.bitCount; /// /// The number of bits needed to encode the given number of values with respect to the @@ -160,7 +161,7 @@ public static (BiseEncodingMode Mode, int BitCount) GetPackingModeBitCount(int r ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(range, 0); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(range, 1 << Log2MaxRangeForBits); - return _packingModeCache[range]; + return PackingModeCache[range]; } /// @@ -190,13 +191,12 @@ public static int GetBitCountForRange(int valuesCount, int range) return GetBitCount(mode, valuesCount, bitCount); } - /// /// The size of a single ISE block in bits /// protected int GetEncodedBlockSize() { - var (blockSize, extraBlockSize) = _encoding switch + var (blockSize, extraBlockSize) = this.encoding switch { BiseEncodingMode.TritEncoding => (5, 8), BiseEncodingMode.QuintEncoding => (3, 7), @@ -204,32 +204,40 @@ protected int GetEncodedBlockSize() _ => (0, 0), }; - return extraBlockSize + blockSize * _bitCount; + return extraBlockSize + (blockSize * this.bitCount); } - private static int[] FlattenEncodings(int[][] jagged, int stride) { var flat = new int[jagged.Length * stride]; for (int i = 0; i < jagged.Length; i++) { for (int j = 0; j < stride; j++) - flat[i * stride + j] = jagged[i][j]; + { + flat[(i * stride) + j] = jagged[i][j]; + } } + return flat; } private static (BiseEncodingMode, int)[] InitPackingModeCache() { var cache = new (BiseEncodingMode, int)[1 << Log2MaxRangeForBits]; + // Precompute for all valid ranges [1, 255] for (int range = 1; range < cache.Length; range++) { int index = -1; for (int i = 0; i < MaxRanges.Length; i++) { - if (MaxRanges[i] >= range) { index = i; break; } + if (MaxRanges[i] >= range) + { + index = i; + break; + } } + int maxValue = index < 0 ? MaxRanges[MaxRanges.Length - 1] + 1 : MaxRanges[index] + 1; @@ -247,10 +255,13 @@ private static (BiseEncodingMode, int)[] InitPackingModeCache() } if (encodingMode == BiseEncodingMode.Unknown) + { throw new InvalidOperationException($"Invalid range for BISE encoding: {range}"); + } cache[range] = (encodingMode, int.Log2(maxValue / (int)encodingMode)); } + return cache; } } diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs index aac0547f..6c0464b7 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs @@ -7,21 +7,22 @@ namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding; internal sealed class BoundedIntegerSequenceDecoder : BoundedIntegerSequenceCodec { + private static readonly BoundedIntegerSequenceDecoder?[] Cache = new BoundedIntegerSequenceDecoder?[256]; - private static readonly BoundedIntegerSequenceDecoder?[] _cache = new BoundedIntegerSequenceDecoder?[256]; - - - public BoundedIntegerSequenceDecoder(int range) : base(range) { } - + public BoundedIntegerSequenceDecoder(int range) + : base(range) + { + } public static BoundedIntegerSequenceDecoder GetCached(int range) { - var decoder = _cache[range]; + var decoder = Cache[range]; if (decoder is null) { decoder = new BoundedIntegerSequenceDecoder(range); - _cache[range] = decoder; + Cache[range] = decoder; } + return decoder; } @@ -31,12 +32,12 @@ public static BoundedIntegerSequenceDecoder GetCached(int range) /// The number of values to decode. /// The source of values to decode from. /// The span to write decoded values into. - /// - /// + /// Thrown when the encoded block size is too large. + /// Thrown when there are not enough bits to decode. public void Decode(int valuesCount, ref BitStream bitSource, Span result) { - int totalBitCount = GetBitCount(_encoding, valuesCount, _bitCount); - int bitsPerBlock = GetEncodedBlockSize(); + int totalBitCount = GetBitCount(this.Encoding, valuesCount, this.BitCount); + int bitsPerBlock = this.GetEncodedBlockSize(); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(bitsPerBlock, 64); Span blockResult = stackalloc int[5]; @@ -47,25 +48,33 @@ public void Decode(int valuesCount, ref BitStream bitSource, Span result) { int bitsToRead = Math.Min(bitsRemaining, bitsPerBlock); if (!bitSource.TryGetBits(bitsToRead, out ulong blockBits)) + { throw new InvalidOperationException("Not enough bits in BitStream to decode BISE block"); + } - if (_encoding == BiseEncodingMode.BitEncoding) + if (this.Encoding == BiseEncodingMode.BitEncoding) { if (resultIndex < valuesCount) + { result[resultIndex++] = (int)blockBits; + } } else { - int decoded = DecodeISEBlock(_encoding, blockBits, _bitCount, blockResult); + int decoded = DecodeISEBlock(this.Encoding, blockBits, this.BitCount, blockResult); for (int i = 0; i < decoded && resultIndex < valuesCount; ++i) + { result[resultIndex++] = blockResult[i]; + } } bitsRemaining -= bitsPerBlock; } if (resultIndex < valuesCount) + { throw new InvalidOperationException("Decoded fewer values than expected from BISE block"); + } } /// @@ -76,12 +85,12 @@ public void Decode(int valuesCount, ref BitStream bitSource, Span result) /// The number of values to decode. /// The source of values to decode from. /// The decoded values. The collection always contains exactly elements. - /// - /// + /// Thrown when the encoded block size is too large. + /// Thrown when there are not enough bits to decode. public int[] Decode(int valuesCount, ref BitStream bitSource) { var result = new int[valuesCount]; - Decode(valuesCount, ref bitSource, result); + this.Decode(valuesCount, ref bitSource, result); return result; } @@ -98,14 +107,22 @@ public static int DecodeISEBlock(BiseEncodingMode mode, ulong encodedBlock, int { // 5 values, interleaved bits = [2, 2, 1, 2, 1] = 8 bits total int bitPosition = 0; - int mantissa0 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; - ulong encodedTrits = (encodedBlock >> bitPosition) & 0x3; bitPosition += 2; - int mantissa1 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; - encodedTrits |= ((encodedBlock >> bitPosition) & 0x3) << 2; bitPosition += 2; - int mantissa2 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; - encodedTrits |= ((encodedBlock >> bitPosition) & 0x1) << 4; bitPosition += 1; - int mantissa3 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; - encodedTrits |= ((encodedBlock >> bitPosition) & 0x3) << 5; bitPosition += 2; + int mantissa0 = (int)((encodedBlock >> bitPosition) & mantissaMask); + bitPosition += encodedBitCount; + ulong encodedTrits = (encodedBlock >> bitPosition) & 0x3; + bitPosition += 2; + int mantissa1 = (int)((encodedBlock >> bitPosition) & mantissaMask); + bitPosition += encodedBitCount; + encodedTrits |= ((encodedBlock >> bitPosition) & 0x3) << 2; + bitPosition += 2; + int mantissa2 = (int)((encodedBlock >> bitPosition) & mantissaMask); + bitPosition += encodedBitCount; + encodedTrits |= ((encodedBlock >> bitPosition) & 0x1) << 4; + bitPosition += 1; + int mantissa3 = (int)((encodedBlock >> bitPosition) & mantissaMask); + bitPosition += encodedBitCount; + encodedTrits |= ((encodedBlock >> bitPosition) & 0x3) << 5; + bitPosition += 2; int mantissa4 = (int)((encodedBlock >> bitPosition) & mantissaMask); encodedTrits |= ((encodedBlock >> (bitPosition + encodedBitCount)) & 0x1) << 7; @@ -121,10 +138,14 @@ public static int DecodeISEBlock(BiseEncodingMode mode, ulong encodedBlock, int { // 3 values, interleaved bits = [3, 2, 2] = 7 bits total int bitPosition = 0; - int mantissa0 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; - ulong encodedQuints = (encodedBlock >> bitPosition) & 0x7; bitPosition += 3; - int mantissa1 = (int)((encodedBlock >> bitPosition) & mantissaMask); bitPosition += encodedBitCount; - encodedQuints |= ((encodedBlock >> bitPosition) & 0x3) << 3; bitPosition += 2; + int mantissa0 = (int)((encodedBlock >> bitPosition) & mantissaMask); + bitPosition += encodedBitCount; + ulong encodedQuints = (encodedBlock >> bitPosition) & 0x7; + bitPosition += 3; + int mantissa1 = (int)((encodedBlock >> bitPosition) & mantissaMask); + bitPosition += encodedBitCount; + encodedQuints |= ((encodedBlock >> bitPosition) & 0x3) << 3; + bitPosition += 2; int mantissa2 = (int)((encodedBlock >> bitPosition) & mantissaMask); encodedQuints |= ((encodedBlock >> (bitPosition + encodedBitCount)) & 0x3) << 5; diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs index 70f4ff3d..36c011be 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs @@ -7,50 +7,61 @@ namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding; internal sealed class BoundedIntegerSequenceEncoder : BoundedIntegerSequenceCodec { - private readonly List _values = []; + private readonly List values = []; - public BoundedIntegerSequenceEncoder(int range) : base(range) { } + public BoundedIntegerSequenceEncoder(int range) + : base(range) + { + } /// /// Adds a value to the encoding sequence. /// - public void AddValue(int val) => _values.Add(val); + public void AddValue(int val) => this.values.Add(val); /// /// Encodes and writes the stored values encoding to the sink. Repeated calls will produce the same result. /// public void Encode(ref BitStream bitSink) { - int totalBitCount = GetBitCount(_encoding, _values.Count, _bitCount); + int totalBitCount = GetBitCount(this.Encoding, this.values.Count, this.BitCount); int index = 0; int bitsWrittenCount = 0; - while (index < _values.Count) + while (index < this.values.Count) { - switch (_encoding) + switch (this.Encoding) { case BiseEncodingMode.TritEncoding: var trits = new List(); for (int i = 0; i < 5; ++i) { - if (index < _values.Count) trits.Add(_values[index++]); - else trits.Add(0); + if (index < this.values.Count) + { + trits.Add(this.values[index++]); + } + else + { + trits.Add(0); + } } - EncodeISEBlock(trits, _bitCount, ref bitSink, ref bitsWrittenCount, totalBitCount); + + EncodeISEBlock(trits, this.BitCount, ref bitSink, ref bitsWrittenCount, totalBitCount); break; case BiseEncodingMode.QuintEncoding: var quints = new List(); for (int i = 0; i < 3; ++i) { - var value = index < _values.Count - ? _values[index++] + var value = index < this.values.Count + ? this.values[index++] : 0; quints.Add(value); } - EncodeISEBlock(quints, _bitCount, ref bitSink, ref bitsWrittenCount, totalBitCount); + + EncodeISEBlock(quints, this.BitCount, ref bitSink, ref bitsWrittenCount, totalBitCount); break; case BiseEncodingMode.BitEncoding: - bitSink.PutBits((uint)_values[index++], GetEncodedBlockSize()); + bitSink.PutBits((uint)this.values[index++], this.GetEncodedBlockSize()); break; } } @@ -59,9 +70,10 @@ public void Encode(ref BitStream bitSink) /// /// Clear the stored values. /// - public void Reset() => _values.Clear(); + public void Reset() => this.values.Clear(); - private static void EncodeISEBlock(List values, int bitsPerValue, ref BitStream bitSink, ref int bitsWritten, int totalBitCount) where T : unmanaged + private static void EncodeISEBlock(List values, int bitsPerValue, ref BitStream bitSink, ref int bitsWritten, int totalBitCount) + where T : unmanaged { int valueCount = values.Count; int valueRange = (valueCount == 3) ? 5 : 3; @@ -85,10 +97,17 @@ private static void EncodeISEBlock(List values, int bitsPerValue, ref Bi for (int i = 0; i < valueCount; ++i) { tempBitsAdded += bitsPerValue; - if (tempBitsAdded >= totalBitCount) break; + if (tempBitsAdded >= totalBitCount) + { + break; + } + encodedBitCount += interleavedBits[i]; tempBitsAdded += interleavedBits[i]; - if (tempBitsAdded >= totalBitCount) break; + if (tempBitsAdded >= totalBitCount) + { + break; + } } int nonBitEncoding = -1; @@ -99,17 +118,33 @@ private static void EncodeISEBlock(List values, int bitsPerValue, ref Bi { if (valueRange == 5) { - if (QuintEncodings[j][i] != nonBitComponents[i]) { matches = false; break; } + if (QuintEncodings[j][i] != nonBitComponents[i]) + { + matches = false; + break; + } } else { - if (TritEncodings[j][i] != nonBitComponents[i]) { matches = false; break; } + if (TritEncodings[j][i] != nonBitComponents[i]) + { + matches = false; + break; + } } } - if (matches) { nonBitEncoding = j; break; } + + if (matches) + { + nonBitEncoding = j; + break; + } } - if (nonBitEncoding < 0) throw new InvalidOperationException(); + if (nonBitEncoding < 0) + { + throw new InvalidOperationException(); + } int nonBitEncodingCopy = nonBitEncoding; for (int i = 0; i < valueCount; ++i) @@ -119,6 +154,7 @@ private static void EncodeISEBlock(List values, int bitsPerValue, ref Bi bitSink.PutBits((uint)bitComponents[i], bitsPerValue); bitsWritten += bitsPerValue; } + int interleavedBitCount = interleavedBits[i]; int interleavedBitsValue = nonBitEncodingCopy & ((1 << interleavedBitCount) - 1); if (bitsWritten + interleavedBitCount <= totalBitCount) diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/BitQuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/BitQuantizationMap.cs index cc43474e..a6b442af 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/BitQuantizationMap.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/BitQuantizationMap.cs @@ -25,25 +25,41 @@ public BitQuantizationMap(int range, int totalUnquantizedBits) unquantized |= bits >> sourceShiftDown; unquantizedBitCount += destinationShiftUp; } - if (unquantizedBitCount != totalUnquantizedBits) throw new InvalidOperationException(); - _unquantizationMapBuilder.Add(unquantized); + + if (unquantizedBitCount != totalUnquantizedBits) + { + throw new InvalidOperationException(); + } + + this.UnquantizationMapBuilder.Add(unquantized); if (bits > 0) { - int previousUnquantized = _unquantizationMapBuilder[bits - 1]; - while (_quantizationMapBuilder.Count <= (previousUnquantized + unquantized) / 2) - _quantizationMapBuilder.Add(bits - 1); + int previousUnquantized = this.UnquantizationMapBuilder[bits - 1]; + while (this.QuantizationMapBuilder.Count <= (previousUnquantized + unquantized) / 2) + { + this.QuantizationMapBuilder.Add(bits - 1); + } + } + + while (this.QuantizationMapBuilder.Count <= unquantized) + { + this.QuantizationMapBuilder.Add(bits); } - while (_quantizationMapBuilder.Count <= unquantized) _quantizationMapBuilder.Add(bits); } - Freeze(); + this.Freeze(); } private static int CountOnes(int value) { int count = 0; - while (value != 0) { count += value & 1; value >>= 1; } + while (value != 0) + { + count += value & 1; + value >>= 1; + } + return count; } } diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs index d82e1919..f1153800 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs @@ -8,22 +8,22 @@ internal static class Quantization public const int EndpointRangeMinValue = 5; public const int WeightRangeMaxValue = 31; - private static readonly SortedDictionary _endpointMaps = InitEndpointMaps(); - private static readonly SortedDictionary _weightMaps = InitWeightMaps(); + private static readonly SortedDictionary EndpointMaps = InitEndpointMaps(); + private static readonly SortedDictionary WeightMaps = InitWeightMaps(); // Flat lookup tables indexed by range value for O(1) access. // Each slot maps to the QuantizationMap for the greatest supported range <= that index. - private static readonly QuantizationMap?[] _endpointMapByRange = InitEndpointMapFlat(); - private static readonly QuantizationMap?[] _weightMapByRange = InitWeightMapFlat(); + private static readonly QuantizationMap?[] EndpointMapByRange = InitEndpointMapFlat(); + private static readonly QuantizationMap?[] WeightMapByRange = InitWeightMapFlat(); // Pre-computed flat tables for weight unquantization: entry[quantizedValue] = final unquantized weight. // Includes the dq > 32 -> dq + 1 adjustment. Indexed by weight range. // Valid ranges: 1, 2, 3, 4, 5, 7, 9, 11, 15, 19, 23, 31 - private static readonly int[]?[] _unquantizeWeightsFlat = InitializeUnquantizeWeightsFlat(); + private static readonly int[]?[] UnquantizeWeightsFlat = InitializeUnquantizeWeightsFlat(); // Pre-computed flat tables for endpoint unquantization. // Indexed by range value. Valid ranges: 5, 7, 9, 11, 15, 19, 23, 31, 39, 47, 63, 79, 95, 127, 159, 191, 255 - private static readonly int[]?[] _unquantizeEndpointsFlat = InitializeUnquantizeEndpointsFlat(); + private static readonly int[]?[] UnquantizeEndpointsFlat = InitializeUnquantizeEndpointsFlat(); public static int QuantizeCEValueToRange(int value, int rangeMaxValue) { @@ -54,7 +54,11 @@ public static int QuantizeWeightToRange(int weight, int rangeMaxValue) ArgumentOutOfRangeException.ThrowIfLessThan(weight, 0); ArgumentOutOfRangeException.ThrowIfGreaterThan(weight, 64); - if (weight > 33) weight -= 1; + if (weight > 33) + { + weight -= 1; + } + var map = GetQuantMapForWeightRange(rangeMaxValue); return map != null ? map.Quantize(weight) : 0; } @@ -68,7 +72,11 @@ public static int UnquantizeWeightFromRange(int weight, int rangeMaxValue) var map = GetQuantMapForWeightRange(rangeMaxValue); int dequantized = map != null ? map.Unquantize(weight) : 0; - if (dequantized > 32) dequantized += 1; + if (dequantized > 32) + { + dequantized += 1; + } + return dequantized; } @@ -78,8 +86,12 @@ public static int UnquantizeWeightFromRange(int weight, int rangeMaxValue) /// internal static void UnquantizeWeightsBatch(Span weights, int count, int range) { - var table = _unquantizeWeightsFlat[range]; - if (table == null) return; + int[]? table = UnquantizeWeightsFlat[range]; + if (table == null) + { + return; + } + for (int i = 0; i < count; i++) { weights[i] = table[weights[i]]; @@ -92,8 +104,12 @@ internal static void UnquantizeWeightsBatch(Span weights, int count, int ra /// internal static void UnquantizeCEValuesBatch(Span values, int count, int rangeMaxValue) { - var table = _unquantizeEndpointsFlat[rangeMaxValue]; - if (table == null) return; + int[]? table = UnquantizeEndpointsFlat[rangeMaxValue]; + if (table == null) + { + return; + } + for (int i = 0; i < count; i++) { values[i] = table[values[i]]; @@ -152,9 +168,13 @@ private static SortedDictionary InitWeightMaps() for (int i = 0; i < size; i++) { if (maps.TryGetValue(i, out var map)) + { current = map; + } + flat[i] = current; } + return flat; } @@ -166,20 +186,28 @@ private static SortedDictionary InitWeightMaps() private static QuantizationMap? GetQuantMapForValueRange(int r) { - if ((uint)r >= (uint)_endpointMapByRange.Length) return null; - return _endpointMapByRange[r]; + if ((uint)r >= (uint)EndpointMapByRange.Length) + { + return null; + } + + return EndpointMapByRange[r]; } private static QuantizationMap? GetQuantMapForWeightRange(int r) { - if ((uint)r >= (uint)_weightMapByRange.Length) return null; - return _weightMapByRange[r]; + if ((uint)r >= (uint)WeightMapByRange.Length) + { + return null; + } + + return WeightMapByRange[r]; } private static int[]?[] InitializeUnquantizeWeightsFlat() { var tables = new int[]?[WeightRangeMaxValue + 1]; - foreach (var kvp in _weightMaps) + foreach (KeyValuePair kvp in WeightMaps) { int range = kvp.Key; var map = kvp.Value; @@ -189,23 +217,29 @@ private static SortedDictionary InitWeightMaps() int dequantized = map.Unquantize(i); table[i] = dequantized > 32 ? dequantized + 1 : dequantized; } + tables[range] = table; } + return tables; } private static int[]?[] InitializeUnquantizeEndpointsFlat() { var tables = new int[]?[256]; - foreach (var kvp in _endpointMaps) + foreach (KeyValuePair kvp in EndpointMaps) { int range = kvp.Key; var map = kvp.Value; var table = new int[range + 1]; for (int i = 0; i <= range; i++) + { table[i] = map.Unquantize(i); + } + tables[range] = table; } + return tables; } } diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuantizationMap.cs index ac39fad5..ffeb8146 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuantizationMap.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuantizationMap.cs @@ -5,27 +5,32 @@ namespace SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; internal class QuantizationMap { - protected List _quantizationMapBuilder = []; - protected List _unquantizationMapBuilder = []; - // Flat arrays for O(1) lookup on the hot path (set by Freeze) - private int[] _quantizationMap = []; - private int[] _unquantizationMap = []; + private int[] quantizationMap = []; + private int[] unquantizationMap = []; + + protected List QuantizationMapBuilder { get; set; } = []; + + protected List UnquantizationMapBuilder { get; set; } = []; public int Quantize(int x) - => (uint)x < (uint)_quantizationMap.Length - ? _quantizationMap[x] + => (uint)x < (uint)this.quantizationMap.Length + ? this.quantizationMap[x] : 0; public int Unquantize(int x) - => (uint)x < (uint)_unquantizationMap.Length - ? _unquantizationMap[x] + => (uint)x < (uint)this.unquantizationMap.Length + ? this.unquantizationMap[x] : 0; internal static int Log2Floor(int value) { int result = 0; - while ((1 << (result + 1)) <= value) result++; + while ((1 << (result + 1)) <= value) + { + result++; + } + return result; } @@ -34,27 +39,36 @@ internal static int Log2Floor(int value) /// protected void Freeze() { - _unquantizationMap = [.. _unquantizationMapBuilder]; - _quantizationMap = [.. _quantizationMapBuilder]; - _unquantizationMapBuilder = []; - _quantizationMapBuilder = []; + this.unquantizationMap = [.. this.UnquantizationMapBuilder]; + this.quantizationMap = [.. this.QuantizationMapBuilder]; + this.UnquantizationMapBuilder = []; + this.QuantizationMapBuilder = []; } protected void GenerateQuantizationMap() { - if (_unquantizationMapBuilder.Count <= 1) return; - _quantizationMapBuilder.Clear(); + if (this.UnquantizationMapBuilder.Count <= 1) + { + return; + } + + this.QuantizationMapBuilder.Clear(); for (int i = 0; i < 256; ++i) { int bestIndex = 0; int bestScore = int.MaxValue; - for (int index = 0; index < _unquantizationMapBuilder.Count; ++index) + for (int index = 0; index < this.UnquantizationMapBuilder.Count; ++index) { - int diff = i - _unquantizationMapBuilder[index]; + int diff = i - this.UnquantizationMapBuilder[index]; int score = diff * diff; - if (score < bestScore) { bestIndex = index; bestScore = score; } + if (score < bestScore) + { + bestIndex = index; + bestScore = score; + } } - _quantizationMapBuilder.Add(bestIndex); + + this.QuantizationMapBuilder.Add(bestIndex); } } } diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs index 8a89c692..a3afec10 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs @@ -13,11 +13,15 @@ public QuintQuantizationMap(int range, Func unquantFunc) int bitCount = bitsPowerOfTwo == 0 ? 0 : Log2Floor(bitsPowerOfTwo); for (int quint = 0; quint < 5; ++quint) + { for (int bits = 0; bits < (1 << bitCount); ++bits) - _unquantizationMapBuilder.Add(unquantFunc(quint, bits, range)); + { + this.UnquantizationMapBuilder.Add(unquantFunc(quint, bits, range)); + } + } - GenerateQuantizationMap(); - Freeze(); + this.GenerateQuantizationMap(); + this.Freeze(); } internal static int GetUnquantizedValue(int quint, int bits, int range) @@ -32,7 +36,7 @@ internal static int GetUnquantizedValue(int quint, int bits, int range) 159 => ((bits >> 1) & 0xF) is var x ? ((x >> 3) | (x << 5), 6) : default, _ => throw new ArgumentException("Illegal quint encoding") }; - int t = quint * c + b; + int t = (quint * c) + b; t ^= a; t = (a & 0x80) | (t >> 2); return t; @@ -41,7 +45,10 @@ internal static int GetUnquantizedValue(int quint, int bits, int range) internal static int GetUnquantizedWeight(int quint, int bits, int range) { if (range == 4) - return new[] { 0, 16, 32, 47, 63 }[quint]; + { + int[] weights = [0, 16, 32, 47, 63]; + return weights[quint]; + } int a = (bits & 1) != 0 ? 0x7F : 0; var (b, c) = range switch @@ -50,7 +57,7 @@ internal static int GetUnquantizedWeight(int quint, int bits, int range) 19 => ((bits >> 1) & 0x1) is var x ? ((x << 1) | (x << 6), 13) : default, _ => throw new ArgumentException("Illegal quint encoding") }; - int t = quint * c + b; + int t = (quint * c) + b; t ^= a; t = (a & 0x20) | (t >> 2); return t; diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs index 74966ade..c9999390 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs @@ -13,11 +13,15 @@ public TritQuantizationMap(int range, Func unquantFunc) int bitCount = bitsPowerOfTwo == 0 ? 0 : Log2Floor(bitsPowerOfTwo); for (int trit = 0; trit < 3; ++trit) + { for (int bits = 0; bits < (1 << bitCount); ++bits) - _unquantizationMapBuilder.Add(unquantFunc(trit, bits, range)); + { + this.UnquantizationMapBuilder.Add(unquantFunc(trit, bits, range)); + } + } - GenerateQuantizationMap(); - Freeze(); + this.GenerateQuantizationMap(); + this.Freeze(); } internal static int GetUnquantizedValue(int trit, int bits, int range) @@ -33,7 +37,7 @@ internal static int GetUnquantizedValue(int trit, int bits, int range) 191 => ((bits >> 1) & 0x1F) is var x ? ((x >> 4) | (x << 4), 5) : default, _ => throw new ArgumentException("Illegal trit encoding") }; - int t = trit * c + b; + int t = (trit * c) + b; t ^= a; t = (a & 0x80) | (t >> 2); return t; @@ -42,12 +46,14 @@ internal static int GetUnquantizedValue(int trit, int bits, int range) internal static int GetUnquantizedWeight(int trit, int bits, int range) { if (range == 2) + { return trit switch { 0 => 0, 1 => 32, _ => 63 }; + } int a = (bits & 1) != 0 ? 0x7F : 0; var (b, c) = range switch @@ -61,7 +67,7 @@ internal static int GetUnquantizedWeight(int trit, int bits, int range) : default, _ => throw new ArgumentException("Illegal trit encoding") }; - int t = trit * c + b; + int t = (trit * c) + b; t ^= a; return (a & 0x20) | (t >> 2); } diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs index a12c081f..927f797a 100644 --- a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs @@ -95,16 +95,21 @@ internal static void DecodeBiseValues(UInt128 bits, int startBit, int bitCount, { ulong val = (lowBits >> bitPos) & mask; if (bitPos + bitsPerValue > 64) + { val |= (highBits << (64 - bitPos)) & mask; + } + result[i] = (int)val; } else { result[i] = (int)((highBits >> (bitPos - 64)) & mask); } + bitPos += bitsPerValue; } } + return; } @@ -152,16 +157,21 @@ internal static void DecodeBiseWeights(UInt128 bits, int weightBitCount, int wei { ulong val = (lowBits >> bitPos) & mask; if (bitPos + bitsPerValue > 64) + { val |= (highBits << (64 - bitPos)) & mask; + } + result[i] = (int)val; } else { result[i] = (int)((highBits >> (bitPos - 64)) & mask); } + bitPos += bitsPerValue; } } + return; } diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs index b75bca3c..3164dbc4 100644 --- a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs @@ -50,9 +50,13 @@ private static void WriteHdrOutputPixels( Span buffer, int pixelCount, in ColorEndpointPair endpointPair, Span texelWeights) { if (endpointPair.IsHdr) + { WriteHdrPixels(buffer, pixelCount, in endpointPair, texelWeights); + } else + { WriteLdrAsHdrPixels(buffer, pixelCount, in endpointPair, texelWeights); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -66,9 +70,13 @@ private static void WriteHdrOutputPixelsToImage( Span texelWeights) { if (endpointPair.IsHdr) + { WriteHdrPixelsToImage(imageBuffer, footprint, dstBaseX, dstBaseY, imageWidth, in endpointPair, texelWeights); + } else + { WriteLdrAsHdrPixelsToImage(imageBuffer, footprint, dstBaseX, dstBaseY, imageWidth, in endpointPair, texelWeights); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -108,13 +116,13 @@ private static void WriteLdrAsHdrPixelsToImage( for (int pixelY = 0; pixelY < footprintHeight; pixelY++) { - int dstRowOffset = (dstBaseY + pixelY) * rowStride + dstBaseX * channelsPerPixel; + int dstRowOffset = ((dstBaseY + pixelY) * rowStride) + (dstBaseX * channelsPerPixel); int srcRowBase = pixelY * footprintWidth; for (int pixelX = 0; pixelX < footprintWidth; pixelX++) { int weight = texelWeights[srcRowBase + pixelX]; - int dstOffset = dstRowOffset + pixelX * channelsPerPixel; + int dstOffset = dstRowOffset + (pixelX * channelsPerPixel); imageBuffer[dstOffset + 0] = InterpolateLdrAsFloat(lowR, highR, weight); imageBuffer[dstOffset + 1] = InterpolateLdrAsFloat(lowG, highG, weight); imageBuffer[dstOffset + 2] = InterpolateLdrAsFloat(lowB, highB, weight); @@ -140,7 +148,7 @@ private static void WriteHdrPixels(Span buffer, int pixelCount, in ColorE if (alphaIsLdr) { - int interpolated = (lowA * (64 - weight) + highA * weight + 32) / 64; + int interpolated = ((lowA * (64 - weight)) + (highA * weight) + 32) / 64; buffer[offset + 3] = (ushort)Math.Clamp(interpolated, 0, 0xFFFF) / 65535.0f; } else @@ -171,20 +179,20 @@ private static void WriteHdrPixelsToImage( for (int pixelY = 0; pixelY < footprintHeight; pixelY++) { - int dstRowOffset = (dstBaseY + pixelY) * rowStride + dstBaseX * channelsPerPixel; + int dstRowOffset = ((dstBaseY + pixelY) * rowStride) + (dstBaseX * channelsPerPixel); int srcRowBase = pixelY * footprintWidth; for (int pixelX = 0; pixelX < footprintWidth; pixelX++) { int weight = texelWeights[srcRowBase + pixelX]; - int dstOffset = dstRowOffset + pixelX * channelsPerPixel; + int dstOffset = dstRowOffset + (pixelX * channelsPerPixel); imageBuffer[dstOffset + 0] = InterpolateHdrAsFloat(lowR, highR, weight); imageBuffer[dstOffset + 1] = InterpolateHdrAsFloat(lowG, highG, weight); imageBuffer[dstOffset + 2] = InterpolateHdrAsFloat(lowB, highB, weight); if (alphaIsLdr) { - int interpolated = (lowA * (64 - weight) + highA * weight + 32) / 64; + int interpolated = ((lowA * (64 - weight)) + (highA * weight) + 32) / 64; imageBuffer[dstOffset + 3] = (ushort)Math.Clamp(interpolated, 0, 0xFFFF) / 65535.0f; } else @@ -200,14 +208,14 @@ private static float InterpolateLdrAsFloat(int p0, int p1, int weight) { int c0 = (p0 << 8) | p0; int c1 = (p1 << 8) | p1; - int interpolated = (c0 * (64 - weight) + c1 * weight + 32) / 64; + int interpolated = ((c0 * (64 - weight)) + (c1 * weight) + 32) / 64; return Math.Clamp(interpolated, 0, 0xFFFF) / 65535.0f; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float InterpolateHdrAsFloat(int p0, int p1, int weight) { - int interpolated = (p0 * (64 - weight) + p1 * weight + 32) / 64; + int interpolated = ((p0 * (64 - weight)) + (p1 * weight) + 32) / 64; ushort clamped = (ushort)Math.Clamp(interpolated, 0, 0xFFFF); ushort halfFloatBits = LogicalBlock.LnsToSf16(clamped); return (float)BitConverter.UInt16BitsToHalf(halfFloatBits); diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs index b57209d9..1381db69 100644 --- a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs @@ -64,8 +64,10 @@ private static void WriteLdrPixels(Span buffer, int pixelCount, in ColorEn for (; i < limit; i += 4) { var weights = Vector128.Create( - texelWeights[i], texelWeights[i + 1], - texelWeights[i + 2], texelWeights[i + 3]); + texelWeights[i], + texelWeights[i + 1], + texelWeights[i + 2], + texelWeights[i + 3]); SimdHelpers.Write4PixelLdr( buffer, i * 4, @@ -120,7 +122,7 @@ private static void WriteLdrPixelsToImage( for (int pixelY = 0; pixelY < footprintHeight; pixelY++) { - int dstRowOffset = (dstBaseY + pixelY) * rowStride + dstBaseX * BytesPerPixelUnorm8; + int dstRowOffset = ((dstBaseY + pixelY) * rowStride) + (dstBaseX * BytesPerPixelUnorm8); int srcRowBase = pixelY * footprintWidth; int pixelX = 0; @@ -131,11 +133,21 @@ private static void WriteLdrPixelsToImage( { int texelIndex = srcRowBase + pixelX; var weights = Vector128.Create( - texelWeights[texelIndex], texelWeights[texelIndex + 1], - texelWeights[texelIndex + 2], texelWeights[texelIndex + 3]); + texelWeights[texelIndex], + texelWeights[texelIndex + 1], + texelWeights[texelIndex + 2], + texelWeights[texelIndex + 3]); SimdHelpers.Write4PixelLdr( - imageBuffer, dstRowOffset + pixelX * BytesPerPixelUnorm8, - lowR, lowG, lowB, lowA, highR, highG, highB, highA, + imageBuffer, + dstRowOffset + (pixelX * BytesPerPixelUnorm8), + lowR, + lowG, + lowB, + lowA, + highR, + highG, + highB, + highA, weights); } } @@ -143,8 +155,16 @@ private static void WriteLdrPixelsToImage( for (; pixelX < footprintWidth; pixelX++) { SimdHelpers.WriteSinglePixelLdr( - imageBuffer, dstRowOffset + pixelX * BytesPerPixelUnorm8, - lowR, lowG, lowB, lowA, highR, highG, highB, highA, + imageBuffer, + dstRowOffset + (pixelX * BytesPerPixelUnorm8), + lowR, + lowG, + lowB, + lowA, + highR, + highG, + highB, + highA, texelWeights[srcRowBase + pixelX]); } } diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointMode.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointMode.cs index 92950ee5..14c9bc37 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointMode.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointMode.cs @@ -35,4 +35,4 @@ internal enum ColorEndpointMode // Number of endpoint modes defined by the ASTC specification. ColorEndpointModeCount -} \ No newline at end of file +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointModeExtensions.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointModeExtensions.cs index b37e6727..c1a6e077 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointModeExtensions.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointModeExtensions.cs @@ -28,4 +28,4 @@ public static bool IsHdr(this ColorEndpointMode mode) ColorEndpointMode.HdrRgbDirectHdrAlpha => true, // Mode 15 _ => false }; -} \ No newline at end of file +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/IColorEndpointPair.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointPair.cs similarity index 100% rename from src/ImageSharp.Textures.Astc/ColorEncoding/IColorEndpointPair.cs rename to src/ImageSharp.Textures.Astc/ColorEncoding/ColorEndpointPair.cs diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs index 4829e8ea..215b3ef2 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs @@ -31,12 +31,16 @@ public static ColorEndpointPair DecodeColorsForModePolymorphic(ReadOnlySpan } } - public static (RgbaColor endpointLowRgba, RgbaColor endpointHighRgba) DecodeColorsForMode(ReadOnlySpan values, int maxValue, ColorEndpointMode mode) + public static (RgbaColor EndpointLowRgba, RgbaColor EndpointHighRgba) DecodeColorsForMode(ReadOnlySpan values, int maxValue, ColorEndpointMode mode) { int count = mode.GetColorValuesCount(); Span unquantizedValues = stackalloc int[count]; int copyLen = Math.Min(count, values.Length); - for (int i = 0; i < copyLen; i++) unquantizedValues[i] = values[i]; + for (int i = 0; i < copyLen; i++) + { + unquantizedValues[i] = values[i]; + } + UnquantizeInline(unquantizedValues, maxValue); var pair = DecodeColorsForModeUnquantized(unquantizedValues, mode); return (pair.LdrLow, pair.LdrHigh); @@ -75,26 +79,28 @@ internal static ColorEndpointPair DecodeColorsForModeUnquantized(ReadOnlySpan> 2) | (unquantizedValues[1] & 0xC0); - int l1 = Math.Min(l0 + (unquantizedValues[1] & 0x3F), 0xFF); - endpointLowRgba = new RgbaColor(l0, l0, l0); - endpointHighRgba = new RgbaColor(l1, l1, l1); - break; - } + { + int l0 = (unquantizedValues[0] >> 2) | (unquantizedValues[1] & 0xC0); + int l1 = Math.Min(l0 + (unquantizedValues[1] & 0x3F), 0xFF); + endpointLowRgba = new RgbaColor(l0, l0, l0); + endpointHighRgba = new RgbaColor(l1, l1, l1); + break; + } + case ColorEndpointMode.LdrLumaAlphaDirect: endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[0], unquantizedValues[0], unquantizedValues[2]); endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[1], unquantizedValues[1], unquantizedValues[3]); break; case ColorEndpointMode.LdrLumaAlphaBaseOffset: - { - var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); - var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); - endpointLowRgba = new RgbaColor(a0, a0, a0, a2); - int highLuma = a0 + b0; - endpointHighRgba = new RgbaColor(highLuma, highLuma, highLuma, a2 + b2); - break; - } + { + var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); + var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); + endpointLowRgba = new RgbaColor(a0, a0, a0, a2); + int highLuma = a0 + b0; + endpointHighRgba = new RgbaColor(highLuma, highLuma, highLuma, a2 + b2); + break; + } + case ColorEndpointMode.LdrRgbBaseScale: endpointLowRgba = new RgbaColor( (unquantizedValues[0] * unquantizedValues[3]) >> 8, @@ -103,50 +109,54 @@ internal static ColorEndpointPair DecodeColorsForModeUnquantized(ReadOnlySpan> 1, - g: (unquantizedValues[3] + unquantizedValues[5]) >> 1, - b: unquantizedValues[5]); - endpointHighRgba = new RgbaColor( - r: (unquantizedValues[0] + unquantizedValues[4]) >> 1, - g: (unquantizedValues[2] + unquantizedValues[4]) >> 1, - b: unquantizedValues[4]); - } - else - { - endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[2], unquantizedValues[4]); - endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[3], unquantizedValues[5]); - } - break; + endpointLowRgba = new RgbaColor( + r: (unquantizedValues[1] + unquantizedValues[5]) >> 1, + g: (unquantizedValues[3] + unquantizedValues[5]) >> 1, + b: unquantizedValues[5]); + endpointHighRgba = new RgbaColor( + r: (unquantizedValues[0] + unquantizedValues[4]) >> 1, + g: (unquantizedValues[2] + unquantizedValues[4]) >> 1, + b: unquantizedValues[4]); } + else + { + endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[2], unquantizedValues[4]); + endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[3], unquantizedValues[5]); + } + + break; + } + case ColorEndpointMode.LdrRgbBaseOffset: + { + var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); + var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); + var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); + if (b0 + b1 + b2 < 0) + { + endpointLowRgba = new RgbaColor( + r: (a0 + b0 + a2 + b2) >> 1, + g: (a1 + b1 + a2 + b2) >> 1, + b: a2 + b2); + endpointHighRgba = new RgbaColor( + r: (a0 + a2) >> 1, + g: (a1 + a2) >> 1, + b: a2); + } + else { - var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); - var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); - var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); - if (b0 + b1 + b2 < 0) - { - endpointLowRgba = new RgbaColor( - r: (a0 + b0 + a2 + b2) >> 1, - g: (a1 + b1 + a2 + b2) >> 1, - b: a2 + b2); - endpointHighRgba = new RgbaColor( - r: (a0 + a2) >> 1, - g: (a1 + a2) >> 1, - b: a2); - } - else - { - endpointLowRgba = new RgbaColor(a0, a1, a2); - endpointHighRgba = new RgbaColor(a0 + b0, a1 + b1, a2 + b2); - } - break; + endpointLowRgba = new RgbaColor(a0, a1, a2); + endpointHighRgba = new RgbaColor(a0 + b0, a1 + b1, a2 + b2); } + + break; + } + case ColorEndpointMode.LdrRgbBaseScaleTwoA: endpointLowRgba = new RgbaColor( r: (unquantizedValues[0] * unquantizedValues[3]) >> 8, @@ -156,55 +166,59 @@ internal static ColorEndpointPair DecodeColorsForModeUnquantized(ReadOnlySpan= sum0) + { + endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[2], unquantizedValues[4], unquantizedValues[6]); + endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[3], unquantizedValues[5], unquantizedValues[7]); + } + else { - int sum0 = unquantizedValues[0] + unquantizedValues[2] + unquantizedValues[4]; - int sum1 = unquantizedValues[1] + unquantizedValues[3] + unquantizedValues[5]; - if (sum1 >= sum0) - { - endpointLowRgba = new RgbaColor(unquantizedValues[0], unquantizedValues[2], unquantizedValues[4], unquantizedValues[6]); - endpointHighRgba = new RgbaColor(unquantizedValues[1], unquantizedValues[3], unquantizedValues[5], unquantizedValues[7]); - } - else - { - endpointLowRgba = new RgbaColor( - r: (unquantizedValues[1] + unquantizedValues[5]) >> 1, - g: (unquantizedValues[3] + unquantizedValues[5]) >> 1, - b: unquantizedValues[5], - a: unquantizedValues[7]); - endpointHighRgba = new RgbaColor( - r: (unquantizedValues[0] + unquantizedValues[4]) >> 1, - g: (unquantizedValues[2] + unquantizedValues[4]) >> 1, - b: unquantizedValues[4], - a: unquantizedValues[6]); - } - break; + endpointLowRgba = new RgbaColor( + r: (unquantizedValues[1] + unquantizedValues[5]) >> 1, + g: (unquantizedValues[3] + unquantizedValues[5]) >> 1, + b: unquantizedValues[5], + a: unquantizedValues[7]); + endpointHighRgba = new RgbaColor( + r: (unquantizedValues[0] + unquantizedValues[4]) >> 1, + g: (unquantizedValues[2] + unquantizedValues[4]) >> 1, + b: unquantizedValues[4], + a: unquantizedValues[6]); } + + break; + } + case ColorEndpointMode.LdrRgbaBaseOffset: + { + var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); + var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); + var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); + var (b3, a3) = BitOperations.TransferPrecision(unquantizedValues[7], unquantizedValues[6]); + if (b0 + b1 + b2 < 0) + { + endpointLowRgba = new RgbaColor( + r: (a0 + b0 + a2 + b2) >> 1, + g: (a1 + b1 + a2 + b2) >> 1, + b: a2 + b2, + a: a3 + b3); + endpointHighRgba = new RgbaColor( + r: (a0 + a2) >> 1, + g: (a1 + a2) >> 1, + b: a2, + a: a3); + } + else { - var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); - var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); - var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); - var (b3, a3) = BitOperations.TransferPrecision(unquantizedValues[7], unquantizedValues[6]); - if (b0 + b1 + b2 < 0) - { - endpointLowRgba = new RgbaColor( - r: (a0 + b0 + a2 + b2) >> 1, - g: (a1 + b1 + a2 + b2) >> 1, - b: a2 + b2, - a: a3 + b3); - endpointHighRgba = new RgbaColor( - r: (a0 + a2) >> 1, - g: (a1 + a2) >> 1, - b: a2, - a: a3); - } - else - { - endpointLowRgba = new RgbaColor(a0, a1, a2, a3); - endpointHighRgba = new RgbaColor(a0 + b0, a1 + b1, a2 + b2, a3 + b3); - } - break; + endpointLowRgba = new RgbaColor(a0, a1, a2, a3); + endpointHighRgba = new RgbaColor(a0 + b0, a1 + b1, a2 + b2, a3 + b3); } + + break; + } + default: endpointLowRgba = RgbaColor.Empty; endpointHighRgba = RgbaColor.Empty; @@ -217,12 +231,19 @@ internal static ColorEndpointPair DecodeColorsForModeUnquantized(ReadOnlySpan values, int maxValue) { - for (int i = 0; i < values.Length; ++i) values[i] = Quantization.UnquantizeCEValueFromRange(values[i], maxValue); + for (int i = 0; i < values.Length; ++i) + { + values[i] = Quantization.UnquantizeCEValueFromRange(values[i], maxValue); + } } } diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncoder.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncoder.cs index 97757292..0ebb90b5 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncoder.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncoder.cs @@ -17,27 +17,37 @@ public static bool UsesBlueContract(int maxValue, ColorEndpointMode mode, List s1; + v[i] = i < values.Count ? values[i] : 0; } + + var unquantizedValues = EndpointCodec.UnquantizeArray(v, maxValue); + int s0 = unquantizedValues[0] + unquantizedValues[2] + unquantizedValues[4]; + int s1 = unquantizedValues[1] + unquantizedValues[3] + unquantizedValues[5]; + return s0 > s1; + } + case ColorEndpointMode.LdrRgbBaseOffset: case ColorEndpointMode.LdrRgbaBaseOffset: + { + int maxValueCount = Math.Max(ColorEndpointMode.LdrRgbBaseOffset.GetColorValuesCount(), ColorEndpointMode.LdrRgbaBaseOffset.GetColorValuesCount()); + var v = new int[maxValueCount]; + for (int i = 0; i < maxValueCount; ++i) { - int maxValueCount = Math.Max(ColorEndpointMode.LdrRgbBaseOffset.GetColorValuesCount(), ColorEndpointMode.LdrRgbaBaseOffset.GetColorValuesCount()); - var v = new int[maxValueCount]; - for (int i = 0; i < maxValueCount; ++i) v[i] = i < values.Count ? values[i] : 0; - var unquantizedValues = EndpointCodec.UnquantizeArray(v, maxValue); - var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); - var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); - var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); - return (b0 + b1 + b2) < 0; + v[i] = i < values.Count ? values[i] : 0; } + + var unquantizedValues = EndpointCodec.UnquantizeArray(v, maxValue); + var (b0, a0) = BitOperations.TransferPrecision(unquantizedValues[1], unquantizedValues[0]); + var (b1, a1) = BitOperations.TransferPrecision(unquantizedValues[3], unquantizedValues[2]); + var (b2, a2) = BitOperations.TransferPrecision(unquantizedValues[5], unquantizedValues[4]); + return (b0 + b1 + b2) < 0; + } + default: return false; } @@ -49,75 +59,87 @@ public static bool EncodeColorsForMode(RgbaColor endpointLowRgba, RgbaColor endp bool needsWeightSwap = false; astcMode = ColorEndpointMode.LdrLumaDirect; int valueCount = encodingMode.GetValuesCount(); - for (int i = values.Count; i < valueCount; ++i) values.Add(0); + for (int i = values.Count; i < valueCount; ++i) + { + values.Add(0); + } switch (encodingMode) { case EndpointEncodingMode.DirectLuma: return EncodeColorsLuma(endpointLowRgba, endpointHighRgba, maxValue, out astcMode, values); case EndpointEncodingMode.DirectLumaAlpha: - { - int avg1 = endpointLowRgba.Average; - int avg2 = endpointHighRgba.Average; - values[0] = Quantization.QuantizeCEValueToRange(avg1, maxValue); - values[1] = Quantization.QuantizeCEValueToRange(avg2, maxValue); - values[2] = Quantization.QuantizeCEValueToRange(endpointLowRgba[3], maxValue); - values[3] = Quantization.QuantizeCEValueToRange(endpointHighRgba[3], maxValue); - astcMode = ColorEndpointMode.LdrLumaAlphaDirect; - } + { + int avg1 = endpointLowRgba.Average; + int avg2 = endpointHighRgba.Average; + values[0] = Quantization.QuantizeCEValueToRange(avg1, maxValue); + values[1] = Quantization.QuantizeCEValueToRange(avg2, maxValue); + values[2] = Quantization.QuantizeCEValueToRange(endpointLowRgba[3], maxValue); + values[3] = Quantization.QuantizeCEValueToRange(endpointHighRgba[3], maxValue); + astcMode = ColorEndpointMode.LdrLumaAlphaDirect; break; + } + case EndpointEncodingMode.BaseScaleRgb: case EndpointEncodingMode.BaseScaleRgba: - { - var baseColor = endpointHighRgba; - var scaled = endpointLowRgba; + { + var baseColor = endpointHighRgba; + var scaled = endpointLowRgba; - int numChannelsGe = 0; - for (int i = 0; i < 3; ++i) numChannelsGe += endpointHighRgba[i] >= endpointLowRgba[i] ? 1 : 0; + int numChannelsGe = 0; + for (int i = 0; i < 3; ++i) + { + numChannelsGe += endpointHighRgba[i] >= endpointLowRgba[i] ? 1 : 0; + } - if (numChannelsGe < 2) - { - needsWeightSwap = true; - var temp = baseColor; baseColor = scaled; scaled = temp; - } + if (numChannelsGe < 2) + { + needsWeightSwap = true; + var temp = baseColor; + baseColor = scaled; + scaled = temp; + } - var quantizedBase = QuantizeColorArray(baseColor, maxValue); - var unquantizedBase = EndpointCodec.UnquantizeArray(quantizedBase, maxValue); + var quantizedBase = QuantizeColorArray(baseColor, maxValue); + var unquantizedBase = EndpointCodec.UnquantizeArray(quantizedBase, maxValue); - int numSamples = 0; - int scaleSum = 0; - for (int i = 0; i < 3; ++i) + int numSamples = 0; + int scaleSum = 0; + for (int i = 0; i < 3; ++i) + { + int x = unquantizedBase[i]; + if (x != 0) { - int x = unquantizedBase[i]; - if (x != 0) - { - ++numSamples; - scaleSum += (scaled[i] * 256) / x; - } + ++numSamples; + scaleSum += (scaled[i] * 256) / x; } + } - values[0] = quantizedBase[0]; - values[1] = quantizedBase[1]; - values[2] = quantizedBase[2]; - if (numSamples > 0) - { - int avgScale = Math.Clamp(scaleSum / numSamples, 0, 255); - values[3] = Quantization.QuantizeCEValueToRange(avgScale, maxValue); - } - else - { - values[3] = maxValue; - } - astcMode = ColorEndpointMode.LdrRgbBaseScale; + values[0] = quantizedBase[0]; + values[1] = quantizedBase[1]; + values[2] = quantizedBase[2]; + if (numSamples > 0) + { + int avgScale = Math.Clamp(scaleSum / numSamples, 0, 255); + values[3] = Quantization.QuantizeCEValueToRange(avgScale, maxValue); + } + else + { + values[3] = maxValue; + } - if (encodingMode == EndpointEncodingMode.BaseScaleRgba) - { - values[4] = Quantization.QuantizeCEValueToRange(scaled[3], maxValue); - values[5] = Quantization.QuantizeCEValueToRange(baseColor[3], maxValue); - astcMode = ColorEndpointMode.LdrRgbBaseScaleTwoA; - } + astcMode = ColorEndpointMode.LdrRgbBaseScale; + + if (encodingMode == EndpointEncodingMode.BaseScaleRgba) + { + values[4] = Quantization.QuantizeCEValueToRange(scaled[3], maxValue); + values[5] = Quantization.QuantizeCEValueToRange(baseColor[3], maxValue); + astcMode = ColorEndpointMode.LdrRgbBaseScaleTwoA; } + break; + } + case EndpointEncodingMode.DirectRbg: case EndpointEncodingMode.DirectRgba: return EncodeColorsRGBA(endpointLowRgba, endpointHighRgba, maxValue, encodingMode == EndpointEncodingMode.DirectRgba, out astcMode, values); @@ -131,7 +153,11 @@ public static bool EncodeColorsForMode(RgbaColor endpointLowRgba, RgbaColor endp private static int[] QuantizeColorArray(RgbaColor c, int maxValue) { var array = new int[RgbaColor.BytesPerPixel]; - for (int i = 0; i < RgbaColor.BytesPerPixel; ++i) array[i] = Quantization.QuantizeCEValueToRange(c[i], maxValue); + for (int i = 0; i < RgbaColor.BytesPerPixel; ++i) + { + array[i] = Quantization.QuantizeCEValueToRange(c[i], maxValue); + } + return array; } @@ -144,7 +170,13 @@ private static bool EncodeColorsLuma(RgbaColor endpointLow, RgbaColor endpointHi int avg2 = endpointHigh.Average; bool needsWeightSwap = false; - if (avg1 > avg2) { needsWeightSwap = true; var temp = avg1; avg1 = avg2; avg2 = temp; } + if (avg1 > avg2) + { + needsWeightSwap = true; + var temp = avg1; + avg1 = avg2; + avg2 = temp; + } int offset = Math.Min(avg2 - avg1, 0x3F); int quantOffLow = Quantization.QuantizeCEValueToRange((avg1 & 0x3F) << 2, maxValue); @@ -263,6 +295,7 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi var blueContractUnquantizedHigh = bcQuantized.UnquantizedHigh(); var blueContractLow = RgbaColorExtensions.WithBlueContract(blueContractUnquantizedLow[0], blueContractUnquantizedLow[1], blueContractUnquantizedLow[2], blueContractUnquantizedLow[3]); var blueContractHigh = RgbaColorExtensions.WithBlueContract(blueContractUnquantizedHigh[0], blueContractUnquantizedHigh[1], blueContractUnquantizedHigh[2], blueContractUnquantizedHigh[3]); + // TODO: How to handle alpha for this entire functions?? var blueContractSquaredError = withAlpha ? RgbaColor.SquaredError(blueContractLow, endpointLowRgba) + RgbaColor.SquaredError(blueContractHigh, endpointHighRgba) @@ -348,38 +381,43 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi private class QuantizedEndpointPair { - private readonly RgbaColor _originalLow; - private readonly RgbaColor _originalHigh; - private readonly int[] _quantizedLow; - private readonly int[] _quantizedHigh; - private readonly int[] _unquantizedLow; - private readonly int[] _unquantizedHigh; + private readonly RgbaColor originalLow; + private readonly RgbaColor originalHigh; + private readonly int[] quantizedLow; + private readonly int[] quantizedHigh; + private readonly int[] unquantizedLow; + private readonly int[] unquantizedHigh; public QuantizedEndpointPair(RgbaColor low, RgbaColor high, int maxValue) { - _originalLow = low; - _originalHigh = high; - _quantizedLow = QuantizeColorArray(low, maxValue); - _quantizedHigh = QuantizeColorArray(high, maxValue); - _unquantizedLow = EndpointCodec.UnquantizeArray(_quantizedLow, maxValue); - _unquantizedHigh = EndpointCodec.UnquantizeArray(_quantizedHigh, maxValue); + this.originalLow = low; + this.originalHigh = high; + this.quantizedLow = QuantizeColorArray(low, maxValue); + this.quantizedHigh = QuantizeColorArray(high, maxValue); + this.unquantizedLow = EndpointCodec.UnquantizeArray(this.quantizedLow, maxValue); + this.unquantizedHigh = EndpointCodec.UnquantizeArray(this.quantizedHigh, maxValue); } - public int[] QuantizedLow() => _quantizedLow; - public int[] QuantizedHigh() => _quantizedHigh; - public int[] UnquantizedLow() => _unquantizedLow; - public int[] UnquantizedHigh() => _unquantizedHigh; - public RgbaColor OriginalLow() => _originalLow; - public RgbaColor OriginalHigh() => _originalHigh; + public int[] QuantizedLow() => this.quantizedLow; + + public int[] QuantizedHigh() => this.quantizedHigh; + + public int[] UnquantizedLow() => this.unquantizedLow; + + public int[] UnquantizedHigh() => this.unquantizedHigh; + + public RgbaColor OriginalLow() => this.originalLow; + + public RgbaColor OriginalHigh() => this.originalHigh; } private class CEEncodingOption { - private readonly int _squaredError; - private readonly QuantizedEndpointPair _quantizedEndpoints; - private readonly bool _swapEndpoints; - private readonly bool _blueContract; - private readonly bool _useOffsetMode; + private readonly int squaredError; + private readonly QuantizedEndpointPair quantizedEndpoints; + private readonly bool swapEndpoints; + private readonly bool blueContract; + private readonly bool useOffsetMode; public CEEncodingOption( int squaredError, @@ -388,23 +426,23 @@ public CEEncodingOption( bool blueContract, bool useOffsetMode) { - _squaredError = squaredError; - _quantizedEndpoints = quantizedEndpoints; - _swapEndpoints = swapEndpoints; - _blueContract = blueContract; - _useOffsetMode = useOffsetMode; + this.squaredError = squaredError; + this.quantizedEndpoints = quantizedEndpoints; + this.swapEndpoints = swapEndpoints; + this.blueContract = blueContract; + this.useOffsetMode = useOffsetMode; } public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List values, ref bool needsWeightSwap) { endpointMode = ColorEndpointMode.LdrLumaDirect; - var unquantizedLowOriginal = _quantizedEndpoints.UnquantizedLow(); - var unquantizedHighOriginal = _quantizedEndpoints.UnquantizedHigh(); + var unquantizedLowOriginal = this.quantizedEndpoints.UnquantizedLow(); + var unquantizedHighOriginal = this.quantizedEndpoints.UnquantizedHigh(); var unquantizedLow = (int[])unquantizedLowOriginal.Clone(); var unquantizedHigh = (int[])unquantizedHighOriginal.Clone(); - if (_useOffsetMode) + if (this.useOffsetMode) { for (int i = 0; i < 4; ++i) { @@ -420,9 +458,9 @@ public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List va } bool swapVals = false; - if (_useOffsetMode) + if (this.useOffsetMode) { - if (_blueContract) + if (this.blueContract) { swapVals = sum1 >= 0; } @@ -431,13 +469,20 @@ public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List va swapVals = sum1 < 0; } - if (swapVals) return false; + if (swapVals) + { + return false; + } } else { - if (_blueContract) + if (this.blueContract) { - if (sum1 == sum0) return false; + if (sum1 == sum0) + { + return false; + } + swapVals = sum1 > sum0; needsWeightSwap = !needsWeightSwap; } @@ -447,16 +492,22 @@ public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List va } } - var quantizedLowOriginal = _quantizedEndpoints.QuantizedLow(); - var quantizedHighOriginal = _quantizedEndpoints.QuantizedHigh(); + var quantizedLowOriginal = this.quantizedEndpoints.QuantizedLow(); + var quantizedHighOriginal = this.quantizedEndpoints.QuantizedHigh(); var quantizedLow = (int[])quantizedLowOriginal.Clone(); var quantizedHigh = (int[])quantizedHighOriginal.Clone(); if (swapVals) { - if (_useOffsetMode) throw new InvalidOperationException(); - var tmp = quantizedLow; quantizedLow = quantizedHigh; quantizedHigh = tmp; + if (this.useOffsetMode) + { + throw new InvalidOperationException(); + } + + var tmp = quantizedLow; + quantizedLow = quantizedHigh; + quantizedHigh = tmp; needsWeightSwap = !needsWeightSwap; } @@ -467,7 +518,7 @@ public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List va values[4] = quantizedLow[2]; values[5] = quantizedHigh[2]; - if (_useOffsetMode) + if (this.useOffsetMode) { endpointMode = ColorEndpointMode.LdrRgbBaseOffset; } @@ -480,11 +531,17 @@ public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List va { values[6] = quantizedLow[3]; values[7] = quantizedHigh[3]; - if (_useOffsetMode) endpointMode = ColorEndpointMode.LdrRgbaBaseOffset; - else endpointMode = ColorEndpointMode.LdrRgbaDirect; + if (this.useOffsetMode) + { + endpointMode = ColorEndpointMode.LdrRgbaBaseOffset; + } + else + { + endpointMode = ColorEndpointMode.LdrRgbaDirect; + } } - if (_swapEndpoints) + if (this.swapEndpoints) { needsWeightSwap = !needsWeightSwap; } @@ -492,7 +549,8 @@ public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List va return true; } - public bool BlueContract() => _blueContract; - public int Error() => _squaredError; + public bool BlueContract() => this.blueContract; + + public int Error() => this.squaredError; } } diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingModeExtensions.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingModeExtensions.cs index 3000d37d..1b81a1ac 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingModeExtensions.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointEncodingModeExtensions.cs @@ -12,4 +12,4 @@ internal static class EndpointEncodingModeExtensions EndpointEncodingMode.DirectRbg or EndpointEncodingMode.BaseScaleRgba => 6, _ => 8 }; -} \ No newline at end of file +} diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs index 7ed98612..538883cd 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs @@ -15,13 +15,16 @@ namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; /// internal static class HdrEndpointDecoder { - public static (RgbaHdrColor low, RgbaHdrColor high) DecodeHdrMode(ReadOnlySpan values, int maxValue, ColorEndpointMode mode) + public static (RgbaHdrColor Low, RgbaHdrColor High) DecodeHdrMode(ReadOnlySpan values, int maxValue, ColorEndpointMode mode) { int count = mode.GetColorValuesCount(); Span unquantizedValues = stackalloc int[count]; int copyLength = Math.Min(count, values.Length); for (int i = 0; i < copyLength; i++) + { unquantizedValues[i] = Quantization.UnquantizeCEValueFromRange(values[i], maxValue); + } + return DecodeHdrModeUnquantized(unquantizedValues, mode); } @@ -30,7 +33,7 @@ public static (RgbaHdrColor low, RgbaHdrColor high) DecodeHdrMode(ReadOnlySpan - public static (RgbaHdrColor low, RgbaHdrColor high) DecodeHdrModeUnquantized(ReadOnlySpan value, ColorEndpointMode mode) + public static (RgbaHdrColor Low, RgbaHdrColor High) DecodeHdrModeUnquantized(ReadOnlySpan value, ColorEndpointMode mode) { return mode switch { @@ -50,7 +53,7 @@ public static (RgbaHdrColor low, RgbaHdrColor high) DecodeHdrModeUnquantized(Rea /// private static int SafeSignedLeftShift(int value, int shift) => (int)((uint)value << shift); - private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrLuminanceLargeRangeCore(int v0, int v1) + private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrLuminanceLargeRangeCore(int v0, int v1) { int y0, y1; if (v1 >= v0) @@ -69,7 +72,7 @@ private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrLuminanceLargeRang return (low, high); } - private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrLuminanceSmallRangeCore(int v0, int v1) + private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrLuminanceSmallRangeCore(int v0, int v1) { int y0, y1; if ((v0 & 0x80) != 0) @@ -85,14 +88,16 @@ private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrLuminanceSmallRang y1 += y0; if (y1 > 0xFFF) + { y1 = 0xFFF; + } var low = new RgbaHdrColor((ushort)(y0 << 4), (ushort)(y0 << 4), (ushort)(y0 << 4), 0x7800); var high = new RgbaHdrColor((ushort)(y1 << 4), (ushort)(y1 << 4), (ushort)(y1 << 4), 0x7800); return (low, high); } - private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbBaseScaleCore(int v0, int v1, int v2, int v3) + private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrRgbBaseScaleCore(int v0, int v1, int v2, int v3) { int modeValue = ((v0 & 0xC0) >> 6) | (((v1 & 0x80) >> 7) << 2) | (((v2 & 0x80) >> 7) << 3); @@ -121,29 +126,90 @@ _ when (modeValue & 0xC) != 0xC => (modeValue >> 2, modeValue & 3), int oneHotMode = 1 << mode; - if ((oneHotMode & 0x30) != 0) green |= bit0 << 6; - if ((oneHotMode & 0x3A) != 0) green |= bit1 << 5; - if ((oneHotMode & 0x30) != 0) blue |= bit2 << 6; - if ((oneHotMode & 0x3A) != 0) blue |= bit3 << 5; + if ((oneHotMode & 0x30) != 0) + { + green |= bit0 << 6; + } + + if ((oneHotMode & 0x3A) != 0) + { + green |= bit1 << 5; + } + + if ((oneHotMode & 0x30) != 0) + { + blue |= bit2 << 6; + } + + if ((oneHotMode & 0x3A) != 0) + { + blue |= bit3 << 5; + } + + if ((oneHotMode & 0x3D) != 0) + { + scale |= bit6 << 5; + } + + if ((oneHotMode & 0x2D) != 0) + { + scale |= bit5 << 6; + } + + if ((oneHotMode & 0x04) != 0) + { + scale |= bit4 << 7; + } - if ((oneHotMode & 0x3D) != 0) scale |= bit6 << 5; - if ((oneHotMode & 0x2D) != 0) scale |= bit5 << 6; - if ((oneHotMode & 0x04) != 0) scale |= bit4 << 7; + if ((oneHotMode & 0x3B) != 0) + { + red |= bit4 << 6; + } - if ((oneHotMode & 0x3B) != 0) red |= bit4 << 6; - if ((oneHotMode & 0x04) != 0) red |= bit3 << 6; + if ((oneHotMode & 0x04) != 0) + { + red |= bit3 << 6; + } + + if ((oneHotMode & 0x10) != 0) + { + red |= bit5 << 7; + } - if ((oneHotMode & 0x10) != 0) red |= bit5 << 7; - if ((oneHotMode & 0x0F) != 0) red |= bit2 << 7; + if ((oneHotMode & 0x0F) != 0) + { + red |= bit2 << 7; + } + + if ((oneHotMode & 0x05) != 0) + { + red |= bit1 << 8; + } + + if ((oneHotMode & 0x0A) != 0) + { + red |= bit0 << 8; + } + + if ((oneHotMode & 0x05) != 0) + { + red |= bit0 << 9; + } - if ((oneHotMode & 0x05) != 0) red |= bit1 << 8; - if ((oneHotMode & 0x0A) != 0) red |= bit0 << 8; + if ((oneHotMode & 0x02) != 0) + { + red |= bit6 << 9; + } - if ((oneHotMode & 0x05) != 0) red |= bit0 << 9; - if ((oneHotMode & 0x02) != 0) red |= bit6 << 9; + if ((oneHotMode & 0x01) != 0) + { + red |= bit3 << 10; + } - if ((oneHotMode & 0x01) != 0) red |= bit3 << 10; - if ((oneHotMode & 0x02) != 0) red |= bit5 << 10; + if ((oneHotMode & 0x02) != 0) + { + red |= bit5 << 10; + } // Shift amounts per mode (from ARM reference) ReadOnlySpan shiftAmounts = [1, 1, 2, 3, 4, 5]; @@ -186,7 +252,7 @@ _ when (modeValue & 0xC) != 0xC => (modeValue >> 2, modeValue & 3), return (low, high); } - private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectCore(int v0, int v1, int v2, int v3, int v4, int v5) + private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrRgbDirectCore(int v0, int v1, int v2, int v3, int v4, int v5) { int modeValue = ((v1 & 0x80) >> 7) | (((v2 & 0x80) >> 7) << 1) | (((v3 & 0x80) >> 7) << 2); int majorComponent = ((v4 & 0x80) >> 7) | (((v5 & 0x80) >> 7) << 1); @@ -228,25 +294,77 @@ private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectCore(int int oneHotModeValue = 1 << modeValue; // Bit placement for 'a' - if ((oneHotModeValue & 0xA4) != 0) a |= bit0 << 9; - if ((oneHotModeValue & 0x8) != 0) a |= bit2 << 9; - if ((oneHotModeValue & 0x50) != 0) a |= bit4 << 9; - if ((oneHotModeValue & 0x50) != 0) a |= bit5 << 10; - if ((oneHotModeValue & 0xA0) != 0) a |= bit1 << 10; - if ((oneHotModeValue & 0xC0) != 0) a |= bit2 << 11; + if ((oneHotModeValue & 0xA4) != 0) + { + a |= bit0 << 9; + } + + if ((oneHotModeValue & 0x8) != 0) + { + a |= bit2 << 9; + } + + if ((oneHotModeValue & 0x50) != 0) + { + a |= bit4 << 9; + } + + if ((oneHotModeValue & 0x50) != 0) + { + a |= bit5 << 10; + } + + if ((oneHotModeValue & 0xA0) != 0) + { + a |= bit1 << 10; + } + + if ((oneHotModeValue & 0xC0) != 0) + { + a |= bit2 << 11; + } // Bit placement for 'c' - if ((oneHotModeValue & 0x4) != 0) c |= bit1 << 6; - if ((oneHotModeValue & 0xE8) != 0) c |= bit3 << 6; - if ((oneHotModeValue & 0x20) != 0) c |= bit2 << 7; + if ((oneHotModeValue & 0x4) != 0) + { + c |= bit1 << 6; + } + + if ((oneHotModeValue & 0xE8) != 0) + { + c |= bit3 << 6; + } + + if ((oneHotModeValue & 0x20) != 0) + { + c |= bit2 << 7; + } // Bit placement for 'b0' and 'b1' - if ((oneHotModeValue & 0x5B) != 0) { b0 |= bit0 << 6; b1 |= bit1 << 6; } - if ((oneHotModeValue & 0x12) != 0) { b0 |= bit2 << 7; b1 |= bit3 << 7; } + if ((oneHotModeValue & 0x5B) != 0) + { + b0 |= bit0 << 6; + b1 |= bit1 << 6; + } + + if ((oneHotModeValue & 0x12) != 0) + { + b0 |= bit2 << 7; + b1 |= bit3 << 7; + } // Bit placement for 'd0' and 'd1' - if ((oneHotModeValue & 0xAF) != 0) { d0 |= bit4 << 5; d1 |= bit5 << 5; } - if ((oneHotModeValue & 0x5) != 0) { d0 |= bit2 << 6; d1 |= bit3 << 6; } + if ((oneHotModeValue & 0xAF) != 0) + { + d0 |= bit4 << 5; + d1 |= bit5 << 5; + } + + if ((oneHotModeValue & 0x5) != 0) + { + d0 |= bit2 << 6; + d1 |= bit3 << 6; + } // Sign-extend d0 and d1 based on dataBits int signExtendShift = 32 - dataBits; @@ -291,7 +409,7 @@ private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectCore(int return (lowResult, highResult); } - private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectLdrAlphaCore(ReadOnlySpan unquantizedValues) + private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrRgbDirectLdrAlphaCore(ReadOnlySpan unquantizedValues) { var (rgbLow, rgbHigh) = UnpackHdrRgbDirectCore(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[3], unquantizedValues[4], unquantizedValues[5]); @@ -303,7 +421,7 @@ private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectLdrAlphaC return (low, high); } - private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectHdrAlphaCore(ReadOnlySpan unquantizedValues) + private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrRgbDirectHdrAlphaCore(ReadOnlySpan unquantizedValues) { var (rgbLow, rgbHigh) = UnpackHdrRgbDirectCore(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[3], unquantizedValues[4], unquantizedValues[5]); @@ -317,7 +435,7 @@ private static (RgbaHdrColor low, RgbaHdrColor high) UnpackHdrRgbDirectHdrAlphaC /// /// Decodes HDR alpha values /// - private static (ushort low, ushort high) UnpackHdrAlpha(int v6, int v7) + private static (ushort Low, ushort High) UnpackHdrAlpha(int v6, int v7) { int selector = ((v6 >> 7) & 1) | ((v7 >> 6) & 2); v6 &= 0x7F; @@ -334,18 +452,22 @@ private static (ushort low, ushort high) UnpackHdrAlpha(int v6, int v7) else { // Complex mode: base + sign-extended offset - v6 |= (v7 << (selector + 1)) & 0x780; - v7 &= (0x3F >> selector); + v6 |= v7 << (selector + 1) & 0x780; + v7 &= 0x3F >> selector; v7 ^= 32 >> selector; v7 -= 32 >> selector; - v6 <<= (4 - selector); - v7 <<= (4 - selector); + v6 <<= 4 - selector; + v7 <<= 4 - selector; v7 += v6; if (v7 < 0) + { v7 = 0; + } else if (v7 > 0xFFF) + { v7 = 0xFFF; + } a0 = v6; a1 = v7; diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs index ad8de230..d92d907a 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs @@ -7,28 +7,35 @@ namespace SixLabors.ImageSharp.Textures.Astc.ColorEncoding; internal sealed class Partition { - - private static readonly System.Collections.Concurrent.ConcurrentDictionary<(Footprint, int, int), Partition> _partitionCache = new(); - - public Footprint Footprint; - public int PartitionCount; - public int? PartitionId; - public int[] Assignment; - + private static readonly System.Collections.Concurrent.ConcurrentDictionary<(Footprint, int, int), Partition> PartitionCache = new(); public Partition(Footprint footprint, int partitionCount, int? id = null) { - Footprint = footprint; PartitionCount = partitionCount; PartitionId = id; Assignment = []; + this.Footprint = footprint; + this.PartitionCount = partitionCount; + this.PartitionId = id; + this.Assignment = []; } + public Footprint Footprint { get; set; } + + public int PartitionCount { get; set; } + + public int? PartitionId { get; set; } + + public int[] Assignment { get; set; } public override bool Equals(object? obj) { - if (obj is not Partition other) return false; + if (obj is not Partition other) + { + return false; + } + return PartitionMetric(this, other) == 0; } - public override int GetHashCode() => HashCode.Combine(Footprint, PartitionCount, PartitionId); + public override int GetHashCode() => HashCode.Combine(this.Footprint, this.PartitionCount, this.PartitionId); public static int PartitionMetric(Partition a, Partition b) { @@ -38,21 +45,27 @@ public static int PartitionMetric(Partition a, Partition b) int width = a.Footprint.Width; int height = a.Footprint.Height; - var pairCounts = new List<(int a, int b, int count)>(); - for (int y = 0; y < 4; ++y) for (int x = 0; x < 4; ++x) pairCounts.Add((x, y, 0)); + var pairCounts = new List<(int A, int B, int Count)>(); + for (int y = 0; y < 4; ++y) + { + for (int x = 0; x < 4; ++x) + { + pairCounts.Add((x, y, 0)); + } + } for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { - int idx = y * width + x; + int idx = (y * width) + x; int aVal = a.Assignment[idx]; int bVal = b.Assignment[idx]; - pairCounts[bVal * 4 + aVal] = (aVal, bVal, pairCounts[bVal * 4 + aVal].count + 1); + pairCounts[(bVal * 4) + aVal] = (aVal, bVal, pairCounts[(bVal * 4) + aVal].Count + 1); } } - var sorted = pairCounts.OrderByDescending(p => p.count).ToList(); + var sorted = pairCounts.OrderByDescending(p => p.Count).ToList(); var assigned = new bool[MaxNumSubsets, MaxNumSubsets]; int pixelsMatched = 0; foreach (var pairCount in sorted) @@ -60,24 +73,31 @@ public static int PartitionMetric(Partition a, Partition b) bool isAssigned = false; for (int i = 0; i < MaxNumSubsets; ++i) { - if (assigned[pairCount.a, i] || assigned[i, pairCount.b]) { isAssigned = true; break; } + if (assigned[pairCount.A, i] || assigned[i, pairCount.B]) + { + isAssigned = true; + break; + } } + if (!isAssigned) { - assigned[pairCount.a, pairCount.b] = true; - pixelsMatched += pairCount.count; + assigned[pairCount.A, pairCount.B] = true; + pixelsMatched += pairCount.Count; } } - return width * height - pixelsMatched; + return (width * height) - pixelsMatched; } // Basic GetASTCPartition implementation using selection function from C++ public static Partition GetASTCPartition(Footprint footprint, int partitionCount, int partitionId) { var key = (footprint, partitionCount, partitionId); - if (_partitionCache.TryGetValue(key, out var cached)) + if (PartitionCache.TryGetValue(key, out var cached)) + { return cached; + } var part = new Partition(footprint, partitionCount, partitionId); int w = footprint.Width; @@ -85,10 +105,15 @@ public static Partition GetASTCPartition(Footprint footprint, int partitionCount var assignment = new int[w * h]; int idx = 0; for (int y = 0; y < h; ++y) + { for (int x = 0; x < w; ++x) + { assignment[idx++] = SelectASTCPartition(partitionId, x, y, 0, partitionCount, footprint.PixelCount); + } + } + part.Assignment = assignment; - _partitionCache.TryAdd(key, part); + PartitionCache.TryAdd(key, part); return part; } @@ -103,12 +128,21 @@ public static Partition FindClosestASTCPartition(Partition candidate) return best; } - // Very small port of selection function; behavior taken from C++ file. private static int SelectASTCPartition(int seed, int x, int y, int z, int partitionCount, int pixelCount) { - if (partitionCount <= 1) return 0; - if (pixelCount < 31) { x <<= 1; y <<= 1; z <<= 1; } + if (partitionCount <= 1) + { + return 0; + } + + if (pixelCount < 31) + { + x <<= 1; + y <<= 1; + z <<= 1; + } + seed += (partitionCount - 1) * 1024; uint randomNumber = (uint)seed; randomNumber ^= randomNumber >> 15; @@ -135,31 +169,80 @@ private static int SelectASTCPartition(int seed, int x, int y, int z, int partit uint seed11 = (randomNumber >> 26) & 0xF; uint seed12 = ((randomNumber >> 30) | (randomNumber << 2)) & 0xF; - seed1 *= seed1; seed2 *= seed2; seed3 *= seed3; seed4 *= seed4; - seed5 *= seed5; seed6 *= seed6; seed7 *= seed7; seed8 *= seed8; - seed9 *= seed9; seed10 *= seed10; seed11 *= seed11; seed12 *= seed12; + seed1 *= seed1; + seed2 *= seed2; + seed3 *= seed3; + seed4 *= seed4; + seed5 *= seed5; + seed6 *= seed6; + seed7 *= seed7; + seed8 *= seed8; + seed9 *= seed9; + seed10 *= seed10; + seed11 *= seed11; + seed12 *= seed12; int sh1, sh2, sh3; - if ((seed & 1) != 0) { sh1 = (seed & 2) != 0 ? 4 : 5; sh2 = (partitionCount == 3) ? 6 : 5; } - else { sh1 = (partitionCount == 3) ? 6 : 5; sh2 = (seed & 2) != 0 ? 4 : 5; } - sh3 = (seed & 0x10) != 0 ? sh1 : sh2; + if ((seed & 1) != 0) + { + sh1 = (seed & 2) != 0 ? 4 : 5; + sh2 = (partitionCount == 3) ? 6 : 5; + } + else + { + sh1 = (partitionCount == 3) ? 6 : 5; + sh2 = (seed & 2) != 0 ? 4 : 5; + } - seed1 >>= sh1; seed2 >>= sh2; seed3 >>= sh1; seed4 >>= sh2; - seed5 >>= sh1; seed6 >>= sh2; seed7 >>= sh1; seed8 >>= sh2; - seed9 >>= sh3; seed10 >>= sh3; seed11 >>= sh3; seed12 >>= sh3; + sh3 = (seed & 0x10) != 0 ? sh1 : sh2; - int a = (int)(seed1 * x + seed2 * y + seed11 * z + (randomNumber >> 14)); - int b = (int)(seed3 * x + seed4 * y + seed12 * z + (randomNumber >> 10)); - int c = (int)(seed5 * x + seed6 * y + seed9 * z + (randomNumber >> 6)); - int d = (int)(seed7 * x + seed8 * y + seed10 * z + (randomNumber >> 2)); + seed1 >>= sh1; + seed2 >>= sh2; + seed3 >>= sh1; + seed4 >>= sh2; + seed5 >>= sh1; + seed6 >>= sh2; + seed7 >>= sh1; + seed8 >>= sh2; + seed9 >>= sh3; + seed10 >>= sh3; + seed11 >>= sh3; + seed12 >>= sh3; + + int a = (int)((seed1 * x) + (seed2 * y) + (seed11 * z) + (randomNumber >> 14)); + int b = (int)((seed3 * x) + (seed4 * y) + (seed12 * z) + (randomNumber >> 10)); + int c = (int)((seed5 * x) + (seed6 * y) + (seed9 * z) + (randomNumber >> 6)); + int d = (int)((seed7 * x) + (seed8 * y) + (seed10 * z) + (randomNumber >> 2)); + + a &= 0x3F; + b &= 0x3F; + c &= 0x3F; + d &= 0x3F; + if (partitionCount <= 3) + { + d = 0; + } - a &= 0x3F; b &= 0x3F; c &= 0x3F; d &= 0x3F; - if (partitionCount <= 3) d = 0; - if (partitionCount <= 2) c = 0; + if (partitionCount <= 2) + { + c = 0; + } - if (a >= b && a >= c && a >= d) return 0; - else if (b >= c && b >= d) return 1; - else if (c >= d) return 2; - else return 3; + if (a >= b && a >= c && a >= d) + { + return 0; + } + else if (b >= c && b >= d) + { + return 1; + } + else if (c >= d) + { + return 2; + } + else + { + return 3; + } } } diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs index 0d59b21d..08cd9e81 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs @@ -34,8 +34,8 @@ public static RgbaColor WithBlueContract(this RgbaColor color) /// public static RgbaColor WithInvertedBlueContract(this RgbaColor color) => new( - r: 2 * color.R - color.B, - g: 2 * color.G - color.B, + r: (2 * color.R) - color.B, + g: (2 * color.G) - color.B, b: color.B, a: color.A); @@ -51,4 +51,4 @@ public static RgbaColor AsOffsetFrom(this RgbaColor color, RgbaColor baseColor) return new RgbaColor(offset[0], offset[1], offset[2], offset[3]); } -} \ No newline at end of file +} diff --git a/src/ImageSharp.Textures.Astc/Core/BitOperations.cs b/src/ImageSharp.Textures.Astc/Core/BitOperations.cs index dc5a9db9..f59628cc 100644 --- a/src/ImageSharp.Textures.Astc/Core/BitOperations.cs +++ b/src/ImageSharp.Textures.Astc/Core/BitOperations.cs @@ -11,11 +11,15 @@ internal static class BitOperations public static UInt128 GetBits(UInt128 value, int start, int length) { if (length <= 0) + { return UInt128.Zero; + } var shifted = value >> start; if (length >= 128) + { return shifted; + } if (length >= 64) { @@ -43,7 +47,9 @@ public static UInt128 GetBits(UInt128 value, int start, int length) public static ulong GetBits(ulong value, int start, int length) { if (length <= 0) + { return 0UL; + } int totalBits = sizeof(ulong) * 8; ulong mask = length == totalBits @@ -59,7 +65,7 @@ public static ulong GetBits(ulong value, int start, int length) /// /// The 'bit_transfer_signed' function defined in Section C.2.14 of the ASTC specification /// - public static (int a, int b) TransferPrecision(int a, int b) + public static (int A, int B) TransferPrecision(int a, int b) { b >>= 1; b |= a & 0x80; @@ -67,7 +73,9 @@ public static (int a, int b) TransferPrecision(int a, int b) a &= 0x3F; if ((a & 0x20) != 0) + { a -= 0x40; + } return (a, b); } @@ -77,7 +85,7 @@ public static (int a, int b) TransferPrecision(int a, int b) /// and returns the two values in [0, 255] that will reconstruct |a| and |b| when /// passed to the function. /// - public static (int a, int b) TransferPrecisionInverse(int a, int b) + public static (int A, int B) TransferPrecisionInverse(int a, int b) { ArgumentOutOfRangeException.ThrowIfLessThan(a, -32); ArgumentOutOfRangeException.ThrowIfGreaterThan(a, 31); @@ -85,7 +93,9 @@ public static (int a, int b) TransferPrecisionInverse(int a, int b) ArgumentOutOfRangeException.ThrowIfGreaterThan(b, byte.MaxValue); if (a < 0) + { a += 0x40; + } a <<= 1; a |= b & 0x80; @@ -94,4 +104,4 @@ public static (int a, int b) TransferPrecisionInverse(int a, int b) return (a, b); } -} \ No newline at end of file +} diff --git a/src/ImageSharp.Textures.Astc/Core/DecimationInfo.cs b/src/ImageSharp.Textures.Astc/Core/DecimationInfo.cs new file mode 100644 index 00000000..4449c939 --- /dev/null +++ b/src/ImageSharp.Textures.Astc/Core/DecimationInfo.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Astc.Core; + +/// +/// Pre-computed weight infill data for a specific (footprint, weightGridX, weightGridY) combination. +/// Stores bilinear interpolation indices and factors in a transposed layout. +/// +internal sealed class DecimationInfo +{ + private readonly int texelCount; + + // Transposed layout: [contribution * TexelCount + texel] + // 4 contributions per texel (bilinear interpolation from weight grid). + // For edge texels where some grid points are out of bounds, factor is 0 and index is 0. + private readonly int[] weightIndices; // size: 4 * TexelCount + private readonly int[] weightFactors; // size: 4 * TexelCount + + public DecimationInfo(int texelCount, int[] weightIndices, int[] weightFactors) + { + this.texelCount = texelCount; + this.weightIndices = weightIndices; + this.weightFactors = weightFactors; + } + + public int TexelCount => this.texelCount; + + public int[] WeightIndices => this.weightIndices; + + public int[] WeightFactors => this.weightFactors; +} diff --git a/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs b/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs index 020181d3..55f37183 100644 --- a/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs +++ b/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs @@ -5,28 +5,6 @@ namespace SixLabors.ImageSharp.Textures.Astc.Core; -/// -/// Pre-computed weight infill data for a specific (footprint, weightGridX, weightGridY) combination. -/// Stores bilinear interpolation indices and factors in a transposed layout. -/// -internal sealed class DecimationInfo -{ - public readonly int TexelCount; - - // Transposed layout: [contribution * TexelCount + texel] - // 4 contributions per texel (bilinear interpolation from weight grid). - // For edge texels where some grid points are out of bounds, factor is 0 and index is 0. - public readonly int[] WeightIndices; // size: 4 * TexelCount - public readonly int[] WeightFactors; // size: 4 * TexelCount - - public DecimationInfo(int texelCount, int[] weightIndices, int[] weightFactors) - { - TexelCount = texelCount; - WeightIndices = weightIndices; - WeightFactors = weightFactors; - } -} - /// /// Caches pre-computed DecimationInfo tables and provides weight infill. /// For each unique (footprint, gridX, gridY) combination, the bilinear interpolation @@ -39,18 +17,19 @@ internal static class DecimationTable private const int GridMin = 2; private const int GridRange = 11; // 12 - 2 + 1 private const int FootprintCount = 14; - private static readonly DecimationInfo?[] _table = new DecimationInfo?[FootprintCount * GridRange * GridRange]; + private static readonly DecimationInfo?[] Table = new DecimationInfo?[FootprintCount * GridRange * GridRange]; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static DecimationInfo Get(Footprint footprint, int gridX, int gridY) { - int index = (int)footprint.Type * GridRange * GridRange + (gridX - GridMin) * GridRange + (gridY - GridMin); - var decimationInfo = _table[index]; + int index = ((int)footprint.Type * GridRange * GridRange) + ((gridX - GridMin) * GridRange) + (gridY - GridMin); + var decimationInfo = Table[index]; if (decimationInfo is null) { decimationInfo = Compute(footprint.Width, footprint.Height, gridX, gridY); - _table[index] = decimationInfo; + Table[index] = decimationInfo; } + return decimationInfo; } @@ -70,10 +49,10 @@ public static void InfillWeights(ReadOnlySpan gridWeights, DecimationInfo d for (int i = 0; i < texelCount; i++) { result[i] = (8 - + gridWeights[weightIndices[i]] * weightFactors[i] - + gridWeights[weightIndices[offset1 + i]] * weightFactors[offset1 + i] - + gridWeights[weightIndices[offset2 + i]] * weightFactors[offset2 + i] - + gridWeights[weightIndices[offset3 + i]] * weightFactors[offset3 + i]) >> 4; + + (gridWeights[weightIndices[i]] * weightFactors[i]) + + (gridWeights[weightIndices[offset1 + i]] * weightFactors[offset1 + i]) + + (gridWeights[weightIndices[offset2 + i]] * weightFactors[offset2 + i]) + + (gridWeights[weightIndices[offset3 + i]] * weightFactors[offset3 + i])) >> 4; } } @@ -99,42 +78,61 @@ private static DecimationInfo Compute(int footprintWidth, int footprintHeight, i for (int texelY = 0; texelY < footprintHeight; ++texelY) { int scaledY = scaleVertical * texelY; - int gridY = (scaledY * maxGridY + 32) >> 6; + int gridY = ((scaledY * maxGridY) + 32) >> 6; int gridRowIndex = gridY >> 4; int fractionY = gridY & 0xF; for (int texelX = 0; texelX < footprintWidth; ++texelX) { int scaledX = scaleHorizontal * texelX; - int gridX = (scaledX * maxGridX + 32) >> 6; + int gridX = ((scaledX * maxGridX) + 32) >> 6; int gridColIndex = gridX >> 4; int fractionX = gridX & 0xF; - int gridPoint0 = gridColIndex + gridWidth * gridRowIndex; + int gridPoint0 = gridColIndex + (gridWidth * gridRowIndex); int gridPoint1 = gridPoint0 + 1; - int gridPoint2 = gridColIndex + gridWidth * (gridRowIndex + 1); + int gridPoint2 = gridColIndex + (gridWidth * (gridRowIndex + 1)); int gridPoint3 = gridPoint2 + 1; - int factor3 = (fractionX * fractionY + 8) >> 4; + int factor3 = ((fractionX * fractionY) + 8) >> 4; int factor2 = fractionY - factor3; int factor1 = fractionX - factor3; int factor0 = 16 - fractionX - fractionY + factor3; // For out-of-bounds grid points, zero the factor and use index 0 (safe dummy) - if (gridPoint3 >= gridLimit) { factor3 = 0; gridPoint3 = 0; } - if (gridPoint2 >= gridLimit) { factor2 = 0; gridPoint2 = 0; } - if (gridPoint1 >= gridLimit) { factor1 = 0; gridPoint1 = 0; } - if (gridPoint0 >= gridLimit) { factor0 = 0; gridPoint0 = 0; } - - indices[0 * texelCount + texelIndex] = gridPoint0; - indices[1 * texelCount + texelIndex] = gridPoint1; - indices[2 * texelCount + texelIndex] = gridPoint2; - indices[3 * texelCount + texelIndex] = gridPoint3; - - factors[0 * texelCount + texelIndex] = factor0; - factors[1 * texelCount + texelIndex] = factor1; - factors[2 * texelCount + texelIndex] = factor2; - factors[3 * texelCount + texelIndex] = factor3; + if (gridPoint3 >= gridLimit) + { + factor3 = 0; + gridPoint3 = 0; + } + + if (gridPoint2 >= gridLimit) + { + factor2 = 0; + gridPoint2 = 0; + } + + if (gridPoint1 >= gridLimit) + { + factor1 = 0; + gridPoint1 = 0; + } + + if (gridPoint0 >= gridLimit) + { + factor0 = 0; + gridPoint0 = 0; + } + + indices[(0 * texelCount) + texelIndex] = gridPoint0; + indices[(1 * texelCount) + texelIndex] = gridPoint1; + indices[(2 * texelCount) + texelIndex] = gridPoint2; + indices[(3 * texelCount) + texelIndex] = gridPoint3; + + factors[(0 * texelCount) + texelIndex] = factor0; + factors[(1 * texelCount) + texelIndex] = factor1; + factors[(2 * texelCount) + texelIndex] = factor2; + factors[(3 * texelCount) + texelIndex] = factor3; texelIndex++; } diff --git a/src/ImageSharp.Textures.Astc/Core/Footprint.cs b/src/ImageSharp.Textures.Astc/Core/Footprint.cs index 215f4d4a..3ccd7027 100644 --- a/src/ImageSharp.Textures.Astc/Core/Footprint.cs +++ b/src/ImageSharp.Textures.Astc/Core/Footprint.cs @@ -8,6 +8,17 @@ namespace SixLabors.ImageSharp.Textures.Astc.Core; /// public readonly record struct Footprint { + private Footprint(FootprintType type, int width, int height) + { + ArgumentOutOfRangeException.ThrowIfNegative(width); + ArgumentOutOfRangeException.ThrowIfNegative(height); + + this.Type = type; + this.Width = width; + this.Height = height; + this.PixelCount = width * height; + } + /// Gets the block width in texels. public int Width { get; } @@ -20,20 +31,11 @@ public readonly record struct Footprint /// Gets the total number of texels in the block (Width * Height). public int PixelCount { get; } - private Footprint(FootprintType type, int width, int height) - { - ArgumentOutOfRangeException.ThrowIfNegative(width); - ArgumentOutOfRangeException.ThrowIfNegative(height); - - this.Type = type; - this.Width = width; - this.Height = height; - this.PixelCount = width * height; - } - /// /// Creates a from the specified . /// + /// The footprint type to create a footprint from. + /// A matching the specified type. public static Footprint FromFootprintType(FootprintType type) => type switch { FootprintType.Footprint4x4 => Get4x4(), @@ -54,17 +56,30 @@ private Footprint(FootprintType type, int width, int height) }; internal static Footprint Get4x4() => new(FootprintType.Footprint4x4, 4, 4); + internal static Footprint Get5x4() => new(FootprintType.Footprint5x4, 5, 4); + internal static Footprint Get5x5() => new(FootprintType.Footprint5x5, 5, 5); + internal static Footprint Get6x5() => new(FootprintType.Footprint6x5, 6, 5); + internal static Footprint Get6x6() => new(FootprintType.Footprint6x6, 6, 6); + internal static Footprint Get8x5() => new(FootprintType.Footprint8x5, 8, 5); + internal static Footprint Get8x6() => new(FootprintType.Footprint8x6, 8, 6); + internal static Footprint Get8x8() => new(FootprintType.Footprint8x8, 8, 8); + internal static Footprint Get10x5() => new(FootprintType.Footprint10x5, 10, 5); + internal static Footprint Get10x6() => new(FootprintType.Footprint10x6, 10, 6); + internal static Footprint Get10x8() => new(FootprintType.Footprint10x8, 10, 8); + internal static Footprint Get10x10() => new(FootprintType.Footprint10x10, 10, 10); + internal static Footprint Get12x10() => new(FootprintType.Footprint12x10, 12, 10); + internal static Footprint Get12x12() => new(FootprintType.Footprint12x12, 12, 12); } diff --git a/src/ImageSharp.Textures.Astc/Core/FootprintType.cs b/src/ImageSharp.Textures.Astc/Core/FootprintType.cs index d7ab3204..65d983cb 100644 --- a/src/ImageSharp.Textures.Astc/Core/FootprintType.cs +++ b/src/ImageSharp.Textures.Astc/Core/FootprintType.cs @@ -10,30 +10,43 @@ public enum FootprintType { /// 4x4 texel block. Footprint4x4, + /// 5x4 texel block. Footprint5x4, + /// 5x5 texel block. Footprint5x5, + /// 6x5 texel block. Footprint6x5, + /// 6x6 texel block. Footprint6x6, + /// 8x5 texel block. Footprint8x5, + /// 8x6 texel block. Footprint8x6, + /// 8x8 texel block. Footprint8x8, + /// 10x5 texel block. Footprint10x5, + /// 10x6 texel block. Footprint10x6, + /// 10x8 texel block. Footprint10x8, + /// 10x10 texel block. Footprint10x10, + /// 12x10 texel block. Footprint12x10, + /// 12x12 texel block. Footprint12x12, } diff --git a/src/ImageSharp.Textures.Astc/Core/RgbColor.cs b/src/ImageSharp.Textures.Astc/Core/RgbColor.cs index 17518b28..5756fd26 100644 --- a/src/ImageSharp.Textures.Astc/Core/RgbColor.cs +++ b/src/ImageSharp.Textures.Astc/Core/RgbColor.cs @@ -5,35 +5,36 @@ namespace SixLabors.ImageSharp.Textures.Astc.Core; internal readonly record struct RgbColor(byte R, byte G, byte B) { + public RgbColor(int r, int g, int b) + : this( + (byte)Math.Clamp(r, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(g, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(b, byte.MinValue, byte.MaxValue)) + { + } + public static int BytesPerPixel => 3; public static RgbColor Empty => default; /// - /// The rounded arithmetic mean of the R, G, and B channels + /// Gets the rounded arithmetic mean of the R, G, and B channels. /// public byte Average { get { - var sum = R + G + B; - return (byte)((sum * 256 + 384) / 768); + var sum = this.R + this.G + this.B; + return (byte)(((sum * 256) + 384) / 768); } } - public RgbColor(int r, int g, int b) : this( - (byte)Math.Clamp(r, byte.MinValue, byte.MaxValue), - (byte)Math.Clamp(g, byte.MinValue, byte.MaxValue), - (byte)Math.Clamp(b, byte.MinValue, byte.MaxValue)) - { - } - public int this[int i] => i switch { - 0 => R, - 1 => G, - 2 => B, + 0 => this.R, + 1 => this.G, + 2 => this.B, _ => throw new ArgumentOutOfRangeException(nameof(i), $"Index must be between 0 and {BytesPerPixel - 1}. Actual value: {i}.") }; @@ -45,6 +46,7 @@ public static int SquaredError(RgbColor a, RgbColor b) int diff = a[i] - b[i]; result += diff * diff; } + return result; } @@ -56,6 +58,6 @@ public static int SquaredError(RgbaColor a, RgbaColor b) int dr = a.R - b.R; int dg = a.G - b.G; int db = a.B - b.B; - return dr * dr + dg * dg + db * db; + return (dr * dr) + (dg * dg) + (db * db); } } diff --git a/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs b/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs index e073c55e..b6125911 100644 --- a/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs +++ b/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs @@ -5,37 +5,38 @@ namespace SixLabors.ImageSharp.Textures.Astc.Core; internal readonly record struct RgbaColor(byte R, byte G, byte B, byte A) { + public RgbaColor(int r, int g, int b, int a = byte.MaxValue) + : this( + (byte)Math.Clamp(r, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(g, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(b, byte.MinValue, byte.MaxValue), + (byte)Math.Clamp(a, byte.MinValue, byte.MaxValue)) + { + } + public static int BytesPerPixel => 4; public static RgbaColor Empty => default; /// - /// The rounded arithmetic mean of the R, G, and B channels + /// Gets the rounded arithmetic mean of the R, G, and B channels. /// public byte Average { get { - var sum = R + G + B; - return (byte)((sum * 256 + 384) / 768); + var sum = this.R + this.G + this.B; + return (byte)(((sum * 256) + 384) / 768); } } - public RgbaColor(int r, int g, int b, int a = byte.MaxValue) : this( - (byte)Math.Clamp(r, byte.MinValue, byte.MaxValue), - (byte)Math.Clamp(g, byte.MinValue, byte.MaxValue), - (byte)Math.Clamp(b, byte.MinValue, byte.MaxValue), - (byte)Math.Clamp(a, byte.MinValue, byte.MaxValue)) - { - } - public int this[int i] => i switch { - 0 => R, - 1 => G, - 2 => B, - 3 => A, + 0 => this.R, + 1 => this.G, + 2 => this.B, + 3 => this.A, _ => throw new ArgumentOutOfRangeException(nameof(i), $"Index must be between 0 and {BytesPerPixel - 1}. Actual value: {i}.") }; @@ -47,12 +48,13 @@ public static int SquaredError(RgbaColor a, RgbaColor b) int diff = a[i] - b[i]; result += diff * diff; } + return result; } public bool IsCloseTo(RgbaColor other, int tolerance) - => Math.Abs(R - other.R) <= tolerance && - Math.Abs(G - other.G) <= tolerance && - Math.Abs(B - other.B) <= tolerance && - Math.Abs(A - other.A) <= tolerance; + => Math.Abs(this.R - other.R) <= tolerance && + Math.Abs(this.G - other.G) <= tolerance && + Math.Abs(this.B - other.B) <= tolerance && + Math.Abs(this.A - other.A) <= tolerance; } diff --git a/src/ImageSharp.Textures.Astc/Core/RgbaHdrColor.cs b/src/ImageSharp.Textures.Astc/Core/RgbaHdrColor.cs index 209b205a..f73b810d 100644 --- a/src/ImageSharp.Textures.Astc/Core/RgbaHdrColor.cs +++ b/src/ImageSharp.Textures.Astc/Core/RgbaHdrColor.cs @@ -20,10 +20,10 @@ internal readonly record struct RgbaHdrColor(ushort R, ushort G, ushort B, ushor /// public ushort this[int i] => i switch { - 0 => R, - 1 => G, - 2 => B, - 3 => A, + 0 => this.R, + 1 => this.G, + 2 => this.B, + 3 => this.A, _ => throw new ArgumentOutOfRangeException(nameof(i), $"Index must be between 0 and 3. Actual value: {i}.") }; @@ -41,11 +41,11 @@ public static RgbaHdrColor FromRgba(RgbaColor ldr) /// the standard white point will be clipped. /// public RgbaColor ToLowDynamicRange() - => new((byte)(R >> 8), (byte)(G >> 8), (byte)(B >> 8), (byte)(A >> 8)); + => new((byte)(this.R >> 8), (byte)(this.G >> 8), (byte)(this.B >> 8), (byte)(this.A >> 8)); public bool IsCloseTo(RgbaHdrColor other, int tolerance) - => Math.Abs(R - other.R) <= tolerance && - Math.Abs(G - other.G) <= tolerance && - Math.Abs(B - other.B) <= tolerance && - Math.Abs(A - other.A) <= tolerance; + => Math.Abs(this.R - other.R) <= tolerance && + Math.Abs(this.G - other.G) <= tolerance && + Math.Abs(this.B - other.B) <= tolerance && + Math.Abs(this.A - other.A) <= tolerance; } diff --git a/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs b/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs index 83cc6e22..32a49ce0 100644 --- a/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs +++ b/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs @@ -29,10 +29,10 @@ public static Vector128 Interpolate4ChannelPixels(int p0, int p1, Vector128 // NOTE: Using >> 6 instead of / 64 because Vector128 division // has no hardware support and decomposes to scalar operations. var w64 = Vec64 - weights; - var c = (c0 * w64 + c1 * weights + Vec32) >> 6; + var c = ((c0 * w64) + (c1 * weights) + Vec32) >> 6; // Quantize: (c * 255 + 32767) >> 16, clamped to [0, 255] - var result = (c * Vec255 + Vec32767) >>> 16; + var result = ((c * Vec255) + Vec32767) >>> 16; return Vector128.Min(Vector128.Max(result, Vector128.Zero), Vec255); } @@ -110,13 +110,21 @@ public static void WriteSinglePixelLdrDualPlane( int dpChannel, int dpWeight) { - output[offset + 0] = (byte)InterpolateChannelScalar(lowR, highR, + output[offset + 0] = (byte)InterpolateChannelScalar( + lowR, + highR, dpChannel == 0 ? dpWeight : weight); - output[offset + 1] = (byte)InterpolateChannelScalar(lowG, highG, + output[offset + 1] = (byte)InterpolateChannelScalar( + lowG, + highG, dpChannel == 1 ? dpWeight : weight); - output[offset + 2] = (byte)InterpolateChannelScalar(lowB, highB, + output[offset + 2] = (byte)InterpolateChannelScalar( + lowB, + highB, dpChannel == 2 ? dpWeight : weight); - output[offset + 3] = (byte)InterpolateChannelScalar(lowA, highA, + output[offset + 3] = (byte)InterpolateChannelScalar( + lowA, + highA, dpChannel == 3 ? dpWeight : weight); } @@ -131,16 +139,27 @@ public static RgbaColor InterpolateColorLdr(RgbaColor low, RgbaColor high, int w [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RgbaColor InterpolateColorLdrDualPlane( - RgbaColor low, RgbaColor high, - int weight, int dualPlaneChannel, int dualPlaneWeight) + RgbaColor low, + RgbaColor high, + int weight, + int dualPlaneChannel, + int dualPlaneWeight) => new( - r: InterpolateChannelScalar(low.R, high.R, + r: InterpolateChannelScalar( + low.R, + high.R, dualPlaneChannel == 0 ? dualPlaneWeight : weight), - g: InterpolateChannelScalar(low.G, high.G, + g: InterpolateChannelScalar( + low.G, + high.G, dualPlaneChannel == 1 ? dualPlaneWeight : weight), - b: InterpolateChannelScalar(low.B, high.B, + b: InterpolateChannelScalar( + low.B, + high.B, dualPlaneChannel == 2 ? dualPlaneWeight : weight), - a: InterpolateChannelScalar(low.A, high.A, + a: InterpolateChannelScalar( + low.A, + high.A, dualPlaneChannel == 3 ? dualPlaneWeight : weight)); [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -148,7 +167,7 @@ internal static int InterpolateChannelScalar(int p0, int p1, int weight) { int c0 = (p0 << 8) | p0; int c1 = (p1 << 8) | p1; - int c = (c0 * (64 - weight) + c1 * weight + 32) / 64; + int c = ((c0 * (64 - weight)) + (c1 * weight) + 32) / 64; int quantized = ((c * 255) + 32767) / 65536; return Math.Clamp(quantized, 0, 255); diff --git a/src/ImageSharp.Textures.Astc/Core/UInt128Extensions.cs b/src/ImageSharp.Textures.Astc/Core/UInt128Extensions.cs index 4e6f1083..4df89401 100644 --- a/src/ImageSharp.Textures.Astc/Core/UInt128Extensions.cs +++ b/src/ImageSharp.Textures.Astc/Core/UInt128Extensions.cs @@ -22,8 +22,16 @@ public static ulong High(this UInt128 value) /// public static UInt128 OnesMask(int n) { - if (n <= 0) return UInt128.Zero; - if (n >= 128) return new UInt128(~0UL, ~0UL); + if (n <= 0) + { + return UInt128.Zero; + } + + if (n >= 128) + { + return new UInt128(~0UL, ~0UL); + } + if (n <= 64) { ulong low = (n == 64) @@ -66,4 +74,4 @@ private static ulong ReverseBits(ulong x) return x; } -} \ No newline at end of file +} diff --git a/src/ImageSharp.Textures.Astc/IO/AstcFile.cs b/src/ImageSharp.Textures.Astc/IO/AstcFile.cs index 59754a9a..7cb7cd82 100644 --- a/src/ImageSharp.Textures.Astc/IO/AstcFile.cs +++ b/src/ImageSharp.Textures.Astc/IO/AstcFile.cs @@ -14,22 +14,26 @@ namespace SixLabors.ImageSharp.Textures.Astc.IO; /// internal record AstcFile { - private readonly AstcFileHeader _header; - private readonly byte[] _blocks; - - public ReadOnlySpan Blocks => _blocks; - public Footprint Footprint { get; } - public int Width => _header.ImageWidth; - public int Height => _header.ImageHeight; - public int Depth => _header.ImageDepth; + private readonly AstcFileHeader header; + private readonly byte[] blocks; internal AstcFile(AstcFileHeader header, byte[] blocks) { - _header = header; - _blocks = blocks; - Footprint = GetFootprint(); + this.header = header; + this.blocks = blocks; + this.Footprint = this.GetFootprint(); } + public ReadOnlySpan Blocks => this.blocks; + + public Footprint Footprint { get; } + + public int Width => this.header.ImageWidth; + + public int Height => this.header.ImageHeight; + + public int Depth => this.header.ImageDepth; + public static AstcFile FromMemory(byte[] data) { var header = AstcFileHeader.FromMemory(data.AsSpan(0, AstcFileHeader.SizeInBytes)); @@ -44,7 +48,7 @@ public static AstcFile FromMemory(byte[] data) /// /// Map the block dimensions in the header to a Footprint, if possible. /// - private Footprint GetFootprint() => (_header.BlockWidth, _header.BlockHeight) switch + private Footprint GetFootprint() => (this.header.BlockWidth, this.header.BlockHeight) switch { (4, 4) => Footprint.FromFootprintType(FootprintType.Footprint4x4), (5, 4) => Footprint.FromFootprintType(FootprintType.Footprint5x4), @@ -60,6 +64,6 @@ public static AstcFile FromMemory(byte[] data) (10, 10) => Footprint.FromFootprintType(FootprintType.Footprint10x10), (12, 10) => Footprint.FromFootprintType(FootprintType.Footprint12x10), (12, 12) => Footprint.FromFootprintType(FootprintType.Footprint12x12), - _ => throw new ArgumentOutOfRangeException($"Unsupported block dimensions: {_header.BlockWidth}x{_header.BlockHeight}"), + _ => throw new ArgumentOutOfRangeException($"Unsupported block dimensions: {this.header.BlockWidth}x{this.header.BlockHeight}"), }; } diff --git a/src/ImageSharp.Textures.Astc/IO/AstcFileHeader.cs b/src/ImageSharp.Textures.Astc/IO/AstcFileHeader.cs index 2922203d..497a6ceb 100644 --- a/src/ImageSharp.Textures.Astc/IO/AstcFileHeader.cs +++ b/src/ImageSharp.Textures.Astc/IO/AstcFileHeader.cs @@ -8,9 +8,9 @@ namespace SixLabors.ImageSharp.Textures.Astc.IO; /// diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj b/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj new file mode 100644 index 00000000..e25a6cc0 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj @@ -0,0 +1,45 @@ + + + + net10.0 + + SixLabors.ImageSharp.Textures.Astc.Reference.Tests + SixLabors.ImageSharp.Textures.Astc.Reference.Tests + true + $(NoWarn);CS8002 + + + + + + + + + + + + + + + + + + + <_TestData Include="..\ImageSharp.Textures.Astc.Tests\TestData\**\*.*" /> + + + + + + + + <_AstcEncNativeFiles Include="$(NuGetPackageRoot)astcencodercsharp\5.3.0\runtimes\**\*.*" /> + + + + + diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs new file mode 100644 index 00000000..908d78cd --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs @@ -0,0 +1,246 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.ComponentModel; +using AwesomeAssertions; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Astc.Reference.Tests.Utils; + +namespace SixLabors.ImageSharp.Textures.Astc.Reference.Tests; + +/// +/// HDR comparison tests between SixLabors.ImageSharp.Textures.Astc and the ARM reference ASTC decoder. +/// These validate that SixLabors.ImageSharp.Textures.Astc produces HDR output matching the official ARM implementation. +/// +public class ReferenceDecoderHdrTests +{ + public static TheoryData AllFootprintTypes => + new() + { + FootprintType.Footprint4x4, + FootprintType.Footprint5x4, + FootprintType.Footprint5x5, + FootprintType.Footprint6x5, + FootprintType.Footprint6x6, + FootprintType.Footprint8x5, + FootprintType.Footprint8x6, + FootprintType.Footprint8x8, + FootprintType.Footprint10x5, + FootprintType.Footprint10x6, + FootprintType.Footprint10x8, + FootprintType.Footprint10x10, + FootprintType.Footprint12x10, + FootprintType.Footprint12x12, + }; + + [Theory] + [InlineData("HDR-A-1x1")] + [InlineData("hdr-tile")] + [InlineData("LDR-A-1x1")] + [InlineData("ldr-tile")] + public void DecompressHdr_WithHdrImage_ShouldMatch(string basename) + { + var filePath = Path.Combine("TestData", "HDR", basename + ".astc"); + + var bytes = File.ReadAllBytes(filePath); + var astcFile = AstcFile.FromMemory(bytes); + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); + + var expected = ReferenceDecoder.DecompressHdr( + astcFile.Blocks, astcFile.Width, astcFile.Height, blockX, blockY); + var actual = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + CompareF16(actual, expected, astcFile.Width, astcFile.Height, basename); + } + + [Theory] + [InlineData("atlas_small_4x4")] + [InlineData("atlas_small_5x5")] + [InlineData("atlas_small_6x6")] + [InlineData("atlas_small_8x8")] + public void DecompressHdr_WithLdrImage_ShouldMatch(string basename) + { + var filePath = Path.Combine("TestData", "Input", basename + ".astc"); + var bytes = File.ReadAllBytes(filePath); + var astcFile = AstcFile.FromMemory(bytes); + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); + + var expected = ReferenceDecoder.DecompressHdr( + astcFile.Blocks, astcFile.Width, astcFile.Height, blockX, blockY); + var actual = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + CompareF16(actual, expected, astcFile.Width, astcFile.Height, basename); + } + + [Theory] + [MemberData(nameof(AllFootprintTypes))] + public void DecompressHdr_SolidColor_ShouldMatch(FootprintType footprintType) + { + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + int width = blockX; + int height = blockY; + + // Single block: R=G=B=2.0, A=1.0 (above LDR range) + var pixels = new Half[width * height * 4]; + for (int index = 0; index < width * height; index++) + { + pixels[index * 4 + 0] = (Half)2.0f; + pixels[index * 4 + 1] = (Half)2.0f; + pixels[index * 4 + 2] = (Half)2.0f; + pixels[index * 4 + 3] = (Half)1.0f; + } + + var compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); + var footprint = Footprint.FromFootprintType(footprintType); + + var expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); + var actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); + + CompareF16(actual, expected, width, height, $"BrightSolid_{footprintType}"); + } + + [Theory] + [MemberData(nameof(AllFootprintTypes))] + public void DecompressHdr_Gradient_ShouldMatch(FootprintType footprintType) + { + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + // 2×2 blocks for HDR gradient + int width = blockX * 2; + int height = blockY * 2; + + // Gradient from 0.0 to 4.0 + var pixels = new Half[width * height * 4]; + for (int row = 0; row < height; row++) + { + for (int col = 0; col < width; col++) + { + int idx = (row * width + col) * 4; + float fraction = (float)(row * width + col) / (width * height - 1); + float value = fraction * 4.0f; + pixels[idx + 0] = (Half)value; + pixels[idx + 1] = (Half)value; + pixels[idx + 2] = (Half)value; + pixels[idx + 3] = (Half)1.0f; + } + } + + var compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); + var footprint = Footprint.FromFootprintType(footprintType); + + var expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); + var actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); + + CompareF16(actual, expected, width, height, $"HdrGradient_{footprintType}"); + } + + [Theory] + [MemberData(nameof(AllFootprintTypes))] + [Description("In ASTC, the encoder picks the best endpoint mode per block. A single image can have some blocks" + + " encoded with LDR modes and others with HDR modes, the encoder optimizes each block independently.")] + public void DecompressHdr_MixedLdrHdr_ShouldMatch(FootprintType footprintType) + { + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + // 2×2 blocks + int width = blockX * 2; + int height = blockY * 2; + int halfWidth = width / 2; + + var pixels = new Half[width * height * 4]; + for (int row = 0; row < height; row++) + { + for (int col = 0; col < width; col++) + { + int idx = (row * width + col) * 4; + if (col < halfWidth) + { + // LDR left half: values in 0.0-1.0 + float fraction = (float)row / (height - 1); + pixels[idx + 0] = (Half)(fraction * 0.8f); + pixels[idx + 1] = (Half)(fraction * 0.5f); + pixels[idx + 2] = (Half)(fraction * 0.3f); + } + else + { + // HDR right half: values above 1.0 + float fraction = (float)row / (height - 1); + pixels[idx + 0] = (Half)(1.0f + fraction * 3.0f); + pixels[idx + 1] = (Half)(0.5f + fraction * 2.0f); + pixels[idx + 2] = (Half)(0.2f + fraction * 1.5f); + } + pixels[idx + 3] = (Half)1.0f; + } + } + + var compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); + var footprint = Footprint.FromFootprintType(footprintType); + + var expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); + var actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); + + CompareF16(actual, expected, width, height, $"MixedLdrHdr_{footprintType}"); + } + + /// + /// Compare float output from SixLabors.ImageSharp.Textures.Astc against FP16 output from the ARM reference decoder. + /// SixLabors.ImageSharp.Textures.Astc outputs float values (bit-cast from FP16 for HDR, normalized for LDR). + /// The ARM reference outputs raw FP16 Half values which are converted to float for comparison. + /// + private static void CompareF16(Span actual, Half[] expected, int width, int height, string label) + { + int channelCount = width * height * RgbaColor.BytesPerPixel; + actual.Length.Should().Be(channelCount, because: $"actual float output size should match for {label}"); + expected.Length.Should().Be(channelCount, because: $"expected F16 output size should match for {label}"); + + int mismatches = 0; + float worstRelDiff = 0; + int worstPixel = -1; + int worstChannel = -1; + + for (int index = 0; index < channelCount; index++) + { + float actualValue = actual[index]; + float expectedValue = (float)expected[index]; + + // Both NaN == match; one NaN == mismatch + if (float.IsNaN(actualValue) && float.IsNaN(expectedValue)) + continue; + if (float.IsNaN(actualValue) || float.IsNaN(expectedValue)) + { + mismatches++; + continue; + } + + float absDiff = MathF.Abs(actualValue - expectedValue); + float maxVal = MathF.Max(MathF.Abs(actualValue), MathF.Max(MathF.Abs(expectedValue), 1e-6f)); + float relDiff = absDiff / maxVal; + + // Use a relative tolerance of 0.1% plus absolute tolerance of one FP16 ULP (~0.001 for values near 1.0) + if (absDiff > 0.001f && relDiff > 0.001f) + { + mismatches++; + if (relDiff > worstRelDiff) + { + worstRelDiff = relDiff; + worstPixel = index / 4; + worstChannel = index % 4; + } + } + } + + if (mismatches > 0) + { + string channelName = worstChannel switch { 0 => "R", 1 => "G", 2 => "B", _ => "A" }; + int pixelX = worstPixel % width; + int pixelY = worstPixel / width; + Assert.Fail( + $"[{label}] {mismatches}/{channelCount} F16 channel mismatches. " + + $"Worst: pixel ({pixelX},{pixelY}) channel {channelName}, " + + $"actual={actual[worstPixel * 4 + worstChannel]:G5} vs " + + $"expected={(float)expected[worstPixel * 4 + worstChannel]:G5} " + + $"(relDiff={worstRelDiff:P2})."); + } + } +} diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs new file mode 100644 index 00000000..63eeffe2 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs @@ -0,0 +1,266 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Astc.Reference.Tests.Utils; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Reference.Tests; + +/// +/// LDR comparison tests between SixLabors.ImageSharp.Textures.Astc and the ARM reference ASTC decoder. +/// These validate that SixLabors.ImageSharp.Textures.Astc produces output matching the official ARM implementation. +/// +public class ReferenceDecoderTests +{ + // Per-channel tolerance for RGBA8 comparisons. + // ASTC spec conformance allows ±1 for UNORM8 output due to rounding differences. + private const int Ldr8BitTolerance = 1; + + public static TheoryData AllFootprintTypes => + new() + { + FootprintType.Footprint4x4, + FootprintType.Footprint5x4, + FootprintType.Footprint5x5, + FootprintType.Footprint6x5, + FootprintType.Footprint6x6, + FootprintType.Footprint8x5, + FootprintType.Footprint8x6, + FootprintType.Footprint8x8, + FootprintType.Footprint10x5, + FootprintType.Footprint10x6, + FootprintType.Footprint10x8, + FootprintType.Footprint10x10, + FootprintType.Footprint12x10, + FootprintType.Footprint12x12, + }; + + [Theory] + [InlineData("atlas_small_4x4")] + [InlineData("atlas_small_5x5")] + [InlineData("atlas_small_6x6")] + [InlineData("atlas_small_8x8")] + [InlineData("checkerboard")] + [InlineData("checkered_4")] + [InlineData("checkered_5")] + [InlineData("checkered_6")] + [InlineData("checkered_7")] + [InlineData("checkered_8")] + [InlineData("checkered_9")] + [InlineData("checkered_10")] + [InlineData("checkered_11")] + [InlineData("checkered_12")] + [InlineData("footprint_4x4")] + [InlineData("footprint_5x4")] + [InlineData("footprint_5x5")] + [InlineData("footprint_6x5")] + [InlineData("footprint_6x6")] + [InlineData("footprint_8x5")] + [InlineData("footprint_8x6")] + [InlineData("footprint_8x8")] + [InlineData("footprint_10x5")] + [InlineData("footprint_10x6")] + [InlineData("footprint_10x8")] + [InlineData("footprint_10x10")] + [InlineData("footprint_12x10")] + [InlineData("footprint_12x12")] + [InlineData("rgb_4x4")] + [InlineData("rgb_5x4")] + [InlineData("rgb_6x6")] + [InlineData("rgb_8x8")] + [InlineData("rgb_12x12")] + public void DecompressLdr_WithImage_ShouldMatch(string basename) + { + var filePath = Path.Combine("TestData", "Input", basename + ".astc"); + var bytes = File.ReadAllBytes(filePath); + var astcFile = AstcFile.FromMemory(bytes); + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); + + var expected = ReferenceDecoder.DecompressLdr( + astcFile.Blocks, astcFile.Width, astcFile.Height, blockX, blockY); + var actual = AstcDecoder.DecompressImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + CompareRgba8(actual, expected, astcFile.Width, astcFile.Height, basename); + } + + [Theory] + [MemberData(nameof(AllFootprintTypes))] + public void DecompressLdr_SolidColor_ShouldMatch(FootprintType footprintType) + { + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + int width = blockX; + int height = blockY; + + // Single solid color block + var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; + for (int index = 0; index < width * height; index++) + { + pixels[index * 4 + 0] = 128; // R + pixels[index * 4 + 1] = 64; // G + pixels[index * 4 + 2] = 200; // B + pixels[index * 4 + 3] = 255; // A + } + + var compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); + var footprint = Footprint.FromFootprintType(footprintType); + + var expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); + var actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); + + CompareRgba8(actual, expected, width, height, $"SolidColor_{footprintType}"); + } + + [Theory] + [MemberData(nameof(AllFootprintTypes))] + public void DecompressLdr_Gradient_ShouldMatch(FootprintType footprintType) + { + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + // 2×2 blocks for gradient + int width = blockX * 2; + int height = blockY * 2; + + var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; + for (int row = 0; row < height; row++) + { + for (int col = 0; col < width; col++) + { + int idx = (row * width + col) * 4; + pixels[idx + 0] = (byte)(255 * col / (width - 1)); // R: left-to-right + pixels[idx + 1] = (byte)(255 * row / (height - 1)); // G: top-to-bottom + pixels[idx + 2] = (byte)(255 - 255 * col / (width - 1)); // B: inverse of R + pixels[idx + 3] = 255; + } + } + + var compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); + var footprint = Footprint.FromFootprintType(footprintType); + + var expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); + var actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); + + CompareRgba8(actual, expected, width, height, $"Gradient_{footprintType}"); + } + + [Theory] + [MemberData(nameof(AllFootprintTypes))] + public void DecompressLdr_RandomNoise_ShouldMatch(FootprintType footprintType) + { + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + // 2×2 blocks + int width = blockX * 2; + int height = blockY * 2; + + var rng = new Random(42); // Fixed seed for reproducibility + var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; + rng.NextBytes(pixels); + // Force alpha to 255 so compression doesn't introduce alpha-related variance + for (int index = 3; index < pixels.Length; index += RgbaColor.BytesPerPixel) + pixels[index] = byte.MaxValue; + + var compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); + var footprint = Footprint.FromFootprintType(footprintType); + + var expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); + var actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); + + CompareRgba8(actual, expected, width, height, $"RandomNoise_{footprintType}"); + } + + [Theory] + [MemberData(nameof(AllFootprintTypes))] + public void DecompressLdr_NonBlockAlignedDimensions_ShouldMatch(FootprintType footprintType) + { + var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + + // Non-block-aligned dimensions: use dimensions that don't evenly divide by block size + int width = blockX + blockX / 2 + 1; // e.g. for 4x4: 7, for 8x8: 13 + int height = blockY + blockY / 2 + 1; + + var rng = new Random(123); + var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; + rng.NextBytes(pixels); + for (int index = 3; index < pixels.Length; index += RgbaColor.BytesPerPixel) + pixels[index] = byte.MaxValue; + + var compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); + var footprint = Footprint.FromFootprintType(footprintType); + + var expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); + var actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); + + CompareRgba8(actual, expected, width, height, $"NonAligned_{footprintType}"); + } + + [Fact] + public void DecompressLdr_VoidExtentBlock_ShouldMatch() + { + // Manually construct a void-extent constant-color block (128 bits): + // Bits [0..8] = 0b111111100 (0x1FC, void-extent marker) + // Bit [9] = 0 (LDR mode) + // Bits [10..11] = 0b11 (reserved, must be 11 for valid void-extent) + // Bits [12..63] = all 1s (no extent coordinates = constant color block) + // Bits [64..79] = R (UNORM16) + // Bits [80..95] = G (UNORM16) + // Bits [96..111] = B (UNORM16) + // Bits [112..127]= A (UNORM16) + + var block = new byte[16]; + ulong low = 0xFFFFFFFFFFFFFDFC; + ulong high = ((ulong)0xFFFF << 48) | ((ulong)0xC000 << 32) | ((ulong)0x4000 << 16) | 0x8000; + BitConverter.TryWriteBytes(block.AsSpan(0, 8), low); + BitConverter.TryWriteBytes(block.AsSpan(8, 8), high); + + const int blockX = 4; + const int blockY = 4; + var footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); + + var expected = ReferenceDecoder.DecompressLdr(block, blockX, blockY, blockX, blockY); + var actual = AstcDecoder.DecompressImage(block, blockX, blockY, footprint); + + CompareRgba8(actual, expected, blockX, blockY, "VoidExtent"); + } + + /// + /// Compare RGBA8 output from both decoders with per-channel tolerance. + /// + private static void CompareRgba8(Span actual, byte[] expected, int width, int height, string label) + { + int pixelCount = width * height * RgbaColor.BytesPerPixel; + actual.Length.Should().Be(pixelCount, because: $"actual output size should match for {label}"); + expected.Length.Should().Be(pixelCount, because: $"expected output size should match for {label}"); + + int mismatches = 0; + int worstDiff = 0; + int worstPixel = -1; + int worstChannel = -1; + + for (int index = 0; index < pixelCount; index++) + { + int diff = Math.Abs(actual[index] - expected[index]); + if (diff > Ldr8BitTolerance) + { + mismatches++; + if (diff > worstDiff) + { + worstDiff = diff; + worstPixel = index / RgbaColor.BytesPerPixel; + worstChannel = index % RgbaColor.BytesPerPixel; + } + } + } + + if (mismatches > 0) + { + string channelName = worstChannel switch { 0 => "R", 1 => "G", 2 => "B", _ => "A" }; + int pixelX = worstPixel % width; + int pixelY = worstPixel / width; + Assert.Fail( + $"[{label}] {mismatches} channel mismatches exceed tolerance ±{Ldr8BitTolerance}. " + + $"Worst: pixel ({pixelX},{pixelY}) channel {channelName}, " + + $"actual={actual[worstPixel * 4 + worstChannel]} vs expected={expected[worstPixel * 4 + worstChannel]} (diff={worstDiff})"); + } + } +} diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/Utils/ReferenceDecoder.cs b/tests/ImageSharp.Textures.Astc.Reference.Tests/Utils/ReferenceDecoder.cs new file mode 100644 index 00000000..8214ecc5 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/Utils/ReferenceDecoder.cs @@ -0,0 +1,230 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using AstcEncoder; +using SixLabors.ImageSharp.Textures.Astc.Core; + +namespace SixLabors.ImageSharp.Textures.Astc.Reference.Tests.Utils; + +/// +/// Wrapper around the ARM reference ASTC encoder/decoder (AstcEncoderCSharp package) +/// for use as a comparison baseline in tests. +/// +internal static class ReferenceDecoder +{ + private static readonly AstcencSwizzle IdentitySwizzle = new() + { + r = AstcencSwz.AstcencSwzR, + g = AstcencSwz.AstcencSwzG, + b = AstcencSwz.AstcencSwzB, + a = AstcencSwz.AstcencSwzA, + }; + + /// + /// Decompress ASTC blocks to RGBA8 (LDR) using the ARM reference decoder. + /// + public static byte[] DecompressLdr(ReadOnlySpan blocks, int w, int h, int blockX, int blockY) + { + var error = Astcenc.AstcencConfigInit( + AstcencProfile.AstcencPrfLdr, + (uint)blockX, (uint)blockY, 1, + Astcenc.AstcencPreFastest, + AstcencFlags.DecompressOnly, + out var config); + ThrowOnError(error, "ConfigInit(LDR)"); + + error = Astcenc.AstcencContextAlloc(ref config, 1, out var context); + ThrowOnError(error, "ContextAlloc(LDR)"); + + try + { + int pixelCount = w * h; + var outputBytes = new byte[pixelCount * 4]; // RGBA8 + + var image = new AstcencImage + { + dimX = (uint)w, + dimY = (uint)h, + dimZ = 1, + dataType = AstcencType.AstcencTypeU8, + data = outputBytes, + }; + + // We need a mutable copy of blocks for the Span parameter + var blocksCopy = blocks.ToArray(); + error = Astcenc.AstcencDecompressImage(context, blocksCopy, ref image, IdentitySwizzle, 0); + ThrowOnError(error, "DecompressImage(LDR)"); + + return outputBytes; + } + finally + { + Astcenc.AstcencContextFree(context); + } + } + + /// + /// Decompress ASTC blocks to FP16 RGBA (HDR) using the ARM reference decoder. + /// + public static Half[] DecompressHdr(ReadOnlySpan blocks, int w, int h, int blockX, int blockY) + { + var error = Astcenc.AstcencConfigInit( + AstcencProfile.AstcencPrfHdr, + (uint)blockX, (uint)blockY, 1, + Astcenc.AstcencPreFastest, + AstcencFlags.DecompressOnly, + out var config); + ThrowOnError(error, "ConfigInit(HDR)"); + + error = Astcenc.AstcencContextAlloc(ref config, 1, out var context); + ThrowOnError(error, "ContextAlloc(HDR)"); + + try + { + int pixelCount = w * h; + var outputHalves = new Half[pixelCount * 4]; // RGBA FP16 + var outputBytes = MemoryMarshal.AsBytes(outputHalves.AsSpan()).ToArray(); + + var image = new AstcencImage + { + dimX = (uint)w, + dimY = (uint)h, + dimZ = 1, + dataType = AstcencType.AstcencTypeF16, + data = outputBytes, + }; + + var blocksCopy = blocks.ToArray(); + error = Astcenc.AstcencDecompressImage(context, blocksCopy, ref image, IdentitySwizzle, 0); + ThrowOnError(error, "DecompressImage(HDR)"); + + // Copy the decompressed bytes back into the Half array + MemoryMarshal.AsBytes(outputHalves.AsSpan()).Clear(); + outputBytes.AsSpan().CopyTo(MemoryMarshal.AsBytes(outputHalves.AsSpan())); + + return outputHalves; + } + finally + { + Astcenc.AstcencContextFree(context); + } + } + + /// + /// Compress RGBA8 pixel data to ASTC using the ARM reference encoder (LDR). + /// + public static byte[] CompressLdr(byte[] pixels, int w, int h, int blockX, int blockY) + { + var error = Astcenc.AstcencConfigInit( + AstcencProfile.AstcencPrfLdr, + (uint)blockX, (uint)blockY, 1, + Astcenc.AstcencPreMedium, + 0, + out var config); + ThrowOnError(error, "ConfigInit(CompressLDR)"); + + error = Astcenc.AstcencContextAlloc(ref config, 1, out var context); + ThrowOnError(error, "ContextAlloc(CompressLDR)"); + + try + { + var image = new AstcencImage + { + dimX = (uint)w, + dimY = (uint)h, + dimZ = 1, + dataType = AstcencType.AstcencTypeU8, + data = pixels, + }; + + int blocksWide = (w + blockX - 1) / blockX; + int blocksHigh = (h + blockY - 1) / blockY; + var compressedData = new byte[blocksWide * blocksHigh * 16]; + + error = Astcenc.AstcencCompressImage(context, ref image, IdentitySwizzle, compressedData, 0); + ThrowOnError(error, "CompressImage(LDR)"); + + return compressedData; + } + finally + { + Astcenc.AstcencContextFree(context); + } + } + + /// + /// Compress FP16 RGBA pixel data to ASTC using the ARM reference encoder (HDR). + /// + public static byte[] CompressHdr(Half[] pixels, int w, int h, int blockX, int blockY) + { + var error = Astcenc.AstcencConfigInit( + AstcencProfile.AstcencPrfHdr, + (uint)blockX, (uint)blockY, 1, + Astcenc.AstcencPreMedium, + 0, + out var config); + ThrowOnError(error, "ConfigInit(CompressHDR)"); + + error = Astcenc.AstcencContextAlloc(ref config, 1, out var context); + ThrowOnError(error, "ContextAlloc(CompressHDR)"); + + try + { + var pixelBytes = MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray(); + + var image = new AstcencImage + { + dimX = (uint)w, + dimY = (uint)h, + dimZ = 1, + dataType = AstcencType.AstcencTypeF16, + data = pixelBytes, + }; + + int blocksWide = (w + blockX - 1) / blockX; + int blocksHigh = (h + blockY - 1) / blockY; + var compressedData = new byte[blocksWide * blocksHigh * 16]; + + error = Astcenc.AstcencCompressImage(context, ref image, IdentitySwizzle, compressedData, 0); + ThrowOnError(error, "CompressImage(HDR)"); + + return compressedData; + } + finally + { + Astcenc.AstcencContextFree(context); + } + } + + /// + /// Map a FootprintType to its (blockX, blockY) dimensions. + /// + public static (int blockX, int blockY) ToBlockDimensions(FootprintType footprint) => footprint switch + { + FootprintType.Footprint4x4 => (4, 4), + FootprintType.Footprint5x4 => (5, 4), + FootprintType.Footprint5x5 => (5, 5), + FootprintType.Footprint6x5 => (6, 5), + FootprintType.Footprint6x6 => (6, 6), + FootprintType.Footprint8x5 => (8, 5), + FootprintType.Footprint8x6 => (8, 6), + FootprintType.Footprint8x8 => (8, 8), + FootprintType.Footprint10x5 => (10, 5), + FootprintType.Footprint10x6 => (10, 6), + FootprintType.Footprint10x8 => (10, 8), + FootprintType.Footprint10x10 => (10, 10), + FootprintType.Footprint12x10 => (12, 10), + FootprintType.Footprint12x12 => (12, 12), + _ => throw new ArgumentOutOfRangeException(nameof(footprint)), + }; + + private static void ThrowOnError(AstcencError error, string operation) + { + if (error != AstcencError.AstcencSuccess) + { + var message = Astcenc.GetErrorString(error) ?? error.ToString(); + throw new InvalidOperationException($"ARM ASTC encoder {operation} failed: {message}"); + } + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/BitOperationsTests.cs b/tests/ImageSharp.Textures.Astc.Tests/BitOperationsTests.cs new file mode 100644 index 00000000..eb1ca547 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/BitOperationsTests.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class BitOperationsTests +{ + [Fact] + public void GetBits_UInt128WithLowBits_ShouldExtractCorrectly() + { + UInt128 value = new UInt128(0x1234567890ABCDEF, 0xFEDCBA0987654321); + + var result = BitOperations.GetBits(value, 0, 8); + + result.Low().Should().Be(0x21UL); + } + + [Fact] + public void GetBits_UInt128WithZeroLength_ShouldReturnZero() + { + UInt128 value = new UInt128(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF); + + var result = BitOperations.GetBits(value, 0, 0); + + result.Should().Be(UInt128.Zero); + } + + [Fact] + public void GetBits_ULongWithLowBits_ShouldExtractCorrectly() + { + ulong value = 0xFEDCBA0987654321; + + var result = BitOperations.GetBits(value, 0, 8); + + result.Should().Be(0x21UL); + } + + [Fact] + public void GetBits_ULongWithZeroLength_ShouldReturnZero() + { + ulong value = 0xFFFFFFFFFFFFFFFF; + + var result = BitOperations.GetBits(value, 0, 0); + + result.Should().Be(0UL); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(10, 20)] + [InlineData(128, 255)] + [InlineData(255, 128)] + [InlineData(64, 64)] + public void TransferPrecision_WithSameInput_ShouldBeDeterministic(int inputA, int inputB) + { + var (a1, b1) = BitOperations.TransferPrecision(inputA, inputB); + var (a2, b2) = BitOperations.TransferPrecision(inputA, inputB); + + a1.Should().Be(a2); + b1.Should().Be(b2); + } + + [Fact] + public void TransferPrecision_WithAllValidByteInputs_ShouldNotThrow() + { + for (int a = byte.MinValue; a <= byte.MaxValue; a++) + { + for (int b = byte.MinValue; b <= byte.MaxValue; b++) + { + var action = () => BitOperations.TransferPrecision(a, b); + action.Should().NotThrow(); + } + } + } + + [Theory] + [InlineData(0, 0)] + [InlineData(5, 10)] + [InlineData(10, 255)] + [InlineData(31, 128)] + [InlineData(-32, 200)] + [InlineData(-1, 100)] + public void TransferPrecisionInverse_WithSameInput_ShouldBeDeterministic(int inputA, int inputB) + { + var (a1, b1) = BitOperations.TransferPrecisionInverse(inputA, inputB); + var (a2, b2) = BitOperations.TransferPrecisionInverse(inputA, inputB); + + a1.Should().Be(a2); + b1.Should().Be(b2); + } + + [Theory] + [InlineData(-33, 128)] // a too small + [InlineData(32, 128)] // a too large + [InlineData(0, -1)] // b too small + [InlineData(0, 256)] // b too large + public void TransferPrecisionInverse_WithInvalidInput_ShouldThrowArgumentOutOfRangeException(int a, int b) + { + var action = () => BitOperations.TransferPrecisionInverse(a, b); + + action.Should().Throw(); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(10, 20)] + [InlineData(31, 255)] + [InlineData(-32, 128)] + [InlineData(-1, 200)] + public void TransferPrecision_AfterInverse_ShouldReturnOriginalValues(int originalA, int originalB) + { + var (encodedA, encodedB) = BitOperations.TransferPrecisionInverse(originalA, originalB); + + // Apply regular to decode + var (decodedA, decodedB) = BitOperations.TransferPrecision(encodedA, encodedB); + + decodedA.Should().Be(originalA); + decodedB.Should().Be(originalB); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/BitStreamTests.cs b/tests/ImageSharp.Textures.Astc.Tests/BitStreamTests.cs new file mode 100644 index 00000000..ae5c3108 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/BitStreamTests.cs @@ -0,0 +1,193 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.IO; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class BitStreamTests +{ + [Fact] + public void Constructor_WithBitsAndLength_ShouldInitializeCorrectly() + { + var stream = new BitStream(0b1010101010101010UL, 32); + + stream.Bits.Should().Be(32); + } + + [Fact] + public void Constructor_WithoutParameters_ShouldInitializeEmpty() + { + var stream = new BitStream(); + + stream.Bits.Should().Be(0); + } + + [Fact] + public void TryGetBits_WithSingleBitFromZero_ShouldReturnZero() + { + var stream = new BitStream(0UL, 1); + + var success = stream.TryGetBits(1, out var bits); + + success.Should().BeTrue(); + bits.Should().Be(0U); + } + + [Fact] + public void TryGetBits_StreamEnd_ShouldReturnFalse() + { + var stream = new BitStream(0UL, 1); + stream.TryGetBits(1, out _); + + var success = stream.TryGetBits(1, out var _); + + success.Should().BeFalse(); + } + + [Fact] + public void TryGetBits_WithAlternatingBitPattern_ShouldExtractCorrectly() + { + var stream = new BitStream(0b1010101010101010UL, 32); + + stream.TryGetBits(1, out var bits1).Should().BeTrue(); + bits1.Should().Be(0U); + + stream.TryGetBits(3, out var bits2).Should().BeTrue(); + bits2.Should().Be(0b101U); + + stream.TryGetBits(8, out var bits3).Should().BeTrue(); + bits3.Should().Be(0b10101010U); + + stream.Bits.Should().Be(20); + + stream.TryGetBits(20, out var bits4).Should().BeTrue(); + bits4.Should().Be(0b1010U); + stream.Bits.Should().Be(0); + } + + [Fact] + public void TryGetBits_With64BitsOfOnes_ShouldReturnAllOnes() + { + const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; + var stream = new BitStream(allBits, 64); + + // Check initial state + stream.Bits.Should().Be(64); + + var success = stream.TryGetBits(64, out var bits); + + success.Should().BeTrue(); + bits.Should().Be(allBits); + stream.Bits.Should().Be(0); + } + + [Fact] + public void TryGetBits_With40BitsFromFullBits_ShouldReturnLower40Bits() + { + const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; + const ulong expected40Bits = 0x000000FFFFFFFFFFUL; + var stream = new BitStream(allBits, 64); + + // Check initial state + stream.Bits.Should().Be(64); + + var success = stream.TryGetBits(40, out var bits); + + success.Should().BeTrue(); + bits.Should().Be(expected40Bits); + stream.Bits.Should().Be(24); + } + + [Fact] + public void TryGetBits_WithZeroBits_ShouldReturnZeroAndNotConsume() + { + const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; + const ulong expected40Bits = 0x000000FFFFFFFFFFUL; + var stream = new BitStream(allBits, 32); + + stream.TryGetBits(0, out var bits1).Should().BeTrue(); + bits1.Should().Be(0UL); + + stream.TryGetBits(32, out var bits2).Should().BeTrue(); + bits2.Should().Be(expected40Bits & 0xFFFFFFFFUL); + + stream.TryGetBits(0, out var bits3).Should().BeTrue(); + bits3.Should().Be(0UL); + stream.Bits.Should().Be(0); + } + + [Fact] + public void PutBits_WithSmallValues_ShouldAccumulateCorrectly() + { + var stream = new BitStream(); + + stream.PutBits(0U, 1); + stream.PutBits(0b11U, 2); + + stream.Bits.Should().Be(3); + stream.TryGetBits(3, out var bits).Should().BeTrue(); + bits.Should().Be(0b110U); + } + + [Fact] + public void PutBits_With64BitsOfOnes_ShouldStoreCorrectly() + { + const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; + var stream = new BitStream(); + + stream.PutBits(allBits, 64); + + stream.Bits.Should().Be(64); + stream.TryGetBits(64, out var bits).Should().BeTrue(); + bits.Should().Be(allBits); + stream.Bits.Should().Be(0); + } + + [Fact] + public void PutBits_With40BitsOfOnes_ShouldMaskTo40Bits() + { + const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; + const ulong expected40Bits = 0x000000FFFFFFFFFFUL; + var stream = new BitStream(); + + stream.PutBits(allBits, 40); + + stream.TryGetBits(40, out var bits).Should().BeTrue(); + bits.Should().Be(expected40Bits); + stream.Bits.Should().Be(0); + } + + [Fact] + public void PutBits_WithZeroBitsInterspersed_ShouldReturnValue() + { + const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; + const ulong expected40Bits = 0x000000FFFFFFFFFFUL; + var stream = new BitStream(); + + stream.PutBits(0U, 0); + stream.PutBits((uint)(allBits & 0xFFFFFFFFUL), 32); + stream.PutBits(0U, 0); + + stream.TryGetBits(32, out var bits).Should().BeTrue(); + bits.Should().Be(expected40Bits & 0xFFFFFFFFUL); + stream.Bits.Should().Be(0); + } + + [Fact] + public void PutBits_ThenGetBits_ShouldReturnValue() + { + var stream = new BitStream(); + const uint value1 = 0b101; + const uint value2 = 0b11001100; + + stream.PutBits(value1, 3); + stream.PutBits(value2, 8); + + stream.TryGetBits(3, out var retrieved1).Should().BeTrue(); + retrieved1.Should().Be(value1); + stream.TryGetBits(8, out var retrieved2).Should().BeTrue(); + retrieved2.Should().Be(value2); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/CodecTests.cs b/tests/ImageSharp.Textures.Astc.Tests/CodecTests.cs new file mode 100644 index 00000000..0bbd64d1 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/CodecTests.cs @@ -0,0 +1,132 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Astc.Tests.Utils; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class CodecTests +{ + [Fact] + public void ASTCDecompressToRGBA_WithZeroWidth_ShouldReturnEmpty() + { + var data = new byte[256]; + const int height = 16; + + var result = AstcDecoder.DecompressImage(data, 0, height, FootprintType.Footprint4x4); + + result.ToArray().Should().BeEmpty(); + } + + [Fact] + public void ASTCDecompressToRGBA_WithZeroHeight_ShouldReturnEmpty() + { + var data = new byte[256]; + const int width = 16; + + var result = AstcDecoder.DecompressImage(data, width, 0, FootprintType.Footprint4x4); + + result.ToArray().Should().BeEmpty(); + } + + [Fact] + public void ASTCDecompressToRGBA_WithDataSizeNotMultipleOfBlockSize_ShouldReturnEmpty() + { + var data = new byte[256]; + const int width = 16; + const int height = 16; + var invalidData = data.AsSpan(0, data.Length - 1).ToArray(); + + var result = AstcDecoder.DecompressImage(invalidData, width, height, FootprintType.Footprint4x4); + + result.ToArray().Should().BeEmpty(); + } + + [Fact] + public void ASTCDecompressToRGBA_WithMismatchedBlockCount_ShouldReturnEmpty() + { + var data = new byte[256]; + const int width = 16; + const int height = 16; + var mismatchedData = data.AsSpan(0, data.Length - PhysicalBlock.SizeInBytes).ToArray(); + + var result = AstcDecoder.DecompressImage(mismatchedData, width, height, FootprintType.Footprint4x4); + + result.ToArray().Should().BeEmpty(); + } + + [Theory] + [InlineData("atlas_small_4x4", FootprintType.Footprint4x4, 256, 256)] + [InlineData("atlas_small_5x5", FootprintType.Footprint5x5, 256, 256)] + [InlineData("atlas_small_6x6", FootprintType.Footprint6x6, 256, 256)] + [InlineData("atlas_small_8x8", FootprintType.Footprint8x8, 256, 256)] + public void ASTCDecompressToRGBA_WithValidData_ShouldMatchExpected( + string imageName, + FootprintType footprintType, + int width, + int height) + { + var astcData = FileBasedHelpers.LoadASTCFile(imageName); + var footprint = Footprint.FromFootprintType(footprintType); + int blockWidth = footprint.Width; + int blockHeight = footprint.Height; + int blocksWide = (width + blockWidth - 1) / blockWidth; + int blocksHigh = (height + blockHeight - 1) / blockHeight; + int expectedBlockCount = blocksWide * blocksHigh; + + // Check ASTC data structure + (astcData.Length % PhysicalBlock.SizeInBytes).Should().Be(0, "astc byte length must be multiple of block size"); + (astcData.Length / PhysicalBlock.SizeInBytes).Should().Be(expectedBlockCount, $"ASTC block count should match expected"); + + // Verify all blocks can be unpacked + for (int i = 0; i < astcData.Length; i += PhysicalBlock.SizeInBytes) + { + var block = astcData.AsSpan(i, PhysicalBlock.SizeInBytes).ToArray(); + var bits = new UInt128(BitConverter.ToUInt64(block, 8), BitConverter.ToUInt64(block, 0)); + var info = BlockInfo.Decode(bits); + var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, bits, in info); + + logicalBlock.Should().NotBeNull("all blocks should unpack successfully"); + } + + var decodedPixels = AstcDecoder.DecompressImage(astcData, width, height, footprintType); + var actualImage = new ImageBuffer(decodedPixels.ToArray(), width, height, 4); + + var expectedImagePath = Path.Combine("TestData", "Expected", imageName + ".bmp"); + var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedImagePath); + ImageUtils.CompareSumOfSquaredDifferences(expectedImage, actualImage, 0.1); + } + + [Theory] + [InlineData("atlas_small_4x4", FootprintType.Footprint4x4, 256, 256)] + [InlineData("atlas_small_5x5", FootprintType.Footprint5x5, 256, 256)] + [InlineData("atlas_small_6x6", FootprintType.Footprint6x6, 256, 256)] + [InlineData("atlas_small_8x8", FootprintType.Footprint8x8, 256, 256)] + public void DecompressToImage_WithAstcFile_ShouldMatchExpected( + string imageName, + FootprintType footprint, + int width, + int height) + { + var astcPath = Path.Combine("TestData", "Input", imageName + ".astc"); + var astcBytes = File.ReadAllBytes(astcPath); + var file = AstcFile.FromMemory(astcBytes); + + // Check file header + file.Footprint.Type.Should().Be(footprint); + file.Width.Should().Be(width); + file.Height.Should().Be(height); + + var decodedPixels = AstcDecoder.DecompressImage(file); + var actualImage = new ImageBuffer(decodedPixels.ToArray(), width, height, 4); + + var expectedImagePath = Path.Combine("TestData", "Expected", imageName + ".bmp"); + var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedImagePath); + ImageUtils.CompareSumOfSquaredDifferences(expectedImage, actualImage, 0.1); + } + +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/EndpointCodecTests.cs b/tests/ImageSharp.Textures.Astc.Tests/EndpointCodecTests.cs new file mode 100644 index 00000000..e598d554 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/EndpointCodecTests.cs @@ -0,0 +1,366 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class EndpointCodecTests +{ + [Theory] + [InlineData(EndpointEncodingMode.DirectLuma)] + [InlineData(EndpointEncodingMode.DirectLumaAlpha)] + [InlineData(EndpointEncodingMode.BaseScaleRgb)] + [InlineData(EndpointEncodingMode.BaseScaleRgba)] + [InlineData(EndpointEncodingMode.DirectRbg)] + [InlineData(EndpointEncodingMode.DirectRgba)] + internal void EncodeColorsForMode_WithVariousRanges_ShouldProduceValidQuantizedValues(EndpointEncodingMode mode) + { + var low = new RgbaColor(0, 0, 0, 0); + var high = new RgbaColor(255, 255, 255, 255); + + for (int quantRange = 5; quantRange < 256; quantRange++) + { + var values = new List(); + EndpointEncoder.EncodeColorsForMode(low, high, quantRange, mode, out var _, values); + + // Assert value count matches expected + values.Should().HaveCount(mode.GetValuesCount()); + + // Assert all values are within quantization range + values.Should().AllSatisfy(v => v.Should().BeInRange(0, quantRange)); + } + } + + [Theory] + [InlineData(EndpointEncodingMode.DirectLuma)] + [InlineData(EndpointEncodingMode.DirectLumaAlpha)] + [InlineData(EndpointEncodingMode.BaseScaleRgb)] + [InlineData(EndpointEncodingMode.BaseScaleRgba)] + [InlineData(EndpointEncodingMode.DirectRbg)] + [InlineData(EndpointEncodingMode.DirectRgba)] + internal void EncodeDecodeColors_WithBlackAndWhite_ShouldPreserveColors(EndpointEncodingMode mode) + { + var white = new RgbaColor(255, 255, 255, 255); + var black = new RgbaColor(0, 0, 0, 255); + + for (int quantRange = 5; quantRange < 256; ++quantRange) + { + var (low, high) = EncodeAndDecodeColors(white, black, quantRange, mode); + + (low == white).Should().BeTrue(); + (high == black).Should().BeTrue(); + } + } + + [Fact] + public void UsesBlueContract_WithDirectModes_ShouldDetectCorrectly() + { + var values = new List { 132, 127, 116, 112, 183, 180, 31, 22 }; + + EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbDirect, values).Should().BeTrue(); + EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbaDirect, values).Should().BeTrue(); + } + + [Fact] + public void UsesBlueContract_WithOffsetModes_ShouldDetectBasedOnBitFlags() + { + var baseValues = new List { 132, 127, 116, 112, 183, 180, 31, 22 }; + + var valuesClearedBit6 = new List(baseValues); + valuesClearedBit6[1] &= 0xBF; + valuesClearedBit6[3] &= 0xBF; + valuesClearedBit6[5] &= 0xBF; + valuesClearedBit6[7] &= 0xBF; + + EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbBaseOffset, valuesClearedBit6).Should().BeFalse(); + EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbaBaseOffset, valuesClearedBit6).Should().BeFalse(); + + var valuesSetBit6 = new List(baseValues); + valuesSetBit6[1] |= 0x40; + valuesSetBit6[3] |= 0x40; + valuesSetBit6[5] |= 0x40; + valuesSetBit6[7] |= 0x40; + + EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbBaseOffset, valuesSetBit6).Should().BeTrue(); + EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbaBaseOffset, valuesSetBit6).Should().BeTrue(); + } + + [Fact] + public void EncodeColorsForMode_WithRgbDirectAndSpecificPairs_ShouldUseBlueContract() + { + var pairs = new[] + { + (new RgbaColor(22, 18, 30, 59), new RgbaColor(162, 148, 155, 59)), + (new RgbaColor(22, 30, 27, 36), new RgbaColor(228, 221, 207, 36)), + (new RgbaColor(54, 60, 55, 255), new RgbaColor(23, 30, 27, 255)) + }; + + const int endpointRange = 31; + + foreach (var (low, high) in pairs) + { + var values = new List(); + EndpointEncoder.EncodeColorsForMode(low, high, endpointRange, EndpointEncodingMode.DirectRbg, out var astcMode, values); + + EndpointEncoder.UsesBlueContract(endpointRange, astcMode, values).Should().BeTrue(); + } + } + + [Fact] + public void EncodeDecodeColors_WithLumaDirect_ShouldProduceLumaValues() + { + var mode = EndpointEncodingMode.DirectLuma; + + var result1 = EncodeAndDecodeColors( + new RgbaColor(247, 248, 246, 255), + new RgbaColor(2, 3, 1, 255), + 255, mode); + + (result1.Low == new RgbaColor(247, 247, 247, 255)).Should().BeTrue(); + (result1.High == new RgbaColor(2, 2, 2, 255)).Should().BeTrue(); + + var result2 = EncodeAndDecodeColors( + new RgbaColor(80, 80, 50, 255), + new RgbaColor(99, 255, 6, 255), + 255, mode); + + (result2.Low == new RgbaColor(70, 70, 70, 255)).Should().BeTrue(); + (result2.High == new RgbaColor(120, 120, 120, 255)).Should().BeTrue(); + + var result3 = EncodeAndDecodeColors( + new RgbaColor(247, 248, 246, 255), + new RgbaColor(2, 3, 1, 255), + 15, mode); + + (result3.Low == new RgbaColor(255, 255, 255, 255)).Should().BeTrue(); + (result3.High == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); + + var result4 = EncodeAndDecodeColors( + new RgbaColor(64, 127, 192, 255), + new RgbaColor(0, 0, 0, 255), + 63, mode); + + (result4.Low == new RgbaColor(130, 130, 130, 255)).Should().BeTrue(); + (result4.High == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); + } + + [Fact] + public void EncodeDecodeColors_WithLumaAlphaDirect_ShouldPreserveLumaAndAlpha() + { + var mode = EndpointEncodingMode.DirectLumaAlpha; + + // Grey with varying alpha + var result1 = EncodeAndDecodeColors( + new RgbaColor(64, 127, 192, 127), + new RgbaColor(0, 0, 0, 20), + 63, mode); + + ((result1.Low == new RgbaColor(130, 130, 130, 125)) || + result1.Low.IsCloseTo(new RgbaColor(130, 130, 130, 125), 1)).Should().BeTrue(); + ((result1.High == new RgbaColor(0, 0, 0, 20)) || + result1.High.IsCloseTo(new RgbaColor(0, 0, 0, 20), 1)).Should().BeTrue(); + + // Different alpha values + var result2 = EncodeAndDecodeColors( + new RgbaColor(247, 248, 246, 250), + new RgbaColor(2, 3, 1, 172), + 255, mode); + + (result2.Low == new RgbaColor(247, 247, 247, 250)).Should().BeTrue(); + (result2.High == new RgbaColor(2, 2, 2, 172)).Should().BeTrue(); + } + + [Fact] + public void EncodeDecodeColors_WithRgbDirectAndRandomColors_ShouldPreserveColors() + { + var mode = EndpointEncodingMode.DirectRbg; + var random = new Random(unchecked((int)0xdeadbeef)); + + for (int i = 0; i < 100; ++i) + { + var low = new RgbaColor(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); + var high = new RgbaColor(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); + var (Low, High) = EncodeAndDecodeColors(low, high, 255, mode); + + (Low == low).Should().BeTrue(); + (High == high).Should().BeTrue(); + } + } + + [Fact] + public void EncodeDecodeColors_WithRgbDirectAndSpecificColors_ShouldMatchExpected() + { + var mode = EndpointEncodingMode.DirectRbg; + + var result1 = EncodeAndDecodeColors( + new RgbaColor(64, 127, 192, 255), + new RgbaColor(0, 0, 0, 255), + 63, mode); + + (result1.Low == new RgbaColor(65, 125, 190, 255)).Should().BeTrue(); + (result1.High == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); + + var result2 = EncodeAndDecodeColors( + new RgbaColor(0, 0, 0, 255), + new RgbaColor(64, 127, 192, 255), + 63, mode); + + (result2.Low == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); + (result2.High == new RgbaColor(65, 125, 190, 255)).Should().BeTrue(); + } + + [Fact] + public void EncodeDecodeColors_WithRgbBaseScaleAndIdenticalColors_ShouldBeCloseToOriginal() + { + var mode = EndpointEncodingMode.BaseScaleRgb; + var random = new Random(unchecked((int)0xdeadbeef)); + + for (int i = 0; i < 100; ++i) + { + var color = new RgbaColor(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); + var result = EncodeAndDecodeColors(color, color, 255, mode); + + result.Low.IsCloseTo(color, 1).Should().BeTrue(); + result.High.IsCloseTo(color, 1).Should().BeTrue(); + } + } + + [Fact] + public void EncodeDecodeColors_WithRgbBaseScaleAndDifferentColors_ShouldMatchExpected() + { + var mode = EndpointEncodingMode.BaseScaleRgb; + var low = new RgbaColor(20, 4, 40, 255); + var high = new RgbaColor(80, 16, 160, 255); + + var result1 = EncodeAndDecodeColors(low, high, 255, mode); + result1.Low.IsCloseTo(low, 0).Should().BeTrue(); + result1.High.IsCloseTo(high, 0).Should().BeTrue(); + + var result2 = EncodeAndDecodeColors(low, high, 127, mode); + result2.Low.IsCloseTo(low, 1).Should().BeTrue(); + result2.High.IsCloseTo(high, 1).Should().BeTrue(); + } + + public static IEnumerable RgbBaseOffsetColorPairs() + { + yield return new object[] { new RgbaColor(80, 16, 112, 255), new RgbaColor(87, 18, 132, 255) }; + yield return new object[] { new RgbaColor(80, 74, 82, 255), new RgbaColor(90, 92, 110, 255) }; + yield return new object[] { new RgbaColor(0, 0, 0, 255), new RgbaColor(2, 2, 2, 255) }; + } + + [Theory] + [MemberData(nameof(RgbBaseOffsetColorPairs))] + internal void DecodeColorsForMode_WithRgbBaseOffset_AndSpecificColorPairs_ShouldDecodeCorrectly( + RgbaColor expectedLow, RgbaColor expectedHigh) + { + var values = EncodeRgbBaseOffset(expectedLow, expectedHigh); + var (decLow, decHigh) = EndpointCodec.DecodeColorsForMode(values, 255, ColorEndpointMode.LdrRgbBaseOffset); + + (decLow == expectedLow).Should().BeTrue(); + (decHigh == expectedHigh).Should().BeTrue(); + } + + [Fact] + public void DecodeColorsForMode_WithRgbBaseOffset_AndIdenticalColors_ShouldDecodeCorrectly() + { + var random = new Random(unchecked((int)0xdeadbeef)); + + for (int i = 0; i < 100; ++i) + { + int r = random.Next(0, 256); + int g = random.Next(0, 256); + int b = random.Next(0, 256); + + // Ensure even channels (reference test skips odd) + if (((r | g | b) & 1) != 0) continue; + + var color = new RgbaColor(r, g, b, 255); + var values = EncodeRgbBaseOffset(color, color); + var (decLow, decHigh) = EndpointCodec.DecodeColorsForMode(values, 255, ColorEndpointMode.LdrRgbBaseOffset); + + (decLow == color).Should().BeTrue(); + (decHigh == color).Should().BeTrue(); + } + } + + private static int[] EncodeRgbBaseOffset(RgbaColor low, RgbaColor high) + { + var values = new List(); + for (int i = 0; i < 3; ++i) + { + bool isLarge = low[i] >= 128; + values.Add((low[i] * 2) & 0xFF); + int diff = (high[i] - low[i]) * 2; + if (isLarge) diff |= 0x80; + values.Add(diff); + } + return values.ToArray(); + } + + [Fact] + public void DecodeCheckerboard_ShouldDecodeToGrayscaleEndpoints() + { + string astcFilePath = Path.Combine("TestData", "Input", "checkerboard.astc"); + byte[] astcData = File.ReadAllBytes(astcFilePath); + + int blocksDecoded = 0; + + for (int i = 0; i < astcData.Length; i += PhysicalBlock.SizeInBytes) + { + // Read block bytes + UInt128 blockData = BinaryPrimitives.ReadUInt128LittleEndian(astcData.AsSpan(i, PhysicalBlock.SizeInBytes)); + var physicalBlock = PhysicalBlock.Create(blockData); + + // Unpack to intermediate block + var intermediateBlock = IntermediateBlock.UnpackIntermediateBlock(physicalBlock); + intermediateBlock.Should().NotBeNull("checkerboard blocks should not be void extent"); + var ib = intermediateBlock!.Value; + + // Verify endpoints exist + ib.EndpointCount.Should().BeGreaterThan(0, "block should have endpoints"); + + int colorRange = IntermediateBlock.EndpointRangeForBlock(ib); + colorRange.Should().BeGreaterThan(0, "color range should be valid"); + + // Check all endpoint pairs decode successfully to grayscale colors + for (int ep = 0; ep < ib.EndpointCount; ep++) + { + var endpoints = ib.Endpoints[ep]; + ReadOnlySpan colorSpan = ((ReadOnlySpan)endpoints.Colors)[..endpoints.ColorCount]; + var (low, high) = EndpointCodec.DecodeColorsForMode( + colorSpan, + colorRange, + endpoints.Mode); + + // Assert - Checkerboard should produce grayscale colors (R == G == B) + low.R.Should().Be(low.G, $"block {i} low endpoint should be grayscale"); + low.G.Should().Be(low.B, $"block {i} low endpoint should be grayscale"); + high.R.Should().Be(high.G, $"block {i} high endpoint should be grayscale"); + high.G.Should().Be(high.B, $"block {i} high endpoint should be grayscale"); + } + + blocksDecoded++; + } + + // Verify we decoded a reasonable number of blocks + blocksDecoded.Should().BeGreaterThan(0, "should have decoded at least one block"); + } + + private static (RgbaColor Low, RgbaColor High) EncodeAndDecodeColors( + RgbaColor low, + RgbaColor high, + int quantRange, + EndpointEncodingMode mode) + { + var values = new List(); + var needsSwap = EndpointEncoder.EncodeColorsForMode(low, high, quantRange, mode, out var astcMode, values); + var (decLow, decHigh) = EndpointCodec.DecodeColorsForMode(values.ToArray(), quantRange, astcMode); + + return needsSwap ? (decHigh, decLow) : (decLow, decHigh); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/FootprintTests.cs b/tests/ImageSharp.Textures.Astc.Tests/FootprintTests.cs new file mode 100644 index 00000000..05e2a053 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/FootprintTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class FootprintTests +{ + [Theory] + [InlineData(FootprintType.Footprint4x4, 4, 4)] + [InlineData(FootprintType.Footprint5x4, 5, 4)] + [InlineData(FootprintType.Footprint5x5, 5, 5)] + [InlineData(FootprintType.Footprint6x5, 6, 5)] + [InlineData(FootprintType.Footprint6x6, 6, 6)] + [InlineData(FootprintType.Footprint8x5, 8, 5)] + [InlineData(FootprintType.Footprint8x6, 8, 6)] + [InlineData(FootprintType.Footprint8x8, 8, 8)] + [InlineData(FootprintType.Footprint10x5, 10, 5)] + [InlineData(FootprintType.Footprint10x6, 10, 6)] + [InlineData(FootprintType.Footprint10x8, 10, 8)] + [InlineData(FootprintType.Footprint10x10, 10, 10)] + [InlineData(FootprintType.Footprint12x10, 12, 10)] + [InlineData(FootprintType.Footprint12x12, 12, 12)] + public void FromFootprintType_WithValidType_ShouldReturnCorrectDimensions( + FootprintType type, int expectedWidth, int expectedHeight) + { + var footprint = Footprint.FromFootprintType(type); + + footprint.Type.Should().Be(type); + footprint.Width.Should().Be(expectedWidth); + footprint.Height.Should().Be(expectedHeight); + footprint.PixelCount.Should().Be(expectedWidth * expectedHeight); + } + + [Fact] + public void FromFootprintType_WithAllValidTypes_ShouldReturnUniqueFootprints() + { + var allTypes = new[] + { + FootprintType.Footprint4x4, FootprintType.Footprint5x4, FootprintType.Footprint5x5, + FootprintType.Footprint6x5, FootprintType.Footprint6x6, FootprintType.Footprint8x5, + FootprintType.Footprint8x6, FootprintType.Footprint8x8, FootprintType.Footprint10x5, + FootprintType.Footprint10x6, FootprintType.Footprint10x8, FootprintType.Footprint10x10, + FootprintType.Footprint12x10, FootprintType.Footprint12x12 + }; + + var footprints = allTypes.Select(Footprint.FromFootprintType).ToList(); + + footprints.Should().HaveCount(allTypes.Length); + footprints.Should().OnlyHaveUniqueItems(); + } + + [Fact] + public void Footprint_PixelCount_ShouldEqualWidthTimesHeight() + { + var footprint = Footprint.FromFootprintType(FootprintType.Footprint10x8); + + footprint.PixelCount.Should().Be(footprint.Width * footprint.Height); + footprint.PixelCount.Should().Be(80); + } + + [Fact] + public void Footprint_ValueEquality_WithSameType_ShouldBeEqual() + { + var footprint1 = Footprint.FromFootprintType(FootprintType.Footprint6x6); + var footprint2 = Footprint.FromFootprintType(FootprintType.Footprint6x6); + + footprint1.Should().Be(footprint2); + (footprint1 == footprint2).Should().BeTrue(); + } + + [Fact] + public void Footprint_ValueEquality_WithDifferentType_ShouldNotBeEqual() + { + var footprint1 = Footprint.FromFootprintType(FootprintType.Footprint6x6); + var footprint2 = Footprint.FromFootprintType(FootprintType.Footprint8x8); + + footprint1.Should().NotBe(footprint2); + (footprint1 != footprint2).Should().BeTrue(); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrComparisonTests.cs b/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrComparisonTests.cs new file mode 100644 index 00000000..1e4fa827 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrComparisonTests.cs @@ -0,0 +1,210 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests.HDR; + +/// +/// Comparing HDR and LDR ASTC decoding behavior using real reference files. +/// +public class HdrComparisonTests +{ + [Fact] + public void HdrFile_DecodedWithHdrApi_ShouldPreserveExtendedRange() + { + // HDR files should decode to values potentially exceeding 1.0 + var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // Decode with HDR API + var hdrResult = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + // Verify we get Float16 output + hdrResult.Length.Should().Be(4); // 1 pixel, 4 channels + + // HDR content can have values > 1.0 (this file may or may not, but should allow it) + foreach (var value in hdrResult) + { + float.IsNaN(value).Should().BeFalse(); + float.IsInfinity(value).Should().BeFalse(); + value.Should().BeGreaterThanOrEqualTo(0.0f); + } + } + + [Fact] + public void LdrFile_DecodedWithHdrApi_ShouldUpscaleToHdrRange() + { + // LDR files decoded with HDR API should produce values in 0.0-1.0 range + var astcPath = Path.Combine("TestData", "HDR", "LDR-A-1x1.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // Decode with HDR API + var hdrResult = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + hdrResult.Length.Should().Be(4); + + // LDR content should map to 0.0-1.0 range when decoded with HDR API + foreach (var value in hdrResult) + { + value.Should().BeGreaterThanOrEqualTo(0.0f); + value.Should().BeLessThanOrEqualTo(1.0f); + } + } + + [Fact] + public void HdrFile_DecodedWithLdrApi_ShouldClampToByteRange() + { + // HDR files decoded with LDR API should clamp to 0-255 + var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // Decode with LDR API + var ldrResult = AstcDecoder.DecompressImage(astcFile); + + ldrResult.Length.Should().Be(4); + + // All values must be in LDR range + foreach (var value in ldrResult) + { + value.Should().BeGreaterThanOrEqualTo((byte)0); + value.Should().BeLessThanOrEqualTo((byte)255); + } + } + + [Fact] + public void LdrFile_DecodedWithBothApis_ShouldProduceConsistentValues() + { + // LDR content should produce equivalent results with both APIs + var astcPath = Path.Combine("TestData", "HDR", "LDR-A-1x1.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // Decode with both APIs + var ldrResult = AstcDecoder.DecompressImage(astcFile); + var hdrResult = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + // Compare results - LDR byte should map to HDR float / 255.0 + for (int i = 0; i < 4; i++) + { + byte ldrValue = ldrResult[i]; + float hdrValue = hdrResult[i]; + + float expectedHdr = ldrValue / 255.0f; + + Math.Abs(hdrValue - expectedHdr).Should().BeLessThan(0.01f); + } + } + + [Fact] + public void HdrTile_ShouldDecodeSuccessfully() + { + // Test larger HDR tile decoding + var astcPath = Path.Combine("TestData", "HDR", "hdr-tile.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + var hdrResult = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + // Should produce Width * Height * 4 values + hdrResult.Length.Should().Be(astcFile.Width * astcFile.Height * 4); + + foreach (var value in hdrResult) + { + float.IsNaN(value).Should().BeFalse(); + float.IsInfinity(value).Should().BeFalse(); + } + } + + [Fact] + public void LdrTile_ShouldDecodeSuccessfully() + { + // Test larger LDR tile decoding + var astcPath = Path.Combine("TestData", "HDR", "ldr-tile.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // Decode with both APIs + var ldrResult = AstcDecoder.DecompressImage(astcFile); + var hdrResult = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + // Both should produce correct output sizes + ldrResult.Length.Should().Be(astcFile.Width * astcFile.Height * 4); + hdrResult.Length.Should().Be(astcFile.Width * astcFile.Height * 4); + } + + [Fact] + public void SameFootprint_HdrVsLdr_ShouldBothDecode() + { + // Verify files with same footprint decode correctly + var hdrPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + var ldrPath = Path.Combine("TestData", "HDR", "LDR-A-1x1.astc"); + + var hdrData = File.ReadAllBytes(hdrPath); + var ldrData = File.ReadAllBytes(ldrPath); + + var hdrFile = AstcFile.FromMemory(hdrData); + var ldrFile = AstcFile.FromMemory(ldrData); + + // Both are 1x1 with 6x6 footprint + hdrFile.Width.Should().Be(ldrFile.Width); + hdrFile.Height.Should().Be(ldrFile.Height); + hdrFile.Footprint.Width.Should().Be(ldrFile.Footprint.Width); + hdrFile.Footprint.Height.Should().Be(ldrFile.Footprint.Height); + + // Both should decode successfully with HDR API + var hdrDecoded = AstcDecoder.DecompressHdrImage( + hdrFile.Blocks, hdrFile.Width, hdrFile.Height, hdrFile.Footprint); + var ldrDecoded = AstcDecoder.DecompressHdrImage( + ldrFile.Blocks, ldrFile.Width, ldrFile.Height, ldrFile.Footprint); + + hdrDecoded.Length.Should().Be(4); + ldrDecoded.Length.Should().Be(4); + } + + [Fact] + public void HdrColor_FromLdr_ShouldMatchLdrToHdrApiConversion() + { + // Verify that HdrColor.FromRgba() produces same results as decoding LDR with HDR API + var astcPath = Path.Combine("TestData", "HDR", "LDR-A-1x1.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // Decode with LDR API to get byte values + var ldrBytes = AstcDecoder.DecompressImage(astcFile); + + // Convert LDR bytes to HDR using HdrColor + var ldrColor = new RgbaColor(ldrBytes[0], ldrBytes[1], ldrBytes[2], ldrBytes[3]); + var hdrFromLdr = RgbaHdrColor.FromRgba(ldrColor); + + // Decode with HDR API + var hdrDirect = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + + // Compare: UNORM16 normalized values should match HDR API output + for (int i = 0; i < 4; i++) + { + float fromConversion = hdrFromLdr[i] / 65535.0f; + float fromDirect = hdrDirect[i]; + + Math.Abs(fromConversion - fromDirect).Should().BeLessThan(0.0001f); + } + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrDecoderTests.cs b/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrDecoderTests.cs new file mode 100644 index 00000000..f585dce8 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrDecoderTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests.HDR; + +public class HdrDecoderTests +{ + [Fact] + public void DecompressToFloat16_WithValidBlock_ShouldProduceCorrectOutputSize() + { + // Create a simple 4x4 block (16 bytes) + var astcData = new byte[16]; + + var footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); + + // Decompress using HDR API + var hdrResult = AstcDecoder.DecompressHdrImage(astcData, 4, 4, footprint); + + // Verify output size: 4x4 pixels, 4 Half values (RGBA) per pixel + hdrResult.Length.Should().Be(4 * 4 * 4); // 64 Half values total + + foreach (var value in hdrResult) + { + float.IsNaN(value).Should().BeFalse(); + float.IsInfinity(value).Should().BeFalse(); + // Values should be in reasonable range for normalized colors + value.Should().BeGreaterThanOrEqualTo(0.0f); + value.Should().BeLessThanOrEqualTo(1.1f); // Allow slight overshoot for HDR + } + } + + [Fact] + public void DecompressToFloat16_WithDifferentFootprints_ShouldWork() + { + // Test that HDR API works with various footprint types + var footprints = new[] + { + FootprintType.Footprint4x4, + FootprintType.Footprint5x5, + FootprintType.Footprint6x6, + FootprintType.Footprint8x8 + }; + + foreach (var footprint in footprints) + { + // Create a simple test: 1 block (footprint size) of zeros + var fp = Footprint.FromFootprintType(footprint); + var astcData = new byte[16]; // One ASTC block (all zeros = void extent block) + + var result = AstcDecoder.DecompressHdrImage(astcData, fp.Width, fp.Height, footprint); + + // Should produce footprint.Width * footprint.Height pixels, each with 4 Half values + result.Length.Should().Be(fp.Width * fp.Height * 4); + } + } + + [Fact] + public void ASTCDecompressToFloat16_WithInvalidData_ShouldReturnEmpty() + { + var emptyData = Array.Empty(); + + var result = AstcDecoder.DecompressHdrImage(emptyData, 64, 64, FootprintType.Footprint4x4); + + result.Length.Should().Be(0); + } + + [Fact] + public void DecompressToFloat16_WithZeroDimensions_ShouldReturnEmpty() + { + var astcData = new byte[16]; + var footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); + + var result = AstcDecoder.DecompressHdrImage(astcData, 0, 0, footprint); + + result.Length.Should().Be(0); + } + + [Fact] + public void HdrColor_LdrRoundTrip_ShouldPreserveValues() + { + var ldrColor = new RgbaColor(50, 100, 150, 200); + + var hdrColor = RgbaHdrColor.FromRgba(ldrColor); + var backToLdr = hdrColor.ToLowDynamicRange(); + + backToLdr.R.Should().Be(ldrColor.R); + backToLdr.G.Should().Be(ldrColor.G); + backToLdr.B.Should().Be(ldrColor.B); + backToLdr.A.Should().Be(ldrColor.A); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrImageTests.cs b/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrImageTests.cs new file mode 100644 index 00000000..799c2774 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrImageTests.cs @@ -0,0 +1,146 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.ComponentModel; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests.HDR; + +/// +/// Tests using real HDR ASTC files from the ARM astc-encoder reference repository. +/// These tests validate that our HDR implementation produces valid output for +/// actual HDR-compressed ASTC data. +/// +public class HdrImageTests +{ + [Fact] + [Description("Verify that the ASTC file header is correctly parsed for HDR content, including footprint detection")] + public void DecodeHdrFile_VerifyFootprintDetection() + { + var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // The HDR-A-1x1.astc file has a 6x6 footprint based on the header + astcFile.Footprint.Width.Should().Be(6); + astcFile.Footprint.Height.Should().Be(6); + astcFile.Footprint.Type.Should().Be(FootprintType.Footprint6x6); + } + + [Fact] + public void DecodeHdrAstcFile_1x1Pixel_ShouldProduceValidHdrOutput() + { + var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + var hdrResult = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, + astcFile.Width, + astcFile.Height, + astcFile.Footprint); + + // Should produce 1 pixel with 4 values (RGBA) + hdrResult.Length.Should().Be(RgbaColor.BytesPerPixel); + + // HDR values can exceed 1.0 + // Just verify they're in a reasonable range (0.0 to 10.0) + foreach (var value in hdrResult) + { + value.Should().BeGreaterThanOrEqualTo(0.0f); + value.Should().BeLessThan(10.0f); + } + } + + [Fact] + public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() + { + var astcPath = Path.Combine("TestData", "HDR", "hdr-tile.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + var hdrResult = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, + astcFile.Width, + astcFile.Height, + astcFile.Footprint); + + // Should produce Width * Height pixels, each with 4 values + hdrResult.Length.Should().Be(astcFile.Width * astcFile.Height * RgbaColor.BytesPerPixel); + + // Verify at least some HDR values exceed 1.0 (typical for HDR content) + int valuesGreaterThanOne = 0; + foreach (var v in hdrResult) + { + if (v > 1.0f) + valuesGreaterThanOne++; + } + valuesGreaterThanOne.Should().Be(64); + } + + [Fact] + [Description("Verify that HDR ASTC files can be decoded with the LDR API, producing clamped values")] + public void DecodeHdrAstcFile_WithLdrApi_ShouldClampValues() + { + var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + + if (!File.Exists(astcPath)) + { + return; + } + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // Decode using LDR API + var ldrResult = AstcDecoder.DecompressImage(astcFile); + + // Should produce 1 pixel with 4 bytes (RGBA) + ldrResult.Length.Should().Be(RgbaColor.BytesPerPixel); + + // All values should be in LDR range + foreach (var value in ldrResult) + { + value.Should().BeGreaterThanOrEqualTo(byte.MinValue); + value.Should().BeLessThanOrEqualTo(byte.MaxValue); + } + } + + [Fact] + [Description("Verify that HDR and LDR APIs produce consistent relative channel values for the same HDR ASTC file")] + public void HdrAndLdrApis_OnSameHdrFile_ShouldProduceConsistentRelativeValues() + { + var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + + var astcData = File.ReadAllBytes(astcPath); + var astcFile = AstcFile.FromMemory(astcData); + + // Decode with both APIs + var hdrResult = AstcDecoder.DecompressHdrImage( + astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); + var ldrResult = AstcDecoder.DecompressImage(astcFile); + + // Both should produce output for 1 pixel + hdrResult.Length.Should().Be(4); + ldrResult.Length.Should().Be(4); + + // The relative ordering of RGB channels should be consistent between APIs. + // If HDR channel i > channel j, then LDR channel i should be >= channel j + // (accounting for clamping at 255). + for (int i = 0; i < 3; i++) + { + for (int j = i + 1; j < 3; j++) + { + if (hdrResult[i] > hdrResult[j]) + ldrResult[i].Should().BeGreaterThanOrEqualTo(ldrResult[j]); + else if (hdrResult[i] < hdrResult[j]) + ldrResult[i].Should().BeLessThanOrEqualTo(ldrResult[j]); + } + } + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/HDR/RgbaHdrColorTests.cs b/tests/ImageSharp.Textures.Astc.Tests/HDR/RgbaHdrColorTests.cs new file mode 100644 index 00000000..bd7b8060 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/HDR/RgbaHdrColorTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.Core; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests.HDR; + +public class RgbaHdrColorTests +{ + [Fact] + public void Constructor_WithValidValues_ShouldInitializeCorrectly() + { + var color = new RgbaHdrColor(1000, 2000, 3000, 4000); + + color.R.Should().Be(1000); + color.G.Should().Be(2000); + color.B.Should().Be(3000); + color.A.Should().Be(4000); + } + + [Fact] + public void Indexer_WithValidIndices_ShouldReturnCorrectChannels() + { + var color = new RgbaHdrColor(1000, 2000, 3000, 4000); + + color[0].Should().Be(1000); + color[1].Should().Be(2000); + color[2].Should().Be(3000); + color[3].Should().Be(4000); + } + + [Fact] + public void Indexer_WithInvalidIndex_ShouldThrowException() + { + var color = new RgbaHdrColor(1000, 2000, 3000, 4000); + + Action act = () => _ = color[4]; + + act.Should().Throw(); + } + + [Fact] + public void FromLdr_WithMinMaxValues_ShouldScaleCorrectly() + { + var ldrColor = new RgbaColor(0, 127, 255, 200); + + var hdrColor = RgbaHdrColor.FromRgba(ldrColor); + + hdrColor.R.Should().Be(0); // 0 * 257 = 0 + hdrColor.G.Should().Be(32639); // 127 * 257 = 32639 + hdrColor.B.Should().Be(65535); // 255 * 257 = 65535 + hdrColor.A.Should().Be(51400); // 200 * 257 = 51400 + } + + [Fact] + public void ToLdr_WithHdrValues_ShouldDownscaleCorrectly() + { + var hdrColor = new RgbaHdrColor(0, 32639, 65535, 51400); + + var ldrColor = hdrColor.ToLowDynamicRange(); + + ldrColor.R.Should().Be(0); // 0 >> 8 = 0 + ldrColor.G.Should().Be(127); // 32639 >> 8 = 127 + ldrColor.B.Should().Be(255); // 65535 >> 8 = 255 + ldrColor.A.Should().Be(200); // 51400 >> 8 = 200 + } + + [Fact] + public void FromLdr_ToLdr_RoundTrip_ShouldPreserveValues() + { + var original = new RgbaColor(50, 100, 150, 200); + + var hdrColor = RgbaHdrColor.FromRgba(original); + var result = hdrColor.ToLowDynamicRange(); + + result.R.Should().Be(original.R); + result.G.Should().Be(original.G); + result.B.Should().Be(original.B); + result.A.Should().Be(original.A); + } + + [Fact] + public void IsCloseTo_WithSimilarColors_ShouldReturnTrue() + { + var color1 = new RgbaHdrColor(1000, 2000, 3000, 4000); + var color2 = new RgbaHdrColor(1005, 1995, 3002, 3998); + + var result = color1.IsCloseTo(color2, 10); + + result.Should().BeTrue(); + } + + [Fact] + public void IsCloseTo_WithDifferentColors_ShouldReturnFalse() + { + var color1 = new RgbaHdrColor(1000, 2000, 3000, 4000); + var color2 = new RgbaHdrColor(1020, 2000, 3000, 4000); + + var result = color1.IsCloseTo(color2, 10); + + result.Should().BeFalse(); + } + + [Fact] + public void Empty_ShouldReturnBlackTransparent() + { + var empty = RgbaHdrColor.Empty; + + empty.R.Should().Be(0); + empty.G.Should().Be(0); + empty.B.Should().Be(0); + empty.A.Should().Be(0); + } + +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/ImageSharp.Textures.Astc.Tests.csproj b/tests/ImageSharp.Textures.Astc.Tests/ImageSharp.Textures.Astc.Tests.csproj new file mode 100644 index 00000000..0eba26e5 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/ImageSharp.Textures.Astc.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + SixLabors.ImageSharp.Textures.Astc.Tests + SixLabors.ImageSharp.Textures.Astc.Tests + true + + + + + + + + + + + + + + + + + + <_TestData Include="TestData\**\*.*" /> + + + + + diff --git a/tests/ImageSharp.Textures.Astc.Tests/IntegerSequenceCodecTests.cs b/tests/ImageSharp.Textures.Astc.Tests/IntegerSequenceCodecTests.cs new file mode 100644 index 00000000..8166e624 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/IntegerSequenceCodecTests.cs @@ -0,0 +1,320 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.ComponentModel; +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; +using SixLabors.ImageSharp.Textures.Astc.IO; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class IntegerSequenceCodecTests +{ + [Fact] + [Description("1 to 31 are the densest packing of valid encodings and those supported by the codec.")] + public void GetPackingModeBitCount_ForValidRange_ShouldNotReturnUnknownMode() + { + for (int i = 1; i < 32; ++i) + { + var (mode, _) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(i); + mode.Should().NotBe(BiseEncodingMode.Unknown, $"Range {i} should not yield Unknown encoding mode"); + } + } + + [Fact] + public void GetPackingModeBitCount_ForValidRange_ShouldMatchExpectedValues() + { + (BiseEncodingMode Mode, int BitCount)[] expected = + [ + (BiseEncodingMode.BitEncoding, 1), // Range 1 + (BiseEncodingMode.TritEncoding, 0), // Range 2 + (BiseEncodingMode.BitEncoding, 2), // Range 3 + (BiseEncodingMode.QuintEncoding, 0), // Range 4 + (BiseEncodingMode.TritEncoding, 1), // Range 5 + (BiseEncodingMode.BitEncoding, 3), // Range 6 + (BiseEncodingMode.BitEncoding, 3), // Range 7 + (BiseEncodingMode.QuintEncoding, 1), // Range 8 + (BiseEncodingMode.QuintEncoding, 1), // Range 9 + (BiseEncodingMode.TritEncoding, 2), // Range 10 + (BiseEncodingMode.TritEncoding, 2), // Range 11 + (BiseEncodingMode.BitEncoding, 4), // Range 12 + (BiseEncodingMode.BitEncoding, 4), // Range 13 + (BiseEncodingMode.BitEncoding, 4), // Range 14 + (BiseEncodingMode.BitEncoding, 4), // Range 15 + (BiseEncodingMode.QuintEncoding, 2), // Range 16 + (BiseEncodingMode.QuintEncoding, 2), // Range 17 + (BiseEncodingMode.QuintEncoding, 2), // Range 18 + (BiseEncodingMode.QuintEncoding, 2), // Range 19 + (BiseEncodingMode.TritEncoding, 3), // Range 20 + (BiseEncodingMode.TritEncoding, 3), // Range 21 + (BiseEncodingMode.TritEncoding, 3), // Range 22 + (BiseEncodingMode.TritEncoding, 3), // Range 23 + (BiseEncodingMode.BitEncoding, 5), // Range 24 + (BiseEncodingMode.BitEncoding, 5), // Range 25 + (BiseEncodingMode.BitEncoding, 5), // Range 26 + (BiseEncodingMode.BitEncoding, 5), // Range 27 + (BiseEncodingMode.BitEncoding, 5), // Range 28 + (BiseEncodingMode.BitEncoding, 5), // Range 29 + (BiseEncodingMode.BitEncoding, 5), // Range 30 + (BiseEncodingMode.BitEncoding, 5) // Range 31 + ]; + + for (int i = 1; i < 32; ++i) + { + var (mode, bitCount) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(i); + var (expectedMode, expectedBitCount) = expected[i - 1]; + + mode.Should().Be(expectedMode, $"range {i} mode should match"); + bitCount.Should().Be(expectedBitCount, $"range {i} bit count should match"); + } + } + + [Theory] + [InlineData(0)] + [InlineData(256)] + public void GetPackingModeBitCount_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int range) + { + var action = () => BoundedIntegerSequenceCodec.GetPackingModeBitCount(range); + + action.Should().Throw(); + } + + [Theory] + [InlineData(1)] + [InlineData(10)] + [InlineData(32)] + [InlineData(63)] + public void GetBitCount_WithBitEncodingMode1Bit_ShouldReturnValueCount(int valueCount) + { + var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.BitEncoding, valueCount, 1); + var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 1); + + bitCount.Should().Be(valueCount); + bitCountForRange.Should().Be(valueCount); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, 2)] + [InlineData(10, 20)] + [InlineData(32, 64)] + public void GetBitCount_WithBitEncodingMode2Bits_ShouldReturnTwiceValueCount(int valueCount, int expected) + { + var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.BitEncoding, valueCount, 2); + var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 3); + + bitCount.Should().Be(expected); + bitCountForRange.Should().Be(expected); + } + + [Fact] + public void GetBitCount_WithTritEncoding15Values_ShouldReturnExpectedBitCount() + { + const int valueCount = 15; + const int bits = 3; + int expectedBitCount = 8 * 3 + 15 * 3; // 69 bits + + var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.TritEncoding, valueCount, bits); + var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 23); + + bitCount.Should().Be(expectedBitCount); + bitCountForRange.Should().Be(bitCount); + } + + [Fact] + public void GetBitCount_WithTritEncoding13Values_ShouldReturnExpectedBitCount() + { + const int valueCount = 13; + const int bits = 2; + const int expectedBitCount = 47; + + var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.TritEncoding, valueCount, bits); + var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 11); + + bitCount.Should().Be(expectedBitCount); + bitCountForRange.Should().Be(bitCount); + } + + [Fact] + public void GetBitCount_WithQuintEncoding6Values_ShouldReturnExpectedBitCount() + { + const int valueCount = 6; + const int bits = 4; + int expectedBitCount = 7 * 2 + 6 * 4; // 38 bits + + var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.QuintEncoding, valueCount, bits); + var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 79); + + bitCount.Should().Be(expectedBitCount); + bitCountForRange.Should().Be(bitCount); + } + + [Fact] + public void GetBitCount_WithQuintEncoding7Values_ShouldReturnExpectedBitCount() + { + const int valueCount = 7; + const int bits = 3; + int expectedBitCount = 7 * 2 + // First two quint blocks + 6 * 3 + // First two blocks of bits + 3 + // Last quint block without high order four bits + 3; // Last block with one set of three bits + + var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.QuintEncoding, valueCount, bits); + + bitCount.Should().Be(expectedBitCount); + } + + [Fact] + public void EncodeDecode_WithQuintValues_ShouldEncodeAndDecodeExpectedValues() + { + const int valueRange = 79; + var encoder = new BoundedIntegerSequenceEncoder(valueRange); + var values = new[] { 3, 79, 37 }; + + foreach (var value in values) + encoder.AddValue(value); + + // Encode + var bitSink = new BitStream(); + encoder.Encode(ref bitSink); + + // Verify encoded data + bitSink.Bits.Should().Be(19); + bitSink.TryGetBits(19, out var encoded).Should().BeTrue(); + encoded.Should().Be(0x4A7D3UL); + + // Decode + var bitSrc = new BitStream(encoded, 19); + var decoder = new BoundedIntegerSequenceDecoder(valueRange); + var decoded = decoder.Decode(3, ref bitSrc); + + decoded.Should().Equal(values); + } + + [Fact] + public void DecodeThenEncode_WithQuintValues_ShouldPreserveEncoding() + { + var expectedValues = new[] { 16, 18, 17, 4, 7, 14, 10, 0 }; + const ulong encoding = 0x2b9c83dc; + const int range = 19; + + // Decode + var bitSrc = new BitStream(encoding, 64); + var decoder = new BoundedIntegerSequenceDecoder(range); + var decoded = decoder.Decode(expectedValues.Length, ref bitSrc); + + // Check decoded values + decoded.Should().HaveCount(expectedValues.Length); + decoded.Should().Equal(expectedValues); + + // Re-encode + var bitSink = new BitStream(); + var encoder = new BoundedIntegerSequenceEncoder(range); + foreach (var value in expectedValues) + encoder.AddValue(value); + encoder.Encode(ref bitSink); + + // Re-encoded should match original + bitSink.Bits.Should().Be(35); + bitSink.TryGetBits(35, out var reencoded).Should().BeTrue(); + reencoded.Should().Be(encoding); + } + + [Fact] + public void EncodeDecode_WithTritValues_ShouldEncodeAndDecodeExpectedValues() + { + const int valueRange = 11; + var encoder = new BoundedIntegerSequenceEncoder(valueRange); + var values = new[] { 7, 5, 3, 6, 10 }; + + foreach (var value in values) + encoder.AddValue(value); + + // Encode + var bitSink = new BitStream(); + encoder.Encode(ref bitSink); + + // Verify encoded data + bitSink.Bits.Should().Be(18); + bitSink.TryGetBits(18, out var encoded).Should().BeTrue(); + encoded.Should().Be(0x37357UL); + + // Decode + var bitSrc = new BitStream(encoded, 19); + var decoder = new BoundedIntegerSequenceDecoder(valueRange); + var decoded = decoder.Decode(5, ref bitSrc); + + decoded.Should().Equal(values); + } + + [Fact] + public void DecodeThenEncode_WithTritValues_ShouldPreserveEncoding() + { + var expectedValues = new[] { 6, 0, 0, 2, 0, 0, 0, 0, 8, 0, 0, 0, 0, 8, 8, 0 }; + const ulong encoding = 0x0004c0100001006UL; + const int range = 11; + + // Decode + var bitSrc = new BitStream(encoding, 64); + var decoder = new BoundedIntegerSequenceDecoder(range); + var decoded = decoder.Decode(expectedValues.Length, ref bitSrc); + + // Check decoded values + decoded.Should().HaveCount(expectedValues.Length); + decoded.Should().Equal(expectedValues); + + // Re-encode + var bitSink = new BitStream(); + var encoder = new BoundedIntegerSequenceEncoder(range); + foreach (var value in expectedValues) + encoder.AddValue(value); + encoder.Encode(ref bitSink); + + // Assert re-encoded matches original + bitSink.Bits.Should().Be(58); + bitSink.TryGetBits(58, out var reencoded).Should().BeTrue(); + reencoded.Should().Be(encoding); + } + + + + [Fact] + public void EncodeDecode_WithRandomValues_ShouldAlwaysRoundTripCorrectly() + { + var random = new Random(unchecked(0xbad7357)); + const int testCount = 1600; + + for (int test = 0; test < testCount; test++) + { + int valueCount = 4 + random.Next(0, 256) % 44; + int range = 1 + random.Next(0, 256) % 63; + + int bitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, range); + if (bitCount >= 64) continue; + + // Generate random values + var generated = new List(valueCount); + for (int i = 0; i < valueCount; i++) + generated.Add(random.Next(range + 1)); + + // Encode + var bitSink = new BitStream(); + var encoder = new BoundedIntegerSequenceEncoder(range); + foreach (var value in generated) + encoder.AddValue(value); + + encoder.Encode(ref bitSink); + + bitSink.TryGetBits((int)bitSink.Bits, out var encoded).Should().BeTrue(); + + // Decode + var bitSrc = new BitStream(encoded, 64); + var decoder = new BoundedIntegerSequenceDecoder(range); + var decoded = decoder.Decode(valueCount, ref bitSrc); + + decoded.Should().HaveCount(generated.Count); + decoded.Should().Equal(generated); + } + } + +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/IntegrationTests.cs b/tests/ImageSharp.Textures.Astc.Tests/IntegrationTests.cs new file mode 100644 index 00000000..f4cfadc6 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/IntegrationTests.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.IO; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class IntegrationTests +{ + [Theory] + [InlineData("atlas_small_4x4")] + [InlineData("atlas_small_5x5")] + [InlineData("atlas_small_6x6")] + [InlineData("atlas_small_8x8")] + [InlineData("checkerboard")] + [InlineData("checkered_4")] + [InlineData("checkered_5")] + [InlineData("checkered_6")] + [InlineData("checkered_7")] + [InlineData("checkered_8")] + [InlineData("checkered_9")] + [InlineData("checkered_10")] + [InlineData("checkered_11")] + [InlineData("checkered_12")] + [InlineData("footprint_4x4")] + [InlineData("footprint_5x4")] + [InlineData("footprint_5x5")] + [InlineData("footprint_6x5")] + [InlineData("footprint_6x6")] + [InlineData("footprint_8x5")] + [InlineData("footprint_8x6")] + [InlineData("footprint_8x8")] + [InlineData("footprint_10x5")] + [InlineData("footprint_10x6")] + [InlineData("footprint_10x8")] + [InlineData("footprint_10x10")] + [InlineData("footprint_12x10")] + [InlineData("footprint_12x12")] + [InlineData("rgb_4x4")] + [InlineData("rgb_5x4")] + [InlineData("rgb_6x6")] + [InlineData("rgb_8x8")] + [InlineData("rgb_12x12")] + public void DecompressToImage_WithTestdataFile_ShouldDecodeSuccessfully(string basename) + { + var filePath = Path.Combine("TestData", "Input", basename + ".astc"); + var bytes = File.ReadAllBytes(filePath); + var astc = AstcFile.FromMemory(bytes); + + var result = AstcDecoder.DecompressImage(astc); + + result.Length.Should().BeGreaterThan(0, because: $"decoding should succeed for {basename}"); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/IntermediateBlockTests.cs b/tests/ImageSharp.Textures.Astc.Tests/IntermediateBlockTests.cs new file mode 100644 index 00000000..73bfa0f3 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/IntermediateBlockTests.cs @@ -0,0 +1,456 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class IntermediateBlockTests +{ + private static readonly UInt128 ErrorBlock = UInt128.Zero; + + [Fact] + public void UnpackVoidExtent_WithErrorBlock_ShouldReturnNull() + { + var errorBlock = PhysicalBlock.Create(ErrorBlock); + + var result = IntermediateBlock.UnpackVoidExtent(errorBlock); + + result.Should().BeNull(); + } + + [Fact] + public void UnpackIntermediateBlock_WithErrorBlock_ShouldReturnNull() + { + var errorBlock = PhysicalBlock.Create(ErrorBlock); + + var result = IntermediateBlock.UnpackIntermediateBlock(errorBlock); + + result.Should().BeNull(); + } + + [Fact] + public void EndpointRangeForBlock_WithoutWeights_ShouldReturnNegativeOne() + { + var data = new IntermediateBlock.IntermediateBlockData + { + WeightRange = 15, + WeightGridX = 6, + WeightGridY = 6 + }; + + var result = IntermediateBlock.EndpointRangeForBlock(data); + + result.Should().Be(-1); + } + + [Fact] + public void Pack_WithIncorrectNumberOfWeights_ShouldReturnError() + { + var data = new IntermediateBlock.IntermediateBlockData + { + WeightRange = 15, + WeightGridX = 6, + WeightGridY = 6 + }; + + var (error, _) = IntermediateBlockPacker.Pack(data); + + error.Should().NotBeNull(); + error.Should().Contain("Incorrect number of weights"); + } + + [Fact] + public void EndpointRangeForBlock_WithNotEnoughBits_ShouldReturnNegativeTwo() + { + var data = new IntermediateBlock.IntermediateBlockData + { + WeightRange = 1, + PartitionId = 0, + WeightGridX = 8, + WeightGridY = 8, + EndpointCount = 3 + }; + data.Endpoints[0] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; + data.Endpoints[1] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; + data.Endpoints[2] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; + + var result = IntermediateBlock.EndpointRangeForBlock(data); + + result.Should().Be(-2); + } + + [Fact] + public void Pack_WithNotEnoughBitsForColors_ShouldReturnError() + { + var data = new IntermediateBlock.IntermediateBlockData + { + WeightRange = 1, + PartitionId = 0, + WeightGridX = 8, + WeightGridY = 8, + Weights = new int[64], + EndpointCount = 3 + }; + data.Endpoints[0] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; + data.Endpoints[1] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; + data.Endpoints[2] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; + + var (error, _) = IntermediateBlockPacker.Pack(data); + + error.Should().NotBeNull(); + error.Should().Contain("illegal color range"); + } + + [Fact] + public void EndpointRangeForBlock_WithIncreasingWeightGrid_ShouldDecreaseColorRange() + { + var data = new IntermediateBlock.IntermediateBlockData + { + WeightRange = 2, + DualPlaneChannel = null, + EndpointCount = 2 + }; + data.Endpoints[0] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; + data.Endpoints[1] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; + + var weightParams = new List<(int w, int h)>(); + for (int y = 2; y < 8; ++y) + for (int x = 2; x < 8; ++x) + weightParams.Add((x, y)); + + weightParams.Sort((a, b) => (a.w * a.h).CompareTo(b.w * b.h)); + + int lastColorRange = byte.MaxValue; + foreach (var (w, h) in weightParams) + { + data.WeightGridX = w; + data.WeightGridY = h; + int colorRange = IntermediateBlock.EndpointRangeForBlock(data); + + colorRange.Should().BeLessThanOrEqualTo(lastColorRange); + lastColorRange = Math.Min(colorRange, lastColorRange); + } + + lastColorRange.Should().BeLessThan(byte.MaxValue); + } + + [Fact] + public void EndpointRange_WithStandardBlock_ShouldBe255() + { + var block = PhysicalBlock.Create((UInt128)0x0000000001FE000173UL); + + var data = IntermediateBlock.UnpackIntermediateBlock(block); + + block.GetColorValuesRange().Should().Be(255); + data.Should().NotBeNull(); + var ib = data!.Value; + ib.EndpointCount.Should().Be(1); + ib.Endpoints[0].Mode.Should().Be(ColorEndpointMode.LdrLumaDirect); + ib.Endpoints[0].Colors[0].Should().Be(byte.MinValue); + ib.Endpoints[0].Colors[1].Should().Be(byte.MaxValue); + ib.Endpoints[0].ColorCount.Should().Be(2); + ib.EndpointRange.Should().Be(byte.MaxValue); + } + + [Fact] + public void UnpackIntermediateBlock_WithStandardBlock_ShouldReturnCorrectData() + { + var block = PhysicalBlock.Create((UInt128)0x0000000001FE000173UL); + + var result = IntermediateBlock.UnpackIntermediateBlock(block); + + result.Should().NotBeNull(); + var data = result!.Value; + + data.WeightGridX.Should().Be(6); + data.WeightGridY.Should().Be(5); + data.WeightRange.Should().Be(7); + data.PartitionId.Should().BeNull(); + data.DualPlaneChannel.Should().BeNull(); + + data.WeightsCount.Should().Be(30); + data.Weights.AsSpan(0, data.WeightsCount).ToArray().Should().AllBeEquivalentTo(0); + + data.EndpointCount.Should().Be(1); + var endpoint = data.Endpoints[0]; + endpoint.Mode.Should().Be(ColorEndpointMode.LdrLumaDirect); + endpoint.ColorCount.Should().Be(2); + endpoint.Colors[0].Should().Be(byte.MinValue); + endpoint.Colors[1].Should().Be(byte.MaxValue); + } + + [Fact] + public void Pack_WithStandardBlockData_ShouldProduceExpectedBits() + { + var data = new IntermediateBlock.IntermediateBlockData + { + WeightGridX = 6, + WeightGridY = 5, + WeightRange = 7, + PartitionId = null, + DualPlaneChannel = null, + Weights = new int[30] + }; + + var endpoint = new IntermediateBlock.IntermediateEndpointData + { + Mode = ColorEndpointMode.LdrLumaDirect, + ColorCount = 2 + }; + endpoint.Colors[0] = byte.MinValue; + endpoint.Colors[1] = byte.MaxValue; + data.Endpoints[0] = endpoint; + data.EndpointCount = 1; + + var (error, packed) = IntermediateBlockPacker.Pack(data); + + error.Should().BeNull(); + packed.Should().Be((UInt128)0x0000000001FE000173UL); + } + + [Fact] + public void Pack_WithLargeGapInBits_ShouldPreserveOriginalEncoding() + { + var original = new UInt128(0xBEDEAD0000000000UL, 0x0000000001FE032EUL); + var block = PhysicalBlock.Create(original); + var data = IntermediateBlock.UnpackIntermediateBlock(block); + + data.Should().NotBeNull(); + var intermediate = data!.Value; + + // Check unpacked values + intermediate.WeightGridX.Should().Be(2); + intermediate.WeightGridY.Should().Be(3); + intermediate.WeightRange.Should().Be(15); + intermediate.PartitionId.Should().BeNull(); + intermediate.DualPlaneChannel.Should().BeNull(); + intermediate.EndpointCount.Should().Be(1); + intermediate.Endpoints[0].Mode.Should().Be(ColorEndpointMode.LdrLumaDirect); + intermediate.Endpoints[0].ColorCount.Should().Be(2); + intermediate.Endpoints[0].Colors[0].Should().Be(255); + intermediate.Endpoints[0].Colors[1].Should().Be(0); + + // Repack + var (error, repacked) = IntermediateBlockPacker.Pack(intermediate); + + error.Should().BeNull(); + repacked.Should().Be(original); + } + + [Fact] + public void UnpackVoidExtent_WithAllOnesPattern_ShouldReturnZeroColors() + { + var block = PhysicalBlock.Create((UInt128)0xFFFFFFFFFFFFFDFCUL); + + var result = IntermediateBlock.UnpackVoidExtent(block); + + result.Should().NotBeNull(); + var data = result!.Value; + + data.R.Should().Be(0); + data.G.Should().Be(0); + data.B.Should().Be(0); + data.A.Should().Be(0); + + data.Coords.Should().AllSatisfy(c => c.Should().Be((1 << 13) - 1)); + } + + [Fact] + public void UnpackVoidExtent_WithColorData_ShouldReturnCorrectColors() + { + var blockBits = new UInt128(0xdeadbeefdeadbeefUL, 0xFFF8003FFE000DFCUL); + var block = PhysicalBlock.Create(blockBits); + + var result = IntermediateBlock.UnpackVoidExtent(block); + + result.Should().NotBeNull(); + var data = result!.Value; + + data.R.Should().Be(0xbeef); + data.G.Should().Be(0xdead); + data.B.Should().Be(0xbeef); + data.A.Should().Be(0xdead); + + data.Coords[0].Should().Be(0); + data.Coords[1].Should().Be(8191); + data.Coords[2].Should().Be(0); + data.Coords[3].Should().Be(8191); + } + + [Fact] + public void Pack_WithZeroColorVoidExtent_ShouldProduceAllOnesPattern() + { + var data = new IntermediateBlock.VoidExtentData + { + R = 0, + G = 0, + B = 0, + A = 0, + Coords = new ushort[4] + }; + + for (int i = 0; i < 4; ++i) + data.Coords[i] = (ushort)((1 << 13) - 1); + + var (error, packed) = IntermediateBlockPacker.Pack(data); + + error.Should().BeNull(); + packed.Should().Be((UInt128)0xFFFFFFFFFFFFFDFCUL); + } + + [Fact] + public void Pack_WithColorVoidExtent_ShouldProduceExpectedBits() + { + var data = new IntermediateBlock.VoidExtentData + { + R = 0xbeef, + G = 0xdead, + B = 0xbeef, + A = 0xdead, + Coords = new ushort[4] { 0, 8191, 0, 8191 } + }; + + var (error, packed) = IntermediateBlockPacker.Pack(data); + + error.Should().BeNull(); + packed.Should().Be(new UInt128(0xdeadbeefdeadbeefUL, 0xFFF8003FFE000DFCUL)); + } + + [Theory] + [InlineData(0xe8e8eaea20000980UL, 0x20000200cb73f045UL)] + [InlineData(0x3300c30700cb01c5UL, 0x0573907b8c0f6879UL)] + public void PackUnpack_WithSameCEM_ShouldRoundTripCorrectly(ulong high, ulong low) + { + var original = new UInt128(high, low); + var block = PhysicalBlock.Create(original); + + var unpacked = IntermediateBlock.UnpackIntermediateBlock(block); + + unpacked.Should().NotBeNull(); + var ib = unpacked!.Value; + + var (error, repacked) = IntermediateBlockPacker.Pack(ib); + + error.Should().BeNull(); + repacked.Should().Be(original); + } + + [Theory] + [InlineData("checkered_4", 4)] + [InlineData("checkered_5", 5)] + [InlineData("checkered_6", 6)] + [InlineData("checkered_7", 7)] + [InlineData("checkered_8", 8)] + [InlineData("checkered_9", 9)] + [InlineData("checkered_10", 10)] + [InlineData("checkered_11", 11)] + [InlineData("checkered_12", 12)] + public void PackUnpack_WithTestDataBlocks_ShouldPreserveBlockProperties(string imageName, int checkeredDim) + { + const int astcDim = 8; + int imgDim = checkeredDim * astcDim; + var astcData = LoadASTCFile(imageName); + int numBlocks = (imgDim / astcDim) * (imgDim / astcDim); + + (astcData.Length % PhysicalBlock.SizeInBytes).Should().Be(0); + + for (int i = 0; i < numBlocks; ++i) + { + var slice = new ReadOnlySpan(astcData, i * PhysicalBlock.SizeInBytes, PhysicalBlock.SizeInBytes); + var blockBits = new UInt128( + BitConverter.ToUInt64(slice.Slice(8, 8)), + BitConverter.ToUInt64(slice.Slice(0, 8))); + var originalBlock = PhysicalBlock.Create(blockBits); + + // Unpack and repack + UInt128 repacked; + if (originalBlock.IsVoidExtent) + { + var voidData = IntermediateBlock.UnpackVoidExtent(originalBlock); + voidData.Should().NotBeNull(); + + var (error, packed) = IntermediateBlockPacker.Pack(voidData!.Value); + error.Should().BeNull(); + repacked = packed; + } + else + { + var intermediateData = IntermediateBlock.UnpackIntermediateBlock(originalBlock); + intermediateData.Should().NotBeNull(); + var ibData = intermediateData!.Value; + + // Verify endpoint range was set + ibData.EndpointRange.Should().Be(originalBlock.GetColorValuesRange()); + + // Clear endpoint range before repacking (to test calculation) + ibData.EndpointRange = null; + var (error, packed) = IntermediateBlockPacker.Pack(ibData); + error.Should().BeNull(); + repacked = packed; + } + + // Verify repacked block + var repackedBlock = PhysicalBlock.Create(repacked); + VerifyBlockPropertiesMatch(repackedBlock, originalBlock); + } + } + + private static void VerifyBlockPropertiesMatch(PhysicalBlock repacked, PhysicalBlock original) + { + repacked.IsIllegalEncoding.Should().BeFalse(); + + // Verify color bits match + var repackedColorBitCount = repacked.GetColorBitCount().Value; + var repackedColorMask = UInt128Extensions.OnesMask(repackedColorBitCount); + var repackedColorBits = (repacked.BlockBits >> repacked.GetColorStartBit().Value) & repackedColorMask; + + var originalColorBitCount = original.GetColorBitCount().Value; + var originalColorMask = UInt128Extensions.OnesMask(originalColorBitCount); + var originalColorBits = (original.BlockBits >> original.GetColorStartBit().Value) & originalColorMask; + + repackedColorMask.Should().Be(originalColorMask); + repackedColorBits.Should().Be(originalColorBits); + + // Verify void extent properties + repacked.IsVoidExtent.Should().Be(original.IsVoidExtent); + repacked.GetVoidExtentCoordinates().Should().Equal(original.GetVoidExtentCoordinates()); + + // Verify weight properties + repacked.GetWeightGridDimensions().Should().Be(original.GetWeightGridDimensions()); + repacked.GetWeightRange().Should().Be(original.GetWeightRange()); + repacked.GetWeightBitCount().Should().Be(original.GetWeightBitCount()); + repacked.GetWeightStartBit().Should().Be(original.GetWeightStartBit()); + + // Verify dual plane properties + repacked.IsDualPlane.Should().Be(original.IsDualPlane); + repacked.GetDualPlaneChannel().Should().Be(original.GetDualPlaneChannel()); + + // Verify partition properties + repacked.GetPartitionsCount().Should().Be(original.GetPartitionsCount()); + repacked.GetPartitionId().Should().Be(original.GetPartitionId()); + + // Verify color value properties + repacked.GetColorValuesCount().Should().Be(original.GetColorValuesCount()); + repacked.GetColorValuesRange().Should().Be(original.GetColorValuesRange()); + + // Verify endpoint modes for all partitions + var numParts = repacked.GetPartitionsCount().GetValueOrDefault(0); + for (int j = 0; j < numParts; ++j) + { + repacked.GetEndpointMode(j).Should().Be(original.GetEndpointMode(j)); + } + } + + private static byte[] LoadASTCFile(string basename) + { + var filename = Path.Combine("TestData", "Input", basename + ".astc"); + File.Exists(filename).Should().BeTrue($"Testdata missing: {filename}"); + var data = File.ReadAllBytes(filename); + data.Length.Should().BeGreaterThanOrEqualTo(16, "ASTC file too small"); + return data.Skip(16).ToArray(); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/LogicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Astc.Tests/LogicalAstcBlockTests.cs new file mode 100644 index 00000000..2bcb1750 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/LogicalAstcBlockTests.cs @@ -0,0 +1,392 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.Tests.Utils; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class LogicalAstcBlockTests +{ + [Theory] + [InlineData(FootprintType.Footprint4x4)] + [InlineData(FootprintType.Footprint5x5)] + [InlineData(FootprintType.Footprint8x8)] + [InlineData(FootprintType.Footprint10x10)] + [InlineData(FootprintType.Footprint12x12)] + public void Constructor_WithValidFootprintType_ShoulReturnExpectedFootprint(FootprintType footprintType) + { + var footprint = Footprint.FromFootprintType(footprintType); + var logicalBlock = new LogicalBlock(footprint); + + logicalBlock.GetFootprint().Should().Be(footprint); + logicalBlock.GetFootprint().Type.Should().Be(footprintType); + } + + [Fact] + public void GetFootprint_AfterConstruction_ShouldReturnOriginalFootprint() + { + var footprint = Footprint.Get8x8(); + var logicalBlock = new LogicalBlock(footprint); + + var result = logicalBlock.GetFootprint(); + + result.Should().Be(footprint); + } + + [Theory] + [InlineData(0)] + [InlineData(32)] + [InlineData(64)] + public void SetWeightAt_WithValidWeight_ShouldStoreCorrectly(int weight) + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + + logicalBlock.SetWeightAt(1, 1, weight); + + logicalBlock.WeightAt(1, 1).Should().Be(weight); + } + + [Theory] + [InlineData(-1)] + [InlineData(65)] + [InlineData(100)] + public void SetWeightAt_WithInvalidWeight_ShouldThrowArgumentOutOfRangeException(int weight) + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + + var action = () => logicalBlock.SetWeightAt(0, 0, weight); + + action.Should().Throw(); + } + + [Fact] + public void WeightAt_WithDefaultWeights_ShouldReturnZero() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + + var weight = logicalBlock.WeightAt(2, 2); + + weight.Should().Be(0); + } + + [Fact] + public void IsDualPlane_ByDefault_ShouldBeFalse() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + + var result = logicalBlock.IsDualPlane(); + + result.Should().BeFalse(); + } + + [Fact] + public void SetDualPlaneChannel_WithValidChannel_ShouldEnableDualPlane() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + + logicalBlock.SetDualPlaneChannel(0); + + logicalBlock.IsDualPlane().Should().BeTrue(); + } + + [Fact] + public void SetDualPlaneChannel_WithNegativeValue_ShouldDisableDualPlane() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + logicalBlock.SetDualPlaneChannel(0); + + logicalBlock.SetDualPlaneChannel(-1); + + logicalBlock.IsDualPlane().Should().BeFalse(); + } + + [Fact] + public void SetDualPlaneWeightAt_WhenNotDualPlane_ShouldThrowInvalidOperationException() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + + var action = () => logicalBlock.SetDualPlaneWeightAt(0, 2, 3, 1); + + action.Should().Throw() + .WithMessage("Not a dual plane block"); + } + + [Fact] + public void SetDualPlaneWeightAt_AfterEnablingDualPlane_ShouldPreserveOriginalWeight() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + logicalBlock.SetWeightAt(2, 3, 2); + logicalBlock.SetDualPlaneChannel(0); + + logicalBlock.SetDualPlaneWeightAt(0, 2, 3, 1); + + logicalBlock.WeightAt(2, 3).Should().Be(2); + logicalBlock.DualPlaneWeightAt(0, 2, 3).Should().Be(1); + } + + [Fact] + public void DualPlaneWeightAt_ForNonDualPlaneChannel_ShouldReturnOriginalWeight() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + logicalBlock.SetWeightAt(2, 3, 2); + logicalBlock.SetDualPlaneChannel(0); + logicalBlock.SetDualPlaneWeightAt(0, 2, 3, 1); + + for (int i = 1; i < 4; ++i) + { + logicalBlock.DualPlaneWeightAt(i, 2, 3).Should().Be(2); + } + } + + [Fact] + public void DualPlaneWeightAt_WhenNotDualPlane_ShouldReturnWeightAt() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + logicalBlock.SetWeightAt(2, 3, 42); + + var result = logicalBlock.DualPlaneWeightAt(0, 2, 3); + + result.Should().Be(42); + } + + [Fact] + public void SetDualPlaneWeightAt_ThenDisableDualPlane_ShouldResetToOriginalWeight() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + logicalBlock.SetWeightAt(2, 3, 2); + logicalBlock.SetDualPlaneChannel(0); + logicalBlock.SetDualPlaneWeightAt(0, 2, 3, 1); + + logicalBlock.SetDualPlaneChannel(-1); + + logicalBlock.IsDualPlane().Should().BeFalse(); + logicalBlock.WeightAt(2, 3).Should().Be(2); + for (int i = 0; i < 4; ++i) + { + logicalBlock.DualPlaneWeightAt(i, 2, 3).Should().Be(2); + } + } + + [Fact] + public void SetEndpoints_WithValidColors_ShouldStoreCorrectly() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + var color1 = new RgbaColor(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); + var color2 = new RgbaColor(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); + + logicalBlock.SetEndpoints(color1, color2, 0); + + // No direct getter, but we can verify through ColorAt + logicalBlock.SetWeightAt(0, 0, 0); + logicalBlock.SetWeightAt(1, 1, 64); + + var colorAtMinWeight = logicalBlock.ColorAt(0, 0); + var colorAtMaxWeight = logicalBlock.ColorAt(1, 1); + + colorAtMinWeight.R.Should().Be(color1.R); + colorAtMaxWeight.R.Should().BeCloseTo(color2.R, 1); + } + + [Fact] + public void ColorAt_WithCheckerboardWeights_ShouldInterpolateCorrectly() + { + var logicalBlock = new LogicalBlock(Footprint.Get8x8()); + + // Create checkerboard weight pattern + for (int j = 0; j < 8; ++j) + { + for (int i = 0; i < 8; ++i) + { + if (((i ^ j) & 1) == 1) + logicalBlock.SetWeightAt(i, j, 0); + else + logicalBlock.SetWeightAt(i, j, 64); + } + } + + var endpointA = new RgbaColor(123, 45, 67, 89); + var endpointB = new RgbaColor(101, 121, 31, 41); + logicalBlock.SetEndpoints(endpointA, endpointB, 0); + + for (int j = 0; j < 8; ++j) + { + for (int i = 0; i < 8; ++i) + { + var color = logicalBlock.ColorAt(i, j); + if (((i ^ j) & 1) == 1) + { + // Weight 0 = first endpoint + color.R.Should().Be(endpointA.R); + color.G.Should().Be(endpointA.G); + color.B.Should().Be(endpointA.B); + color.A.Should().Be(endpointA.A); + } + else + { + // Weight 64 = second endpoint + color.R.Should().Be(endpointB.R); + color.G.Should().Be(endpointB.G); + color.B.Should().Be(endpointB.B); + color.A.Should().Be(endpointB.A); + } + } + } + } + + [Theory] + [InlineData(-1, 0)] + [InlineData(0, -1)] + [InlineData(4, 0)] + [InlineData(0, 4)] + public void ColorAt_WithOutOfBoundsCoordinates_ShouldThrowArgumentOutOfRangeException(int x, int y) + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + + var action = () => logicalBlock.ColorAt(x, y); + + action.Should().Throw(); + } + + [Fact] + public void SetPartition_WithValidPartition_ShouldUpdateCorrectly() + { + var footprint = Footprint.Get8x8(); + var logicalBlock = new LogicalBlock(footprint); + + // Create partition with 2 subsets, all pixels assigned to subset 0 + var newPartition = new Partition(footprint, 2, 5) + { + Assignment = new int[footprint.PixelCount] + }; + + logicalBlock.SetPartition(newPartition); + + // Should be able to set endpoints for both valid partitions (0 and 1) + var redEndpoint = new RgbaColor(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); + var blackEndpoint = new RgbaColor(byte.MinValue, byte.MinValue, byte.MinValue, byte.MaxValue); + var greenEndpoint = new RgbaColor(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); + + var setEndpoint0 = () => logicalBlock.SetEndpoints(redEndpoint, blackEndpoint, 0); + var setEndpoint1 = () => logicalBlock.SetEndpoints(greenEndpoint, blackEndpoint, 1); + + setEndpoint0.Should().NotThrow(); + setEndpoint1.Should().NotThrow(); + + // Should not be able to set endpoints for non-existent partition 2 + var setEndpoint2 = () => logicalBlock.SetEndpoints(redEndpoint, blackEndpoint, 2); + setEndpoint2.Should().Throw(); + } + + [Fact] + public void SetPartition_WithDifferentFootprint_ShouldThrowInvalidOperationException() + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + var wrongPartition = new Partition(Footprint.Get8x8(), 1, 0) + { + Assignment = new int[64] + }; + + var action = () => logicalBlock.SetPartition(wrongPartition); + + action.Should() + .Throw() + .WithMessage("New partitions may not be for a different footprint"); + } + + [Theory] + [InlineData(-1)] + [InlineData(2)] + public void SetEndpoints_WithInvalidSubset_ShouldThrowArgumentOutOfRangeException(int subset) + { + var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + var color1 = new RgbaColor(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); + var color2 = new RgbaColor(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); + + var action = () => logicalBlock.SetEndpoints(color1, color2, subset); + + action.Should().Throw(); + } + + [Fact] + public void UnpackLogicalBlock_WithErrorBlock_ShouldReturnNull() + { + var bits = UInt128.Zero; + var info = BlockInfo.Decode(bits); + + var result = LogicalBlock.UnpackLogicalBlock(Footprint.Get8x8(), bits, in info); + + result.Should().BeNull(); + } + + [Fact] + public void UnpackLogicalBlock_WithVoidExtentBlock_ShouldReturnLogicalBlock() + { + var bits = (UInt128)0xFFFFFFFFFFFFFDFCUL; + var info = BlockInfo.Decode(bits); + + var result = LogicalBlock.UnpackLogicalBlock(Footprint.Get8x8(), bits, in info); + + result.Should().NotBeNull(); + result!.GetFootprint().Should().Be(Footprint.Get8x8()); + } + + [Fact] + public void UnpackLogicalBlock_WithStandardBlock_ShouldReturnLogicalBlock() + { + var bits = (UInt128)0x0000000001FE000173UL; + var info = BlockInfo.Decode(bits); + + var result = LogicalBlock.UnpackLogicalBlock(Footprint.Get6x5(), bits, in info); + + result.Should().NotBeNull(); + result!.GetFootprint().Should().Be(Footprint.Get6x5()); + } + + [Theory] + // Synthetic test images + [InlineData("footprint_4x4", false, FootprintType.Footprint4x4, 32, 32)] + [InlineData("footprint_5x4", false, FootprintType.Footprint5x4, 32, 32)] + [InlineData("footprint_5x5", false, FootprintType.Footprint5x5, 32, 32)] + [InlineData("footprint_6x5", false, FootprintType.Footprint6x5, 32, 32)] + [InlineData("footprint_6x6", false, FootprintType.Footprint6x6, 32, 32)] + [InlineData("footprint_8x5", false, FootprintType.Footprint8x5, 32, 32)] + [InlineData("footprint_8x6", false, FootprintType.Footprint8x6, 32, 32)] + [InlineData("footprint_8x8", false, FootprintType.Footprint8x8, 32, 32)] + [InlineData("footprint_10x5", false, FootprintType.Footprint10x5, 32, 32)] + [InlineData("footprint_10x6", false, FootprintType.Footprint10x6, 32, 32)] + [InlineData("footprint_10x8", false, FootprintType.Footprint10x8, 32, 32)] + [InlineData("footprint_10x10", false, FootprintType.Footprint10x10, 32, 32)] + [InlineData("footprint_12x10", false, FootprintType.Footprint12x10, 32, 32)] + [InlineData("footprint_12x12", false, FootprintType.Footprint12x12, 32, 32)] + // RGB without alpha images + [InlineData("rgb_4x4", false, FootprintType.Footprint4x4, 224, 288)] + [InlineData("rgb_5x4", false, FootprintType.Footprint5x4, 224, 288)] + [InlineData("rgb_6x6", false, FootprintType.Footprint6x6, 224, 288)] + [InlineData("rgb_8x8", false, FootprintType.Footprint8x8, 224, 288)] + [InlineData("rgb_12x12", false, FootprintType.Footprint12x12, 224, 288)] + // RGB with alpha images + [InlineData("atlas_small_4x4", true, FootprintType.Footprint4x4, 256, 256)] + [InlineData("atlas_small_5x5", true, FootprintType.Footprint5x5, 256, 256)] + [InlineData("atlas_small_6x6", true, FootprintType.Footprint6x6, 256, 256)] + [InlineData("atlas_small_8x8", true, FootprintType.Footprint8x8, 256, 256)] + public void UnpackLogicalBlock_FromImage_ShouldDecodeCorrectly( + string imageName, + bool hasAlpha, + FootprintType footprintType, + int width, + int height) + { + var footprint = Footprint.FromFootprintType(footprintType); + var astcData = FileBasedHelpers.LoadASTCFile(imageName); + + var decodedImage = ImageBuffer.FromAstcBuffer(footprint, astcData, width, height, hasAlpha); + + var expectedPath = Path.Combine("TestData", "Expected", imageName + ".bmp"); + var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedPath); + ImageUtils.CompareSumOfSquaredDifferences(expectedImage, decodedImage, 0.1); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/PartitionTests.cs b/tests/ImageSharp.Textures.Astc.Tests/PartitionTests.cs new file mode 100644 index 00000000..4422c756 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/PartitionTests.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class PartitionTests +{ + + [Fact] + public void PartitionMetric_WithSimplePartitions_ShouldCalculateCorrectDistance() + { + var partitionA = new Partition(Footprint.Get6x6(), 2) + { + Assignment = + [ + 0,0,0,0,0,0, + 0,0,0,0,0,0, + 0,0,0,0,0,0, + 0,0,0,0,0,0, + 0,0,0,0,0,0, + 0,0,0,0,0,1 + ] + }; + + var partitionB = new Partition(Footprint.Get6x6(), 2) + { + Assignment = + [ + 1,0,0,0,0,0, + 0,0,0,0,0,0, + 0,0,0,0,0,0, + 0,0,0,0,0,0, + 0,0,0,0,0,0, + 0,0,0,0,0,0 + ] + }; + + int distance = Partition.PartitionMetric(partitionA, partitionB); + + distance.Should().Be(2); + } + + [Fact] + public void PartitionMetric_WithDifferentPartCounts_ShouldCalculateCorrectDistance() + { + var partitionA = new Partition(Footprint.Get4x4(), 2) + { + Assignment = + [ + 2,2,2,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,1 + ] + }; + + var partitionB = new Partition(Footprint.Get4x4(), 3) + { + Assignment = + [ + 1,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0 + ] + }; + + int distance = Partition.PartitionMetric(partitionA, partitionB); + + distance.Should().Be(3); + } + + [Fact] + public void PartitionMetric_WithDifferentMapping_ShouldCalculateCorrectDistance() + { + var partitionA = new Partition(Footprint.Get4x4(), 2) + { + Assignment = + [ + 0,1,2,2, + 2,2,2,2, + 2,2,2,2, + 2,2,2,2 + ] + }; + + var partitionB = new Partition(Footprint.Get4x4(), 3) + { + Assignment = + [ + 1,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0 + ] + }; + + int distance = Partition.PartitionMetric(partitionA, partitionB); + + distance.Should().Be(1); + } + + [Fact] + public void GetASTCPartition_WithSpecificParameters_ShouldReturnExpectedAssignment() + { + int[] expected = + [ + 0,0,0,0,1,1,1,2,2,2, + 0,0,0,0,1,1,1,2,2,2, + 0,0,0,0,1,1,1,2,2,2, + 0,0,0,0,1,1,1,2,2,2, + 0,0,0,0,1,1,1,2,2,2, + 0,0,0,0,1,1,1,2,2,2 + ]; + + var partition = Partition.GetASTCPartition(Footprint.Get10x6(), 3, 557); + + partition.Assignment.Should().Equal(expected); + } + + [Fact] + public void GetASTCPartition_WithDifferentIds_ShouldProduceUniqueAssignments() + { + var partition0 = Partition.GetASTCPartition(Footprint.Get6x6(), 2, 0); + var partition1 = Partition.GetASTCPartition(Footprint.Get6x6(), 2, 1); + + partition0.Assignment.Should().NotEqual(partition1.Assignment); + } + + + + [Fact] + public void FindClosestASTCPartition_ShouldPreservePartitionCount() + { + var partition = new Partition(Footprint.Get6x6(), 2) + { + Assignment = + [ + 0,0,1,1,1,0, + 0,0,0,0,0,0, + 0,0,0,0,0,0, + 0,1,1,1,1,1, + 0,0,0,0,0,0, + 1,1,1,1,1,1 + ] + }; + + var closestAstcPartition = Partition.FindClosestASTCPartition(partition); + + closestAstcPartition.PartitionCount.Should().Be(partition.PartitionCount); + } + + [Fact] + public void FindClosestASTCPartition_WithModifiedPartition_ShouldReturnValidASTCPartition() + { + var astcPartition = Partition.GetASTCPartition(Footprint.Get12x12(), 3, 0x3CB); + var modifiedPartition = new Partition(astcPartition.Footprint, astcPartition.PartitionCount) + { + Assignment = [.. astcPartition.Assignment] + }; + modifiedPartition.Assignment[0]++; + + // Find closest ASTC partition + var closestPartition = Partition.FindClosestASTCPartition(modifiedPartition); + + // The closest partition should be a valid ASTC partition with the same footprint and number of parts + closestPartition.Footprint.Should().Be(astcPartition.Footprint); + closestPartition.PartitionCount.Should().Be(astcPartition.PartitionCount); + closestPartition.PartitionId.Should().HaveValue("returned partition should have a valid ID"); + + // Verify we can retrieve the same partition again using its ID + var verifyPartition = Partition.GetASTCPartition( + closestPartition.Footprint, + closestPartition.PartitionCount, + closestPartition.PartitionId!.Value); + verifyPartition.Should().Be(closestPartition); + } + + [Theory] + [InlineData(FootprintType.Footprint4x4)] + [InlineData(FootprintType.Footprint5x4)] + [InlineData(FootprintType.Footprint5x5)] + [InlineData(FootprintType.Footprint6x5)] + [InlineData(FootprintType.Footprint6x6)] + [InlineData(FootprintType.Footprint8x5)] + [InlineData(FootprintType.Footprint8x6)] + [InlineData(FootprintType.Footprint8x8)] + [InlineData(FootprintType.Footprint10x5)] + [InlineData(FootprintType.Footprint10x6)] + [InlineData(FootprintType.Footprint10x8)] + [InlineData(FootprintType.Footprint10x10)] + [InlineData(FootprintType.Footprint12x10)] + [InlineData(FootprintType.Footprint12x12)] + public void FindClosestASTCPartition_WithRandomPartitions_ShouldReturnFewerOrEqualSubsets(FootprintType footprintType) + { + var footprint = Footprint.FromFootprintType(footprintType); + var random = new Random(unchecked((int)0xdeadbeef)); + + const int numTests = 15; // Tests per footprint type + for (int i = 0; i < numTests; i++) + { + // Create random partition + int numParts = 2 + random.Next(3); // 2, 3, or 4 parts + var assignment = new int[footprint.PixelCount]; + for (int j = 0; j < footprint.PixelCount; j++) + { + assignment[j] = random.Next(numParts); + } + var partition = new Partition(footprint, numParts) + { + Assignment = assignment + }; + + var astcPartition = Partition.FindClosestASTCPartition(partition); + + // Matched partition should have fewer or equal subsets + astcPartition.PartitionCount + .Should() + .BeLessThanOrEqualTo( + partition.PartitionCount, + $"Footprint {footprintType}, Test #{i}: Selected partition with ID {astcPartition.PartitionId?.ToString() ?? "null"}"); + } + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/PhysicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Astc.Tests/PhysicalAstcBlockTests.cs new file mode 100644 index 00000000..58ea8fb7 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/PhysicalAstcBlockTests.cs @@ -0,0 +1,616 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class PhysicalAstcBlockTests +{ + private static readonly UInt128 ErrorBlock = UInt128.Zero; + + [Fact] + public void Create_WithUInt64_ShouldRoundTripBlockBits() + { + const ulong expectedLow = 0x0000000001FE000173UL; + + var block = PhysicalBlock.Create(expectedLow); + + block.BlockBits.Should().Be((UInt128)expectedLow); + } + + [Fact] + public void Create_WithUInt128_ShouldRoundTripBlockBits() + { + var expected = (UInt128)0x12345678ABCDEF00UL | ((UInt128)0xCAFEBABEDEADBEEFUL << 64); + + var block = PhysicalBlock.Create(expected); + + block.BlockBits.Should().Be(expected); + } + + [Fact] + public void Create_WithMatchingUInt64AndUInt128_ShouldProduceIdenticalBlocks() + { + const ulong value = 0x0000000001FE000173UL; + + var block1 = PhysicalBlock.Create(value); + var block2 = PhysicalBlock.Create((UInt128)value); + + block1.BlockBits.Should().Be(block2.BlockBits); + } + + [Fact] + public void IsVoidExtent_WithKnownVoidExtentPattern_ShouldReturnTrue() + { + var block = PhysicalBlock.Create((UInt128)0xFFFFFFFFFFFFFDFCUL); + + block.IsVoidExtent.Should().BeTrue(); + } + + [Fact] + public void IsVoidExtent_WithStandardBlock_ShouldReturnFalse() + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + block.IsVoidExtent.Should().BeFalse(); + } + + [Fact] + public void IsVoidExtent_WithErrorBlock_ShouldReturnFalse() + { + var block = PhysicalBlock.Create(ErrorBlock); + + block.IsVoidExtent.Should().BeFalse(); + } + + [Fact] + public void GetVoidExtentCoordinates_WithValidVoidExtentBlock_ShouldReturnExpectedCoordinates() + { + var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + + var coords = block.GetVoidExtentCoordinates(); + + coords.Should().NotBeNull(); + coords.Should().HaveCount(4); + coords![0].Should().Be(0); + coords[1].Should().Be(8191); + coords[2].Should().Be(0); + coords[3].Should().Be(8191); + } + + [Fact] + public void GetVoidExtentCoordinates_WithAllOnesPattern_ShouldReturnNull() + { + var block = PhysicalBlock.Create(0xFFFFFFFFFFFFFDFCUL); + + var coords = block.GetVoidExtentCoordinates(); + + block.IsVoidExtent.Should().BeTrue(); + coords.Should().BeNull(); + } + + [Fact] + public void Create_WithInvalidVoidExtentCoordinates_ShouldBeIllegalEncoding() + { + var block1 = PhysicalBlock.Create(0x0008004002001DFCUL); + var block2 = PhysicalBlock.Create(0x0007FFC001FFFDFCUL); + + block1.IsIllegalEncoding.Should().BeTrue(); + block2.IsIllegalEncoding.Should().BeTrue(); + } + + [Fact] + public void Create_WithModifiedHighBitsOnVoidExtent_ShouldStillBeValid() + { + var original = PhysicalBlock.Create(0xFFF8003FFE000DFCUL, 0UL); + var modified = PhysicalBlock.Create(0xFFF8003FFE000DFCUL, 0xdeadbeefdeadbeef); + + original.IsIllegalEncoding.Should().BeFalse(); + original.IsVoidExtent.Should().BeTrue(); + modified.IsIllegalEncoding.Should().BeFalse(); + modified.IsVoidExtent.Should().BeTrue(); + } + + [Fact] + public void GetWeightRange_WithValidBlock_ShouldReturn7() + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + var weightRange = block.GetWeightRange(); + + weightRange.Should().HaveValue(); + weightRange.Should().Be(7); + } + + [Fact] + public void GetWeightRange_WithTooManyBits_ShouldReturnNull() + { + var block = PhysicalBlock.Create(0x0000000001FE000373UL); + + var weightRange = block.GetWeightRange(); + + weightRange.Should().BeNull(); + } + + [Fact] + public void GetWeightRange_WithOneBitPerWeight_ShouldReturn1() + { + var block = PhysicalBlock.Create(0x4000000000800D44UL); + + var weightRange = block.GetWeightRange(); + + weightRange.Should().HaveValue(); + weightRange.Should().Be(1); + } + + [Fact] + public void GetWeightRange_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var weightRange = block.GetWeightRange(); + + weightRange.Should().BeNull(); + } + + [Fact] + public void GetWeightGridDimensions_WithValidBlock_ShouldReturn6x5() + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + var dims = block.GetWeightGridDimensions(); + + dims.Should().NotBeNull(); + dims!.Value.Width.Should().Be(6); + dims.Value.Height.Should().Be(5); + } + + [Fact] + public void GetWeightGridDimensions_WithTooManyBitsForGrid_ShouldReturnNull() + { + var block = PhysicalBlock.Create(0x0000000001FE000373UL); + + var dims = block.GetWeightGridDimensions(); + + dims.Should().BeNull(); + var error = block.IdentifyInvalidEncodingIssues(); + error.Should().Contain("Invalid block encoding"); + } + + [Fact] + public void GetWeightGridDimensions_WithDualPlaneBlock_ShouldReturn3x5() + { + var block = PhysicalBlock.Create(0x0000000001FE0005FFUL); + + var dims = block.GetWeightGridDimensions(); + + dims.Should().NotBeNull(); + dims!.Value.Width.Should().Be(3); + dims.Value.Height.Should().Be(5); + } + + [Fact] + public void GetWeightGridDimensions_WithNonSharedCEM_ShouldReturn8x8() + { + var block = PhysicalBlock.Create(0x4000000000800D44UL); + + var dims = block.GetWeightGridDimensions(); + + dims.Should().NotBeNull(); + dims!.Value.Width.Should().Be(8); + dims.Value.Height.Should().Be(8); + } + + [Fact] + public void GetWeightGridDimensions_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var dims = block.GetWeightGridDimensions(); + + dims.Should().BeNull(); + } + + [Fact] + public void IsDualPlane_WithSinglePlaneBlock_ShouldReturnFalse() + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + block.IsDualPlane.Should().BeFalse(); + } + + [Fact] + public void IsDualPlane_WithDualPlaneBlock_ShouldReturnTrue() + { + var block = PhysicalBlock.Create(0x0000000001FE0005FFUL); + + block.IsDualPlane.Should().BeTrue(); + } + + [Fact] + public void IsDualPlane_WithErrorBlock_ShouldReturnFalse() + { + var block = PhysicalBlock.Create(ErrorBlock); + + block.IsDualPlane.Should().BeFalse(); + } + + [Fact] + public void IsDualPlane_WithInvalidEncoding_ShouldReturnFalse() + { + var block = PhysicalBlock.Create(0x0000000001FE000573UL); + + block.IsDualPlane.Should().BeFalse(); + block.GetWeightGridDimensions().Should().BeNull(); + block.IdentifyInvalidEncodingIssues().Should().Contain("Invalid block encoding"); + } + + [Fact] + public void IsDualPlane_WithValidSinglePlaneBlock_ShouldHaveValidEncoding() + { + var block = PhysicalBlock.Create(0x0000000001FE000108UL); + + block.IsDualPlane.Should().BeFalse(); + block.IsIllegalEncoding.Should().BeFalse(); + } + + [Fact] + public void GetWeightBitCount_WithStandardBlock_ShouldReturn90() + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + var bitCount = block.GetWeightBitCount(); + + bitCount.Should().Be(90); + } + + [Fact] + public void GetWeightBitCount_WithDualPlaneBlock_ShouldReturn90() + { + var block = PhysicalBlock.Create(0x0000000001FE0005FFUL); + + var bitCount = block.GetWeightBitCount(); + + bitCount.Should().Be(90); + } + + [Fact] + public void GetWeightBitCount_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var bitCount = block.GetWeightBitCount(); + + bitCount.Should().BeNull(); + } + + [Fact] + public void GetWeightBitCount_WithVoidExtent_ShouldReturnNull() + { + var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + + var bitCount = block.GetWeightBitCount(); + + bitCount.Should().BeNull(); + } + + [Fact] + public void GetWeightBitCount_WithInvalidBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(0x0000000001FE000573UL); + + var bitCount = block.GetWeightBitCount(); + + bitCount.Should().BeNull(); + } + + [Fact] + public void GetWeightStartBit_WithNonSharedCEM_ShouldReturn64() + { + var block = PhysicalBlock.Create(0x4000000000800D44UL); + + var startBit = block.GetWeightStartBit(); + + startBit.Should().Be(64); + } + + [Fact] + public void GetWeightStartBit_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var startBit = block.GetWeightStartBit(); + + startBit.Should().BeNull(); + } + + [Fact] + public void GetWeightStartBit_WithVoidExtent_ShouldReturnNull() + { + var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + + var startBit = block.GetWeightStartBit(); + + startBit.Should().BeNull(); + } + + [Fact] + public void IsIllegalEncoding_WithValidBlocks_ShouldReturnFalse() + { + PhysicalBlock.Create(0x0000000001FE000173UL).IsIllegalEncoding.Should().BeFalse(); + PhysicalBlock.Create(0x0000000001FE0005FFUL).IsIllegalEncoding.Should().BeFalse(); + PhysicalBlock.Create(0x0000000001FE000108UL).IsIllegalEncoding.Should().BeFalse(); + } + + [Fact] + public void IdentifyInvalidEncodingIssues_WithZeroBlock_ShouldReturnReservedBlockModeError() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var error = block.IdentifyInvalidEncodingIssues(); + + error.Should().NotBeNull(); + error.Should().Contain("Invalid block encoding"); + } + + [Fact] + public void IdentifyInvalidEncodingIssues_WithTooManyWeightBits_ShouldReturnError() + { + var block = PhysicalBlock.Create(0x0000000001FE000573UL); + + var error = block.IdentifyInvalidEncodingIssues(); + + error.Should().NotBeNull(); + error.Should().Contain("Invalid block encoding"); + } + + [Theory] + [InlineData(0x0000000001FE0005A8UL)] + [InlineData(0x0000000001FE000588UL)] + [InlineData(0x0000000001FE00002UL)] + public void IdentifyInvalidEncodingIssues_WithInvalidBlocks_ShouldReturnError(ulong blockBits) + { + var block = PhysicalBlock.Create(blockBits); + + var error = block.IdentifyInvalidEncodingIssues(); + + error.Should().NotBeNull(); + } + + [Fact] + public void IdentifyInvalidEncodingIssues_WithDualPlaneFourPartitions_ShouldReturnError() + { + var block = PhysicalBlock.Create(0x000000000000001D1FUL); + + var error = block.IdentifyInvalidEncodingIssues(); + + block.GetPartitionsCount().Should().BeNull(); + error.Should().NotBeNull(); + error.Should().Contain("Invalid block encoding"); + } + + [Theory] + [InlineData(0x000000000000000973UL)] + [InlineData(0x000000000000001173UL)] + [InlineData(0x000000000000001973UL)] + public void GetPartitionsCount_WithInvalidPartitionConfig_ShouldReturnNull(ulong blockBits) + { + var block = PhysicalBlock.Create(blockBits); + + var partitions = block.GetPartitionsCount(); + + partitions.Should().BeNull(); + } + + [Theory] + [InlineData(0x0000000001FE000173UL, 1)] + [InlineData(0x0000000001FE0005FFUL, 1)] + [InlineData(0x0000000001FE000108UL, 1)] + [InlineData(0x4000000000800D44UL, 2)] + public void GetPartitionsCount_WithValidBlock_ShouldReturnExpectedCount(ulong blockBits, int expectedCount) + { + var block = PhysicalBlock.Create(blockBits); + + var count = block.GetPartitionsCount(); + + count.Should().Be(expectedCount); + } + + [Theory] + [InlineData(0x4000000000FFED44UL, 0x3FF)] + [InlineData(0x4000000000AAAD44UL, 0x155)] + public void GetPartitionId_WithValidMultiPartitionBlock_ShouldReturnExpectedId(ulong blockBits, int expectedId) + { + var block = PhysicalBlock.Create(blockBits); + + var partitionId = block.GetPartitionId(); + + partitionId.Should().Be(expectedId); + } + + [Fact] + public void GetPartitionId_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var partitionId = block.GetPartitionId(); + + partitionId.Should().BeNull(); + } + + [Fact] + public void GetPartitionId_WithVoidExtent_ShouldReturnNull() + { + var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + + var partitionId = block.GetPartitionId(); + + partitionId.Should().BeNull(); + } + + [Fact] + public void GetEndpointMode_WithFourPartitionBlock_ShouldReturnSameModeForAll() + { + var block = PhysicalBlock.Create(0x000000000000001961UL); + + for (int i = 0; i < 4; ++i) + { + var mode = block.GetEndpointMode(i); + mode.Should().Be(ColorEndpointMode.LdrLumaDirect); + } + } + + [Fact] + public void GetEndpointMode_WithNonSharedCEM_ShouldReturnDifferentModes() + { + var block = PhysicalBlock.Create(0x4000000000800D44UL); + + var mode0 = block.GetEndpointMode(0); + var mode1 = block.GetEndpointMode(1); + + mode0.Should().Be(ColorEndpointMode.LdrLumaDirect); + mode1.Should().Be(ColorEndpointMode.LdrLumaBaseOffset); + } + + [Fact] + public void GetEndpointMode_WithVoidExtent_ShouldReturnNull() + { + var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + + var mode = block.GetEndpointMode(0); + + mode.Should().BeNull(); + } + + [Theory] + [InlineData(1)] + [InlineData(-1)] + [InlineData(100)] + public void GetEndpointMode_WithInvalidPartitionIndex_ShouldReturnNull(int index) + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + var mode = block.GetEndpointMode(index); + + mode.Should().BeNull(); + } + + [Fact] + public void GetColorValuesCount_WithStandardBlock_ShouldReturn2() + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + var count = block.GetColorValuesCount(); + + count.Should().Be(2); + } + + [Fact] + public void GetColorValuesCount_WithVoidExtent_ShouldReturn4() + { + var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + + var count = block.GetColorValuesCount(); + + count.Should().Be(4); + } + + [Fact] + public void GetColorValuesCount_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var count = block.GetColorValuesCount(); + + count.Should().BeNull(); + } + + [Fact] + public void GetColorBitCount_WithStandardBlock_ShouldReturn16() + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + var bitCount = block.GetColorBitCount(); + + bitCount.Should().Be(16); + } + + [Fact] + public void GetColorBitCount_WithVoidExtent_ShouldReturn64() + { + var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + + var bitCount = block.GetColorBitCount(); + + bitCount.Should().Be(64); + } + + [Fact] + public void GetColorBitCount_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var bitCount = block.GetColorBitCount(); + + bitCount.Should().BeNull(); + } + + [Fact] + public void GetColorValuesRange_WithStandardBlock_ShouldReturn255() + { + var block = PhysicalBlock.Create(0x0000000001FE000173UL); + + var range = block.GetColorValuesRange(); + + range.Should().Be(255); + } + + [Fact] + public void GetColorValuesRange_WithVoidExtent_ShouldReturnMaxUInt16() + { + var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + + var range = block.GetColorValuesRange(); + + range.Should().Be((1 << 16) - 1); + } + + [Fact] + public void GetColorValuesRange_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var range = block.GetColorValuesRange(); + + range.Should().BeNull(); + } + + [Theory] + [InlineData(0x0000000001FE000173UL, 17)] + [InlineData(0x0000000001FE0005FFUL, 17)] + [InlineData(0x0000000001FE000108UL, 17)] + [InlineData(0x4000000000FFED44UL, 29)] + [InlineData(0x4000000000AAAD44UL, 29)] + [InlineData(0xFFF8003FFE000DFCUL, 64)] + public void GetColorStartBit_WithVariousBlocks_ShouldReturnExpectedValue(ulong blockBits, int expectedStartBit) + { + var block = PhysicalBlock.Create(blockBits); + + var startBit = block.GetColorStartBit(); + + startBit.Should().Be(expectedStartBit); + } + + [Fact] + public void GetColorStartBit_WithErrorBlock_ShouldReturnNull() + { + var block = PhysicalBlock.Create(ErrorBlock); + + var startBit = block.GetColorStartBit(); + + startBit.Should().BeNull(); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/QuantizationTests.cs b/tests/ImageSharp.Textures.Astc.Tests/QuantizationTests.cs new file mode 100644 index 00000000..f6603ace --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/QuantizationTests.cs @@ -0,0 +1,390 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class QuantizationTests +{ + [Fact] + public void QuantizeCEValueToRange_WithMaxValue_ShouldNotExceedRange() + { + for (int range = Quantization.EndpointRangeMinValue; range <= byte.MaxValue; range++) + { + Quantization.QuantizeCEValueToRange(byte.MaxValue, range).Should().BeLessThanOrEqualTo(range); + } + } + + [Fact] + public void QuantizeWeightToRange_WithMaxValue_ShouldNotExceedRange() + { + for (int range = 1; range < Quantization.WeightRangeMaxValue; range++) + { + Quantization.QuantizeWeightToRange(64, range).Should().BeLessThanOrEqualTo(range); + } + } + + [Fact] + public void QuantizeCEValueToRange_WithVariousValues_ShouldNotExceedRange() + { + var ranges = BoundedIntegerSequenceCodec.MaxRanges; + var testValues = new[] { 0, 4, 15, 22, 66, 91, 126 }; + + foreach (var range in ranges.Where(r => r >= Quantization.EndpointRangeMinValue)) + { + foreach (var value in testValues) + { + Quantization.QuantizeCEValueToRange(value, range).Should().BeLessThanOrEqualTo(range); + } + } + } + + [Fact] + public void QuantizeWeightToRange_WithVariousValues_ShouldNotExceedRange() + { + var ranges = BoundedIntegerSequenceCodec.MaxRanges; + var testValues = new[] { 0, 4, 15, 22 }; + + foreach (var range in ranges.Where(r => r <= Quantization.WeightRangeMaxValue)) + { + foreach (var value in testValues) + { + Quantization.QuantizeWeightToRange(value, range).Should().BeLessThanOrEqualTo(range); + } + } + } + + [Fact] + public void QuantizeWeight_ThenUnquantize_ShouldReturnOriginalQuantizedValue() + { + var ranges = BoundedIntegerSequenceCodec.MaxRanges; + + foreach (var range in ranges.Where(r => r <= Quantization.WeightRangeMaxValue)) + { + for (int quantizedValue = 0; quantizedValue <= range; ++quantizedValue) + { + var unquantized = Quantization.UnquantizeWeightFromRange(quantizedValue, range); + var requantized = Quantization.QuantizeWeightToRange(unquantized, range); + + requantized.Should().Be(quantizedValue); + } + } + } + + [Fact] + public void QuantizeCEValue_ThenUnquantize_ShouldReturnOriginalQuantizedValue() + { + var ranges = BoundedIntegerSequenceCodec.MaxRanges; + + foreach (var range in ranges.Where(r => r >= Quantization.EndpointRangeMinValue)) + { + for (int quantizedValue = 0; quantizedValue <= range; ++quantizedValue) + { + var unquantized = Quantization.UnquantizeCEValueFromRange(quantizedValue, range); + var requantized = Quantization.QuantizeCEValueToRange(unquantized, range); + + requantized.Should().Be(quantizedValue); + } + } + } + + [Theory] + [InlineData(2, 7)] + [InlineData(7, 7)] + [InlineData(39, 63)] + [InlineData(66, 79)] + [InlineData(91, 191)] + [InlineData(126, 255)] + [InlineData(255, 255)] + public void UnquantizeCEValueFromRange_ShouldProduceValidByteValue(int quantizedValue, int range) + { + var result = Quantization.UnquantizeCEValueFromRange(quantizedValue, range); + + result.Should().BeLessThan(256); + } + + [Theory] + [InlineData(0, 1)] + [InlineData(2, 7)] + [InlineData(7, 7)] + [InlineData(29, 31)] + public void UnquantizeWeightFromRange_ShouldNotExceed64(int quantizedValue, int range) + { + var result = Quantization.UnquantizeWeightFromRange(quantizedValue, range); + + result.Should().BeLessThanOrEqualTo(64); + } + + [Fact] + public void Quantize_WithDesiredRange_ShouldMatchExpectedRangeOutput() + { + var ranges = BoundedIntegerSequenceCodec.MaxRanges; + int rangeIndex = 0; + + for (int desiredRange = 1; desiredRange <= byte.MaxValue; ++desiredRange) + { + while (rangeIndex + 1 < ranges.Length && ranges[rangeIndex + 1] <= desiredRange) + ++rangeIndex; + + int expectedRange = ranges[rangeIndex]; + + // Test CE values + if (desiredRange >= Quantization.EndpointRangeMinValue) + { + var testValues = new[] { 0, 13, 173, 208, 255 }; + foreach (var value in testValues) + { + Quantization.QuantizeCEValueToRange(value, desiredRange) + .Should().Be(Quantization.QuantizeCEValueToRange(value, expectedRange)); + } + } + + // Test weight values + if (desiredRange <= Quantization.WeightRangeMaxValue) + { + var testValues = new[] { 0, 12, 23, 63 }; + foreach (var value in testValues) + { + Quantization.QuantizeWeightToRange(value, desiredRange) + .Should().Be(Quantization.QuantizeWeightToRange(value, expectedRange)); + } + } + } + + rangeIndex.Should().Be(ranges.Length - 1); + } + + [Fact] + public void QuantizeCEValueToRange_WithRangeByteMax_ShouldBeIdentity() + { + for (int value = byte.MinValue; value <= byte.MaxValue; value++) + { + Quantization.QuantizeCEValueToRange(value, byte.MaxValue).Should().Be(value); + } + } + + [Fact] + public void QuantizeCEValueToRange_ShouldBeMonotonicIncreasing() + { + for (int numBits = 3; numBits < 8; numBits++) + { + int range = (1 << numBits) - 1; + int lastQuantizedValue = -1; + + for (int value = byte.MinValue; value <= byte.MaxValue; value++) + { + int quantizedValue = Quantization.QuantizeCEValueToRange(value, range); + + quantizedValue.Should().BeGreaterThanOrEqualTo(lastQuantizedValue); + lastQuantizedValue = quantizedValue; + } + + lastQuantizedValue.Should().Be(range); + } + } + + [Fact] + public void QuantizeWeightToRange_ShouldBeMonotonicallyIncreasing() + { + for (int numBits = 3; numBits < 8; ++numBits) + { + int range = (1 << numBits) - 1; + + if (range > Quantization.WeightRangeMaxValue) + continue; + + int lastQuantizedValue = -1; + + for (int value = 0; value <= 64; ++value) + { + int quantizedValue = Quantization.QuantizeWeightToRange(value, range); + + quantizedValue.Should().BeGreaterThanOrEqualTo(lastQuantizedValue); + lastQuantizedValue = quantizedValue; + } + + lastQuantizedValue.Should().Be(range); + } + } + + [Fact] + public void QuantizeCEValueToRange_WithSmallBitRanges_ShouldQuantizeLowValuesToZero() + { + for (int numBits = 1; numBits <= 8; ++numBits) + { + int range = (1 << numBits) - 1; + + if (range < Quantization.EndpointRangeMinValue) + continue; + + const int cevBits = 8; + int halfMaxQuantBits = Math.Max(0, cevBits - numBits - 1); + int largestCevToZero = (1 << halfMaxQuantBits) - 1; + + Quantization.QuantizeCEValueToRange(largestCevToZero, range).Should().Be(0); + } + } + + [Fact] + public void QuantizeWeightToRange_WithSmallBitRanges_ShouldQuantizeLowValuesToZero() + { + for (int numBits = 1; numBits <= 8; numBits++) + { + int range = (1 << numBits) - 1; + + if (range > Quantization.WeightRangeMaxValue) + continue; + + const int weightBits = 6; + int halfMaxQuantBits = Math.Max(0, weightBits - numBits - 1); + int largestWeightToZero = (1 << halfMaxQuantBits) - 1; + + Quantization.QuantizeWeightToRange(largestWeightToZero, range).Should().Be(0); + } + } + + [Fact] + public void UnquantizeWeightFromRange_WithQuintRange_ShouldMatchExpected() + { + var values = new List { 4, 6, 4, 6, 7, 5, 7, 5 }; + var quintExpected = new List { 14, 21, 14, 21, 43, 50, 43, 50 }; + + var quantized = values.Select(v => Quantization.UnquantizeWeightFromRange(v, 9)).ToList(); + + quantized.Should().Equal(quintExpected); + } + + [Fact] + public void UnquantizeWeightFromRange_WithTritRange_ShouldMatchExpected() + { + var values = new List { 4, 6, 4, 6, 7, 5, 7, 5 }; + var tritExpected = new List { 5, 23, 5, 23, 41, 59, 41, 59 }; + + var quantized = values.Select(v => Quantization.UnquantizeWeightFromRange(v, 11)).ToList(); + + quantized.Should().Equal(tritExpected); + } + + [Fact] + public void QuantizeCEValueToRange_WithInvalidMinRange_ShouldThrowArgumentOutOfRangeException() + { + for (int range = 0; range < Quantization.EndpointRangeMinValue; range++) + { + var action = () => Quantization.QuantizeCEValueToRange(0, range); + action.Should().Throw(); + } + } + + [Fact] + public void UnquantizeCEValueFromRange_WithInvalidMinRange_ShouldThrowArgumentOutOfRangeException() + { + for (int range = 0; range < Quantization.EndpointRangeMinValue; range++) + { + var action = () => Quantization.UnquantizeCEValueFromRange(0, range); + action.Should().Throw(); + } + } + + [Fact] + public void QuantizeWeightToRange_WithZeroRange_ShouldThrowArgumentOutOfRangeException() + { + var action = () => Quantization.QuantizeWeightToRange(0, 0); + + action.Should().Throw(); + } + + [Fact] + public void UnquantizeWeightFromRange_WithZeroRange_ShouldThrowArgumentOutOfRangeException() + { + var action = () => Quantization.UnquantizeWeightFromRange(0, 0); + + action.Should().Throw(); + } + + [Theory] + [InlineData(-1, 10)] + [InlineData(256, 7)] + [InlineData(10000, 17)] + public void QuantizeCEValueToRange_WithInvalidValue_ShouldThrowArgumentOutOfRangeException(int value, int range) + { + var action = () => Quantization.QuantizeCEValueToRange(value, range); + + action.Should().Throw(); + } + + [Theory] + [InlineData(-1, 10)] + [InlineData(8, 7)] + [InlineData(-1000, 17)] + public void UnquantizeCEValueFromRange_WithInvalidValue_ShouldThrowArgumentOutOfRangeException(int value, int range) + { + var action = () => Quantization.UnquantizeCEValueFromRange(value, range); + + action.Should().Throw(); + } + + [Theory] + [InlineData(0, -7)] + [InlineData(0, 257)] + public void QuantizeCEValueToRange_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int value, int range) + { + var action = () => Quantization.QuantizeCEValueToRange(value, range); + + action.Should().Throw(); + } + + [Theory] + [InlineData(0, -17)] + [InlineData(0, 256)] + public void UnquantizeCEValueFromRange_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int value, int range) + { + var action = () => Quantization.UnquantizeCEValueFromRange(value, range); + + action.Should().Throw(); + } + + [Theory] + [InlineData(-1, 10)] + [InlineData(256, 7)] + [InlineData(10000, 17)] + public void QuantizeWeightToRange_WithInvalidValue_ShouldThrowArgumentOutOfRangeException(int value, int range) + { + var action = () => Quantization.QuantizeWeightToRange(value, range); + + action.Should().Throw(); + } + + [Theory] + [InlineData(-1, 10)] + [InlineData(8, 7)] + [InlineData(-1000, 17)] + public void UnquantizeWeightFromRange_WithInvalidValue_ShouldThrowArgumentOutOfRangeException(int value, int range) + { + var action = () => Quantization.UnquantizeWeightFromRange(value, range); + + action.Should().Throw(); + } + + [Theory] + [InlineData(0, -7)] + [InlineData(0, 32)] + public void QuantizeWeightToRange_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int value, int range) + { + var action = () => Quantization.QuantizeWeightToRange(value, range); + + action.Should().Throw(); + } + + [Theory] + [InlineData(0, -17)] + [InlineData(0, 64)] + public void UnquantizeWeightFromRange_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int value, int range) + { + var action = () => Quantization.UnquantizeWeightFromRange(value, range); + + action.Should().Throw(); + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/A.png b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/A.png new file mode 100644 index 00000000..7d1a3678 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/A.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a31a312bc20c9696a17e2857526b42b0e9b7d024d624fe91a417b12151a9915b +size 53774 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/FlightHelmet_baseColor.png b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/FlightHelmet_baseColor.png new file mode 100644 index 00000000..121fa21d --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/FlightHelmet_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e5567daf7a3ac83f6ebc04605f3605ac17c19a153b6b8aadd2596690209c34c +size 2778518 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/filelist.txt b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/filelist.txt new file mode 100644 index 00000000..56558836 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/filelist.txt @@ -0,0 +1,6 @@ +posx.jpg +negx.jpg +posy.jpg +negy.jpg +posz.jpg +negz.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negx.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negx.jpg new file mode 100644 index 00000000..4b420e53 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:681d4598e743ff5ada3e8679c7241b0a338afedee094289fbc88818c44c37c97 +size 743985 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negy.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negy.jpg new file mode 100644 index 00000000..ce1c8ac5 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0fe09207f833fd7c9939ebaa28921da832f48e5b502135ff4e5f76e98bd2fd3 +size 586742 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negz.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negz.jpg new file mode 100644 index 00000000..361149b8 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negz.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1eb32c8055b52ddf230bfe4846f78fa4ce03600541e7d3569d893dda0c14d3d7 +size 646123 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posx.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posx.jpg new file mode 100644 index 00000000..3ae03904 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a90ffea9ac8bf9bbfab93aa25ff23222a84decdc7d96c58bc6edbb1e9c9cce7 +size 721206 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posy.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posy.jpg new file mode 100644 index 00000000..49fd8974 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8e77108e821e7f11e16937152a700b6995ee996b7fedb50a4d6e9ee7d3f04db +size 674410 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posz.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posz.jpg new file mode 100644 index 00000000..227469be --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posz.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6f08f424f2c93f45c1525f756b1365c0a89651ca107d5fc9649b4370e25bb4b +size 616391 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/readme.txt b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/readme.txt new file mode 100644 index 00000000..8b404c27 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/readme.txt @@ -0,0 +1,13 @@ +Author +====== + +This is the work of Emil Persson, aka Humus. +http://www.humus.name + + + +License +======= + +This work is licensed under a Creative Commons Attribution 3.0 Unported License. +http://creativecommons.org/licenses/by/3.0/ diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg new file mode 100644 index 00000000..56f50812 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7824d03409aa2059cf00de8c319b56df3a6805d9f76ea7a7c6242da0ff101ed +size 189930 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg new file mode 100644 index 00000000..88b5d484 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fedb7076a37938b044428ac5ac608ff6f665358773c51c5412a8d22e9d1175e0 +size 195262 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg new file mode 100644 index 00000000..1a2950f1 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35a10620acf7e73ffb55132b9e814ddbbaa45df1c445eefcef06b7458cb29967 +size 108801 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_height.png b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_height.png new file mode 100644 index 00000000..a0821106 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2d24186ce5a5b9c718ad7b6365a7e22680ce85abfdbed8a37ad9e4205d4d012 +size 456564 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal.jpg new file mode 100644 index 00000000..27c64184 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be3169943652ad478fe1f38569feb4aa1da0ff6db37a4cd22fec063fe8aea50f +size 329643 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png new file mode 100644 index 00000000..82ffa417 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d525152f126ca682ac2ed54ed01a28300c88072582bc005d0846f556d99fbe24 +size 2143908 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg new file mode 100644 index 00000000..a48d7d33 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:808018bf8dde084ac3e23493c3e26ea7c85335551cab20681454f6702d29eafc +size 118946 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/readme.txt b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/readme.txt new file mode 100644 index 00000000..649d4903 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/readme.txt @@ -0,0 +1,11 @@ +Author +====== + +This is the work of Katsuagi from https://3dtextures.me. + + +License +======= + +This work is licensed under a Creative Commons Zero 1.0 +http://creativecommons.org/publicdomain/zero/1.0/legalcode diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/LA.png b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/LA.png new file mode 100644 index 00000000..85750bea --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/LA.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c8c1a3ec4c9dce1a7082229f58b596fc4030b6e92a184399f2c2e0abdd0a6ce +size 6996 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/R.png b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/R.png new file mode 100644 index 00000000..0293e900 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/R.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8247b0f7fe68e3e79383be1e54a10e8c2ac9227c0d938f628604de123799211e +size 3857 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/RG.png b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/RG.png new file mode 100644 index 00000000..5b70f533 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/RG.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d62d83441c65a4e060fa5574ae485d71bd50ddad08ae201aa3bd286ae58fb230 +size 5630 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/filelist.txt b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/filelist.txt new file mode 100644 index 00000000..56558836 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/filelist.txt @@ -0,0 +1,6 @@ +posx.jpg +negx.jpg +posy.jpg +negy.jpg +posz.jpg +negz.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negx.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negx.jpg new file mode 100644 index 00000000..e6140aa5 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b29bbd856363b3095f3447a1f47419b8a4d94032e3b8e69377a4f002b064d9b9 +size 1039406 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negy.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negy.jpg new file mode 100644 index 00000000..97c86925 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63401877a11eeef4016b947e46554f1e6e61ad60db7d5e7bebdd378d1751bb1d +size 994070 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negz.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negz.jpg new file mode 100644 index 00000000..b8f54d55 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negz.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b52d48844e642f93443922088143cb466a7b46c9b3d31bb2b6a3f4b91c68787c +size 911570 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posx.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posx.jpg new file mode 100644 index 00000000..6682af4f --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posx.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d28f2c6d384e919b93cf94d625677f3231032e691fa3832926f99f8752a8c56 +size 1036660 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posy.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posy.jpg new file mode 100644 index 00000000..7bdeadbd --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:823f80f5dafac7c2dccf0b192867433e4e0f27d195541da02e99684ec28f79a4 +size 631474 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posz.jpg b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posz.jpg new file mode 100644 index 00000000..7613d5ee --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posz.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76cfa0cc922ac66794e5939b1f000edc051fc84200ef4663c4de02766e2f45d1 +size 871498 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/readme.txt b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/readme.txt new file mode 100644 index 00000000..8b404c27 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/readme.txt @@ -0,0 +1,13 @@ +Author +====== + +This is the work of Emil Persson, aka Humus. +http://www.humus.name + + + +License +======= + +This work is licensed under a Creative Commons Attribution 3.0 Unported License. +http://creativecommons.org/licenses/by/3.0/ diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_4x4.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_4x4.bmp new file mode 100644 index 00000000..27dbb5ce --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_4x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:960c183b5a1f139466dea2d3196bc58b426935545bd203793d2a745fd8dbc29e +size 262282 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_5x5.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_5x5.bmp new file mode 100644 index 00000000..9dbc9152 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_5x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:959bce6386ac4ee035cfbbbdbf9537e3f2a5ebd5795fbd531dea301a4c6f74ba +size 262282 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_6x6.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_6x6.bmp new file mode 100644 index 00000000..841123a8 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_6x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e70d5d7a9eb7c280f397eefe5c71a78530508fde1caebabe9fda39a91aa5847 +size 262282 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_8x8.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_8x8.bmp new file mode 100644 index 00000000..719856d8 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_8x8.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3ca7675d2e01e56fc63f2a2b59b2c852d2b5793e21a2e1125ccf81c8cdf3431 +size 262282 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x10.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x10.bmp new file mode 100644 index 00000000..6be39794 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x10.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:713880d96d562a92648bb306d57cddfac45805e99853dec6c7cf7ef461cfd795 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x5.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x5.bmp new file mode 100644 index 00000000..fc7cd4e9 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f661da916564a6c8dee8cd6ae6961c95a48416788cf0f52649a31b5ff36bafb5 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x6.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x6.bmp new file mode 100644 index 00000000..e41db3ff --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa618100f436836c42104732573952180f39eafe3c64b87a99b264fb284487dc +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x8.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x8.bmp new file mode 100644 index 00000000..33c468a4 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x8.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6be48ee1e53b3cc868a23dd3ca9510f8af3f8f47ae1417aee95daed15581a4e3 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x10.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x10.bmp new file mode 100644 index 00000000..2e863d5e --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x10.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f753526bd5c19656a44b837fac0c535470a7c5b9bef0c4cdbc8b3987fdf348c4 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x12.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x12.bmp new file mode 100644 index 00000000..3a6977ff --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x12.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7314cdfe475599d2c55645fad6b51379a129447931ae6b82538711942ecbee4 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_4x4.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_4x4.bmp new file mode 100644 index 00000000..c5d83d2e --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_4x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a16fbf67b4bb9a0ec3f3e3b78006f39610f7869e0cdb4bd945d660957d1090e +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x4.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x4.bmp new file mode 100644 index 00000000..9dd29e11 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd3a8f13222fa52a43678b3d4269ac5dee826f9f2ffb69061650e5c4300fb1e6 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x5.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x5.bmp new file mode 100644 index 00000000..6f9543fc --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:273af3da91ccbfac3de9e04df9582e13808feda0fb263f52389238b96165105d +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x5.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x5.bmp new file mode 100644 index 00000000..8426e2c2 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2623e574e64e88268571ec139aea89607fa7a4b149bb740cdcd37c55aac0d0b6 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x6.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x6.bmp new file mode 100644 index 00000000..a31d92ef --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b31d247567aa583f7c6cd1b0dc3ec311f97a2fe7893a2c4d82916786717ac622 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x5.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x5.bmp new file mode 100644 index 00000000..6cb11fdf --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x5.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d87633a26957084bec6973eb41f8946c5b7bccf2924e1c90c1690b4f926cf74 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x6.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x6.bmp new file mode 100644 index 00000000..f0de8688 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6073c67e6b13bdc2688a2217119eb6bdee85f7507ce98b2d466fbf8db90b37b1 +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x8.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x8.bmp new file mode 100644 index 00000000..d1f867de --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x8.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2f0ec842b80a95bffc742c66b2e19b85faf7736f0737f9a9da86ae965c839de +size 3210 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_12x12.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_12x12.bmp new file mode 100644 index 00000000..29f43633 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_12x12.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d70f50efa5c0bfc990e024be872ed270f43874e34a43a4d7d8a6fbfbb7f1ddc +size 193674 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_4x4.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_4x4.bmp new file mode 100644 index 00000000..3874c6d0 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_4x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ddf8ba01848174bdc1eadfaa4da351e9a41cf8acc238813dfd260219423f785 +size 193674 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_5x4.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_5x4.bmp new file mode 100644 index 00000000..cf14c284 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_5x4.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08d8d5d3181d8a12ec47ed00c477f6fe9f5d93069a0bf2ebfe1b9ce17b2f8f67 +size 193674 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_6x6.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_6x6.bmp new file mode 100644 index 00000000..1e9dc2e3 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_6x6.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54f44a68a25232bd4624400e93385ac3cc332a3cda293cb617911e42630d889a +size 193674 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_8x8.bmp b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_8x8.bmp new file mode 100644 index 00000000..9ec02dd9 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_8x8.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d612273927b74c4b07ee99ffa5028572b28689fc23f24c4c32ac40c3d6b7a7e6 +size 193674 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.astc new file mode 100644 index 0000000000000000000000000000000000000000..57d5e89784e61db55679b1ee30c6b6c411d68b2a GIT binary patch literal 32 ecmWe$y)cH2jggUo0fPSghkypV1bYVu1{(mRx(nI> literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.exr b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.exr new file mode 100644 index 00000000..c1f5f61c --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e09a64448ffd3e0a4da4b4c46836242d1446ecdfcd5a15f5feb6a17ba9b4100 +size 335 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.astc new file mode 100644 index 0000000000000000000000000000000000000000..2786dedbc8942c442f5a1de8f0777b6a3cac687b GIT binary patch literal 32 gcmWe$y)cH2jggUo0fPSg{SOA(+H2P`F#P`y0H$OOZ2$lO literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.png b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.png new file mode 100644 index 00000000..e1eb6355 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:335e2a1d19f3d572d5664c899853f4e7eb9caa595ea8888e5f2cddb85f64c9da +size 509 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-complex.exr b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-complex.exr new file mode 100644 index 00000000..9a3d6700 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-complex.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6a16a419ec8f487be26cb223c3770923a03e22d9327ae2903c1888761217107 +size 825 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-tile.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-tile.astc new file mode 100644 index 0000000000000000000000000000000000000000..aa2157617a289760d1566fee5591b85589675c0c GIT binary patch literal 80 ycmWe$y)cG_g^`1S0f-nG82;Bxa1@^uWcH8#>s z$}EX5%1MncN>57V;?j*&uu#yBP_W=)Vq)NEVQBf+!O+0~q?^g&|7&q*`PXs)h#6X% LS{lG+kSPWL0Dny8 literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/ldr-tile.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/ldr-tile.astc new file mode 100644 index 0000000000000000000000000000000000000000..ab785780bd530eeb8aeacbc32c849287e92634c3 GIT binary patch literal 80 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�#<4OZ5m3ax`x~GM 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 F{{jEVJFfr$ literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_5x5.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_5x5.astc new file mode 100644 index 0000000000000000000000000000000000000000..2831c90ed5159bea214808788e7ffc257cb529fc GIT binary patch literal 43280 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 literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_8x8.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_8x8.astc new file mode 100644 index 0000000000000000000000000000000000000000..a8805f2851754a6e72240edcc481bd5b9d1c875c GIT binary patch literal 16400 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 literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkerboard.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkerboard.astc new file mode 100644 index 0000000000000000000000000000000000000000..79acdca3732f0b72f25640c5df6b4b188aa1b847 GIT binary patch literal 80 icmWe$y)cG@gHeEi0f-nG7+hEx{sAe5&{YsfDjxtEr564G literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_10.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_10.astc new file mode 100644 index 0000000000000000000000000000000000000000..e3b196578c32a6a1d8dc9b911c1a164bb2fc5310 GIT binary patch literal 1616 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 literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_12.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_12.astc new file mode 100644 index 0000000000000000000000000000000000000000..a82583a9ed5bb9ef978b8f11021695d78527b236 GIT binary patch literal 2320 zcmZXVdrVVz6vuy^h*lRX7AOL4+JXfNL1nX$2p!UbwAxmj3WE_16S+|#7)2(JBAJ6p zcqu*sYq7Psf#Prx7{<-XkjO(AigQfHL%^XcP8}>h?tbT%>up)XKfjz$&-tG3IX!T{ z(Z$T3&Dw$x{$nBZ>NPp_;q!-wNzxXTk_M@~6^9Ta5lP3}+9D#{-O0D_swxi;3}8Mg zOr5|=ox3l^+TN|6r@NRAn4h948;gzEO@}B8@ zcXwdmjvXXPAm2AR(onPBagwxgaA{IzxDN|+o0DDr3kC^PT4?A}#6mYD2O$DQb*kSp zcSpU|G25$c^t^@}*;HJF!t{hV^^}LFy8&uq-c|KP`WO9uDSL(bRTX8$|&?~BnX(2B?P*8i?97ngPG7`~t& zC1u|}@KfRUC*QOQc+2_n`LVGg5yQ*n3Wd=KdXBmt&sOGPy;gb(3W9^1niyUvymE!b z0$w$ml<`jHbpHACprC8l7=CC-sZ34=JuTtoy#w)>x6-fICnna`GQ3WA|Nc0huPq7- z;aFww@q9Bz`!ZUgA1Y2D@y+1!6$8K{(32~j|9GU?{lblJ9l-w&1Sh|D z6Yth&G*^89AyD(`^6zH+LFom4_KKRsEm7;2r!-M|L}+8^?+g4dF!?2~=Y76wnC7?L zcJooZYCv^dHiv^(jzDR*uQ&$G!>Or#qW8neDJ#p~9{OFLj`}=Xp|<5d1O=H)@$rm5 zE-q)zEL;fl5=REl@LPlHSc9cLp5f!-+S_5iwrJzey=4KEUzUD)c_k)-pR^vW zHYaE0O5i1(9NsR_Yo%v&G%s(~EQTK%dim1P5qNRS`U##l@Rs>YODikq&SiL`vA@5g z19*Yb;V0H<$}fw1WoLKj5LiU(F&fLt3 z^?So*(RIN8pbeg+^Y!-X^*%nZKTmH)Pt;O+sr@OMOJw#k<+ za~_Ei6}tu$4B``q?_V7fkW@dAk3gb5`BF|jJYH6oM#Jz94xynrIlzmY^G|amn77)`{QQm%t(M`%Vv#5! z0{Eosn?3Z6IK36$-*0ap8_V#mt$lqXBhYVLz=a<>mQa3K^t)+OU0qQT!$(9MIZ{&t zJX>ET^xQI?x3Ot&hu9_yv%yRB$( zUg)Je?{b@#m`wQo5NP{#l}F=M9H;h$K)Mr!*ZkV4`gp#wqQzsnI}_?gXZyt_6YNj* z-Q=+)C+U0yGE6Q#vegdr=6zDfsLv|8gTG!j(`k@{Yzf4cmq&Rh89wCJ#GW)tFSSpT z#9CQr@tx1!#KxKTOB-#v*p&%#;7#|dRwpM11%W@h_yJ{lBj~gIefjwbg%2KB^3BcW z`=h&CIOcZ(<5u$;8+-9$?OKLUPj6{~{lovi{*64Wnm^rtgTdQdC}en@E-fuE5cnsn VkN=aoALLrzlav&NLLwo_{{U*k>68Ef literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_4.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_4.astc new file mode 100644 index 0000000000000000000000000000000000000000..ac60716458fc4b169f60f9f1f045d58ad1f34e48 GIT binary patch literal 272 zcmWe$y)cG@gHeHj0f-nG7+6H5kNVZBK2z^FUMOSH_c${kCVef-67gL}n*RUiXJByQ zGztibba?!U<->;${QUfx9n1m3PhzhN7YT#qT{F}KRP3AlI(r#dfzteJ9WDZIo*!3T zcVv_3e}?}IY#c@#@)9Bg4a<%)0J;BDFR18x|JlW`Lzt7FA84fO1a?hU*^QD)ZJ-MD7;>H^?Se`DG`PD?>~G1`X^wq_Z7!g2CNc~`5D+Ue`NZ&UYvL}0k?l}QUCw| literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_5.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_5.astc new file mode 100644 index 0000000000000000000000000000000000000000..0389c53d54b7d1ab03a84b91b3aefa2c07135632 GIT binary patch literal 416 zcmV;R0bl+TtD#&72mvSn04M+e0RRAHustobP)Lf_CJF}!`1tr~a22o%fhJ8{IRW1L z5JCuLh=++fJ|y=eXBH0+@bK_uFbXis%5)CGIskx!e0+RGs4h^MSZ;>6gf9ypA0HoS zum?-Z5#=c6=_26&|Nj^UP&=?vuEbs0)I%5nPyzU5P%&_|bFq2NOalN94-XFp&{FUe zomkozzlaP4Kn1`>IAF<4r5qz4$_0wx;NTxb2{Hq7%V=`Kg82CO2L}fv@pmhwVSQ$M z9Eiu`2?T>hkRZ#ii>E{Qb`T&B@9^+N5CD+ZOSJ7P{XGzag98I;C>rqhFEaApLJI%? z4*&pZP)SSay(c=3WFup6&N=60P)ShQw3UY4cm_W{z`#BRP)X3s$e`~*Y}*}G0RI3a z%?{9OPT+kI!yym|1OiuS5MPij=du=Il>%6wbI!qOXcN%j0>o8n92A^$&c_&Ka1~Il zVY%1h!~+2V0sj7Jhy#$uO3#`9Z5RLl{{a97up|&VM}Ko^|6CLXzz;x05IRjotv89T K_7Vu=;Naldf38pf literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_6.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_6.astc new file mode 100644 index 0000000000000000000000000000000000000000..210dd340e128b789e0e9d22e81780a7ebae342cb GIT binary patch literal 592 zcmWe$y)cG@gVBJ20f-nG7+g;CZAiMfL`IOA`40%No#5MWyNmf$tU0p~!+(bVnHfwQ zq-q(CluhCJ&&dK6=l{sgf;o70Bp{yYDPrv8z zP%~l$y6(V#HV%FP74=p_;J^Xb6MUs&ozh3H=B(!fxhJ!Oc|yHj z`<^9HJPd#S|NpO&%M?0SpNp4iK0h!tnh&~kGkvIfbmu+GpFhkXz;c#vgV|NGcX58!KkeDYkQAUM2S0|K8fygD__LyD`x-rnEYe&=^S=l4){ckCeuECm2` zFaU72a9R8)@wAQ3tqt-)o+6a)y~*(Y`#KmEL}Ch*_LwukS=ti47$AwI%^m55>%UD! z3|D$n8ogPoV(60>zpfQ|7Kf!R644{4G7_>|Nli3fHNX%eQ`*w>h)kO*oUeAtO?l4R z72``y5uic@aF<-*=^I+?5dl8HBc9ut@5#x`m+=tsD0IKiedBuhj#?Fo#3=T$u|8AH zo8K@TNAIz>En9A`J;lv%dHs>vS91Dj+1QXI!{h%q%b6KMtQhnY+3|XW<9VK{iPQ-! z?Q)A;s1HZJ&-p;v@ZyC;1yq2cKNw4VjmbHrO5;s@z6izbNAh+V6HA`~5ClP#Z!c}n zHKtBIi!mT+Wg^x$Z+sQr|A{2TqRK3Z9u^N49Di~)6bfM&hCH2+wtDA64TMnWr#)?( zZMazTxJueqobGF**to&F^_`j1rHVMx9|#~{b!+pv9`)a??ZD&l;Dnw=tSg6l8z4{e zBs}r$qAzTY|cQRWJ!Lc3X3cZmd_LInXeait-ivALcXMcIa?8P<^k|^u%PR zLDv7t{||~PDU;^{=xZIny6GOqaa@S05wS<{iF#XIG`3%nS=%XewtZAkOvd^~+UFHwZM|?g~ s;Eh?iV{0r?VNcO1)i*L{nvJ%oij8P~c-IQ|dX-a_P=OXz(=Tf+!M~AdEtAF`+0M!YH!BQNhkm zF33PAC?seK2~r^hL8K7u9|aZFRB<&F&wYH$kLTt4Ufx%=$GaLD6#WG8dldxn^)3A! zlDxf@q<`mf9M{nSr&67r1Oe;yhlhQAu+b=rBnc-Hxtvl7x3_CF$s}AX()9E+tkqhr zdOe)YK0I`H!yeDZhW!7_OP$W)KpqbJ{j;+$NxIzz18lRgtSG|a@W8;?8B9?}M+yb} z{?0I`r*N${H01Nag5dQY9KbwZF2B6MJ3C5csRS=9oSeM9!8AQS-qr@Uv;>1{HOw&2 z&y$m|PPe_?+6t#qOG|EyuNMmOIBYf>3>*hnt7^5|4PRVb zU*FxqBO}Mho0~AlMIz}mTvwOR%lCP6lgY^YkHymIsVUSuojxDKz&$+_b#( l|BsI!A9g$P`T6s6`Mi=zqmiPJ^ZeW#O~Zaa%UUdw^aCvTFy{aO literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_9.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_9.astc new file mode 100644 index 0000000000000000000000000000000000000000..b5c962a14fec5b1639e250cde842eb541d4aa59a GIT binary patch literal 1312 zcmXw(3s4(X6o&6+VY9Z71i}R5A&8f+o_aiXX-duc7Q?e z|FS8KB(c{4fFBHijZNl%d3nvxGYkP5m{wWL2n+$B2YLb|HymyOF~|ZVFhU`Mg(iu}ejLYI1?;kmiJER#iSLfqWzfj)z;d46@V;Kes~TD*YdYJSMQ z;Z3)`mQJWKAkUpyX?pbR^Yfe4fW=~AG$?fKE(t+AP_Egg==X`H+Ao^e)>AnXD0y(62sH>G=G}p}IS>E9%?7(bV+e{Q2?0M6R{dTh*fy zVn{#*C(vEePenVvrvL zGzb{N)_h;FcrF047XtBj3>kSpmZ;Tgl}aTS6SUhWcb$B0YD5yqLufqk`(Y!OE-psu z60ihJuJJH*R$Z2wlspgave78}h)oX;-tsE&VYAur$OtkJ@ziH^SL0m3Mft^U3SDA> zv)OFMFxa2*9>-{I%65GbSQ$!P`x^1Iw7~wzE7%2s9WMR%vpPADNG+>mixUo9Agg$K zy#$J&_S6Xa9_CG7cObzA*r2I2!|Bph&i+bIh6xySO79BKUN+UbuGf(!6T5sNy@IVd z*BL4{eka)2fc+6(&92PuEqY>3it(6%14@G)m*~dS2sA$`2tk7!W#; za;U{#+QH?PJCC$t6%!h<)F6E*OWAcq2Pi-ZG-AWBHnS@8W+~9Sfk##~xcFM=XJJup zAw{lt6s~A~I?i7Tcv>#l%4gt+OL*M+;EF!==)&LY=|db|!~>P`db15`5A08<%bShA zARf#5AbkI2=dZ5VA|50;$=BRF>M3=xUvPlOrGoDl#VMN|{<+w02I4X@k@MJt@vO&A z?lTs_^cW25YMlTn+m_O9quTftcrzez5Bbhs5ww*{ht%N|FFHGaDXQU z=ch4sx$|BTTx!X`{r*#wLJ<+cFapr@_V01;e-C*}N6S~95YX4g2O@9z|AFzzx}|*= zpevPpu!0G|`sBw;b{A1t-xz5kKA2GB R*8#t_>noM9u@VWx{0C`gEPMa} literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x10.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x10.astc new file mode 100644 index 0000000000000000000000000000000000000000..6799cd08644ee3b49c2cd00ef70f057d2986eaa7 GIT binary patch literal 272 zcmWe$y)cH0i&24r0f-nG80^_2=ZjkGd-;`@VL#JPc@KN`#^bgL$o!S>Q#%lR6XoT_ zZ!a-4sH|O^d$+j0-k#mCT-0LPL70B7O!G-*Lf><}bHxRXjg5B$`Jcjq%j2ap#SKAx z6Xo5-Z?7Zt1LcLxMgAo??}VuTpSk@Z?;%Osp8Bqc&2DE~LGDZK$eRf>-$eO*v2Q&C z1B2?ywd;ZQn<#&5PW}FR0_)Bfj_RudSWT3dI;Q~nytXeK^;ZS(nkesljKV)$H!<() P+O@iC*Y>Vm8@e9=ccO2b literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x5.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x5.astc new file mode 100644 index 0000000000000000000000000000000000000000..cbfe4f2644855fc34eedbb41f3ff215bf638582a GIT binary patch literal 464 zcmWe$y)cH0l~I9#0f-nG7$&o1FBc8E_H^nEAqSEa=+X*jhO(KJCJ^H+L z_hc65deNYDdlCAxm)lk$^CzEAU4-DXDnAC={|d=|{cO>oSu0`sojlkky=Styugg;- zjxf1?MRkjv&rGDoKvpAux%|C^}v9%%ntxcxx&LAnxu;&Yk)v2$vR&j;zx=lU~$ zEf1W3X)W8I`)}p`%5!Q@pUF_n)*)K<01%nA(Bh-)Fx5 zx4-YiYegCJo6BY%lb+A+-zjQw=rBTm`9|9WWd8h{sT~OZedhg7`};OrM(8&$6t!5j z8m4~|C+DObo|^u3T810hwPH5kU$f=4FKzhIf1i2z+5Wy6yJ7l)@fBdI{OMoPu%{o$pR|gnu6?}}kUyaYr2oo!H#k4aDd4~6y+iyFUA@jHYuk73Nej@9e zzPma9dhRppHw(MX*^N*izuCDAncsiEe9E-b4_P&)^{t5ee4m*;TG*{*GD5w1w{saX zKYe@ol)S|-^(M+6+e*H_p1^Cn!Il420I!MiQRmd}uLD?ZH#h?MtR~96&lA5Fg<9?} KbpKlvXbS+Paax!F literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_12x12.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_12x12.astc new file mode 100644 index 0000000000000000000000000000000000000000..d94d0a6c0534e4f2db4a35e618a3170f50598d8a GIT binary patch literal 160 zcmWe$y)cG{hf#rn0f-nG7>t?xSBpCBc>Yk7Rafex+ec%j`rEEW2>yNMjsL5AwtReO z=`n3?%6}hYCi!Ynr;hCi_5Q0}ixB+v%#CNOds_B0GB_x2{J-FtG1F%kc9*PrL0)F% h=zr;D#!Skk<|PQeBUAYF!U-v@j8Dq{t^B1s8vqSLKaBtY literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_4x4.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_4x4.astc new file mode 100644 index 0000000000000000000000000000000000000000..fdf3cd6b5f796f4bc1249b9a8466a1f4ffce06f0 GIT binary patch literal 1040 zcmYk4Ur19?9LHz(3Q9gKXLaYi$Ln;6rM<{tPOLRjQ!A)m!ary3x<9@+Oj<5u8b~#) zr36mVha)~7UTkh9!ei7f&!}t6)YF2wOZz8=bZT`xGx_#=lA)3&+p2DcbaKB zjgv^wmnM;YO1U&sqTUl zb?sVhEuMeV?-Ll&x7F7p`UH6(#kjAhdIsj!p!>tIb}zd2zJfVaH&+oZ>f838Gya7!<6nULBmea>TSRT!@Z&sHwq*4tlRRRRzywCgK4eST z$1KrcCNJDu?Ztij@IFS&tB|p*0=!?$ zH!Z7BUh4&Q;rTbnU;1iR>qvjLbmX>>HeX@09g3p!E^yA06yxgdFh<;BI`oFLPx)s?Yp> zsNwact4HW@HQrYXA2IW=urL_3TJgR*_@sT4%Uu~E#nYy>*w+I(>DW86kA}I*uxdS( z2j?#yI80G)P9uF@LHcvM^wiGm0?o#ItZ;HYC3B}3zyF|V1m>7W$p4D^hNk)E@C#7F q4;=qN{+|vQvBUrJIAQWKgaw87M$OiV;tHFAnm)nMI+gq9#r%J!SettQ literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_5x4.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_5x4.astc new file mode 100644 index 0000000000000000000000000000000000000000..0683059469ee0cba5e0732cc0e373c48438b3b28 GIT binary patch literal 912 zcmYk4Uq};i0LSP30ap>@Bnm<&49@NDq}`dYX=mg1atjTS8bW&WB}?i-jwMCl9t>hs z6k8BP^A93ZRgWt=C-=EL-`}_TB5)(Zr zQdMs-;7hQ*bekP(8(^)b zM=yhFKO!uniHLn$_hDpIMrRp^!4esh(M3gL(C@CWJKDc1_y+^kE9+`rs!2vy8Qb<^ zpQByP+sMl34@F}L-x}&&`XfxuB$j69A3D|bwAqVJM(o|mHLV{J<$}w*_m9M zCct^^&G}(A-`1Gd&y$FljJ#JIow~n^jLXQpNElosQ!gPTO zsYAXc)aq-oLQ~g|)P7F07paLldEIxaIoHPKN>onwF+_|*;);XUeF_ohkd#OmERrb> z$zY_x7{zkPC&k8>`-1=Smv@I+d~V~vh)#1BJ{fg-lDTOD_%XUB3!iDC4VLKIENopp z<}M;G!!sh`(fvgtCBtcqG&n+LWOzZ+SnYSfyFzZCtK2UTwiGX%;NTId-et+U-+=pk zdU4~!rHx@~!{|rXdf~IHvW zd>kK(ZZDG>|L52TrEW)0o^~)bw7y`^9^NjhvhVI!)`t6x|F|~*)vvjsv@f<&`Ccp^ zlYTdl|He|~-rQR%Kt9kukp0!H|L#h&{R8v;)bsNznr?*UGsr90C)7`{XW#w0vIAk> zTK3Z)d#CKVzct4}noa%RM0yh-M_{(@1AYmJl%J3UKeHo{Xg5*2O)3I zUcWvt0hvE}cXHL5qntJkyE3BO0`1wCUoW2mwrrU+gThpxeL1FC^K7%?bYI9Z?3g~egSkg+$*j2TN37&LRQA;Y`DenH?wa>w zX_Z`u!oJ$ZE@q#Z$D;08E}u1zL#b#jke~DG_$<5GC#vRgvb~dBk7q}l<-UmaNT7YMLH6m~Oy7BLW=7;ZSC%56eZA{@C)CY_*$4C=kLnvM z-8a8v-bg|GcgEzwUz-Peb?(J++?d_m!7R$f_}u)=#0SeloGx4jhJUtyT0ti){DAS9 zbDeFbYz6yFV0`MX1*O08E}%R#{V}Q8=EXXk^;JEoeIPV2dV&&%tdT@-;_t?d3R~p0 z#n~ru$Zk>)P5RAgq`5`@W$|Qtc2!5qkd)k+S&9mbKYpj%vs;GxW+3x@GZSl&`H8iq Y6A=75&ipsuIm+cNUQUx|P~lh!0GYQ@6#xJL literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x5.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x5.astc new file mode 100644 index 0000000000000000000000000000000000000000..d0d455d0bfbe88657447554b07ce424c94ec22c7 GIT binary patch literal 688 zcmWe$y)cH2l~I9#0f-nG7$&piP8Qa?^0f1S07uRH;&`Twu0Ccz>g;~ZbNeC3P*4u! zukrhFFYL#^I2fNN{fAxtkGOIeKd1i3y!Ida`XPL^^JTUdj`E#3a_YeL>ixBoSt9F& z^$uKwnLnAOaJse|f)BFK?^9jar+IOo0aKae`R3)WR{)xi~A7jC$pUY z+~2qFqT?hL&S#bLeuMPOzN=GxH&6E+%)O@X?%BTk=L+V}XD^-Y9JgjKr_cn3KVKh< z1J&oGy_=W!ZeJNp{h7LV|JvTk^+Wh-kBe+CoZ>rj&;jRbN@M@ zeWoww*=Fr?&5~=Fb)D&B=o#Ovf3YwAg=WbyeFz7d&y)7TF7HKL*$b$Da_U~pYkRS; z56oBU;QnNCyk949@}Cnzt3tv4l}Wj$lJZX{1*jjy=P^yOvrUO}O@Zn6O_>+^WMAwP zIfkO|K>0JNPyXdTkt+l8!SSP0_av_ENnIb9f3A~zlFD&?8ShDdoP-)?+y%K;=3br3 zy?HwK#A6=f`f#7dCQa3U0Bje3=|5xQ|fBgLl0QWaWf&c&j literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x6.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x6.astc new file mode 100644 index 0000000000000000000000000000000000000000..47e038efd950f3aa773346512bf171d0bd151f82 GIT binary patch literal 592 zcmWe$y)cH2jZuMt0f-nG80^{8CyQ#Fd->J0!Jhf2d51lF{c2qYWd8KSt`W%m^|xaS zkom_ymv$id`&7pI1u7uCQ%f|#=9JJT&XI^=- zzi-A)nE4wGlF!II`>XOyRyR$KV{JW9zm92IoNZd2Ynog`&|i>!zG?eH)9%HB`Amk% zZ&K6b@}Ak2rO7E2{Ab$8m(!LuuP-gGE=`W1pngB|#Et!Z4GUrB1NG;~JeaHUV6V;t zS)hA?`p=j=_-pe()-?gFA84QNgV@jqwXtA6$iCEseYpwuN)tf#gTk}4Bd-(Y|NYFB zGyD58I$`GTXZ|h5ua%X}$Y3$EoH<@>KeMpDx)U;A+uc42neQAQUWCk#F3;~m@SP7f SOe}cM$nffSUA=eK{oMdz%lqB{ literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x5.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x5.astc new file mode 100644 index 0000000000000000000000000000000000000000..4ef49d657dd4a259865df557c89edb457b41f523 GIT binary patch literal 464 zcmWe$y)cG@l~I9#0f-nG80NF*&J;Gg@|08S0>hurkDt$H@7-w}hRk1kGq?nLaZ1*k&H5MmN>-^LoT<}dle6I>nVp-J`~)>W%-`5yamnW3 zLYb4BmCp&X>i-4ujVuM3Wcarz90Box`gLqm;#^bag{H_U75xY57jiaal3Bl5$pxez zD4)}o5!;qBZ(54nq5_b4lPm<2q*=Ep?1Gv%$x<;%T6Bvdm_MK0voyG@pb_T2`Rtif ti>D#@#!SDJ_|3Ai4L2`S_SnQ0>hv0pT+01cWyThLFTW#9$bRVKl#0Q z3Nk;iUf5*ES%m(=`Nkp0{E7R6OOW{+?-x%&=3Ax^VvPigG&lpVeSKkSMd}CUzqv347Wj6w&CQvL VZmS)D%r}qqE_xJM!67wZWLd(E5Wt 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 + /// Locates a test data file by searching up from the benchmark directory and into the known test data location. + /// + /// Relative path from the test data root (e.g. "Input/atlas_small_4x4.astc"). + /// Full path to the test data file, or throws if not found. + public static string FindTestData(string relativePath) + { + // Walk up from the current directory, searching for ImageSharp.Textures.Astc.Tests/TestData + string dir = AppContext.BaseDirectory; + for (int i = 0; i < 10; ++i) + { + string testDataDir = Path.Combine(dir, "ImageSharp.Textures.Astc.Tests", "TestData"); + string candidate = Path.Combine(testDataDir, relativePath); + if (File.Exists(candidate)) + { + return Path.GetFullPath(candidate); + } + + dir = Path.GetFullPath(Path.Combine(dir, "..")); + } + + throw new FileNotFoundException($"Could not locate test data file: {relativePath}"); + } + } +} diff --git a/tests/ImageSharp.Textures.Benchmarks/DecodingBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/DecodingBenchmark.cs new file mode 100644 index 00000000..4af0f8d1 --- /dev/null +++ b/tests/ImageSharp.Textures.Benchmarks/DecodingBenchmark.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +namespace SixLabors.ImageSharp.Textures.Benchmarks +{ + [MemoryDiagnoser] + public class DecodingBenchmark + { + private AstcFile? _astcFile; + + [GlobalSetup] + public void Setup() + { + string path = BenchmarkTestDataLocator.FindTestData(Path.Combine("Input", "atlas_small_4x4.astc")); + byte[] astcData = File.ReadAllBytes(path); + _astcFile = AstcFile.FromMemory(astcData); + } + + [Benchmark] + public bool ParseBlock() + { + ReadOnlySpan blocks = _astcFile!.Blocks; + Span blockBytes = stackalloc byte[16]; + blocks.Slice(0, 16).CopyTo(blockBytes); + ulong low = BitConverter.ToUInt64(blockBytes); + ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + PhysicalBlock phyiscalBlock = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); + + return !phyiscalBlock.IsIllegalEncoding; + } + + [Benchmark] + public bool DecodeEndpoints() + { + ReadOnlySpan blocks = _astcFile!.Blocks; + Span blockBytes = stackalloc byte[16]; + blocks.Slice(0, 16).CopyTo(blockBytes); + ulong low = BitConverter.ToUInt64(blockBytes); + ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + PhysicalBlock physicalBlock = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); + + IntermediateBlock.IntermediateBlockData? blockData = IntermediateBlock.UnpackIntermediateBlock(physicalBlock); + + return blockData is not null; + } + + [Benchmark] + public bool Partitioning() + { + ReadOnlySpan blocks = _astcFile!.Blocks; + Span blockBytes = stackalloc byte[16]; + blocks.Slice(0, 16).CopyTo(blockBytes); + ulong low = BitConverter.ToUInt64(blockBytes); + ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + UInt128 bits = (UInt128)low | ((UInt128)high << 64); + BlockInfo info = BlockInfo.Decode(bits); + LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(Footprint.Get4x4(), bits, in info) + ?? throw new InvalidOperationException("Failed to unpack block"); + + return logicalBlock is not null; + } + } +} diff --git a/tests/ImageSharp.Textures.Benchmarks/ImageSharp.Textures.Benchmarks.csproj b/tests/ImageSharp.Textures.Benchmarks/ImageSharp.Textures.Benchmarks.csproj index 528ca0cf..810de4bd 100644 --- a/tests/ImageSharp.Textures.Benchmarks/ImageSharp.Textures.Benchmarks.csproj +++ b/tests/ImageSharp.Textures.Benchmarks/ImageSharp.Textures.Benchmarks.csproj @@ -17,5 +17,9 @@ + + + + From cab129cf7e6b38bbfa94958ae27146fe10420e3c Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:39:13 +0100 Subject: [PATCH 31/37] Update style and formatting to ImageSharp standard --- src/ImageSharp.Textures.Astc/AstcDecoder.cs | 95 +++- .../BoundedIntegerSequenceCodec.cs | 123 ++--- .../BoundedIntegerSequenceDecoder.cs | 77 ++-- .../BoundedIntegerSequenceEncoder.cs | 78 +++- .../Quantize/BitQuantizationMap.cs | 32 +- .../BiseEncoding/Quantize/Quantization.cs | 70 ++- .../BiseEncoding/Quantize/QuantizationMap.cs | 54 ++- .../Quantize/QuintQuantizationMap.cs | 19 +- .../Quantize/TritQuantizationMap.cs | 16 +- .../BlockDecoder/FusedBlockDecoder.cs | 10 + .../BlockDecoder/FusedHdrBlockDecoder.cs | 24 +- .../BlockDecoder/FusedLdrBlockDecoder.cs | 38 +- .../ColorEncoding/ColorEndpointMode.cs | 2 +- .../ColorEndpointModeExtensions.cs | 2 +- ...orEndpointPair.cs => ColorEndpointPair.cs} | 0 .../ColorEncoding/EndpointCodec.cs | 225 +++++---- .../ColorEncoding/EndpointEncoder.cs | 284 +++++++----- .../EndpointEncodingModeExtensions.cs | 2 +- .../ColorEncoding/HdrEndpointDecoder.cs | 208 +++++++-- .../ColorEncoding/Partition.cs | 173 +++++-- .../ColorEncoding/RgbaColorExtensions.cs | 6 +- .../Core/BitOperations.cs | 16 +- .../Core/DecimationInfo.cs | 32 ++ .../Core/DecimationTable.cs | 96 ++-- .../Core/Footprint.cs | 37 +- .../Core/FootprintType.cs | 13 + src/ImageSharp.Textures.Astc/Core/RgbColor.cs | 30 +- .../Core/RgbaColor.cs | 40 +- .../Core/RgbaHdrColor.cs | 18 +- .../Core/SimdHelpers.cs | 45 +- .../Core/UInt128Extensions.cs | 14 +- src/ImageSharp.Textures.Astc/IO/AstcFile.cs | 30 +- .../IO/AstcFileHeader.cs | 4 +- src/ImageSharp.Textures.Astc/IO/BitStream.cs | 118 +++-- .../TexelBlock/BlockInfo.cs | 86 +++- .../TexelBlock/IntermediateBlock.cs | 74 ++- .../TexelBlock/IntermediateBlockPacker.cs | 146 ++++-- .../TexelBlock/LogicalBlock.cs | 435 +++++++++++------- .../TexelBlock/PhysicalBlock.cs | 132 ++++-- 39 files changed, 1919 insertions(+), 985 deletions(-) rename src/ImageSharp.Textures.Astc/ColorEncoding/{IColorEndpointPair.cs => ColorEndpointPair.cs} (100%) create mode 100644 src/ImageSharp.Textures.Astc/Core/DecimationInfo.cs diff --git a/src/ImageSharp.Textures.Astc/AstcDecoder.cs b/src/ImageSharp.Textures.Astc/AstcDecoder.cs index 50018aaf..6c101ba0 100644 --- a/src/ImageSharp.Textures.Astc/AstcDecoder.cs +++ b/src/ImageSharp.Textures.Astc/AstcDecoder.cs @@ -3,10 +3,10 @@ using System.Buffers; using System.Buffers.Binary; +using SixLabors.ImageSharp.Textures.Astc.BlockDecoder; using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; -using SixLabors.ImageSharp.Textures.Astc.BlockDecoder; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; namespace SixLabors.ImageSharp.Textures.Astc; @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Textures.Astc; ///

v%fcTaL{NYj%p<=}R-K{2h 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 literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_5x4.astc b/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_5x4.astc new file mode 100644 index 0000000000000000000000000000000000000000..b9e09236a22b9790a3b31b2268aaf5f0c9df1ff8 GIT binary patch literal 51855 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�f&Uft**^6@ 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 Data.Length; + + public ImageBuffer(byte[] data, int width, int height, int bytesPerPixel) + { + Data = data; + BytesPerPixel = bytesPerPixel; + Width = width; + Height = height; + int rowBytes = width * bytesPerPixel; + Stride = (rowBytes + (Align - 1)) / Align * Align; + } + + public static ImageBuffer Allocate(int width, int height, int bytesPerPixel) + { + int rowBytes = width * bytesPerPixel; + var stride = (rowBytes + (Align - 1)) / Align * Align; + var data = new byte[stride * height]; + + return new ImageBuffer(data, width, height, bytesPerPixel); + } + + public static ImageBuffer FromAstcBuffer(Footprint footprint, byte[] astcData, int width, int height, bool hasAlpha) + { + var decodedImage = Allocate(width, height, hasAlpha ? RgbaColor.BytesPerPixel : RgbColor.BytesPerPixel); + + int blockWidth = footprint.Width; + int blockHeight = footprint.Height; + + for (int i = 0; i < astcData.Length; i += PhysicalBlock.SizeInBytes) + { + int blockIndex = i / PhysicalBlock.SizeInBytes; + int blocksWide = (width + blockWidth - 1) / blockWidth; + int blockX = blockIndex % blocksWide; + int blockY = blockIndex / blocksWide; + + var blockSpan = astcData.AsSpan(i, PhysicalBlock.SizeInBytes).ToArray(); + var bits = new UInt128( + BitConverter.ToUInt64(blockSpan, 8), + BitConverter.ToUInt64(blockSpan, 0)); + var info = BlockInfo.Decode(bits); + + var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, bits, in info); + logicalBlock.Should().NotBeNull(); + + for (int y = 0; y < blockHeight; ++y) + { + for (int x = 0; x < blockWidth; ++x) + { + int px = blockWidth * blockX + x; + int py = blockHeight * blockY + y; + if (px >= width || py >= height) continue; + + var decoded = logicalBlock!.ColorAt(x, y); + int row = py * decodedImage.Stride; + int off = row + px * decodedImage.BytesPerPixel; + decodedImage.Data[off + 0] = decoded.R; + decodedImage.Data[off + 1] = decoded.G; + decodedImage.Data[off + 2] = decoded.B; + if (hasAlpha) decodedImage.Data[off + 3] = decoded.A; + } + } + } + + return decodedImage; + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/Utils/ImageUtils.cs b/tests/ImageSharp.Textures.Astc.Tests/Utils/ImageUtils.cs new file mode 100644 index 00000000..2013123b --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/Utils/ImageUtils.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests.Utils; + +internal static class ImageUtils +{ + public static void CompareSumOfSquaredDifferences(ImageBuffer expected, ImageBuffer actual, double threshold) + { + actual.DataSize.Should().Be(expected.DataSize); + actual.Stride.Should().Be(expected.Stride); + actual.BytesPerPixel.Should().Be(expected.BytesPerPixel); + + var expectedData = expected.Data; + var actualData = actual.Data; + + double sum = 0.0; + for (int i = 0; i < actualData.Length; ++i) + { + double diff = (double)actualData[i] - expectedData[i]; + sum += diff * diff; + } + + sum.Should().BeLessThanOrEqualTo(threshold * actualData.Length, because: $"Per pixel {(sum / actualData.Length)}, expected <= {threshold}"); + if (sum > threshold * actualData.Length) + { + actualData.Should().BeEquivalentTo(expectedData); + } + } +} diff --git a/tests/ImageSharp.Textures.Astc.Tests/WeightInfillTests.cs b/tests/ImageSharp.Textures.Astc.Tests/WeightInfillTests.cs new file mode 100644 index 00000000..dfa5c849 --- /dev/null +++ b/tests/ImageSharp.Textures.Astc.Tests/WeightInfillTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; +using SixLabors.ImageSharp.Textures.Astc.Core; +using AwesomeAssertions; + +namespace SixLabors.ImageSharp.Textures.Astc.Tests; + +public class WeightInfillTests +{ + [Theory] + [InlineData(4, 4, 3, 32)] + [InlineData(4, 4, 7, 48)] + [InlineData(2, 4, 7, 24)] + [InlineData(2, 4, 1, 8)] + [InlineData(4, 5, 2, 32)] + [InlineData(4, 4, 2, 26)] + [InlineData(4, 5, 5, 52)] + [InlineData(4, 4, 5, 42)] + [InlineData(3, 3, 4, 21)] + [InlineData(4, 4, 4, 38)] + [InlineData(3, 7, 4, 49)] + [InlineData(4, 3, 19, 52)] + [InlineData(4, 4, 19, 70)] + public void CountBitsForWeights_WithVariousParameters_ShouldReturnCorrectBitCount( + int width, int height, int range, int expectedBitCount) + { + var bitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(width * height, range); + + bitCount.Should().Be(expectedBitCount); + } + + [Fact] + public void InfillWeights_With3x3Grid_ShouldBilinearlyInterpolateTo5x5() + { + int[] weights = [1, 3, 5, 3, 5, 7, 5, 7, 9]; + int[] expected = [1, 2, 3, 4, 5, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 4, 5, 6, 7, 8, 5, 6, 7, 8, 9]; + + var footprint = Footprint.Get5x5(); + var di = DecimationTable.Get(footprint, 3, 3); + var result = new int[footprint.PixelCount]; + DecimationTable.InfillWeights(weights, di, result); + + result.Should().HaveCount(expected.Length); + result.Should().Equal(expected); + } +} diff --git a/tests/ImageSharp.Textures.Benchmarks/AstcFullImageDecodeBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/AstcFullImageDecodeBenchmark.cs new file mode 100644 index 00000000..b285c940 --- /dev/null +++ b/tests/ImageSharp.Textures.Benchmarks/AstcFullImageDecodeBenchmark.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +namespace SixLabors.ImageSharp.Textures.Benchmarks; + +[MemoryDiagnoser] +public class AstcFullImageDecodeBenchmark +{ + private AstcFile? _astcFile; + + [GlobalSetup] + public void Setup() + { + string path = BenchmarkTestDataLocator.FindTestData(Path.Combine("Input", "atlas_small_4x4.astc")); + byte[] astcData = File.ReadAllBytes(path); + _astcFile = AstcFile.FromMemory(astcData); + } + + [Benchmark] + public void FullImageDecode() + { + ReadOnlySpan blocks = _astcFile!.Blocks; + int numBlocks = blocks.Length / 16; + Span blockBytes = stackalloc byte[16]; + for (int i = 0; i < numBlocks; ++i) + { + blocks.Slice(i * 16, 16).CopyTo(blockBytes); + ulong low = BitConverter.ToUInt64(blockBytes); + ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + PhysicalBlock block = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); + IntermediateBlock.IntermediateBlockData? _ = IntermediateBlock.UnpackIntermediateBlock(block); + } + } +} diff --git a/tests/ImageSharp.Textures.Benchmarks/BenchmarkTestDataLocator.cs b/tests/ImageSharp.Textures.Benchmarks/BenchmarkTestDataLocator.cs new file mode 100644 index 00000000..13d103ff --- /dev/null +++ b/tests/ImageSharp.Textures.Benchmarks/BenchmarkTestDataLocator.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Textures.Benchmarks +{ + public static class BenchmarkTestDataLocator + { + ///

/// /// ASTC block and decoded image dimensions in texels. -/// +/// /// For 2D images the Z dimension must be set to 1. -/// +/// /// Note that the image is not required to be an exact multiple of the compressed block /// size; the compressed data may include padding that is discarded during decompression. /// diff --git a/src/ImageSharp.Textures.Astc/IO/BitStream.cs b/src/ImageSharp.Textures.Astc/IO/BitStream.cs index 71bd157e..900eda3a 100644 --- a/src/ImageSharp.Textures.Astc/IO/BitStream.cs +++ b/src/ImageSharp.Textures.Astc/IO/BitStream.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. - +using System.Globalization; using SixLabors.ImageSharp.Textures.Astc.Core; namespace SixLabors.ImageSharp.Textures.Astc.IO; @@ -11,27 +11,28 @@ namespace SixLabors.ImageSharp.Textures.Astc.IO; /// internal struct BitStream { - private ulong _low; - private ulong _high; - private uint _dataSize; // number of valid bits in the 128-bit buffer - - public uint Bits => _dataSize; + private ulong low; + private ulong high; + private uint dataSize; // number of valid bits in the 128-bit buffer public BitStream(ulong data = 0, uint dataSize = 0) { - _low = data; - _high = 0; - _dataSize = dataSize; + this.low = data; + this.high = 0; + this.dataSize = dataSize; } public BitStream(UInt128 data, uint dataSize) { - _low = data.Low(); - _high = data.High(); - _dataSize = dataSize; + this.low = data.Low(); + this.high = data.High(); + this.dataSize = dataSize; } - public void PutBits(T x, int size) where T : unmanaged + public uint Bits => this.dataSize; + + public void PutBits(T x, int size) + where T : unmanaged { ulong value = x switch { @@ -39,57 +40,60 @@ public void PutBits(T x, int size) where T : unmanaged ulong ul => ul, ushort us => us, byte b => b, - _ => Convert.ToUInt64(x) + _ => Convert.ToUInt64(x, CultureInfo.InvariantCulture) }; - if (_dataSize + (uint)size > 128) + if (this.dataSize + (uint)size > 128) + { throw new InvalidOperationException("Not enough space in BitStream"); + } - if (_dataSize < 64) + if (this.dataSize < 64) { - int lowFree = (int)(64 - _dataSize); + int lowFree = (int)(64 - this.dataSize); if (size <= lowFree) { - _low |= (value & MaskFor(size)) << (int)_dataSize; + this.low |= (value & MaskFor(size)) << (int)this.dataSize; } else { - _low |= (value & MaskFor(lowFree)) << (int)_dataSize; - _high |= (value >> lowFree) & MaskFor(size - lowFree); + this.low |= (value & MaskFor(lowFree)) << (int)this.dataSize; + this.high |= (value >> lowFree) & MaskFor(size - lowFree); } } else { - int shift = (int)(_dataSize - 64); - _high |= (value & MaskFor(size)) << shift; + int shift = (int)(this.dataSize - 64); + this.high |= (value & MaskFor(size)) << shift; } - _dataSize += (uint)size; + this.dataSize += (uint)size; } /// /// Attempt to retrieve the specified number of bits from the buffer. /// The buffer is shifted accordingly if successful. /// - public bool TryGetBits(int count, out T bits) where T : unmanaged + public bool TryGetBits(int count, out T bits) + where T : unmanaged { T? result = null; if (typeof(T) == typeof(UInt128)) { - result = (T?)(object?)GetBitsUInt128(count); + result = (T?)(object?)this.GetBitsUInt128(count); } - else if (count <= _dataSize) + else if (count <= this.dataSize) { ulong value = count switch { 0 => 0, - <= 64 => _low & MaskFor(count), - _ => _low + <= 64 => this.low & MaskFor(count), + _ => this.low }; - ShiftBuffer(count); - object boxed = Convert.ChangeType(value, typeof(T)); + this.ShiftBuffer(count); + object boxed = Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); result = (T)boxed; } @@ -100,27 +104,37 @@ public bool TryGetBits(int count, out T bits) where T : unmanaged public bool TryGetBits(int count, out ulong bits) { - if (count > _dataSize) { bits = 0; return false; } + if (count > this.dataSize) + { + bits = 0; + return false; + } + bits = count switch { 0 => 0, - <= 64 => _low & MaskFor(count), - _ => _low + <= 64 => this.low & MaskFor(count), + _ => this.low }; - ShiftBuffer(count); + this.ShiftBuffer(count); return true; } public bool TryGetBits(int count, out uint bits) { - if (count > _dataSize) { bits = 0; return false; } + if (count > this.dataSize) + { + bits = 0; + return false; + } + bits = (uint)(count switch { 0 => 0UL, - <= 64 => _low & MaskFor(count), - _ => _low + <= 64 => this.low & MaskFor(count), + _ => this.low }); - ShiftBuffer(count); + this.ShiftBuffer(count); return true; } @@ -131,20 +145,22 @@ private static ulong MaskFor(int bits) private UInt128? GetBitsUInt128(int count) { - if (count > _dataSize) + if (count > this.dataSize) + { return null; + } UInt128 result = count switch { 0 => UInt128.Zero, - <= 64 => (UInt128)(_low & MaskFor(count)), - 128 => new UInt128(_high, _low), + <= 64 => (UInt128)(this.low & MaskFor(count)), + 128 => new UInt128(this.high, this.low), _ => new UInt128( - (count - 64 == 64) ? _high : (_high & MaskFor(count - 64)), - _low) + (count - 64 == 64) ? this.high : (this.high & MaskFor(count - 64)), + this.low) }; - ShiftBuffer(count); + this.ShiftBuffer(count); return result; } @@ -153,20 +169,20 @@ private void ShiftBuffer(int count) { if (count < 64) { - _low = (_low >> count) | (_high << (64 - count)); - _high = _high >> count; + this.low = (this.low >> count) | (this.high << (64 - count)); + this.high = this.high >> count; } else if (count == 64) { - _low = _high; - _high = 0; + this.low = this.high; + this.high = 0; } else { - _low = _high >> (count - 64); - _high = 0; + this.low = this.high >> (count - 64); + this.high = 0; } - _dataSize -= (uint)count; + this.dataSize -= (uint)count; } } diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs b/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs index 22608623..a76cdf24 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs @@ -13,13 +13,13 @@ namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; /// internal struct BlockInfo { - private static readonly int[] _weightRanges = + private static readonly int[] WeightRanges = [-1, -1, 1, 2, 3, 4, 5, 7, -1, -1, 9, 11, 15, 19, 23, 31]; - private static readonly int[] _extraCemBitsForPartition = [0, 2, 5, 8]; + private static readonly int[] ExtraCemBitsForPartition = [0, 2, 5, 8]; // Valid BISE endpoint ranges in descending order (only these produce valid encodings) - private static readonly int[] _validEndpointRanges = + private static readonly int[] ValidEndpointRanges = [255, 191, 159, 127, 95, 79, 63, 47, 39, 31, 23, 19, 15, 11, 9, 7, 5]; public bool IsValid; @@ -53,11 +53,11 @@ internal struct BlockInfo [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly ColorEndpointMode GetEndpointMode(int partition) => partition switch { - 0 => EndpointMode0, - 1 => EndpointMode1, - 2 => EndpointMode2, - 3 => EndpointMode3, - _ => EndpointMode0 + 0 => this.EndpointMode0, + 1 => this.EndpointMode1, + 2 => this.EndpointMode2, + 3 => this.EndpointMode3, + _ => this.EndpointMode0 }; /// @@ -87,7 +87,8 @@ public static BlockInfo Decode(UInt128 bits) bool isWidthA6HeightB6 = false; uint rBits; // 3-bit range index component - if ((lowBits & 0x3) != 0) // bits[0:2] != 0 + // bits[0:2] != 0 + if ((lowBits & 0x3) != 0) { ulong modeBits = (lowBits >> 2) & 0x3; // bits[2:4] int a = (int)((lowBits >> 5) & 0x3); // bits[5:7] @@ -105,8 +106,9 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), // Range r[2:0] = {bit4, bit1, bit0} for these modes rBits = (uint)(((lowBits >> 4) & 1) | (((lowBits >> 0) & 0x3) << 1)); } - else // bits[0:2] == 0 + else { + // bits[0:2] == 0 ulong modeBits = (lowBits >> 5) & 0xF; // bits[5:9] int a = (int)((lowBits >> 5) & 0x3); // bits[5:7] @@ -114,7 +116,10 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), { case var _ when (modeBits & 0xC) == 0x0: if ((lowBits & 0xF) == 0) + { return default; // reserved block mode + } + gridWidth = 12; gridHeight = a + 2; break; @@ -148,11 +153,16 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), ? 0u : (uint)((lowBits >> 9) & 1); int rangeIdx = (int)((hBit << 3) | rBits); - if ((uint)rangeIdx >= (uint)_weightRanges.Length) + if ((uint)rangeIdx >= (uint)WeightRanges.Length) + { return default; - int weightRange = _weightRanges[rangeIdx]; + } + + int weightRange = WeightRanges[rangeIdx]; if (weightRange < 0) + { return default; + } // ---- Step 4: Dual plane ---- // WidthA6HeightB6 mode never has dual plane; otherwise check bit 10 @@ -163,18 +173,28 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), // ---- Step 6: Validate weight count ---- int numWeights = gridWidth * gridHeight; - if (isDualPlane) numWeights *= 2; + if (isDualPlane) + { + numWeights *= 2; + } + if (numWeights > 64) + { return default; + } // 4 partitions + dual plane is illegal if (partitionCount == 4 && isDualPlane) + { return default; + } // ---- Step 7: Weight bit count ---- int weightBitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(numWeights, weightRange); if (weightBitCount < 24 || weightBitCount > 96) + { return default; + } // ---- Step 8: Endpoint modes + extra CEM bits ---- ColorEndpointMode cem0 = default, cem1 = default, cem2 = default, cem3 = default; @@ -197,12 +217,14 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), var sharedCem = (ColorEndpointMode)((lowBits >> 25) & 0xF); cem0 = cem1 = cem2 = cem3 = sharedCem; for (int i = 0; i < partitionCount; i++) + { colorValuesCount += sharedCem.GetColorValuesCount(); + } } else { // Non-shared CEM: per-partition modes - numExtraCEMBits = _extraCemBitsForPartition[partitionCount - 1]; + numExtraCEMBits = ExtraCemBitsForPartition[partitionCount - 1]; int extraCemStartPos = 128 - numExtraCEMBits - weightBitCount; var extraCem = BitOperations.GetBits(bits, extraCemStartPos, numExtraCEMBits); @@ -221,30 +243,45 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), c[i] = (int)(cembits & 0x1); cembits >>= 1; } + // Extract m bits (2 bits per partition) for (int i = 0; i < partitionCount; i++) { int m = (int)(cembits & 0x3); cembits >>= 2; - var mode = (ColorEndpointMode)(baseCem + 4 * c[i] + m); + var mode = (ColorEndpointMode)(baseCem + (4 * c[i]) + m); switch (i) { - case 0: cem0 = mode; break; - case 1: cem1 = mode; break; - case 2: cem2 = mode; break; - case 3: cem3 = mode; break; + case 0: + cem0 = mode; + break; + case 1: + cem1 = mode; + break; + case 2: + cem2 = mode; + break; + case 3: + cem3 = mode; + break; } + colorValuesCount += mode.GetColorValuesCount(); } } } if (colorValuesCount > 18) + { return default; + } // ---- Step 9: Dual plane start position and channel ---- int dualPlaneBitStartPos = 128 - weightBitCount - numExtraCEMBits; - if (isDualPlane) dualPlaneBitStartPos -= 2; + if (isDualPlane) + { + dualPlaneBitStartPos -= 2; + } int dualPlaneChannel = isDualPlane ? (int)BitOperations.GetBits(bits, dualPlaneBitStartPos, 2).Low() @@ -257,11 +294,13 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), // Minimum bits needed check int requiredColorBits = ((13 * colorValuesCount) + 4) / 5; if (maxColorBits < requiredColorBits) + { return default; + } // Find max color range that fits (only check valid BISE ranges: 17 vs up to 255) int colorValuesRange = 0, colorBitCount = 0; - foreach (int rv in _validEndpointRanges) + foreach (int rv in ValidEndpointRanges) { int bitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(colorValuesCount, rv); if (bitCount <= maxColorBits) @@ -273,11 +312,12 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), } if (colorValuesRange == 0) + { return default; + } // ---- Step 11: Validate endpoint modes are not HDR for batchable checks ---- // (HDR blocks are still valid, just flagged for downstream use) - return new BlockInfo { IsValid = true, @@ -306,7 +346,9 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), private static bool CheckVoidExtentIsIllegal(UInt128 bits, ulong lowBits) { if (BitOperations.GetBits(bits, 10, 2).Low() != 0x3UL) + { return true; + } int c0 = (int)BitOperations.GetBits(lowBits, 12, 13); int c1 = (int)BitOperations.GetBits(lowBits, 25, 13); diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs index 661a8fba..c1e423b5 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs @@ -20,11 +20,15 @@ internal static class IntermediateBlock public static IntermediateBlockData? UnpackIntermediateBlock(PhysicalBlock physicalBlock) { if (physicalBlock.IsIllegalEncoding || physicalBlock.IsVoidExtent) + { return null; + } var info = BlockInfo.Decode(physicalBlock.BlockBits); if (!info.IsValid || info.IsVoidExtent) + { return null; + } return UnpackIntermediateBlock(physicalBlock.BlockBits, in info); } @@ -34,9 +38,12 @@ internal static class IntermediateBlock /// public static IntermediateBlockData? UnpackIntermediateBlock(UInt128 bits, in BlockInfo info) { - if (!info.IsValid || info.IsVoidExtent) return null; + if (!info.IsValid || info.IsVoidExtent) + { + return null; + } - var data = new IntermediateBlockData(); + var data = default(IntermediateBlockData); // Use cached values from BlockInfo instead of PhysicalBlock getters var colorBitMask = UInt128Extensions.OnesMask(info.ColorBitCount); @@ -70,6 +77,7 @@ internal static class IntermediateBlock { ep.Colors[j] = colors[colorIndex++]; } + data.Endpoints[i] = ep; } @@ -80,7 +88,11 @@ internal static class IntermediateBlock var weightDecoder = BoundedIntegerSequenceDecoder.GetCached(data.WeightRange); int weightsCount = data.WeightGridX * data.WeightGridY; - if (info.IsDualPlane) weightsCount *= 2; + if (info.IsDualPlane) + { + weightsCount *= 2; + } + data.Weights = new int[weightsCount]; data.WeightsCount = weightsCount; weightDecoder.Decode(weightsCount, ref weightBitStream, data.Weights); @@ -94,7 +106,9 @@ public static int EndpointRangeForBlock(in IntermediateBlockData data) ? 2 : 1; if (BoundedIntegerSequenceCodec.GetBitCountForRange(data.WeightGridX * data.WeightGridY * dualPlaneMultiplier, data.WeightRange) > 96) + { return EndpointRangeInvalidWeightDimensions; + } int partitionCount = data.EndpointCount; int bitsWrittenCount = 11 + 2 @@ -103,17 +117,27 @@ public static int EndpointRangeForBlock(in IntermediateBlockData data) int availableColorBitsCount = ExtraConfigBitPosition(data) - bitsWrittenCount; int colorValuesCount = 0; - for (int i = 0; i < data.EndpointCount; i++) colorValuesCount += data.Endpoints[i].Mode.GetColorValuesCount(); + for (int i = 0; i < data.EndpointCount; i++) + { + colorValuesCount += data.Endpoints[i].Mode.GetColorValuesCount(); + } - int bitsNeededCount = (13 * colorValuesCount + 4) / 5; - if (availableColorBitsCount < bitsNeededCount) return EndpointRangeNotEnoughColorBits; + int bitsNeededCount = ((13 * colorValuesCount) + 4) / 5; + if (availableColorBitsCount < bitsNeededCount) + { + return EndpointRangeNotEnoughColorBits; + } int colorValueRange = byte.MaxValue; for (; colorValueRange > 1; --colorValueRange) { int bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(colorValuesCount, colorValueRange); - if (bitCountForRange <= availableColorBitsCount) break; + if (bitCountForRange <= availableColorBitsCount) + { + break; + } } + return colorValueRange; } @@ -122,13 +146,17 @@ public static int EndpointRangeForBlock(in IntermediateBlockData data) var colorStartBit = physicalBlock.GetColorStartBit(); var colorBitCount = physicalBlock.GetColorBitCount(); if (physicalBlock.IsIllegalEncoding || !physicalBlock.IsVoidExtent || colorStartBit is null || colorBitCount is null) + { return null; + } var colorBits = (physicalBlock.BlockBits >> colorStartBit.Value) & UInt128Extensions.OnesMask(colorBitCount.Value); + // We expect low 64 bits contain the 4x16-bit channels var low = colorBits.Low(); - var data = new VoidExtentData(); + var data = default(VoidExtentData); + // Bit 9 of the block mode indicates HDR (1) vs LDR (0) void extent data.IsHdr = (physicalBlock.BlockBits.Low() & (1UL << 9)) != 0; data.R = (ushort)((low >> 0) & 0xFFFF); @@ -148,7 +176,10 @@ public static int EndpointRangeForBlock(in IntermediateBlockData data) else { ushort allOnes = (ushort)((1 << 13) - 1); - for (int i = 0; i < 4; ++i) data.Coords[i] = allOnes; + for (int i = 0; i < 4; ++i) + { + data.Coords[i] = allOnes; + } } return data; @@ -159,10 +190,20 @@ public static int EndpointRangeForBlock(in IntermediateBlockData data) /// internal static bool SharedEndpointModes(in IntermediateBlockData data) { - if (data.EndpointCount == 0) return true; + if (data.EndpointCount == 0) + { + return true; + } + var first = data.Endpoints[0].Mode; for (int i = 1; i < data.EndpointCount; i++) - if (data.Endpoints[i].Mode != first) return false; + { + if (data.Endpoints[i].Mode != first) + { + return false; + } + } + return true; } @@ -175,11 +216,14 @@ internal static int ExtraConfigBitPosition(in IntermediateBlockData data) int extraConfigBitCount = 0; if (!SharedEndpointModes(data)) { - int encodedCemBitCount = 2 + data.EndpointCount * 3; + int encodedCemBitCount = 2 + (data.EndpointCount * 3); extraConfigBitCount = encodedCemBitCount - 6; } - if (hasDualChannel) extraConfigBitCount += 2; + if (hasDualChannel) + { + extraConfigBitCount += 2; + } return 128 - weightBitCount - extraConfigBitCount; } @@ -199,7 +243,7 @@ internal struct EndpointColorValues { public const int MaxColorValues = 8; #pragma warning disable CS0169, S1144 // Accessed by runtime via [InlineArray] - private int _element0; + private int element0; #pragma warning restore CS0169, S1144 } @@ -233,7 +277,7 @@ internal struct IntermediateEndpointBuffer { public const int MaxPartitions = 4; #pragma warning disable CS0169, S1144 // Accessed by runtime via [InlineArray] - private IntermediateEndpointData _element0; + private IntermediateEndpointData element0; #pragma warning restore CS0169, S1144 } } diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs index b6dbd6d6..c05cc800 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs @@ -10,22 +10,22 @@ namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; internal static class IntermediateBlockPacker { - private static readonly BlockModeInfo[] _blockModeInfoTable = [ - new BlockModeInfo{ MinWeightGridDimX=4, MaxWeightGridDimX=7, MinWeightGridDimY=2, MaxWeightGridDimY=5, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=7, WeightGridYOffsetBitPos=5, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=8, MaxWeightGridDimX=11, MinWeightGridDimY=2, MaxWeightGridDimY=5, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=7, WeightGridYOffsetBitPos=5, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=2, MaxWeightGridDimX=5, MinWeightGridDimY=8, MaxWeightGridDimY=11, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=5, WeightGridYOffsetBitPos=7, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=2, MaxWeightGridDimX=5, MinWeightGridDimY=6, MaxWeightGridDimY=7, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=5, WeightGridYOffsetBitPos=7, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=2, MaxWeightGridDimX=3, MinWeightGridDimY=2, MaxWeightGridDimY=5, R0BitPos=4, R1BitPos=0, R2BitPos=1, WeightGridXOffsetBitPos=7, WeightGridYOffsetBitPos=5, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=12, MaxWeightGridDimX=12, MinWeightGridDimY=2, MaxWeightGridDimY=5, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=-1, WeightGridYOffsetBitPos=5, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=2, MaxWeightGridDimX=5, MinWeightGridDimY=12, MaxWeightGridDimY=12, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=5, WeightGridYOffsetBitPos=-1, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=6, MaxWeightGridDimX=6, MinWeightGridDimY=10, MaxWeightGridDimY=10, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=-1, WeightGridYOffsetBitPos=-1, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=10, MaxWeightGridDimX=10, MinWeightGridDimY=6, MaxWeightGridDimY=6, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=-1, WeightGridYOffsetBitPos=-1, RequireSinglePlaneLowPrec=false }, - new BlockModeInfo{ MinWeightGridDimX=6, MaxWeightGridDimX=9, MinWeightGridDimY=6, MaxWeightGridDimY=9, R0BitPos=4, R1BitPos=2, R2BitPos=3, WeightGridXOffsetBitPos=5, WeightGridYOffsetBitPos=9, RequireSinglePlaneLowPrec=true } + private static readonly BlockModeInfo[] BlockModeInfoTable = [ + new BlockModeInfo { MinWeightGridDimX = 4, MaxWeightGridDimX = 7, MinWeightGridDimY = 2, MaxWeightGridDimY = 5, R0BitPos = 4, R1BitPos = 0, R2BitPos = 1, WeightGridXOffsetBitPos = 7, WeightGridYOffsetBitPos = 5, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 8, MaxWeightGridDimX = 11, MinWeightGridDimY = 2, MaxWeightGridDimY = 5, R0BitPos = 4, R1BitPos = 0, R2BitPos = 1, WeightGridXOffsetBitPos = 7, WeightGridYOffsetBitPos = 5, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 2, MaxWeightGridDimX = 5, MinWeightGridDimY = 8, MaxWeightGridDimY = 11, R0BitPos = 4, R1BitPos = 0, R2BitPos = 1, WeightGridXOffsetBitPos = 5, WeightGridYOffsetBitPos = 7, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 2, MaxWeightGridDimX = 5, MinWeightGridDimY = 6, MaxWeightGridDimY = 7, R0BitPos = 4, R1BitPos = 0, R2BitPos = 1, WeightGridXOffsetBitPos = 5, WeightGridYOffsetBitPos = 7, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 2, MaxWeightGridDimX = 3, MinWeightGridDimY = 2, MaxWeightGridDimY = 5, R0BitPos = 4, R1BitPos = 0, R2BitPos = 1, WeightGridXOffsetBitPos = 7, WeightGridYOffsetBitPos = 5, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 12, MaxWeightGridDimX = 12, MinWeightGridDimY = 2, MaxWeightGridDimY = 5, R0BitPos = 4, R1BitPos = 2, R2BitPos = 3, WeightGridXOffsetBitPos = -1, WeightGridYOffsetBitPos = 5, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 2, MaxWeightGridDimX = 5, MinWeightGridDimY = 12, MaxWeightGridDimY = 12, R0BitPos = 4, R1BitPos = 2, R2BitPos = 3, WeightGridXOffsetBitPos = 5, WeightGridYOffsetBitPos = -1, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 6, MaxWeightGridDimX = 6, MinWeightGridDimY = 10, MaxWeightGridDimY = 10, R0BitPos = 4, R1BitPos = 2, R2BitPos = 3, WeightGridXOffsetBitPos = -1, WeightGridYOffsetBitPos = -1, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 10, MaxWeightGridDimX = 10, MinWeightGridDimY = 6, MaxWeightGridDimY = 6, R0BitPos = 4, R1BitPos = 2, R2BitPos = 3, WeightGridXOffsetBitPos = -1, WeightGridYOffsetBitPos = -1, RequireSinglePlaneLowPrec = false }, + new BlockModeInfo { MinWeightGridDimX = 6, MaxWeightGridDimX = 9, MinWeightGridDimY = 6, MaxWeightGridDimY = 9, R0BitPos = 4, R1BitPos = 2, R2BitPos = 3, WeightGridXOffsetBitPos = 5, WeightGridYOffsetBitPos = 9, RequireSinglePlaneLowPrec = true } ]; - private static readonly uint[] _blockModeMasks = [0x0u, 0x4u, 0x8u, 0xCu, 0x10Cu, 0x0u, 0x80u, 0x180u, 0x1A0u, 0x100u]; + private static readonly uint[] BlockModeMasks = [0x0u, 0x4u, 0x8u, 0xCu, 0x10Cu, 0x0u, 0x80u, 0x180u, 0x1A0u, 0x100u]; - public static (string? error, UInt128 physicalBlockBits) Pack(in IntermediateBlock.IntermediateBlockData data) + public static (string? Error, UInt128 PhysicalBlockBits) Pack(in IntermediateBlock.IntermediateBlockData data) { UInt128 physicalBlockBits = 0; int expectedWeightsCount = data.WeightGridX * data.WeightGridY @@ -42,7 +42,10 @@ public static (string? error, UInt128 physicalBlockBits) Pack(in IntermediateBlo // First we need to encode the block mode. var errorMessage = PackBlockMode(data.WeightGridX, data.WeightGridY, data.WeightRange, data.DualPlaneChannel.HasValue, ref bitSink); - if (errorMessage != null) { return (errorMessage, 0); } + if (errorMessage != null) + { + return (errorMessage, 0); + } // number of partitions minus one int partitionCount = data.EndpointCount; @@ -58,13 +61,19 @@ public static (string? error, UInt128 physicalBlockBits) Pack(in IntermediateBlo var (weightSink, weightBitsCount) = EncodeWeights(data); var (error, extraConfig) = EncodeColorEndpointModes(data, partitionCount, ref bitSink); - if (error != null) return (error, 0); + if (error != null) + { + return (error, 0); + } int colorValueRange = data.EndpointRange.HasValue ? data.EndpointRange.Value : IntermediateBlock.EndpointRangeForBlock(data); if (colorValueRange == -1) + { throw new InvalidOperationException($"{nameof(colorValueRange)} must not be EndpointRangeInvalidWeightDimensions"); + } + if (colorValueRange == -2) { return ("Intermediate block emits illegal color range", 0); @@ -77,10 +86,15 @@ public static (string? error, UInt128 physicalBlockBits) Pack(in IntermediateBlo for (int j = 0; j < ep.ColorCount; j++) { int color = ep.Colors[j]; - if (color > colorValueRange) return ("Color outside available color range!", 0); + if (color > colorValueRange) + { + return ("Color outside available color range!", 0); + } + colorEncoder.AddValue(color); } } + colorEncoder.Encode(ref bitSink); int extraConfigBitPosition = IntermediateBlock.ExtraConfigBitPosition(data); @@ -103,13 +117,18 @@ public static (string? error, UInt128 physicalBlockBits) Pack(in IntermediateBlo bitSink.PutBits((uint)extraConfig, extraConfigBits); } - ArgumentOutOfRangeException.ThrowIfNotEqual(bitSink.Bits, (uint)128 - weightBitsCount); + ArgumentOutOfRangeException.ThrowIfNotEqual(bitSink.Bits, 128u - weightBitsCount); // Flush out the bit writer if (!bitSink.TryGetBits(128 - weightBitsCount, out var astcBits)) + { throw new InvalidOperationException(); + } + if (!weightSink.TryGetBits(weightBitsCount, out var revWeightBits)) + { throw new InvalidOperationException(); + } var combined = astcBits | UInt128Extensions.ReverseBits(revWeightBits); physicalBlockBits = combined; @@ -120,21 +139,23 @@ public static (string? error, UInt128 physicalBlockBits) Pack(in IntermediateBlo return (illegal, physicalBlockBits); } - public static (string? error, UInt128 physicalBlockBits) Pack(IntermediateBlock.VoidExtentData data) + public static (string? Error, UInt128 PhysicalBlockBits) Pack(IntermediateBlock.VoidExtentData data) { // Pack void extent // Assemble the 128-bit value explicitly: low 64 bits = RGBA (4x16) // high 64 bits = 12-bit header (0xDFC) followed by four 13-bit coords. ulong high64 = ((ulong)data.A << 48) | ((ulong)data.B << 32) | ((ulong)data.G << 16) | (ulong)data.R; ulong low64 = 0UL; + // Header occupies lowest 12 bits of the high word low64 |= 0xDFCu; for (int i = 0; i < 4; ++i) { - low64 |= ((ulong)(data.Coords[i] & 0x1FFF)) << (12 + 13 * i); + low64 |= ((ulong)(data.Coords[i] & 0x1FFF)) << (12 + (13 * i)); } UInt128 physicalBlockBits; + // Decide representation: if the RGBA low word is zero we emit the // compact single-ulong representation (low word = header+coords, // high word = 0) to match the reference tests. Otherwise the @@ -142,11 +163,13 @@ public static (string? error, UInt128 physicalBlockBits) Pack(IntermediateBlock. if (high64 == 0UL) { physicalBlockBits = (UInt128)low64; + // using compact void extent representation } else { physicalBlockBits = new UInt128(high64, low64); + // using full void extent representation } @@ -156,14 +179,15 @@ public static (string? error, UInt128 physicalBlockBits) Pack(IntermediateBlock. { throw new InvalidOperationException($"{nameof(Pack)}(void extent) produced illegal encoding"); } + return (illegal, physicalBlockBits); } - private static (string? error, int[] range) GetEncodedWeightRange(int range) + private static (string? Error, int[] Range) GetEncodedWeightRange(int range) { int[][] validRangeEncodings = [ - [0,1,0], [1,1,0], [0,0,1], [1,0,1], [0,1,1], [1,1,1], - [0,1,0], [1,1,0], [0,0,1], [1,0,1], [0,1,1], [1,1,1] + [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1], + [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1] ]; int smallestRange = IntermediateBlock.ValidWeightRanges.First(); @@ -174,7 +198,11 @@ private static (string? error, int[] range) GetEncodedWeightRange(int range) } int index = Array.FindIndex(IntermediateBlock.ValidWeightRanges, v => v >= range); - if (index < 0) index = IntermediateBlock.ValidWeightRanges.Length - 1; + if (index < 0) + { + index = IntermediateBlock.ValidWeightRanges.Length - 1; + } + var encoding = validRangeEncodings[index]; return (null, [encoding[0], encoding[1], encoding[2]]); } @@ -183,15 +211,20 @@ private static (string? error, int[] range) GetEncodedWeightRange(int range) { bool highPrec = range > 7; var (maybeErr, rangeValues) = GetEncodedWeightRange(range); - if (maybeErr != null) return maybeErr; + if (maybeErr != null) + { + return maybeErr; + } // Ensure top two bits of r1 and r2 not both zero per reference if ((rangeValues[1] | rangeValues[2]) <= 0) + { throw new InvalidOperationException($"{nameof(rangeValues)}[1] | {nameof(rangeValues)}[2] must be > 0"); + } - for (int mode = 0; mode < _blockModeInfoTable.Length; ++mode) + for (int mode = 0; mode < BlockModeInfoTable.Length; ++mode) { - var blockMode = _blockModeInfoTable[mode]; + var blockMode = BlockModeInfoTable[mode]; bool isValidMode = true; isValidMode &= blockMode.MinWeightGridDimX <= dimX; isValidMode &= dimX <= blockMode.MaxWeightGridDimX; @@ -200,18 +233,25 @@ private static (string? error, int[] range) GetEncodedWeightRange(int range) isValidMode &= !(blockMode.RequireSinglePlaneLowPrec && dualPlane); isValidMode &= !(blockMode.RequireSinglePlaneLowPrec && highPrec); - if (!isValidMode) continue; + if (!isValidMode) + { + continue; + } - uint encodedMode = _blockModeMasks[mode]; - void setBit(uint value, int offset) + uint encodedMode = BlockModeMasks[mode]; + void SetBit(uint value, int offset) { - if (offset < 0) return; + if (offset < 0) + { + return; + } + encodedMode = (encodedMode & ~(1u << offset)) | ((value & 1u) << offset); } - setBit((uint)rangeValues[0], blockMode.R0BitPos); - setBit((uint)rangeValues[1], blockMode.R1BitPos); - setBit((uint)rangeValues[2], blockMode.R2BitPos); + SetBit((uint)rangeValues[0], blockMode.R0BitPos); + SetBit((uint)rangeValues[1], blockMode.R1BitPos); + SetBit((uint)rangeValues[2], blockMode.R2BitPos); int offsetX = dimX - blockMode.MinWeightGridDimX; int offsetY = dimY - blockMode.MinWeightGridDimY; @@ -236,12 +276,15 @@ void setBit(uint value, int offset) if (!blockMode.RequireSinglePlaneLowPrec) { - setBit((uint)(highPrec ? 1u : 0u), 9); - setBit((uint)(dualPlane ? 1u : 0u), 10); + SetBit((uint)(highPrec ? 1u : 0u), 9); + SetBit((uint)(dualPlane ? 1u : 0u), 10); } if (bitSink.Bits != 0) + { throw new InvalidOperationException($"{nameof(bitSink)}.{nameof(bitSink.Bits)} must be 0"); + } + bitSink.PutBits(encodedMode, 11); return null; } @@ -249,7 +292,7 @@ void setBit(uint value, int offset) return "Could not find viable block mode"; } - private static (BitStream weightSink, int weightBitsCount) EncodeWeights(in IntermediateBlock.IntermediateBlockData data) + private static (BitStream WeightSink, int WeightBitsCount) EncodeWeights(in IntermediateBlock.IntermediateBlockData data) { var weightSink = new BitStream(0UL, 0); var weightsEncoder = new BoundedIntegerSequenceEncoder(data.WeightRange); @@ -257,31 +300,45 @@ private static (BitStream weightSink, int weightBitsCount) EncodeWeights(in Inte ? data.WeightsCount : (data.Weights?.Length ?? 0); if (data.Weights is null) + { throw new InvalidOperationException($"{nameof(data.Weights)} is null in {nameof(EncodeWeights)}"); - for (var i = 0; i < weightCount; i++) weightsEncoder.AddValue(data.Weights[i]); + } + + for (var i = 0; i < weightCount; i++) + { + weightsEncoder.AddValue(data.Weights[i]); + } + weightsEncoder.Encode(ref weightSink); int weightBitsCount = (int)weightSink.Bits; if ((int)weightSink.Bits != BoundedIntegerSequenceCodec.GetBitCountForRange(weightCount, data.WeightRange)) + { throw new InvalidOperationException($"{nameof(weightSink)}.{nameof(weightSink.Bits)} does not match expected bit count"); + } return (weightSink, weightBitsCount); } - private static (string? error, int extraConfig) EncodeColorEndpointModes(in IntermediateBlock.IntermediateBlockData data, int partitionCount, ref BitStream bitSink) + private static (string? Error, int ExtraConfig) EncodeColorEndpointModes(in IntermediateBlock.IntermediateBlockData data, int partitionCount, ref BitStream bitSink) { int extraConfig = 0; bool sharedEndpointMode = IntermediateBlock.SharedEndpointModes(data); if (sharedEndpointMode) { - if (partitionCount > 1) bitSink.PutBits(0u, 2); + if (partitionCount > 1) + { + bitSink.PutBits(0u, 2); + } + bitSink.PutBits((uint)data.Endpoints[0].Mode, 4); } else { // compute min_class, max_class - int minClass = 2; int maxClass = 0; + int minClass = 2; + int maxClass = 0; for (int i = 0; i < data.EndpointCount; i++) { int endpointModeClass = ((int)data.Endpoints[i].Mode) >> 2; @@ -289,7 +346,10 @@ private static (string? error, int extraConfig) EncodeColorEndpointModes(in Inte maxClass = Math.Max(maxClass, endpointModeClass); } - if (maxClass - minClass > 1) return ("Endpoint modes are invalid", 0); + if (maxClass - minClass > 1) + { + return ("Endpoint modes are invalid", 0); + } var cemEncoder = new BitStream(0UL, 0); cemEncoder.PutBits((uint)(minClass + 1), 2); @@ -307,9 +367,11 @@ private static (string? error, int extraConfig) EncodeColorEndpointModes(in Inte cemEncoder.PutBits(epMode, 2); } - int cemBits = 2 + partitionCount * 3; + int cemBits = 2 + (partitionCount * 3); if (!cemEncoder.TryGetBits(cemBits, out uint encodedCem)) + { throw new InvalidOperationException(); + } extraConfig = (int)(encodedCem >> 6); diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs index 98514355..0ef85ab5 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs @@ -10,18 +10,18 @@ namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; internal sealed class LogicalBlock { - private ColorEndpointPair[] _endpoints; - private int _endpointCount; - private int[] _weights; - private Partition _partition; - private DualPlaneData? _dualPlane; + private ColorEndpointPair[] endpoints; + private int endpointCount; + private int[] weights; + private Partition partition; + private DualPlaneData? dualPlane; public LogicalBlock(Footprint footprint) { - _endpoints = [ColorEndpointPair.Ldr(RgbaColor.Empty, RgbaColor.Empty)]; - _endpointCount = 1; - _weights = new int[footprint.PixelCount]; - _partition = new Partition(footprint, 1, 0) + this.endpoints = [ColorEndpointPair.Ldr(RgbaColor.Empty, RgbaColor.Empty)]; + this.endpointCount = 1; + this.weights = new int[footprint.PixelCount]; + this.partition = new Partition(footprint, 1, 0) { Assignment = new int[footprint.PixelCount] }; @@ -29,49 +29,57 @@ public LogicalBlock(Footprint footprint) public LogicalBlock(Footprint footprint, in IntermediateBlock.IntermediateBlockData block) { - _endpoints = new ColorEndpointPair[block.EndpointCount]; - _endpointCount = DecodeEndpoints(in block, _endpoints); - _partition = ComputePartition(footprint, in block); - _weights = new int[footprint.PixelCount]; - CalculateWeights(footprint, in block); + this.endpoints = new ColorEndpointPair[block.EndpointCount]; + this.endpointCount = DecodeEndpoints(in block, this.endpoints); + this.partition = ComputePartition(footprint, in block); + this.weights = new int[footprint.PixelCount]; + this.CalculateWeights(footprint, in block); } public LogicalBlock(Footprint footprint, IntermediateBlock.VoidExtentData block) { - _endpoints = new ColorEndpointPair[1]; - _endpointCount = DecodeEndpoints(block, _endpoints); - _partition = ComputePartition(footprint, block); - _weights = new int[footprint.PixelCount]; + this.endpoints = new ColorEndpointPair[1]; + this.endpointCount = DecodeEndpoints(block, this.endpoints); + this.partition = ComputePartition(footprint, block); + this.weights = new int[footprint.PixelCount]; } /// - /// Direct-decode constructor: decodes directly from raw bits + BlockInfo, + /// Initializes a new instance of the class. + /// Decodes directly from raw bits + BlockInfo, /// bypassing IntermediateBlock and using batch unquantize operations. /// private LogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) { // --- BISE decode + batch unquantize color endpoint values --- Span colors = stackalloc int[info.ColorValuesCount]; - FusedBlockDecoder.DecodeBiseValues(bits, info.ColorStartBit, info.ColorBitCount, - info.ColorValuesRange, info.ColorValuesCount, colors); + FusedBlockDecoder.DecodeBiseValues( + bits, + info.ColorStartBit, + info.ColorBitCount, + info.ColorValuesRange, + info.ColorValuesCount, + colors); Quantization.UnquantizeCEValuesBatch(colors, info.ColorValuesCount, info.ColorValuesRange); // --- Decode endpoints per partition --- - _endpointCount = info.PartitionCount; - _endpoints = new ColorEndpointPair[_endpointCount]; + this.endpointCount = info.PartitionCount; + this.endpoints = new ColorEndpointPair[this.endpointCount]; int colorIndex = 0; - for (int i = 0; i < _endpointCount; i++) + for (int i = 0; i < this.endpointCount; i++) { var mode = info.GetEndpointMode(i); int colorCount = mode.GetColorValuesCount(); ReadOnlySpan slice = colors.Slice(colorIndex, colorCount); - _endpoints[i] = EndpointCodec.DecodeColorsForModePolymorphicUnquantized(slice, mode); + this.endpoints[i] = EndpointCodec.DecodeColorsForModePolymorphicUnquantized(slice, mode); colorIndex += colorCount; } // --- Set up partition --- - _partition = info.PartitionCount > 1 - ? Partition.GetASTCPartition(footprint, info.PartitionCount, + this.partition = info.PartitionCount > 1 + ? Partition.GetASTCPartition( + footprint, + info.PartitionCount, (int)BitOperations.GetBits(bits.Low(), 13, 10)) : GenerateSinglePartition(footprint); @@ -81,16 +89,20 @@ private LogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) int totalWeights = isDualPlane ? gridSize * 2 : gridSize; Span rawWeights = stackalloc int[totalWeights]; - FusedBlockDecoder.DecodeBiseWeights(bits, info.WeightBitCount, info.WeightRange, - totalWeights, rawWeights); + FusedBlockDecoder.DecodeBiseWeights( + bits, + info.WeightBitCount, + info.WeightRange, + totalWeights, + rawWeights); var decimationInfo = DecimationTable.Get(footprint, info.GridWidth, info.GridHeight); - _weights = new int[footprint.PixelCount]; + this.weights = new int[footprint.PixelCount]; if (!isDualPlane) { Quantization.UnquantizeWeightsBatch(rawWeights, gridSize, info.WeightRange); - DecimationTable.InfillWeights(rawWeights[..gridSize], decimationInfo, _weights); + DecimationTable.InfillWeights(rawWeights[..gridSize], decimationInfo, this.weights); } else { @@ -100,96 +112,110 @@ private LogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) for (int i = 0; i < gridSize; i++) { plane0[i] = rawWeights[i * 2]; - plane1[i] = rawWeights[i * 2 + 1]; + plane1[i] = rawWeights[(i * 2) + 1]; } Quantization.UnquantizeWeightsBatch(plane0, gridSize, info.WeightRange); Quantization.UnquantizeWeightsBatch(plane1, gridSize, info.WeightRange); - DecimationTable.InfillWeights(plane0, decimationInfo, _weights); + DecimationTable.InfillWeights(plane0, decimationInfo, this.weights); - _dualPlane = new DualPlaneData + this.dualPlane = new DualPlaneData { Channel = info.DualPlaneChannel, Weights = new int[footprint.PixelCount] }; - DecimationTable.InfillWeights(plane1, decimationInfo, _dualPlane.Weights); + DecimationTable.InfillWeights(plane1, decimationInfo, this.dualPlane.Weights); } } - public Footprint GetFootprint() => _partition.Footprint; + public Footprint GetFootprint() => this.partition.Footprint; public void SetWeightAt(int x, int y, int weight) { if (weight < 0 || weight > 64) + { throw new ArgumentOutOfRangeException(nameof(weight)); + } - _weights[y * GetFootprint().Width + x] = weight; + this.weights[(y * this.GetFootprint().Width) + x] = weight; } - public int WeightAt(int x, int y) => _weights[y * GetFootprint().Width + x]; + public int WeightAt(int x, int y) => this.weights[(y * this.GetFootprint().Width) + x]; public void SetDualPlaneWeightAt(int channel, int x, int y, int weight) { ArgumentOutOfRangeException.ThrowIfNegative(channel); ArgumentOutOfRangeException.ThrowIfGreaterThan(weight, 64); - if (!IsDualPlane()) + if (!this.IsDualPlane()) + { throw new InvalidOperationException("Not a dual plane block"); + } - if (_dualPlane is not null && _dualPlane.Channel == channel) - _dualPlane.Weights[y * GetFootprint().Width + x] = weight; + if (this.dualPlane is not null && this.dualPlane.Channel == channel) + { + this.dualPlane.Weights[(y * this.GetFootprint().Width) + x] = weight; + } else - SetWeightAt(x, y, weight); + { + this.SetWeightAt(x, y, weight); + } } public int DualPlaneWeightAt(int channel, int x, int y) { - if (!IsDualPlane()) - return WeightAt(x, y); + if (!this.IsDualPlane()) + { + return this.WeightAt(x, y); + } - return _dualPlane is not null && _dualPlane.Channel == channel - ? _dualPlane.Weights[y * GetFootprint().Width + x] - : WeightAt(x, y); + return this.dualPlane is not null && this.dualPlane.Channel == channel + ? this.dualPlane.Weights[(y * this.GetFootprint().Width) + x] + : this.WeightAt(x, y); } public RgbaColor ColorAt(int x, int y) { - var footprint = GetFootprint(); + var footprint = this.GetFootprint(); ArgumentOutOfRangeException.ThrowIfNegative(x); ArgumentOutOfRangeException.ThrowIfNegative(y); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(x, footprint.Width); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(y, footprint.Height); - int index = y * footprint.Width + x; - int part = _partition.Assignment[index]; - ref var endpoint = ref _endpoints[part]; + int index = (y * footprint.Width) + x; + int part = this.partition.Assignment[index]; + ref var endpoint = ref this.endpoints[part]; - int weight = _weights[index]; + int weight = this.weights[index]; if (!endpoint.IsHdr) { - if (_dualPlane is not null) + if (this.dualPlane is not null) + { return SimdHelpers.InterpolateColorLdrDualPlane( - endpoint.LdrLow, endpoint.LdrHigh, weight, _dualPlane.Channel, _dualPlane.Weights[index]); + endpoint.LdrLow, endpoint.LdrHigh, weight, this.dualPlane.Channel, this.dualPlane.Weights[index]); + } + return SimdHelpers.InterpolateColorLdr(endpoint.LdrLow, endpoint.LdrHigh, weight); } else { - if (_dualPlane is not null) + if (this.dualPlane is not null) { - int dualPlaneChannel = _dualPlane.Channel; - int dualPlaneWeight = _dualPlane.Weights[index]; + int dualPlaneChannel = this.dualPlane.Channel; + int dualPlaneWeight = this.dualPlane.Weights[index]; + int rWeight = dualPlaneChannel == 0 ? dualPlaneWeight : weight; + int gWeight = dualPlaneChannel == 1 ? dualPlaneWeight : weight; + int bWeight = dualPlaneChannel == 2 ? dualPlaneWeight : weight; + int aWeight = dualPlaneChannel == 3 ? dualPlaneWeight : weight; return new RgbaColor( - r: InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], - dualPlaneChannel == 0 ? dualPlaneWeight : weight) >> 8, - g: InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], - dualPlaneChannel == 1 ? dualPlaneWeight : weight) >> 8, - b: InterpolateChannelHdr(endpoint.HdrLow[2], endpoint.HdrHigh[2], - dualPlaneChannel == 2 ? dualPlaneWeight : weight) >> 8, - a: InterpolateChannelHdr(endpoint.HdrLow[3], endpoint.HdrHigh[3], - dualPlaneChannel == 3 ? dualPlaneWeight : weight) >> 8); + r: InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], rWeight) >> 8, + g: InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], gWeight) >> 8, + b: InterpolateChannelHdr(endpoint.HdrLow[2], endpoint.HdrHigh[2], bWeight) >> 8, + a: InterpolateChannelHdr(endpoint.HdrLow[3], endpoint.HdrHigh[3], aWeight) >> 8); } + return new RgbaColor( r: InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], weight) >> 8, g: InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], weight) >> 8, @@ -207,34 +233,35 @@ public RgbaColor ColorAt(int x, int y) /// public RgbaHdrColor ColorAtHdr(int x, int y) { - var footprint = GetFootprint(); + var footprint = this.GetFootprint(); ArgumentOutOfRangeException.ThrowIfNegative(x); ArgumentOutOfRangeException.ThrowIfNegative(y); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(x, footprint.Width); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(y, footprint.Height); - int index = y * footprint.Width + x; - int part = _partition.Assignment[index]; - ref var endpoint = ref _endpoints[part]; + int index = (y * footprint.Width) + x; + int part = this.partition.Assignment[index]; + ref var endpoint = ref this.endpoints[part]; - int weight = _weights[index]; + int weight = this.weights[index]; if (endpoint.IsHdr) { - if (_dualPlane != null) + if (this.dualPlane != null) { - int dualPlaneChannel = _dualPlane.Channel; - int dualPlaneWeight = _dualPlane.Weights[index]; + int dualPlaneChannel = this.dualPlane.Channel; + int dualPlaneWeight = this.dualPlane.Weights[index]; + int rWeight = dualPlaneChannel == 0 ? dualPlaneWeight : weight; + int gWeight = dualPlaneChannel == 1 ? dualPlaneWeight : weight; + int bWeight = dualPlaneChannel == 2 ? dualPlaneWeight : weight; + int aWeight = dualPlaneChannel == 3 ? dualPlaneWeight : weight; return new RgbaHdrColor( - InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], - dualPlaneChannel == 0 ? dualPlaneWeight : weight), - InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], - dualPlaneChannel == 1 ? dualPlaneWeight : weight), - InterpolateChannelHdr(endpoint.HdrLow[2], endpoint.HdrHigh[2], - dualPlaneChannel == 2 ? dualPlaneWeight : weight), - InterpolateChannelHdr(endpoint.HdrLow[3], endpoint.HdrHigh[3], - dualPlaneChannel == 3 ? dualPlaneWeight : weight)); + InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], rWeight), + InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], gWeight), + InterpolateChannelHdr(endpoint.HdrLow[2], endpoint.HdrHigh[2], bWeight), + InterpolateChannelHdr(endpoint.HdrLow[3], endpoint.HdrHigh[3], aWeight)); } + return new RgbaHdrColor( InterpolateChannelHdr(endpoint.HdrLow[0], endpoint.HdrHigh[0], weight), InterpolateChannelHdr(endpoint.HdrLow[1], endpoint.HdrHigh[1], weight), @@ -243,20 +270,21 @@ public RgbaHdrColor ColorAtHdr(int x, int y) } else { - if (_dualPlane != null) + if (this.dualPlane != null) { - int dualPlaneChannel = _dualPlane.Channel; - int dualPlaneWeight = _dualPlane.Weights[index]; + int dualPlaneChannel = this.dualPlane.Channel; + int dualPlaneWeight = this.dualPlane.Weights[index]; + int rWeight = dualPlaneChannel == 0 ? dualPlaneWeight : weight; + int gWeight = dualPlaneChannel == 1 ? dualPlaneWeight : weight; + int bWeight = dualPlaneChannel == 2 ? dualPlaneWeight : weight; + int aWeight = dualPlaneChannel == 3 ? dualPlaneWeight : weight; return new RgbaHdrColor( - (ushort)(InterpolateChannel(endpoint.LdrLow.R, endpoint.LdrHigh.R, - dualPlaneChannel == 0 ? dualPlaneWeight : weight) * 257), - (ushort)(InterpolateChannel(endpoint.LdrLow.G, endpoint.LdrHigh.G, - dualPlaneChannel == 1 ? dualPlaneWeight : weight) * 257), - (ushort)(InterpolateChannel(endpoint.LdrLow.B, endpoint.LdrHigh.B, - dualPlaneChannel == 2 ? dualPlaneWeight : weight) * 257), - (ushort)(InterpolateChannel(endpoint.LdrLow.A, endpoint.LdrHigh.A, - dualPlaneChannel == 3 ? dualPlaneWeight : weight) * 257)); + (ushort)(InterpolateChannel(endpoint.LdrLow.R, endpoint.LdrHigh.R, rWeight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.G, endpoint.LdrHigh.G, gWeight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.B, endpoint.LdrHigh.B, bWeight) * 257), + (ushort)(InterpolateChannel(endpoint.LdrLow.A, endpoint.LdrHigh.A, aWeight) * 257)); } + return new RgbaHdrColor( (ushort)(InterpolateChannel(endpoint.LdrLow.R, endpoint.LdrHigh.R, weight) * 257), (ushort)(InterpolateChannel(endpoint.LdrLow.G, endpoint.LdrHigh.G, weight) * 257), @@ -276,20 +304,20 @@ public RgbaHdrColor ColorAtHdr(int x, int y) /// public void WriteHdrPixel(int x, int y, Span output) { - var footprint = GetFootprint(); + var footprint = this.GetFootprint(); ArgumentOutOfRangeException.ThrowIfNegative(x); ArgumentOutOfRangeException.ThrowIfNegative(y); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(x, footprint.Width); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(y, footprint.Height); - int index = y * footprint.Width + x; - int part = _partition.Assignment[index]; - ref var endpoint = ref _endpoints[part]; + int index = (y * footprint.Width) + x; + int part = this.partition.Assignment[index]; + ref var endpoint = ref this.endpoints[part]; - int weight = _weights[index]; - int dualPlaneChannel = _dualPlane?.Channel ?? -1; - int dualPlaneWeight = _dualPlane?.Weights[index] ?? weight; + int weight = this.weights[index]; + int dualPlaneChannel = this.dualPlane?.Channel ?? -1; + int dualPlaneWeight = this.dualPlane?.Weights[index] ?? weight; if (endpoint.IsHdr) { @@ -339,82 +367,114 @@ public void WriteHdrPixel(int x, int y, Span output) /// public void WriteAllPixelsLdr(Footprint footprint, Span buffer) { - ref var endpoint0 = ref _endpoints[0]; + ref var endpoint0 = ref this.endpoints[0]; - if (!endpoint0.IsHdr && _partition.PartitionCount == 1) + if (!endpoint0.IsHdr && this.partition.PartitionCount == 1) { // Fast path: single-partition LDR block (most common case) int lowR = endpoint0.LdrLow.R, lowG = endpoint0.LdrLow.G, lowB = endpoint0.LdrLow.B, lowA = endpoint0.LdrLow.A; int highR = endpoint0.LdrHigh.R, highG = endpoint0.LdrHigh.G, highB = endpoint0.LdrHigh.B, highA = endpoint0.LdrHigh.A; - if (_dualPlane == null) + if (this.dualPlane == null) { - WriteLdrSinglePartition(buffer, footprint, lowR, lowG, lowB, lowA, highR, highG, highB, highA); + this.WriteLdrSinglePartition(buffer, footprint, lowR, lowG, lowB, lowA, highR, highG, highB, highA); } else { - int dualPlaneChannel = _dualPlane.Channel; - var dpWeights = _dualPlane.Weights; + int dualPlaneChannel = this.dualPlane.Channel; + var dpWeights = this.dualPlane.Weights; int pixelCount = footprint.PixelCount; for (int i = 0; i < pixelCount; i++) { SimdHelpers.WriteSinglePixelLdrDualPlane( - buffer, i * 4, - lowR, lowG, lowB, lowA, highR, highG, highB, highA, - _weights[i], dualPlaneChannel, dpWeights[i]); + buffer, + i * 4, + lowR, + lowG, + lowB, + lowA, + highR, + highG, + highB, + highA, + this.weights[i], + dualPlaneChannel, + dpWeights[i]); } } } else { // General path: multi-partition or HDR blocks - WriteAllPixelsGeneral(footprint, buffer); + this.WriteAllPixelsGeneral(footprint, buffer); } } public void SetPartition(Partition p) { - if (!p.Footprint.Equals(_partition.Footprint)) + if (!p.Footprint.Equals(this.partition.Footprint)) + { throw new InvalidOperationException("New partitions may not be for a different footprint"); - _partition = p; - if (_endpointCount < p.PartitionCount) + } + + this.partition = p; + if (this.endpointCount < p.PartitionCount) { var newEndpoints = new ColorEndpointPair[p.PartitionCount]; - Array.Copy(_endpoints, newEndpoints, _endpointCount); - for (int i = _endpointCount; i < p.PartitionCount; i++) + Array.Copy(this.endpoints, newEndpoints, this.endpointCount); + for (int i = this.endpointCount; i < p.PartitionCount; i++) + { newEndpoints[i] = ColorEndpointPair.Ldr(RgbaColor.Empty, RgbaColor.Empty); - _endpoints = newEndpoints; + } + + this.endpoints = newEndpoints; } - _endpointCount = p.PartitionCount; + + this.endpointCount = p.PartitionCount; } public void SetEndpoints(RgbaColor firstEndpoint, RgbaColor secondEndpoint, int subset) { ArgumentOutOfRangeException.ThrowIfNegative(subset); - ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(subset, _partition.PartitionCount); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(subset, this.partition.PartitionCount); - _endpoints[subset] = ColorEndpointPair.Ldr(firstEndpoint, secondEndpoint); + this.endpoints[subset] = ColorEndpointPair.Ldr(firstEndpoint, secondEndpoint); } public void SetDualPlaneChannel(int channel) { - if (channel < 0) { _dualPlane = null; } - else if (_dualPlane != null) { _dualPlane.Channel = channel; } - else { _dualPlane = new DualPlaneData { Channel = channel, Weights = (int[])_weights.Clone() }; } + if (channel < 0) + { + this.dualPlane = null; + } + else if (this.dualPlane != null) + { + this.dualPlane.Channel = channel; + } + else + { + this.dualPlane = new DualPlaneData { Channel = channel, Weights = (int[])this.weights.Clone() }; + } } - public bool IsDualPlane() => _dualPlane is not null; + public bool IsDualPlane() => this.dualPlane is not null; public static LogicalBlock? UnpackLogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) { - if (!info.IsValid) return null; + if (!info.IsValid) + { + return null; + } if (info.IsVoidExtent) { // Void extent blocks are rare; fall back to existing PhysicalBlock path var pb = PhysicalBlock.Create(bits); var voidExtentData = IntermediateBlock.UnpackVoidExtent(pb); - if (voidExtentData is null) return null; + if (voidExtentData is null) + { + return null; + } return new LogicalBlock(footprint, voidExtentData.Value); } @@ -439,11 +499,17 @@ internal static ushort LnsToSf16(int lns) int mantissaTransformed; if (mantissaComponent < 512) + { mantissaTransformed = mantissaComponent * 3; + } else if (mantissaComponent < 1536) - mantissaTransformed = mantissaComponent * 4 - 512; + { + mantissaTransformed = (mantissaComponent * 4) - 512; + } else - mantissaTransformed = mantissaComponent * 5 - 2048; + { + mantissaTransformed = (mantissaComponent * 5) - 2048; + } int result = (exponentComponent << 10) | (mantissaTransformed >> 3); return (ushort)Math.Min(result, 0x7BFF); // Clamp to max finite FP16 @@ -452,13 +518,18 @@ internal static ushort LnsToSf16(int lns) private static int DecodeEndpoints(in IntermediateBlock.IntermediateBlockData block, ColorEndpointPair[] endpointPair) { int endpointRange = block.EndpointRange ?? IntermediateBlock.EndpointRangeForBlock(block); - if (endpointRange <= 0) throw new InvalidOperationException("Invalid endpoint range"); + if (endpointRange <= 0) + { + throw new InvalidOperationException("Invalid endpoint range"); + } + for (int i = 0; i < block.EndpointCount; i++) { var ed = block.Endpoints[i]; ReadOnlySpan colorSpan = ((ReadOnlySpan)ed.Colors)[..ed.ColorCount]; endpointPair[i] = EndpointCodec.DecodeColorsForModePolymorphic(colorSpan, endpointRange, ed.Mode); } + return block.EndpointCount; } @@ -480,6 +551,7 @@ private static int DecodeEndpoints(IntermediateBlock.VoidExtentData block, Color (byte)(block.A >> 8)); endpointPair[0] = ColorEndpointPair.Ldr(ldrColor, ldrColor); } + return 1; } @@ -514,20 +586,22 @@ private void CalculateWeights(Footprint footprint, in IntermediateBlock.Intermed unquantized[i] = Quantization.UnquantizeWeightFromRange( block.Weights[i * weightFrequency], block.WeightRange); } - DecimationTable.InfillWeights(unquantized, decimationInfo, _weights); + + DecimationTable.InfillWeights(unquantized, decimationInfo, this.weights); if (block.DualPlaneChannel.HasValue) { var dualPlane = new DualPlaneData(); dualPlane.Channel = block.DualPlaneChannel.Value; dualPlane.Weights = new int[footprint.PixelCount]; - _dualPlane = dualPlane; + this.dualPlane = dualPlane; for (int i = 0; i < gridSize; ++i) { unquantized[i] = Quantization.UnquantizeWeightFromRange( - block.Weights[i * weightFrequency + 1], block.WeightRange); + block.Weights[(i * weightFrequency) + 1], block.WeightRange); } - DecimationTable.InfillWeights(unquantized, decimationInfo, _dualPlane.Weights); + + DecimationTable.InfillWeights(unquantized, decimationInfo, this.dualPlane.Weights); } } @@ -535,7 +609,7 @@ private static int InterpolateChannel(int p0, int p1, int weight) { int c0 = (p0 << 8) | p0; int c1 = (p1 << 8) | p1; - int c = (c0 * (64 - weight) + c1 * weight + 32) / 64; + int c = ((c0 * (64 - weight)) + (c1 * weight) + 32) / 64; int quantized = ((c * byte.MaxValue) + short.MaxValue) / (ushort.MaxValue + 1); return Math.Clamp(quantized, 0, byte.MaxValue); } @@ -548,7 +622,7 @@ private static ushort InterpolateLdrAsUnorm16(int p0, int p1, int weight) { int c0 = (p0 << 8) | p0; int c1 = (p1 << 8) | p1; - int c = (c0 * (64 - weight) + c1 * weight + 32) / 64; + int c = ((c0 * (64 - weight)) + (c1 * weight) + 32) / 64; return (ushort)Math.Clamp(c, 0, 0xFFFF); } @@ -562,7 +636,7 @@ private static ushort InterpolateLdrAsUnorm16(int p0, int p1, int weight) /// private static ushort InterpolateChannelHdr(int p0, int p1, int weight) { - int c = (p0 * (64 - weight) + p1 * weight + 32) / 64; + int c = ((p0 * (64 - weight)) + (p1 * weight) + 32) / 64; return (ushort)Math.Clamp(c, 0, 0xFFFF); } @@ -582,9 +656,17 @@ private void WriteLdrSinglePartition( for (int i = 0; i < pixelCount; i++) { SimdHelpers.WriteSinglePixelLdr( - buffer, i * 4, - lowR, lowG, lowB, lowA, highR, highG, highB, highA, - _weights[i]); + buffer, + i * 4, + lowR, + lowG, + lowB, + lowA, + highR, + highG, + highB, + highA, + this.weights[i]); } } @@ -593,52 +675,77 @@ private void WriteAllPixelsGeneral(Footprint footprint, Span buffer) int pixelCount = footprint.PixelCount; for (int i = 0; i < pixelCount; i++) { - int part = _partition.Assignment[i]; - ref var endpoint = ref _endpoints[part]; + int part = this.partition.Assignment[i]; + ref var endpoint = ref this.endpoints[part]; - int weight = _weights[i]; + int weight = this.weights[i]; if (!endpoint.IsHdr) { - if (_dualPlane is not null) + if (this.dualPlane is not null) { SimdHelpers.WriteSinglePixelLdrDualPlane( - buffer, i * 4, - endpoint.LdrLow.R, endpoint.LdrLow.G, endpoint.LdrLow.B, endpoint.LdrLow.A, - endpoint.LdrHigh.R, endpoint.LdrHigh.G, endpoint.LdrHigh.B, endpoint.LdrHigh.A, - weight, _dualPlane.Channel, _dualPlane.Weights[i]); + buffer, + i * 4, + endpoint.LdrLow.R, + endpoint.LdrLow.G, + endpoint.LdrLow.B, + endpoint.LdrLow.A, + endpoint.LdrHigh.R, + endpoint.LdrHigh.G, + endpoint.LdrHigh.B, + endpoint.LdrHigh.A, + weight, + this.dualPlane.Channel, + this.dualPlane.Weights[i]); } else { SimdHelpers.WriteSinglePixelLdr( - buffer, i * 4, - endpoint.LdrLow.R, endpoint.LdrLow.G, endpoint.LdrLow.B, endpoint.LdrLow.A, - endpoint.LdrHigh.R, endpoint.LdrHigh.G, endpoint.LdrHigh.B, endpoint.LdrHigh.A, + buffer, + i * 4, + endpoint.LdrLow.R, + endpoint.LdrLow.G, + endpoint.LdrLow.B, + endpoint.LdrLow.A, + endpoint.LdrHigh.R, + endpoint.LdrHigh.G, + endpoint.LdrHigh.B, + endpoint.LdrHigh.A, weight); } } else { - int dualPlaneChannel = _dualPlane?.Channel ?? -1; - int dualPlaneWeight = _dualPlane?.Weights[i] ?? weight; - buffer[i * 4 + 0] = (byte)(InterpolateChannelHdr( - endpoint.HdrLow[0], endpoint.HdrHigh[0], - dualPlaneChannel == 0 ? dualPlaneWeight : weight) >> 8); - buffer[i * 4 + 1] = (byte)(InterpolateChannelHdr( - endpoint.HdrLow[1], endpoint.HdrHigh[1], - dualPlaneChannel == 1 ? dualPlaneWeight : weight) >> 8); - buffer[i * 4 + 2] = (byte)(InterpolateChannelHdr( - endpoint.HdrLow[2], endpoint.HdrHigh[2], - dualPlaneChannel == 2 ? dualPlaneWeight : weight) >> 8); - buffer[i * 4 + 3] = (byte)(InterpolateChannelHdr( - endpoint.HdrLow[3], endpoint.HdrHigh[3], - dualPlaneChannel == 3 ? dualPlaneWeight : weight) >> 8); + int dualPlaneChannel = this.dualPlane?.Channel ?? -1; + int dualPlaneWeight = this.dualPlane?.Weights[i] ?? weight; + int rWeight = dualPlaneChannel == 0 ? dualPlaneWeight : weight; + int gWeight = dualPlaneChannel == 1 ? dualPlaneWeight : weight; + int bWeight = dualPlaneChannel == 2 ? dualPlaneWeight : weight; + int aWeight = dualPlaneChannel == 3 ? dualPlaneWeight : weight; + buffer[(i * 4) + 0] = (byte)(InterpolateChannelHdr( + endpoint.HdrLow[0], + endpoint.HdrHigh[0], + rWeight) >> 8); + buffer[(i * 4) + 1] = (byte)(InterpolateChannelHdr( + endpoint.HdrLow[1], + endpoint.HdrHigh[1], + gWeight) >> 8); + buffer[(i * 4) + 2] = (byte)(InterpolateChannelHdr( + endpoint.HdrLow[2], + endpoint.HdrHigh[2], + bWeight) >> 8); + buffer[(i * 4) + 3] = (byte)(InterpolateChannelHdr( + endpoint.HdrLow[3], + endpoint.HdrHigh[3], + aWeight) >> 8); } } } private class DualPlaneData { - public int Channel; - public int[] Weights = []; + public int Channel { get; set; } + + public int[] Weights { get; set; } = []; } } diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs index bd269adc..eafc738f 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs @@ -13,22 +13,22 @@ namespace SixLabors.ImageSharp.Textures.Astc.TexelBlock; internal readonly struct PhysicalBlock { public const int SizeInBytes = 16; - private readonly BlockInfo _info; + private readonly BlockInfo info; + + private PhysicalBlock(UInt128 bits, BlockInfo info) + { + this.BlockBits = bits; + this.info = info; + } public UInt128 BlockBits { get; } - public bool IsVoidExtent => _info.IsVoidExtent; + public bool IsVoidExtent => this.info.IsVoidExtent; - public bool IsIllegalEncoding => !_info.IsValid; + public bool IsIllegalEncoding => !this.info.IsValid; public bool IsDualPlane - => _info.IsValid && !_info.IsVoidExtent && _info.IsDualPlane; - - private PhysicalBlock(UInt128 bits, BlockInfo info) - { - BlockBits = bits; - _info = info; - } + => this.info.IsValid && !this.info.IsVoidExtent && this.info.IsDualPlane; /// /// Factory method to create a PhysicalBlock from raw bits @@ -41,25 +41,28 @@ public static PhysicalBlock Create(UInt128 bits) public static PhysicalBlock Create(ulong low, ulong high) => Create(new UInt128(high, low)); internal (int Width, int Height)? GetWeightGridDimensions() - => _info.IsValid && !_info.IsVoidExtent - ? (_info.GridWidth, _info.GridHeight) + => this.info.IsValid && !this.info.IsVoidExtent + ? (this.info.GridWidth, this.info.GridHeight) : null; internal int? GetWeightRange() - => _info.IsValid && !_info.IsVoidExtent - ? _info.WeightRange + => this.info.IsValid && !this.info.IsVoidExtent + ? this.info.WeightRange : null; internal int[]? GetVoidExtentCoordinates() { - if (!_info.IsVoidExtent) return null; + if (!this.info.IsVoidExtent) + { + return null; + } // If void extent coords are all 1's then these are not valid void extent coords ulong voidExtentMask = 0xFFFFFFFFFFFFFDFFUL; ulong constBlockMode = 0xFFFFFFFFFFFFFDFCUL; - return _info.IsValid && (voidExtentMask & BlockBits.Low()) != constBlockMode - ? DecodeVoidExtentCoordinates(BlockBits) + return this.info.IsValid && (voidExtentMask & this.BlockBits.Low()) != constBlockMode + ? DecodeVoidExtentCoordinates(this.BlockBits) : null; } @@ -68,75 +71,107 @@ public static PhysicalBlock Create(UInt128 bits) /// /// The dual plane channel if enabled, otherwise null. internal int? GetDualPlaneChannel() - => _info.IsValid && _info.IsDualPlane - ? _info.DualPlaneChannel + => this.info.IsValid && this.info.IsDualPlane + ? this.info.DualPlaneChannel : null; internal string? IdentifyInvalidEncodingIssues() { - if (_info.IsValid) return null; - return _info.IsVoidExtent - ? IdentifyVoidExtentIssues(BlockBits) + if (this.info.IsValid) + { + return null; + } + + return this.info.IsVoidExtent + ? IdentifyVoidExtentIssues(this.BlockBits) : "Invalid block encoding"; } internal int? GetWeightBitCount() - => _info.IsValid && !_info.IsVoidExtent - ? _info.WeightBitCount + => this.info.IsValid && !this.info.IsVoidExtent + ? this.info.WeightBitCount : null; internal int? GetWeightStartBit() - => _info.IsValid && !_info.IsVoidExtent - ? 128 - _info.WeightBitCount + => this.info.IsValid && !this.info.IsVoidExtent + ? 128 - this.info.WeightBitCount : null; internal int? GetPartitionsCount() - => _info.IsValid && !_info.IsVoidExtent - ? _info.PartitionCount + => this.info.IsValid && !this.info.IsVoidExtent + ? this.info.PartitionCount : null; internal int? GetPartitionId() { - if (!_info.IsValid || _info.IsVoidExtent || _info.PartitionCount == 1) return null; - return (int)BitOperations.GetBits(BlockBits.Low(), 13, 10); + if (!this.info.IsValid || this.info.IsVoidExtent || this.info.PartitionCount == 1) + { + return null; + } + + return (int)BitOperations.GetBits(this.BlockBits.Low(), 13, 10); } internal ColorEndpointMode? GetEndpointMode(int partition) { - if (!_info.IsValid || _info.IsVoidExtent) return null; - if (partition < 0 || partition >= _info.PartitionCount) return null; - return _info.GetEndpointMode(partition); + if (!this.info.IsValid || this.info.IsVoidExtent) + { + return null; + } + + if (partition < 0 || partition >= this.info.PartitionCount) + { + return null; + } + + return this.info.GetEndpointMode(partition); } internal int? GetColorStartBit() { - if (_info.IsVoidExtent) return 64; - return _info.IsValid - ? _info.ColorStartBit + if (this.info.IsVoidExtent) + { + return 64; + } + + return this.info.IsValid + ? this.info.ColorStartBit : null; } internal int? GetColorValuesCount() { - if (_info.IsVoidExtent) return 4; - return _info.IsValid - ? _info.ColorValuesCount + if (this.info.IsVoidExtent) + { + return 4; + } + + return this.info.IsValid + ? this.info.ColorValuesCount : null; } internal int? GetColorBitCount() { - if (_info.IsVoidExtent) return 64; - return _info.IsValid - ? _info.ColorBitCount + if (this.info.IsVoidExtent) + { + return 64; + } + + return this.info.IsValid + ? this.info.ColorBitCount : null; } internal int? GetColorValuesRange() { - if (_info.IsVoidExtent) return (1 << 16) - 1; - return _info.IsValid - ? _info.ColorValuesRange + if (this.info.IsVoidExtent) + { + return (1 << 16) - 1; + } + + return this.info.IsValid + ? this.info.ColorValuesRange : null; } @@ -146,8 +181,9 @@ internal static int[] DecodeVoidExtentCoordinates(UInt128 astcBits) var coords = new int[4]; for (int i = 0; i < 4; ++i) { - coords[i] = (int)BitOperations.GetBits(lowBits, 12 + 13 * i, 13); + coords[i] = (int)BitOperations.GetBits(lowBits, 12 + (13 * i), 13); } + return coords; } @@ -157,7 +193,9 @@ internal static int[] DecodeVoidExtentCoordinates(UInt128 astcBits) private static string? IdentifyVoidExtentIssues(UInt128 bits) { if (BitOperations.GetBits(bits, 10, 2).Low() != 0x3UL) + { return "Reserved bits set for void extent block"; + } ulong lowBits = bits.Low(); int c0 = (int)BitOperations.GetBits(lowBits, 12, 13); @@ -169,7 +207,9 @@ internal static int[] DecodeVoidExtentCoordinates(UInt128 astcBits) bool coordsAll1s = c0 == all1s && c1 == all1s && c2 == all1s && c3 == all1s; if (!coordsAll1s && (c0 >= c1 || c2 >= c3)) + { return "Void extent texture coordinates are invalid"; + } return null; } From b79ef15b6866b9cd9c8539d1710b24b0c1607d22 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:15:24 +0100 Subject: [PATCH 32/37] Move Astc tests into existing test project --- ImageSharp.Textures.sln | 15 ---- src/Directory.Build.props | 1 - ...Sharp.Textures.Astc.Reference.Tests.csproj | 2 +- .../ImageSharp.Textures.Astc.Tests.csproj | 30 -------- .../AstcDecodingBenchmark.cs | 67 +++++++++++++++++ ...nchmark.cs => AstcImageDecodeBenchmark.cs} | 6 +- .../BenchmarkTestDataLocator.cs | 36 +++++----- .../DecodingBenchmark.cs | 68 ------------------ .../Formats/Astc}/BitOperationsTests.cs | 2 +- .../Formats/Astc}/BitStreamTests.cs | 2 +- .../Formats/Astc}/CodecTests.cs | 11 +-- .../Formats/Astc}/EndpointCodecTests.cs | 5 +- .../Formats/Astc}/FootprintTests.cs | 2 +- .../Formats/Astc}/HDR/HdrComparisonTests.cs | 22 +++--- .../Formats/Astc}/HDR/HdrDecoderTests.cs | 3 +- .../Formats/Astc}/HDR/HdrImageTests.cs | 14 ++-- .../Formats/Astc}/HDR/RgbaHdrColorTests.cs | 2 +- .../Astc}/IntegerSequenceCodecTests.cs | 2 +- .../Formats/Astc}/IntegrationTests.cs | 6 +- .../Formats/Astc}/IntermediateBlockTests.cs | 5 +- .../Formats/Astc}/LogicalAstcBlockTests.cs | 6 +- .../Formats/Astc}/PartitionTests.cs | 2 +- .../Formats/Astc}/PhysicalAstcBlockTests.cs | 2 +- .../Formats/Astc}/QuantizationTests.cs | 2 +- .../Formats/Astc}/Utils/FileBasedHelpers.cs | 12 +++- .../Formats/Astc}/Utils/ImageBuffer.cs | 2 +- .../Formats/Astc}/Utils/ImageUtils.cs | 2 +- .../Formats/Astc}/WeightInfillTests.cs | 2 +- .../ImageSharp.Textures.Tests.csproj | 5 ++ .../Input/Astc}/Expected/A.png | 0 .../Astc}/Expected/FlightHelmet_baseColor.png | 0 .../Expected/GoldenGateBridge3/filelist.txt | 0 .../Astc}/Expected/GoldenGateBridge3/negx.jpg | 0 .../Astc}/Expected/GoldenGateBridge3/negy.jpg | 0 .../Astc}/Expected/GoldenGateBridge3/negz.jpg | 0 .../Astc}/Expected/GoldenGateBridge3/posx.jpg | 0 .../Astc}/Expected/GoldenGateBridge3/posy.jpg | 0 .../Astc}/Expected/GoldenGateBridge3/posz.jpg | 0 .../Expected/GoldenGateBridge3/readme.txt | 0 .../Iron_Bars/Iron_Bars_001_Opacity.jpg | 0 .../Iron_Bars_001_ambientOcclusion.jpg | 0 .../Iron_Bars/Iron_Bars_001_basecolor.jpg | 0 .../Iron_Bars/Iron_Bars_001_height.png | 0 .../Iron_Bars/Iron_Bars_001_normal.jpg | 0 .../Iron_Bars_001_normal_unnormalized.png | 0 .../Iron_Bars/Iron_Bars_001_roughness.jpg | 0 .../Input/Astc}/Expected/Iron_Bars/readme.txt | 0 .../Input/Astc}/Expected/LA.png | 0 .../Input/Astc}/Expected/R.png | 0 .../Input/Astc}/Expected/RG.png | 0 .../Astc}/Expected/Yokohama3/filelist.txt | 0 .../Input/Astc}/Expected/Yokohama3/negx.jpg | 0 .../Input/Astc}/Expected/Yokohama3/negy.jpg | 0 .../Input/Astc}/Expected/Yokohama3/negz.jpg | 0 .../Input/Astc}/Expected/Yokohama3/posx.jpg | 0 .../Input/Astc}/Expected/Yokohama3/posy.jpg | 0 .../Input/Astc}/Expected/Yokohama3/posz.jpg | 0 .../Input/Astc}/Expected/Yokohama3/readme.txt | 0 .../Input/Astc}/Expected/atlas_small_4x4.bmp | 0 .../Input/Astc}/Expected/atlas_small_5x5.bmp | 0 .../Input/Astc}/Expected/atlas_small_6x6.bmp | 0 .../Input/Astc}/Expected/atlas_small_8x8.bmp | 0 .../Input/Astc}/Expected/footprint_10x10.bmp | 0 .../Input/Astc}/Expected/footprint_10x5.bmp | 0 .../Input/Astc}/Expected/footprint_10x6.bmp | 0 .../Input/Astc}/Expected/footprint_10x8.bmp | 0 .../Input/Astc}/Expected/footprint_12x10.bmp | 0 .../Input/Astc}/Expected/footprint_12x12.bmp | 0 .../Input/Astc}/Expected/footprint_4x4.bmp | 0 .../Input/Astc}/Expected/footprint_5x4.bmp | 0 .../Input/Astc}/Expected/footprint_5x5.bmp | 0 .../Input/Astc}/Expected/footprint_6x5.bmp | 0 .../Input/Astc}/Expected/footprint_6x6.bmp | 0 .../Input/Astc}/Expected/footprint_8x5.bmp | 0 .../Input/Astc}/Expected/footprint_8x6.bmp | 0 .../Input/Astc}/Expected/footprint_8x8.bmp | 0 .../Input/Astc}/Expected/rgb_12x12.bmp | 0 .../Input/Astc}/Expected/rgb_4x4.bmp | 0 .../Input/Astc}/Expected/rgb_5x4.bmp | 0 .../Input/Astc}/Expected/rgb_6x6.bmp | 0 .../Input/Astc}/Expected/rgb_8x8.bmp | 0 .../Input/Astc}/HDR/HDR-A-1x1.astc | Bin .../Input/Astc}/HDR/HDR-A-1x1.exr | 0 .../Input/Astc}/HDR/LDR-A-1x1.astc | Bin .../Input/Astc}/HDR/LDR-A-1x1.png | 0 .../Input/Astc}/HDR/hdr-complex.exr | 0 .../Input/Astc}/HDR/hdr-tile.astc | Bin .../Input/Astc}/HDR/hdr.exr | 0 .../Input/Astc}/HDR/hdr.hdr | Bin .../Input/Astc}/HDR/ldr-tile.astc | Bin .../Input/Astc}/Input/atlas_small_4x4.astc | Bin .../Input/Astc}/Input/atlas_small_5x5.astc | Bin .../Input/Astc}/Input/atlas_small_6x6.astc | Bin .../Input/Astc}/Input/atlas_small_8x8.astc | Bin .../Input/Astc}/Input/checkerboard.astc | Bin .../Input/Astc}/Input/checkered_10.astc | Bin .../Input/Astc}/Input/checkered_11.astc | Bin .../Input/Astc}/Input/checkered_12.astc | Bin .../Input/Astc}/Input/checkered_4.astc | Bin .../Input/Astc}/Input/checkered_5.astc | Bin .../Input/Astc}/Input/checkered_6.astc | Bin .../Input/Astc}/Input/checkered_7.astc | Bin .../Input/Astc}/Input/checkered_8.astc | Bin .../Input/Astc}/Input/checkered_9.astc | Bin .../Input/Astc}/Input/footprint_10x10.astc | Bin .../Input/Astc}/Input/footprint_10x5.astc | Bin .../Input/Astc}/Input/footprint_10x6.astc | Bin .../Input/Astc}/Input/footprint_10x8.astc | Bin .../Input/Astc}/Input/footprint_12x10.astc | Bin .../Input/Astc}/Input/footprint_12x12.astc | Bin .../Input/Astc}/Input/footprint_4x4.astc | Bin .../Input/Astc}/Input/footprint_5x4.astc | Bin .../Input/Astc}/Input/footprint_5x5.astc | Bin .../Input/Astc}/Input/footprint_6x5.astc | Bin .../Input/Astc}/Input/footprint_6x6.astc | Bin .../Input/Astc}/Input/footprint_8x5.astc | Bin .../Input/Astc}/Input/footprint_8x6.astc | Bin .../Input/Astc}/Input/footprint_8x8.astc | Bin .../Input/Astc}/Input/rgb_12x12.astc | Bin .../Input/Astc}/Input/rgb_4x4.astc | Bin .../Input/Astc}/Input/rgb_5x4.astc | Bin .../Input/Astc}/Input/rgb_6x6.astc | Bin .../Input/Astc}/Input/rgb_8x8.astc | Bin 123 files changed, 158 insertions(+), 178 deletions(-) delete mode 100644 tests/ImageSharp.Textures.Astc.Tests/ImageSharp.Textures.Astc.Tests.csproj create mode 100644 tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs rename tests/ImageSharp.Textures.Benchmarks/{AstcFullImageDecodeBenchmark.cs => AstcImageDecodeBenchmark.cs} (85%) delete mode 100644 tests/ImageSharp.Textures.Benchmarks/DecodingBenchmark.cs rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/BitOperationsTests.cs (98%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/BitStreamTests.cs (98%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/CodecTests.cs (92%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/EndpointCodecTests.cs (98%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/FootprintTests.cs (98%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/HDR/HdrComparisonTests.cs (89%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/HDR/HdrDecoderTests.cs (96%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/HDR/HdrImageTests.cs (90%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/HDR/RgbaHdrColorTests.cs (98%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/IntegerSequenceCodecTests.cs (99%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/IntegrationTests.cs (87%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/IntermediateBlockTests.cs (98%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/LogicalAstcBlockTests.cs (98%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/PartitionTests.cs (99%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/PhysicalAstcBlockTests.cs (99%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/QuantizationTests.cs (99%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/Utils/FileBasedHelpers.cs (81%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/Utils/ImageBuffer.cs (97%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/Utils/ImageUtils.cs (93%) rename tests/{ImageSharp.Textures.Astc.Tests => ImageSharp.Textures.Tests/Formats/Astc}/WeightInfillTests.cs (96%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/A.png (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/FlightHelmet_baseColor.png (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/GoldenGateBridge3/filelist.txt (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/GoldenGateBridge3/negx.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/GoldenGateBridge3/negy.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/GoldenGateBridge3/negz.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/GoldenGateBridge3/posx.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/GoldenGateBridge3/posy.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/GoldenGateBridge3/posz.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/GoldenGateBridge3/readme.txt (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Iron_Bars/Iron_Bars_001_height.png (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Iron_Bars/Iron_Bars_001_normal.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Iron_Bars/readme.txt (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/LA.png (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/R.png (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/RG.png (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Yokohama3/filelist.txt (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Yokohama3/negx.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Yokohama3/negy.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Yokohama3/negz.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Yokohama3/posx.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Yokohama3/posy.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Yokohama3/posz.jpg (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/Yokohama3/readme.txt (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/atlas_small_4x4.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/atlas_small_5x5.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/atlas_small_6x6.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/atlas_small_8x8.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_10x10.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_10x5.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_10x6.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_10x8.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_12x10.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_12x12.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_4x4.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_5x4.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_5x5.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_6x5.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_6x6.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_8x5.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_8x6.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/footprint_8x8.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/rgb_12x12.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/rgb_4x4.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/rgb_5x4.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/rgb_6x6.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Expected/rgb_8x8.bmp (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/HDR-A-1x1.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/HDR-A-1x1.exr (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/LDR-A-1x1.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/LDR-A-1x1.png (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/hdr-complex.exr (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/hdr-tile.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/hdr.exr (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/hdr.hdr (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/HDR/ldr-tile.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/atlas_small_4x4.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/atlas_small_5x5.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/atlas_small_6x6.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/atlas_small_8x8.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkerboard.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_10.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_11.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_12.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_4.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_5.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_6.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_7.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_8.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/checkered_9.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_10x10.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_10x5.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_10x6.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_10x8.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_12x10.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_12x12.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_4x4.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_5x4.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_5x5.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_6x5.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_6x6.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_8x5.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_8x6.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/footprint_8x8.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/rgb_12x12.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/rgb_4x4.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/rgb_5x4.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/rgb_6x6.astc (100%) rename tests/{ImageSharp.Textures.Astc.Tests/TestData => Images/Input/Astc}/Input/rgb_8x8.astc (100%) diff --git a/ImageSharp.Textures.sln b/ImageSharp.Textures.sln index b57b5731..3f610385 100644 --- a/ImageSharp.Textures.sln +++ b/ImageSharp.Textures.sln @@ -51,8 +51,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Textures.Astc", "src\ImageSharp.Textures.Astc\ImageSharp.Textures.Astc.csproj", "{AE37301B-3635-4C61-A026-DEB2E1328DD1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Textures.Astc.Tests", "tests\ImageSharp.Textures.Astc.Tests\ImageSharp.Textures.Astc.Tests.csproj", "{BE98A221-C320-49AA-A7E9-3A67CC6830D0}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Textures.Astc.Reference.Tests", "tests\ImageSharp.Textures.Astc.Reference.Tests\ImageSharp.Textures.Astc.Reference.Tests.csproj", "{E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}" EndProject Global @@ -125,18 +123,6 @@ Global {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|x64.Build.0 = Release|Any CPU {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|x86.ActiveCfg = Release|Any CPU {AE37301B-3635-4C61-A026-DEB2E1328DD1}.Release|x86.Build.0 = Release|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|x64.ActiveCfg = Debug|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|x64.Build.0 = Debug|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|x86.ActiveCfg = Debug|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Debug|x86.Build.0 = Debug|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|Any CPU.Build.0 = Release|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|x64.ActiveCfg = Release|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|x64.Build.0 = Release|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|x86.ActiveCfg = Release|Any CPU - {BE98A221-C320-49AA-A7E9-3A67CC6830D0}.Release|x86.Build.0 = Release|Any CPU {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|Any CPU.Build.0 = Debug|Any CPU {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -162,7 +148,6 @@ Global {E6224AB7-6987-4BA1-B2A6-ECFB7DA281DE} = {9F1F0B0F-704F-4B71-89EF-EE36042A27C9} {9F19EBB4-32DB-4AFE-A5E4-722EDFAAE04B} = {E6224AB7-6987-4BA1-B2A6-ECFB7DA281DE} {AE37301B-3635-4C61-A026-DEB2E1328DD1} = {5E6840D2-9CBB-4FDE-8378-33086CF5A8D8} - {BE98A221-C320-49AA-A7E9-3A67CC6830D0} = {6DF92068-B792-4038-8E3F-5FDF2E026BE7} {E15C2E1A-FCD6-42B1-82D2-2C72CC4DC720} = {6DF92068-B792-4038-8E3F-5FDF2E026BE7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5736a886..7060cf45 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -25,7 +25,6 @@ -

v%fcTaL{NYj%p<=}R-K{2h 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 literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Astc/rgb_5x4.astc b/tests/Images/Input/Astc/rgb_5x4.astc new file mode 100644 index 0000000000000000000000000000000000000000..b9e09236a22b9790a3b31b2268aaf5f0c9df1ff8 GIT binary patch literal 51855 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�f&Uft**^6@ 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 Date: Tue, 3 Feb 2026 17:17:24 +0100 Subject: [PATCH 02/37] Update to use AstcSharp --- .../TextureFormats/Decoding/AstcDecoder.cs | 34 ++++++++++++++----- .../Formats/Astc/AstcImageComparisonTests.cs | 13 +++---- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs index 4d5abb21..cd32e728 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/AstcDecoder.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AstcSharp.Core; + namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; ///

diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj b/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj index e25a6cc0..b88ef10f 100644 --- a/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj @@ -25,7 +25,7 @@ - <_TestData Include="..\ImageSharp.Textures.Astc.Tests\TestData\**\*.*" /> + <_TestData Include="..\Images\Input\Astc\**\*.*" /> - - - net8.0 - SixLabors.ImageSharp.Textures.Astc.Tests - SixLabors.ImageSharp.Textures.Astc.Tests - true - - - - - - - - - - - - - - - - - - <_TestData Include="TestData\**\*.*" /> - - - - - diff --git a/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs new file mode 100644 index 00000000..346c7cf7 --- /dev/null +++ b/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Textures.Astc.Core; +using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Astc.TexelBlock; + +namespace SixLabors.ImageSharp.Textures.Benchmarks; + +[MemoryDiagnoser] +public class AstcDecodingBenchmark +{ + private AstcFile? _astcFile; + + [GlobalSetup] + public void Setup() + { + string path = BenchmarkTestDataLocator.FindAstcTestData(Path.Combine("Input", "atlas_small_4x4.astc")); + byte[] astcData = File.ReadAllBytes(path); + _astcFile = AstcFile.FromMemory(astcData); + } + + [Benchmark] + public bool ParseBlock() + { + ReadOnlySpan blocks = _astcFile!.Blocks; + Span blockBytes = stackalloc byte[16]; + blocks.Slice(0, 16).CopyTo(blockBytes); + ulong low = BitConverter.ToUInt64(blockBytes); + ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + PhysicalBlock phyiscalBlock = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); + + return !phyiscalBlock.IsIllegalEncoding; + } + + [Benchmark] + public bool DecodeEndpoints() + { + ReadOnlySpan blocks = _astcFile!.Blocks; + Span blockBytes = stackalloc byte[16]; + blocks.Slice(0, 16).CopyTo(blockBytes); + ulong low = BitConverter.ToUInt64(blockBytes); + ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + PhysicalBlock physicalBlock = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); + + IntermediateBlock.IntermediateBlockData? blockData = IntermediateBlock.UnpackIntermediateBlock(physicalBlock); + + return blockData is not null; + } + + [Benchmark] + public bool Partitioning() + { + ReadOnlySpan blocks = _astcFile!.Blocks; + Span blockBytes = stackalloc byte[16]; + blocks.Slice(0, 16).CopyTo(blockBytes); + ulong low = BitConverter.ToUInt64(blockBytes); + ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + UInt128 bits = (UInt128)low | ((UInt128)high << 64); + BlockInfo info = BlockInfo.Decode(bits); + LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(Footprint.Get4x4(), bits, in info) + ?? throw new InvalidOperationException("Failed to unpack block"); + + return logicalBlock is not null; + } +} diff --git a/tests/ImageSharp.Textures.Benchmarks/AstcFullImageDecodeBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs similarity index 85% rename from tests/ImageSharp.Textures.Benchmarks/AstcFullImageDecodeBenchmark.cs rename to tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs index b285c940..1f167138 100644 --- a/tests/ImageSharp.Textures.Benchmarks/AstcFullImageDecodeBenchmark.cs +++ b/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs @@ -8,20 +8,20 @@ namespace SixLabors.ImageSharp.Textures.Benchmarks; [MemoryDiagnoser] -public class AstcFullImageDecodeBenchmark +public class AstcImageDecodeBenchmark { private AstcFile? _astcFile; [GlobalSetup] public void Setup() { - string path = BenchmarkTestDataLocator.FindTestData(Path.Combine("Input", "atlas_small_4x4.astc")); + string path = BenchmarkTestDataLocator.FindAstcTestData(Path.Combine("Input", "atlas_small_4x4.astc")); byte[] astcData = File.ReadAllBytes(path); _astcFile = AstcFile.FromMemory(astcData); } [Benchmark] - public void FullImageDecode() + public void ImageDecode() { ReadOnlySpan blocks = _astcFile!.Blocks; int numBlocks = blocks.Length / 16; diff --git a/tests/ImageSharp.Textures.Benchmarks/BenchmarkTestDataLocator.cs b/tests/ImageSharp.Textures.Benchmarks/BenchmarkTestDataLocator.cs index 13d103ff..1b34770f 100644 --- a/tests/ImageSharp.Textures.Benchmarks/BenchmarkTestDataLocator.cs +++ b/tests/ImageSharp.Textures.Benchmarks/BenchmarkTestDataLocator.cs @@ -1,32 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Benchmarks +namespace SixLabors.ImageSharp.Textures.Benchmarks; + +public static class BenchmarkTestDataLocator { - public static class BenchmarkTestDataLocator + private const string SolutionFileName = "ImageSharp.Textures.sln"; + + /// + /// Locates a test data file under tests/Images/Input/Astc by walking up + /// from the benchmark output directory to find the solution root. + /// + /// Relative path from the Astc test data root (e.g. "Input/atlas_small_4x4.astc"). + /// Full path to the test data file. + /// Thrown when the file cannot be found. + public static string FindAstcTestData(string relativePath) { - /// - /// Locates a test data file by searching up from the benchmark directory and into the known test data location. - /// - /// Relative path from the test data root (e.g. "Input/atlas_small_4x4.astc"). - /// Full path to the test data file, or throws if not found. - public static string FindTestData(string relativePath) + string dir = AppContext.BaseDirectory; + for (int i = 0; i < 10; ++i) { - // Walk up from the current directory, searching for ImageSharp.Textures.Astc.Tests/TestData - string dir = AppContext.BaseDirectory; - for (int i = 0; i < 10; ++i) + if (File.Exists(Path.Combine(dir, SolutionFileName))) { - string testDataDir = Path.Combine(dir, "ImageSharp.Textures.Astc.Tests", "TestData"); - string candidate = Path.Combine(testDataDir, relativePath); + string candidate = Path.Combine(dir, "tests", "Images", "Input", "Astc", relativePath); if (File.Exists(candidate)) { return Path.GetFullPath(candidate); } - - dir = Path.GetFullPath(Path.Combine(dir, "..")); } - throw new FileNotFoundException($"Could not locate test data file: {relativePath}"); + dir = Path.GetFullPath(Path.Combine(dir, "..")); } + + throw new FileNotFoundException($"Could not locate test data file: {relativePath}"); } } diff --git a/tests/ImageSharp.Textures.Benchmarks/DecodingBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/DecodingBenchmark.cs deleted file mode 100644 index 4af0f8d1..00000000 --- a/tests/ImageSharp.Textures.Benchmarks/DecodingBenchmark.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Textures.Astc.Core; -using SixLabors.ImageSharp.Textures.Astc.IO; -using SixLabors.ImageSharp.Textures.Astc.TexelBlock; - -namespace SixLabors.ImageSharp.Textures.Benchmarks -{ - [MemoryDiagnoser] - public class DecodingBenchmark - { - private AstcFile? _astcFile; - - [GlobalSetup] - public void Setup() - { - string path = BenchmarkTestDataLocator.FindTestData(Path.Combine("Input", "atlas_small_4x4.astc")); - byte[] astcData = File.ReadAllBytes(path); - _astcFile = AstcFile.FromMemory(astcData); - } - - [Benchmark] - public bool ParseBlock() - { - ReadOnlySpan blocks = _astcFile!.Blocks; - Span blockBytes = stackalloc byte[16]; - blocks.Slice(0, 16).CopyTo(blockBytes); - ulong low = BitConverter.ToUInt64(blockBytes); - ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); - PhysicalBlock phyiscalBlock = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); - - return !phyiscalBlock.IsIllegalEncoding; - } - - [Benchmark] - public bool DecodeEndpoints() - { - ReadOnlySpan blocks = _astcFile!.Blocks; - Span blockBytes = stackalloc byte[16]; - blocks.Slice(0, 16).CopyTo(blockBytes); - ulong low = BitConverter.ToUInt64(blockBytes); - ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); - PhysicalBlock physicalBlock = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); - - IntermediateBlock.IntermediateBlockData? blockData = IntermediateBlock.UnpackIntermediateBlock(physicalBlock); - - return blockData is not null; - } - - [Benchmark] - public bool Partitioning() - { - ReadOnlySpan blocks = _astcFile!.Blocks; - Span blockBytes = stackalloc byte[16]; - blocks.Slice(0, 16).CopyTo(blockBytes); - ulong low = BitConverter.ToUInt64(blockBytes); - ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); - UInt128 bits = (UInt128)low | ((UInt128)high << 64); - BlockInfo info = BlockInfo.Decode(bits); - LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(Footprint.Get4x4(), bits, in info) - ?? throw new InvalidOperationException("Failed to unpack block"); - - return logicalBlock is not null; - } - } -} diff --git a/tests/ImageSharp.Textures.Astc.Tests/BitOperationsTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs similarity index 98% rename from tests/ImageSharp.Textures.Astc.Tests/BitOperationsTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs index eb1ca547..ed49da95 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/BitOperationsTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class BitOperationsTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/BitStreamTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs similarity index 98% rename from tests/ImageSharp.Textures.Astc.Tests/BitStreamTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs index ae5c3108..c79e1d84 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/BitStreamTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.Textures.Astc.IO; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class BitStreamTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/CodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs similarity index 92% rename from tests/ImageSharp.Textures.Astc.Tests/CodecTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs index 0bbd64d1..5fbf477b 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/CodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs @@ -1,13 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; -using SixLabors.ImageSharp.Textures.Astc.Tests.Utils; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class CodecTests { @@ -96,7 +97,7 @@ public void ASTCDecompressToRGBA_WithValidData_ShouldMatchExpected( var decodedPixels = AstcDecoder.DecompressImage(astcData, width, height, footprintType); var actualImage = new ImageBuffer(decodedPixels.ToArray(), width, height, 4); - var expectedImagePath = Path.Combine("TestData", "Expected", imageName + ".bmp"); + var expectedImagePath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedImagePath); ImageUtils.CompareSumOfSquaredDifferences(expectedImage, actualImage, 0.1); } @@ -112,7 +113,7 @@ public void DecompressToImage_WithAstcFile_ShouldMatchExpected( int width, int height) { - var astcPath = Path.Combine("TestData", "Input", imageName + ".astc"); + var astcPath = FileBasedHelpers.GetInputPath(imageName + ".astc"); var astcBytes = File.ReadAllBytes(astcPath); var file = AstcFile.FromMemory(astcBytes); @@ -124,7 +125,7 @@ public void DecompressToImage_WithAstcFile_ShouldMatchExpected( var decodedPixels = AstcDecoder.DecompressImage(file); var actualImage = new ImageBuffer(decodedPixels.ToArray(), width, height, 4); - var expectedImagePath = Path.Combine("TestData", "Expected", imageName + ".bmp"); + var expectedImagePath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedImagePath); ImageUtils.CompareSumOfSquaredDifferences(expectedImage, actualImage, 0.1); } diff --git a/tests/ImageSharp.Textures.Astc.Tests/EndpointCodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs similarity index 98% rename from tests/ImageSharp.Textures.Astc.Tests/EndpointCodecTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs index e598d554..7efe0062 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/EndpointCodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs @@ -5,9 +5,10 @@ using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class EndpointCodecTests { @@ -305,7 +306,7 @@ private static int[] EncodeRgbBaseOffset(RgbaColor low, RgbaColor high) [Fact] public void DecodeCheckerboard_ShouldDecodeToGrayscaleEndpoints() { - string astcFilePath = Path.Combine("TestData", "Input", "checkerboard.astc"); + string astcFilePath = FileBasedHelpers.GetInputPath("checkerboard.astc"); byte[] astcData = File.ReadAllBytes(astcFilePath); int blocksDecoded = 0; diff --git a/tests/ImageSharp.Textures.Astc.Tests/FootprintTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs similarity index 98% rename from tests/ImageSharp.Textures.Astc.Tests/FootprintTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs index 05e2a053..15489ae5 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/FootprintTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class FootprintTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs similarity index 89% rename from tests/ImageSharp.Textures.Astc.Tests/HDR/HdrComparisonTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs index 1e4fa827..81e5a5e9 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrComparisonTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs @@ -1,11 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests.HDR; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; /// /// Comparing HDR and LDR ASTC decoding behavior using real reference files. @@ -16,7 +18,7 @@ public class HdrComparisonTests public void HdrFile_DecodedWithHdrApi_ShouldPreserveExtendedRange() { // HDR files should decode to values potentially exceeding 1.0 - var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -41,7 +43,7 @@ public void HdrFile_DecodedWithHdrApi_ShouldPreserveExtendedRange() public void LdrFile_DecodedWithHdrApi_ShouldUpscaleToHdrRange() { // LDR files decoded with HDR API should produce values in 0.0-1.0 range - var astcPath = Path.Combine("TestData", "HDR", "LDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("LDR-A-1x1.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -64,7 +66,7 @@ public void LdrFile_DecodedWithHdrApi_ShouldUpscaleToHdrRange() public void HdrFile_DecodedWithLdrApi_ShouldClampToByteRange() { // HDR files decoded with LDR API should clamp to 0-255 - var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -86,7 +88,7 @@ public void HdrFile_DecodedWithLdrApi_ShouldClampToByteRange() public void LdrFile_DecodedWithBothApis_ShouldProduceConsistentValues() { // LDR content should produce equivalent results with both APIs - var astcPath = Path.Combine("TestData", "HDR", "LDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("LDR-A-1x1.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -112,7 +114,7 @@ public void LdrFile_DecodedWithBothApis_ShouldProduceConsistentValues() public void HdrTile_ShouldDecodeSuccessfully() { // Test larger HDR tile decoding - var astcPath = Path.Combine("TestData", "HDR", "hdr-tile.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("hdr-tile.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -134,7 +136,7 @@ public void HdrTile_ShouldDecodeSuccessfully() public void LdrTile_ShouldDecodeSuccessfully() { // Test larger LDR tile decoding - var astcPath = Path.Combine("TestData", "HDR", "ldr-tile.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("ldr-tile.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -153,8 +155,8 @@ public void LdrTile_ShouldDecodeSuccessfully() public void SameFootprint_HdrVsLdr_ShouldBothDecode() { // Verify files with same footprint decode correctly - var hdrPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); - var ldrPath = Path.Combine("TestData", "HDR", "LDR-A-1x1.astc"); + var hdrPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); + var ldrPath = FileBasedHelpers.GetHdrPath("LDR-A-1x1.astc"); var hdrData = File.ReadAllBytes(hdrPath); var ldrData = File.ReadAllBytes(ldrPath); @@ -182,7 +184,7 @@ public void SameFootprint_HdrVsLdr_ShouldBothDecode() public void HdrColor_FromLdr_ShouldMatchLdrToHdrApiConversion() { // Verify that HdrColor.FromRgba() produces same results as decoding LDR with HDR API - var astcPath = Path.Combine("TestData", "HDR", "LDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("LDR-A-1x1.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); diff --git a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs similarity index 96% rename from tests/ImageSharp.Textures.Astc.Tests/HDR/HdrDecoderTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs index f585dce8..005e8dad 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs @@ -1,10 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests.HDR; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; public class HdrDecoderTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrImageTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs similarity index 90% rename from tests/ImageSharp.Textures.Astc.Tests/HDR/HdrImageTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs index 799c2774..2b396ceb 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/HDR/HdrImageTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs @@ -2,11 +2,13 @@ // Licensed under the Six Labors Split License. using System.ComponentModel; +using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests.HDR; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; /// /// Tests using real HDR ASTC files from the ARM astc-encoder reference repository. @@ -19,7 +21,7 @@ public class HdrImageTests [Description("Verify that the ASTC file header is correctly parsed for HDR content, including footprint detection")] public void DecodeHdrFile_VerifyFootprintDetection() { - var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -33,7 +35,7 @@ public void DecodeHdrFile_VerifyFootprintDetection() [Fact] public void DecodeHdrAstcFile_1x1Pixel_ShouldProduceValidHdrOutput() { - var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -59,7 +61,7 @@ public void DecodeHdrAstcFile_1x1Pixel_ShouldProduceValidHdrOutput() [Fact] public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() { - var astcPath = Path.Combine("TestData", "HDR", "hdr-tile.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("hdr-tile.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -87,7 +89,7 @@ public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() [Description("Verify that HDR ASTC files can be decoded with the LDR API, producing clamped values")] public void DecodeHdrAstcFile_WithLdrApi_ShouldClampValues() { - var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); if (!File.Exists(astcPath)) { @@ -115,7 +117,7 @@ public void DecodeHdrAstcFile_WithLdrApi_ShouldClampValues() [Description("Verify that HDR and LDR APIs produce consistent relative channel values for the same HDR ASTC file")] public void HdrAndLdrApis_OnSameHdrFile_ShouldProduceConsistentRelativeValues() { - var astcPath = Path.Combine("TestData", "HDR", "HDR-A-1x1.astc"); + var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); diff --git a/tests/ImageSharp.Textures.Astc.Tests/HDR/RgbaHdrColorTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs similarity index 98% rename from tests/ImageSharp.Textures.Astc.Tests/HDR/RgbaHdrColorTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs index bd7b8060..69dc3e88 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/HDR/RgbaHdrColorTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests.HDR; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; public class RgbaHdrColorTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/IntegerSequenceCodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs similarity index 99% rename from tests/ImageSharp.Textures.Astc.Tests/IntegerSequenceCodecTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs index 8166e624..fc410fc3 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/IntegerSequenceCodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Textures.Astc.IO; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class IntegerSequenceCodecTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/IntegrationTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs similarity index 87% rename from tests/ImageSharp.Textures.Astc.Tests/IntegrationTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs index f4cfadc6..df73cc81 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/IntegrationTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.IO; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class IntegrationTests { @@ -44,7 +46,7 @@ public class IntegrationTests [InlineData("rgb_12x12")] public void DecompressToImage_WithTestdataFile_ShouldDecodeSuccessfully(string basename) { - var filePath = Path.Combine("TestData", "Input", basename + ".astc"); + var filePath = FileBasedHelpers.GetInputPath(basename + ".astc"); var bytes = File.ReadAllBytes(filePath); var astc = AstcFile.FromMemory(bytes); diff --git a/tests/ImageSharp.Textures.Astc.Tests/IntermediateBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs similarity index 98% rename from tests/ImageSharp.Textures.Astc.Tests/IntermediateBlockTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs index 73bfa0f3..210b079a 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/IntermediateBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs @@ -4,9 +4,10 @@ using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class IntermediateBlockTests { @@ -447,7 +448,7 @@ private static void VerifyBlockPropertiesMatch(PhysicalBlock repacked, PhysicalB private static byte[] LoadASTCFile(string basename) { - var filename = Path.Combine("TestData", "Input", basename + ".astc"); + var filename = FileBasedHelpers.GetInputPath(basename + ".astc"); File.Exists(filename).Should().BeTrue($"Testdata missing: {filename}"); var data = File.ReadAllBytes(filename); data.Length.Should().BeGreaterThanOrEqualTo(16, "ASTC file too small"); diff --git a/tests/ImageSharp.Textures.Astc.Tests/LogicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs similarity index 98% rename from tests/ImageSharp.Textures.Astc.Tests/LogicalAstcBlockTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs index 2bcb1750..a3f712d8 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/LogicalAstcBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs @@ -3,11 +3,11 @@ using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; -using SixLabors.ImageSharp.Textures.Astc.Tests.Utils; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class LogicalAstcBlockTests { @@ -385,7 +385,7 @@ public void UnpackLogicalBlock_FromImage_ShouldDecodeCorrectly( var decodedImage = ImageBuffer.FromAstcBuffer(footprint, astcData, width, height, hasAlpha); - var expectedPath = Path.Combine("TestData", "Expected", imageName + ".bmp"); + var expectedPath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedPath); ImageUtils.CompareSumOfSquaredDifferences(expectedImage, decodedImage, 0.1); } diff --git a/tests/ImageSharp.Textures.Astc.Tests/PartitionTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs similarity index 99% rename from tests/ImageSharp.Textures.Astc.Tests/PartitionTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs index 4422c756..eb386166 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/PartitionTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class PartitionTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/PhysicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs similarity index 99% rename from tests/ImageSharp.Textures.Astc.Tests/PhysicalAstcBlockTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs index 58ea8fb7..d2183f85 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/PhysicalAstcBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Textures.Astc.TexelBlock; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class PhysicalAstcBlockTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/QuantizationTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs similarity index 99% rename from tests/ImageSharp.Textures.Astc.Tests/QuantizationTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs index f6603ace..a8d87ae3 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/QuantizationTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class QuantizationTests { diff --git a/tests/ImageSharp.Textures.Astc.Tests/Utils/FileBasedHelpers.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs similarity index 81% rename from tests/ImageSharp.Textures.Astc.Tests/Utils/FileBasedHelpers.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs index e4d3a2d9..c7a51718 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/Utils/FileBasedHelpers.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs @@ -3,13 +3,21 @@ using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests.Utils; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; internal static class FileBasedHelpers { + private static readonly string AstcTestDataRoot = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "Astc"); + + public static string GetInputPath(string relativePath) => Path.Combine(AstcTestDataRoot, "Input", relativePath); + + public static string GetExpectedPath(string relativePath) => Path.Combine(AstcTestDataRoot, "Expected", relativePath); + + public static string GetHdrPath(string relativePath) => Path.Combine(AstcTestDataRoot, "HDR", relativePath); + public static byte[] LoadASTCFile(string basename) { - var filename = Path.Combine("TestData", "Input", basename + ".astc"); + var filename = GetInputPath(basename + ".astc"); File.Exists(filename).Should().BeTrue(because: $"Testdata missing: {filename}"); var data = File.ReadAllBytes(filename); data.Length.Should().BeGreaterThanOrEqualTo(16, because: "ASTC file too small"); diff --git a/tests/ImageSharp.Textures.Astc.Tests/Utils/ImageBuffer.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageBuffer.cs similarity index 97% rename from tests/ImageSharp.Textures.Astc.Tests/Utils/ImageBuffer.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageBuffer.cs index 40faf86f..e922df95 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/Utils/ImageBuffer.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageBuffer.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Textures.Astc.TexelBlock; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests.Utils; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; internal class ImageBuffer { diff --git a/tests/ImageSharp.Textures.Astc.Tests/Utils/ImageUtils.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageUtils.cs similarity index 93% rename from tests/ImageSharp.Textures.Astc.Tests/Utils/ImageUtils.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageUtils.cs index 2013123b..17e31b9f 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/Utils/ImageUtils.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageUtils.cs @@ -3,7 +3,7 @@ using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests.Utils; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; internal static class ImageUtils { diff --git a/tests/ImageSharp.Textures.Astc.Tests/WeightInfillTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs similarity index 96% rename from tests/ImageSharp.Textures.Astc.Tests/WeightInfillTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs index dfa5c849..d0047d2e 100644 --- a/tests/ImageSharp.Textures.Astc.Tests/WeightInfillTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; -namespace SixLabors.ImageSharp.Textures.Astc.Tests; +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class WeightInfillTests { diff --git a/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj b/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj index ac9c7c4c..2d3473c2 100644 --- a/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj +++ b/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj @@ -10,11 +10,16 @@ + + + + + PreserveNewest diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/A.png b/tests/Images/Input/Astc/Expected/A.png similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/A.png rename to tests/Images/Input/Astc/Expected/A.png diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/FlightHelmet_baseColor.png b/tests/Images/Input/Astc/Expected/FlightHelmet_baseColor.png similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/FlightHelmet_baseColor.png rename to tests/Images/Input/Astc/Expected/FlightHelmet_baseColor.png diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/filelist.txt b/tests/Images/Input/Astc/Expected/GoldenGateBridge3/filelist.txt similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/filelist.txt rename to tests/Images/Input/Astc/Expected/GoldenGateBridge3/filelist.txt diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negx.jpg b/tests/Images/Input/Astc/Expected/GoldenGateBridge3/negx.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negx.jpg rename to tests/Images/Input/Astc/Expected/GoldenGateBridge3/negx.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negy.jpg b/tests/Images/Input/Astc/Expected/GoldenGateBridge3/negy.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negy.jpg rename to tests/Images/Input/Astc/Expected/GoldenGateBridge3/negy.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negz.jpg b/tests/Images/Input/Astc/Expected/GoldenGateBridge3/negz.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/negz.jpg rename to tests/Images/Input/Astc/Expected/GoldenGateBridge3/negz.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posx.jpg b/tests/Images/Input/Astc/Expected/GoldenGateBridge3/posx.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posx.jpg rename to tests/Images/Input/Astc/Expected/GoldenGateBridge3/posx.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posy.jpg b/tests/Images/Input/Astc/Expected/GoldenGateBridge3/posy.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posy.jpg rename to tests/Images/Input/Astc/Expected/GoldenGateBridge3/posy.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posz.jpg b/tests/Images/Input/Astc/Expected/GoldenGateBridge3/posz.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/posz.jpg rename to tests/Images/Input/Astc/Expected/GoldenGateBridge3/posz.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/readme.txt b/tests/Images/Input/Astc/Expected/GoldenGateBridge3/readme.txt similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/GoldenGateBridge3/readme.txt rename to tests/Images/Input/Astc/Expected/GoldenGateBridge3/readme.txt diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg b/tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg rename to tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_Opacity.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg b/tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg rename to tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_ambientOcclusion.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg b/tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg rename to tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_basecolor.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_height.png b/tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_height.png similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_height.png rename to tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_height.png diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal.jpg b/tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_normal.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal.jpg rename to tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_normal.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png b/tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png rename to tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_normal_unnormalized.png diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg b/tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg rename to tests/Images/Input/Astc/Expected/Iron_Bars/Iron_Bars_001_roughness.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/readme.txt b/tests/Images/Input/Astc/Expected/Iron_Bars/readme.txt similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Iron_Bars/readme.txt rename to tests/Images/Input/Astc/Expected/Iron_Bars/readme.txt diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/LA.png b/tests/Images/Input/Astc/Expected/LA.png similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/LA.png rename to tests/Images/Input/Astc/Expected/LA.png diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/R.png b/tests/Images/Input/Astc/Expected/R.png similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/R.png rename to tests/Images/Input/Astc/Expected/R.png diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/RG.png b/tests/Images/Input/Astc/Expected/RG.png similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/RG.png rename to tests/Images/Input/Astc/Expected/RG.png diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/filelist.txt b/tests/Images/Input/Astc/Expected/Yokohama3/filelist.txt similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/filelist.txt rename to tests/Images/Input/Astc/Expected/Yokohama3/filelist.txt diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negx.jpg b/tests/Images/Input/Astc/Expected/Yokohama3/negx.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negx.jpg rename to tests/Images/Input/Astc/Expected/Yokohama3/negx.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negy.jpg b/tests/Images/Input/Astc/Expected/Yokohama3/negy.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negy.jpg rename to tests/Images/Input/Astc/Expected/Yokohama3/negy.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negz.jpg b/tests/Images/Input/Astc/Expected/Yokohama3/negz.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/negz.jpg rename to tests/Images/Input/Astc/Expected/Yokohama3/negz.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posx.jpg b/tests/Images/Input/Astc/Expected/Yokohama3/posx.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posx.jpg rename to tests/Images/Input/Astc/Expected/Yokohama3/posx.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posy.jpg b/tests/Images/Input/Astc/Expected/Yokohama3/posy.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posy.jpg rename to tests/Images/Input/Astc/Expected/Yokohama3/posy.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posz.jpg b/tests/Images/Input/Astc/Expected/Yokohama3/posz.jpg similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/posz.jpg rename to tests/Images/Input/Astc/Expected/Yokohama3/posz.jpg diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/readme.txt b/tests/Images/Input/Astc/Expected/Yokohama3/readme.txt similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/Yokohama3/readme.txt rename to tests/Images/Input/Astc/Expected/Yokohama3/readme.txt diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_4x4.bmp b/tests/Images/Input/Astc/Expected/atlas_small_4x4.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_4x4.bmp rename to tests/Images/Input/Astc/Expected/atlas_small_4x4.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_5x5.bmp b/tests/Images/Input/Astc/Expected/atlas_small_5x5.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_5x5.bmp rename to tests/Images/Input/Astc/Expected/atlas_small_5x5.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_6x6.bmp b/tests/Images/Input/Astc/Expected/atlas_small_6x6.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_6x6.bmp rename to tests/Images/Input/Astc/Expected/atlas_small_6x6.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_8x8.bmp b/tests/Images/Input/Astc/Expected/atlas_small_8x8.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/atlas_small_8x8.bmp rename to tests/Images/Input/Astc/Expected/atlas_small_8x8.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x10.bmp b/tests/Images/Input/Astc/Expected/footprint_10x10.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x10.bmp rename to tests/Images/Input/Astc/Expected/footprint_10x10.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x5.bmp b/tests/Images/Input/Astc/Expected/footprint_10x5.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x5.bmp rename to tests/Images/Input/Astc/Expected/footprint_10x5.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x6.bmp b/tests/Images/Input/Astc/Expected/footprint_10x6.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x6.bmp rename to tests/Images/Input/Astc/Expected/footprint_10x6.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x8.bmp b/tests/Images/Input/Astc/Expected/footprint_10x8.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_10x8.bmp rename to tests/Images/Input/Astc/Expected/footprint_10x8.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x10.bmp b/tests/Images/Input/Astc/Expected/footprint_12x10.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x10.bmp rename to tests/Images/Input/Astc/Expected/footprint_12x10.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x12.bmp b/tests/Images/Input/Astc/Expected/footprint_12x12.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_12x12.bmp rename to tests/Images/Input/Astc/Expected/footprint_12x12.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_4x4.bmp b/tests/Images/Input/Astc/Expected/footprint_4x4.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_4x4.bmp rename to tests/Images/Input/Astc/Expected/footprint_4x4.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x4.bmp b/tests/Images/Input/Astc/Expected/footprint_5x4.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x4.bmp rename to tests/Images/Input/Astc/Expected/footprint_5x4.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x5.bmp b/tests/Images/Input/Astc/Expected/footprint_5x5.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_5x5.bmp rename to tests/Images/Input/Astc/Expected/footprint_5x5.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x5.bmp b/tests/Images/Input/Astc/Expected/footprint_6x5.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x5.bmp rename to tests/Images/Input/Astc/Expected/footprint_6x5.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x6.bmp b/tests/Images/Input/Astc/Expected/footprint_6x6.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_6x6.bmp rename to tests/Images/Input/Astc/Expected/footprint_6x6.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x5.bmp b/tests/Images/Input/Astc/Expected/footprint_8x5.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x5.bmp rename to tests/Images/Input/Astc/Expected/footprint_8x5.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x6.bmp b/tests/Images/Input/Astc/Expected/footprint_8x6.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x6.bmp rename to tests/Images/Input/Astc/Expected/footprint_8x6.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x8.bmp b/tests/Images/Input/Astc/Expected/footprint_8x8.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/footprint_8x8.bmp rename to tests/Images/Input/Astc/Expected/footprint_8x8.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_12x12.bmp b/tests/Images/Input/Astc/Expected/rgb_12x12.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_12x12.bmp rename to tests/Images/Input/Astc/Expected/rgb_12x12.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_4x4.bmp b/tests/Images/Input/Astc/Expected/rgb_4x4.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_4x4.bmp rename to tests/Images/Input/Astc/Expected/rgb_4x4.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_5x4.bmp b/tests/Images/Input/Astc/Expected/rgb_5x4.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_5x4.bmp rename to tests/Images/Input/Astc/Expected/rgb_5x4.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_6x6.bmp b/tests/Images/Input/Astc/Expected/rgb_6x6.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_6x6.bmp rename to tests/Images/Input/Astc/Expected/rgb_6x6.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_8x8.bmp b/tests/Images/Input/Astc/Expected/rgb_8x8.bmp similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Expected/rgb_8x8.bmp rename to tests/Images/Input/Astc/Expected/rgb_8x8.bmp diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.astc b/tests/Images/Input/Astc/HDR/HDR-A-1x1.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.astc rename to tests/Images/Input/Astc/HDR/HDR-A-1x1.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.exr b/tests/Images/Input/Astc/HDR/HDR-A-1x1.exr similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/HDR-A-1x1.exr rename to tests/Images/Input/Astc/HDR/HDR-A-1x1.exr diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.astc b/tests/Images/Input/Astc/HDR/LDR-A-1x1.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.astc rename to tests/Images/Input/Astc/HDR/LDR-A-1x1.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.png b/tests/Images/Input/Astc/HDR/LDR-A-1x1.png similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/LDR-A-1x1.png rename to tests/Images/Input/Astc/HDR/LDR-A-1x1.png diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-complex.exr b/tests/Images/Input/Astc/HDR/hdr-complex.exr similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-complex.exr rename to tests/Images/Input/Astc/HDR/hdr-complex.exr diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-tile.astc b/tests/Images/Input/Astc/HDR/hdr-tile.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr-tile.astc rename to tests/Images/Input/Astc/HDR/hdr-tile.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr.exr b/tests/Images/Input/Astc/HDR/hdr.exr similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr.exr rename to tests/Images/Input/Astc/HDR/hdr.exr diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr.hdr b/tests/Images/Input/Astc/HDR/hdr.hdr similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/hdr.hdr rename to tests/Images/Input/Astc/HDR/hdr.hdr diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/ldr-tile.astc b/tests/Images/Input/Astc/HDR/ldr-tile.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/HDR/ldr-tile.astc rename to tests/Images/Input/Astc/HDR/ldr-tile.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_4x4.astc b/tests/Images/Input/Astc/Input/atlas_small_4x4.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_4x4.astc rename to tests/Images/Input/Astc/Input/atlas_small_4x4.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_5x5.astc b/tests/Images/Input/Astc/Input/atlas_small_5x5.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_5x5.astc rename to tests/Images/Input/Astc/Input/atlas_small_5x5.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_6x6.astc b/tests/Images/Input/Astc/Input/atlas_small_6x6.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_6x6.astc rename to tests/Images/Input/Astc/Input/atlas_small_6x6.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_8x8.astc b/tests/Images/Input/Astc/Input/atlas_small_8x8.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/atlas_small_8x8.astc rename to tests/Images/Input/Astc/Input/atlas_small_8x8.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkerboard.astc b/tests/Images/Input/Astc/Input/checkerboard.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkerboard.astc rename to tests/Images/Input/Astc/Input/checkerboard.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_10.astc b/tests/Images/Input/Astc/Input/checkered_10.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_10.astc rename to tests/Images/Input/Astc/Input/checkered_10.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_11.astc b/tests/Images/Input/Astc/Input/checkered_11.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_11.astc rename to tests/Images/Input/Astc/Input/checkered_11.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_12.astc b/tests/Images/Input/Astc/Input/checkered_12.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_12.astc rename to tests/Images/Input/Astc/Input/checkered_12.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_4.astc b/tests/Images/Input/Astc/Input/checkered_4.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_4.astc rename to tests/Images/Input/Astc/Input/checkered_4.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_5.astc b/tests/Images/Input/Astc/Input/checkered_5.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_5.astc rename to tests/Images/Input/Astc/Input/checkered_5.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_6.astc b/tests/Images/Input/Astc/Input/checkered_6.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_6.astc rename to tests/Images/Input/Astc/Input/checkered_6.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_7.astc b/tests/Images/Input/Astc/Input/checkered_7.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_7.astc rename to tests/Images/Input/Astc/Input/checkered_7.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_8.astc b/tests/Images/Input/Astc/Input/checkered_8.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_8.astc rename to tests/Images/Input/Astc/Input/checkered_8.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_9.astc b/tests/Images/Input/Astc/Input/checkered_9.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/checkered_9.astc rename to tests/Images/Input/Astc/Input/checkered_9.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x10.astc b/tests/Images/Input/Astc/Input/footprint_10x10.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x10.astc rename to tests/Images/Input/Astc/Input/footprint_10x10.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x5.astc b/tests/Images/Input/Astc/Input/footprint_10x5.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x5.astc rename to tests/Images/Input/Astc/Input/footprint_10x5.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x6.astc b/tests/Images/Input/Astc/Input/footprint_10x6.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x6.astc rename to tests/Images/Input/Astc/Input/footprint_10x6.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x8.astc b/tests/Images/Input/Astc/Input/footprint_10x8.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_10x8.astc rename to tests/Images/Input/Astc/Input/footprint_10x8.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_12x10.astc b/tests/Images/Input/Astc/Input/footprint_12x10.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_12x10.astc rename to tests/Images/Input/Astc/Input/footprint_12x10.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_12x12.astc b/tests/Images/Input/Astc/Input/footprint_12x12.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_12x12.astc rename to tests/Images/Input/Astc/Input/footprint_12x12.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_4x4.astc b/tests/Images/Input/Astc/Input/footprint_4x4.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_4x4.astc rename to tests/Images/Input/Astc/Input/footprint_4x4.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_5x4.astc b/tests/Images/Input/Astc/Input/footprint_5x4.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_5x4.astc rename to tests/Images/Input/Astc/Input/footprint_5x4.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_5x5.astc b/tests/Images/Input/Astc/Input/footprint_5x5.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_5x5.astc rename to tests/Images/Input/Astc/Input/footprint_5x5.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x5.astc b/tests/Images/Input/Astc/Input/footprint_6x5.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x5.astc rename to tests/Images/Input/Astc/Input/footprint_6x5.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x6.astc b/tests/Images/Input/Astc/Input/footprint_6x6.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_6x6.astc rename to tests/Images/Input/Astc/Input/footprint_6x6.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x5.astc b/tests/Images/Input/Astc/Input/footprint_8x5.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x5.astc rename to tests/Images/Input/Astc/Input/footprint_8x5.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x6.astc b/tests/Images/Input/Astc/Input/footprint_8x6.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x6.astc rename to tests/Images/Input/Astc/Input/footprint_8x6.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x8.astc b/tests/Images/Input/Astc/Input/footprint_8x8.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/footprint_8x8.astc rename to tests/Images/Input/Astc/Input/footprint_8x8.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_12x12.astc b/tests/Images/Input/Astc/Input/rgb_12x12.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_12x12.astc rename to tests/Images/Input/Astc/Input/rgb_12x12.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_4x4.astc b/tests/Images/Input/Astc/Input/rgb_4x4.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_4x4.astc rename to tests/Images/Input/Astc/Input/rgb_4x4.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_5x4.astc b/tests/Images/Input/Astc/Input/rgb_5x4.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_5x4.astc rename to tests/Images/Input/Astc/Input/rgb_5x4.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_6x6.astc b/tests/Images/Input/Astc/Input/rgb_6x6.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_6x6.astc rename to tests/Images/Input/Astc/Input/rgb_6x6.astc diff --git a/tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_8x8.astc b/tests/Images/Input/Astc/Input/rgb_8x8.astc similarity index 100% rename from tests/ImageSharp.Textures.Astc.Tests/TestData/Input/rgb_8x8.astc rename to tests/Images/Input/Astc/Input/rgb_8x8.astc From b9cc7b1dff298d7957f4e25d95a80393ef9460cd Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:42:10 +0100 Subject: [PATCH 33/37] Use ImageSharp test utilites to load and compare images --- .../Formats/Astc/CodecTests.cs | 33 ++++---- .../Formats/Astc/LogicalAstcBlockTests.cs | 58 +++++++++++-- .../Formats/Astc/Utils/FileBasedHelpers.cs | 56 ------------- .../Formats/Astc/Utils/ImageBuffer.cs | 84 ------------------- .../Formats/Astc/Utils/ImageUtils.cs | 32 ------- 5 files changed, 70 insertions(+), 193 deletions(-) delete mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageBuffer.cs delete mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageUtils.cs diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs index 5fbf477b..bc06c02d 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs @@ -1,11 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -94,12 +97,13 @@ public void ASTCDecompressToRGBA_WithValidData_ShouldMatchExpected( logicalBlock.Should().NotBeNull("all blocks should unpack successfully"); } - var decodedPixels = AstcDecoder.DecompressImage(astcData, width, height, footprintType); - var actualImage = new ImageBuffer(decodedPixels.ToArray(), width, height, 4); + byte[] decodedPixels = AstcDecoder.DecompressImage(astcData, width, height, footprintType).ToArray(); + using Image actualImage = Image.LoadPixelData(decodedPixels, width, height); + actualImage.Mutate(x => x.Flip(FlipMode.Vertical)); - var expectedImagePath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); - var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedImagePath); - ImageUtils.CompareSumOfSquaredDifferences(expectedImage, actualImage, 0.1); + string expectedImagePath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); + using Image expectedImage = Image.Load(expectedImagePath); + ImageComparer.TolerantPercentage(0.1f).VerifySimilarity(expectedImage, actualImage); } [Theory] @@ -113,21 +117,22 @@ public void DecompressToImage_WithAstcFile_ShouldMatchExpected( int width, int height) { - var astcPath = FileBasedHelpers.GetInputPath(imageName + ".astc"); - var astcBytes = File.ReadAllBytes(astcPath); - var file = AstcFile.FromMemory(astcBytes); + string astcPath = FileBasedHelpers.GetInputPath(imageName + ".astc"); + byte[] astcBytes = File.ReadAllBytes(astcPath); + AstcFile file = AstcFile.FromMemory(astcBytes); // Check file header file.Footprint.Type.Should().Be(footprint); file.Width.Should().Be(width); file.Height.Should().Be(height); - var decodedPixels = AstcDecoder.DecompressImage(file); - var actualImage = new ImageBuffer(decodedPixels.ToArray(), width, height, 4); + byte[] decodedPixels = AstcDecoder.DecompressImage(file).ToArray(); + using Image actualImage = Image.LoadPixelData(decodedPixels, width, height); + actualImage.Mutate(x => x.Flip(FlipMode.Vertical)); - var expectedImagePath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); - var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedImagePath); - ImageUtils.CompareSumOfSquaredDifferences(expectedImage, actualImage, 0.1); + string expectedImagePath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); + using Image expectedImage = Image.Load(expectedImagePath); + ImageComparer.TolerantPercentage(0.1f).VerifySimilarity(expectedImage, actualImage); } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs index a3f712d8..c99e0cdf 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; +using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; +using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -380,13 +382,55 @@ public void UnpackLogicalBlock_FromImage_ShouldDecodeCorrectly( int width, int height) { - var footprint = Footprint.FromFootprintType(footprintType); - var astcData = FileBasedHelpers.LoadASTCFile(imageName); + Footprint footprint = Footprint.FromFootprintType(footprintType); + byte[] astcData = FileBasedHelpers.LoadASTCFile(imageName); + + using Image decodedImage = DecodeAstcBlocksToImage(footprint, astcData, width, height); + + string expectedPath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); + using Image expectedImage = Image.Load(expectedPath); + ImageComparer.TolerantPercentage(1.0f).VerifySimilarity(expectedImage, decodedImage); + } + + private static Image DecodeAstcBlocksToImage(Footprint footprint, byte[] astcData, int width, int height) + { + // ASTC uses x/y ordering, so we flip Y to match ImageSharp's row/column origin. + var image = new Image(width, height); + int blockWidth = footprint.Width; + int blockHeight = footprint.Height; + int blocksWide = (width + blockWidth - 1) / blockWidth; - var decodedImage = ImageBuffer.FromAstcBuffer(footprint, astcData, width, height, hasAlpha); + for (int i = 0; i < astcData.Length; i += PhysicalBlock.SizeInBytes) + { + int blockIndex = i / PhysicalBlock.SizeInBytes; + int blockX = blockIndex % blocksWide; + int blockY = blockIndex / blocksWide; + + byte[] blockSpan = astcData.AsSpan(i, PhysicalBlock.SizeInBytes).ToArray(); + var bits = new UInt128( + BitConverter.ToUInt64(blockSpan, 8), + BitConverter.ToUInt64(blockSpan, 0)); + BlockInfo info = BlockInfo.Decode(bits); + LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, bits, in info); + Assert.NotNull(logicalBlock); + + for (int y = 0; y < blockHeight; ++y) + { + for (int x = 0; x < blockWidth; ++x) + { + int px = (blockWidth * blockX) + x; + int py = (blockHeight * blockY) + y; + if (px >= width || py >= height) + { + continue; + } + + RgbaColor decoded = logicalBlock!.ColorAt(x, y); + image[px, height - 1 - py] = new Rgba32(decoded.R, decoded.G, decoded.B, decoded.A); + } + } + } - var expectedPath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); - var expectedImage = FileBasedHelpers.LoadExpectedImage(expectedPath); - ImageUtils.CompareSumOfSquaredDifferences(expectedImage, decodedImage, 0.1); + return image; } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs index c7a51718..78f098fe 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs @@ -23,60 +23,4 @@ public static byte[] LoadASTCFile(string basename) data.Length.Should().BeGreaterThanOrEqualTo(16, because: "ASTC file too small"); return data.Skip(16).ToArray(); } - - public static ImageBuffer LoadExpectedImage(string path) - { - const int BmpHeaderSize = 54; - var data = File.ReadAllBytes(path); - data.Length.Should().BeGreaterThanOrEqualTo(BmpHeaderSize); - data[0].Should().Be((byte)'B'); - data[1].Should().Be((byte)'M'); - - uint dataPos = BitConverter.ToUInt32(data, 0x0A); - uint imageSize = BitConverter.ToUInt32(data, 0x22); - ushort bitsPerPixel = BitConverter.ToUInt16(data, 0x1C); - int width = BitConverter.ToInt32(data, 0x12); - int height = BitConverter.ToInt32(data, 0x16); - - if (height < 0) height = -height; - if (imageSize == 0) imageSize = (uint)(width * height * (bitsPerPixel / 8)); - if (dataPos < BmpHeaderSize) dataPos = BmpHeaderSize; - - (bitsPerPixel == 24 || bitsPerPixel == 32).Should().BeTrue(because: "BMP bits per pixel mismatch, expected 24 or 32"); - - var result = ImageBuffer.Allocate(width, height, bitsPerPixel == 24 ? 3 : 4); - imageSize.Should().BeLessThanOrEqualTo((uint)result.DataSize); - - var stride = result.Stride; - - for (int row = 0; row < height; ++row) - { - Array.Copy(data, (int)dataPos + row * stride, result.Data, row * stride, width * (bitsPerPixel / 8)); - } - - if (bitsPerPixel == 32) - { - for (int row = 0; row < height; ++row) - { - int rowOffset = row * stride; - for (int i = 3; i < stride; i += 4) - { - (result.Data[rowOffset + i - 1], result.Data[rowOffset + i - 3]) = (result.Data[rowOffset + i - 3], result.Data[rowOffset + i - 1]); - } - } - } - else - { - for (int row = 0; row < height; ++row) - { - int rowOffset = row * stride; - for (int i = 2; i < stride; i += 3) - { - (result.Data[rowOffset + i], result.Data[rowOffset + i - 2]) = (result.Data[rowOffset + i - 2], result.Data[rowOffset + i]); - } - } - } - - return result; - } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageBuffer.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageBuffer.cs deleted file mode 100644 index e922df95..00000000 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageBuffer.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Textures.Astc.Core; -using SixLabors.ImageSharp.Textures.Astc.TexelBlock; -using AwesomeAssertions; - -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; - -internal class ImageBuffer -{ - public const int Align = 4; - - public byte[] Data { get; } - public int Stride { get; } - public int BytesPerPixel { get; } - public int Width { get; } - public int Height { get; } - public int DataSize => Data.Length; - - public ImageBuffer(byte[] data, int width, int height, int bytesPerPixel) - { - Data = data; - BytesPerPixel = bytesPerPixel; - Width = width; - Height = height; - int rowBytes = width * bytesPerPixel; - Stride = (rowBytes + (Align - 1)) / Align * Align; - } - - public static ImageBuffer Allocate(int width, int height, int bytesPerPixel) - { - int rowBytes = width * bytesPerPixel; - var stride = (rowBytes + (Align - 1)) / Align * Align; - var data = new byte[stride * height]; - - return new ImageBuffer(data, width, height, bytesPerPixel); - } - - public static ImageBuffer FromAstcBuffer(Footprint footprint, byte[] astcData, int width, int height, bool hasAlpha) - { - var decodedImage = Allocate(width, height, hasAlpha ? RgbaColor.BytesPerPixel : RgbColor.BytesPerPixel); - - int blockWidth = footprint.Width; - int blockHeight = footprint.Height; - - for (int i = 0; i < astcData.Length; i += PhysicalBlock.SizeInBytes) - { - int blockIndex = i / PhysicalBlock.SizeInBytes; - int blocksWide = (width + blockWidth - 1) / blockWidth; - int blockX = blockIndex % blocksWide; - int blockY = blockIndex / blocksWide; - - var blockSpan = astcData.AsSpan(i, PhysicalBlock.SizeInBytes).ToArray(); - var bits = new UInt128( - BitConverter.ToUInt64(blockSpan, 8), - BitConverter.ToUInt64(blockSpan, 0)); - var info = BlockInfo.Decode(bits); - - var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, bits, in info); - logicalBlock.Should().NotBeNull(); - - for (int y = 0; y < blockHeight; ++y) - { - for (int x = 0; x < blockWidth; ++x) - { - int px = blockWidth * blockX + x; - int py = blockHeight * blockY + y; - if (px >= width || py >= height) continue; - - var decoded = logicalBlock!.ColorAt(x, y); - int row = py * decodedImage.Stride; - int off = row + px * decodedImage.BytesPerPixel; - decodedImage.Data[off + 0] = decoded.R; - decodedImage.Data[off + 1] = decoded.G; - decodedImage.Data[off + 2] = decoded.B; - if (hasAlpha) decodedImage.Data[off + 3] = decoded.A; - } - } - } - - return decodedImage; - } -} diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageUtils.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageUtils.cs deleted file mode 100644 index 17e31b9f..00000000 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/ImageUtils.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using AwesomeAssertions; - -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; - -internal static class ImageUtils -{ - public static void CompareSumOfSquaredDifferences(ImageBuffer expected, ImageBuffer actual, double threshold) - { - actual.DataSize.Should().Be(expected.DataSize); - actual.Stride.Should().Be(expected.Stride); - actual.BytesPerPixel.Should().Be(expected.BytesPerPixel); - - var expectedData = expected.Data; - var actualData = actual.Data; - - double sum = 0.0; - for (int i = 0; i < actualData.Length; ++i) - { - double diff = (double)actualData[i] - expectedData[i]; - sum += diff * diff; - } - - sum.Should().BeLessThanOrEqualTo(threshold * actualData.Length, because: $"Per pixel {(sum / actualData.Length)}, expected <= {threshold}"); - if (sum > threshold * actualData.Length) - { - actualData.Should().BeEquivalentTo(expectedData); - } - } -} From 517959737447ef1a78332f632621f9c9d696a6c8 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:57:18 +0100 Subject: [PATCH 34/37] Use ImageSharp test utils to load images --- .../Formats/Astc/CodecTests.cs | 9 +++---- .../Formats/Astc/EndpointCodecTests.cs | 3 +-- .../Formats/Astc/HDR/HdrComparisonTests.cs | 19 +++++++------- .../Formats/Astc/HDR/HdrImageTests.cs | 11 ++++---- .../Formats/Astc/IntegrationTests.cs | 3 +-- .../Formats/Astc/IntermediateBlockTests.cs | 3 +-- .../Formats/Astc/LogicalAstcBlockTests.cs | 5 ++-- .../Formats/Astc/Utils/FileBasedHelpers.cs | 26 ------------------- tests/ImageSharp.Textures.Tests/TestImages.cs | 7 +++++ 9 files changed, 30 insertions(+), 56 deletions(-) delete mode 100644 tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs index bc06c02d..190eb8d6 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; using AwesomeAssertions; @@ -74,7 +73,7 @@ public void ASTCDecompressToRGBA_WithValidData_ShouldMatchExpected( int width, int height) { - var astcData = FileBasedHelpers.LoadASTCFile(imageName); + var astcData = TestFile.Create(Path.Combine(TestImages.Astc.InputFolder, imageName + ".astc")).Bytes[16..]; var footprint = Footprint.FromFootprintType(footprintType); int blockWidth = footprint.Width; int blockHeight = footprint.Height; @@ -101,7 +100,7 @@ public void ASTCDecompressToRGBA_WithValidData_ShouldMatchExpected( using Image actualImage = Image.LoadPixelData(decodedPixels, width, height); actualImage.Mutate(x => x.Flip(FlipMode.Vertical)); - string expectedImagePath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); + string expectedImagePath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.ExpectedFolder, imageName + ".bmp")); using Image expectedImage = Image.Load(expectedImagePath); ImageComparer.TolerantPercentage(0.1f).VerifySimilarity(expectedImage, actualImage); } @@ -117,7 +116,7 @@ public void DecompressToImage_WithAstcFile_ShouldMatchExpected( int width, int height) { - string astcPath = FileBasedHelpers.GetInputPath(imageName + ".astc"); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.InputFolder, imageName + ".astc")); byte[] astcBytes = File.ReadAllBytes(astcPath); AstcFile file = AstcFile.FromMemory(astcBytes); @@ -130,7 +129,7 @@ public void DecompressToImage_WithAstcFile_ShouldMatchExpected( using Image actualImage = Image.LoadPixelData(decodedPixels, width, height); actualImage.Mutate(x => x.Flip(FlipMode.Vertical)); - string expectedImagePath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); + string expectedImagePath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.ExpectedFolder, imageName + ".bmp")); using Image expectedImage = Image.Load(expectedImagePath); ImageComparer.TolerantPercentage(0.1f).VerifySimilarity(expectedImage, actualImage); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs index 7efe0062..6540678a 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs @@ -5,7 +5,6 @@ using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -306,7 +305,7 @@ private static int[] EncodeRgbBaseOffset(RgbaColor low, RgbaColor high) [Fact] public void DecodeCheckerboard_ShouldDecodeToGrayscaleEndpoints() { - string astcFilePath = FileBasedHelpers.GetInputPath("checkerboard.astc"); + string astcFilePath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.InputFolder, "checkerboard.astc")); byte[] astcData = File.ReadAllBytes(astcFilePath); int blocksDecoded = 0; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs index 81e5a5e9..6faf43d0 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs @@ -4,7 +4,6 @@ using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; @@ -18,7 +17,7 @@ public class HdrComparisonTests public void HdrFile_DecodedWithHdrApi_ShouldPreserveExtendedRange() { // HDR files should decode to values potentially exceeding 1.0 - var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -43,7 +42,7 @@ public void HdrFile_DecodedWithHdrApi_ShouldPreserveExtendedRange() public void LdrFile_DecodedWithHdrApi_ShouldUpscaleToHdrRange() { // LDR files decoded with HDR API should produce values in 0.0-1.0 range - var astcPath = FileBasedHelpers.GetHdrPath("LDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -66,7 +65,7 @@ public void LdrFile_DecodedWithHdrApi_ShouldUpscaleToHdrRange() public void HdrFile_DecodedWithLdrApi_ShouldClampToByteRange() { // HDR files decoded with LDR API should clamp to 0-255 - var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -88,7 +87,7 @@ public void HdrFile_DecodedWithLdrApi_ShouldClampToByteRange() public void LdrFile_DecodedWithBothApis_ShouldProduceConsistentValues() { // LDR content should produce equivalent results with both APIs - var astcPath = FileBasedHelpers.GetHdrPath("LDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -114,7 +113,7 @@ public void LdrFile_DecodedWithBothApis_ShouldProduceConsistentValues() public void HdrTile_ShouldDecodeSuccessfully() { // Test larger HDR tile decoding - var astcPath = FileBasedHelpers.GetHdrPath("hdr-tile.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "hdr-tile.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -136,7 +135,7 @@ public void HdrTile_ShouldDecodeSuccessfully() public void LdrTile_ShouldDecodeSuccessfully() { // Test larger LDR tile decoding - var astcPath = FileBasedHelpers.GetHdrPath("ldr-tile.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "ldr-tile.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -155,8 +154,8 @@ public void LdrTile_ShouldDecodeSuccessfully() public void SameFootprint_HdrVsLdr_ShouldBothDecode() { // Verify files with same footprint decode correctly - var hdrPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); - var ldrPath = FileBasedHelpers.GetHdrPath("LDR-A-1x1.astc"); + var hdrPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); + var ldrPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); var hdrData = File.ReadAllBytes(hdrPath); var ldrData = File.ReadAllBytes(ldrPath); @@ -184,7 +183,7 @@ public void SameFootprint_HdrVsLdr_ShouldBothDecode() public void HdrColor_FromLdr_ShouldMatchLdrToHdrApiConversion() { // Verify that HdrColor.FromRgba() produces same results as decoding LDR with HDR API - var astcPath = FileBasedHelpers.GetHdrPath("LDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs index 2b396ceb..a07ca27e 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs @@ -5,7 +5,6 @@ using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; @@ -21,7 +20,7 @@ public class HdrImageTests [Description("Verify that the ASTC file header is correctly parsed for HDR content, including footprint detection")] public void DecodeHdrFile_VerifyFootprintDetection() { - var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -35,7 +34,7 @@ public void DecodeHdrFile_VerifyFootprintDetection() [Fact] public void DecodeHdrAstcFile_1x1Pixel_ShouldProduceValidHdrOutput() { - var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -61,7 +60,7 @@ public void DecodeHdrAstcFile_1x1Pixel_ShouldProduceValidHdrOutput() [Fact] public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() { - var astcPath = FileBasedHelpers.GetHdrPath("hdr-tile.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "hdr-tile.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); @@ -89,7 +88,7 @@ public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() [Description("Verify that HDR ASTC files can be decoded with the LDR API, producing clamped values")] public void DecodeHdrAstcFile_WithLdrApi_ShouldClampValues() { - var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); if (!File.Exists(astcPath)) { @@ -117,7 +116,7 @@ public void DecodeHdrAstcFile_WithLdrApi_ShouldClampValues() [Description("Verify that HDR and LDR APIs produce consistent relative channel values for the same HDR ASTC file")] public void HdrAndLdrApis_OnSameHdrFile_ShouldProduceConsistentRelativeValues() { - var astcPath = FileBasedHelpers.GetHdrPath("HDR-A-1x1.astc"); + var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); var astcData = File.ReadAllBytes(astcPath); var astcFile = AstcFile.FromMemory(astcData); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs index df73cc81..550d225d 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.IO; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -46,7 +45,7 @@ public class IntegrationTests [InlineData("rgb_12x12")] public void DecompressToImage_WithTestdataFile_ShouldDecodeSuccessfully(string basename) { - var filePath = FileBasedHelpers.GetInputPath(basename + ".astc"); + var filePath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.InputFolder, basename + ".astc")); var bytes = File.ReadAllBytes(filePath); var astc = AstcFile.FromMemory(bytes); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs index 210b079a..964dce18 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs @@ -4,7 +4,6 @@ using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -448,7 +447,7 @@ private static void VerifyBlockPropertiesMatch(PhysicalBlock repacked, PhysicalB private static byte[] LoadASTCFile(string basename) { - var filename = FileBasedHelpers.GetInputPath(basename + ".astc"); + var filename = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.InputFolder, basename + ".astc")); File.Exists(filename).Should().BeTrue($"Testdata missing: {filename}"); var data = File.ReadAllBytes(filename); data.Length.Should().BeGreaterThanOrEqualTo(16, "ASTC file too small"); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs index c99e0cdf..0019433a 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs @@ -5,7 +5,6 @@ using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; -using SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; using AwesomeAssertions; @@ -383,11 +382,11 @@ public void UnpackLogicalBlock_FromImage_ShouldDecodeCorrectly( int height) { Footprint footprint = Footprint.FromFootprintType(footprintType); - byte[] astcData = FileBasedHelpers.LoadASTCFile(imageName); + byte[] astcData = TestFile.Create(Path.Combine(TestImages.Astc.InputFolder, imageName + ".astc")).Bytes[16..]; using Image decodedImage = DecodeAstcBlocksToImage(footprint, astcData, width, height); - string expectedPath = FileBasedHelpers.GetExpectedPath(imageName + ".bmp"); + string expectedPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.ExpectedFolder, imageName + ".bmp")); using Image expectedImage = Image.Load(expectedPath); ImageComparer.TolerantPercentage(1.0f).VerifySimilarity(expectedImage, decodedImage); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs deleted file mode 100644 index 78f098fe..00000000 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/Utils/FileBasedHelpers.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using AwesomeAssertions; - -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.Utils; - -internal static class FileBasedHelpers -{ - private static readonly string AstcTestDataRoot = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "Astc"); - - public static string GetInputPath(string relativePath) => Path.Combine(AstcTestDataRoot, "Input", relativePath); - - public static string GetExpectedPath(string relativePath) => Path.Combine(AstcTestDataRoot, "Expected", relativePath); - - public static string GetHdrPath(string relativePath) => Path.Combine(AstcTestDataRoot, "HDR", relativePath); - - public static byte[] LoadASTCFile(string basename) - { - var filename = GetInputPath(basename + ".astc"); - File.Exists(filename).Should().BeTrue(because: $"Testdata missing: {filename}"); - var data = File.ReadAllBytes(filename); - data.Length.Should().BeGreaterThanOrEqualTo(16, because: "ASTC file too small"); - return data.Skip(16).ToArray(); - } -} diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index c405b47a..fc8d636b 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -8,6 +8,13 @@ namespace SixLabors.ImageSharp.Textures.Tests; /// public static class TestImages { + public static class Astc + { + public const string InputFolder = "Astc/Input"; + public const string ExpectedFolder = "Astc/Expected"; + public const string HdrFolder = "Astc/HDR"; + } + public static class Ktx { public const string Rgba = "rgba8888.ktx"; From d52ee778fc1d31cad3be0664da6b4d5278b48793 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:10:05 +0100 Subject: [PATCH 35/37] Fix style warnings in tests --- .../ReferenceDecoderHdrTests.cs | 3 + .../ReferenceDecoderTests.cs | 8 +- .../AstcDecodingBenchmark.cs | 10 +-- .../AstcImageDecodeBenchmark.cs | 6 +- .../Formats/Astc/BitOperationsTests.cs | 2 +- .../Formats/Astc/BitStreamTests.cs | 14 +-- .../Formats/Astc/CodecTests.cs | 3 +- .../Formats/Astc/EndpointCodecTests.cs | 15 ++-- .../Formats/Astc/FootprintTests.cs | 2 +- .../Formats/Astc/HDR/HdrComparisonTests.cs | 2 +- .../Formats/Astc/HDR/HdrDecoderTests.cs | 3 +- .../Formats/Astc/HDR/HdrImageTests.cs | 3 +- .../Formats/Astc/HDR/RgbaHdrColorTests.cs | 3 +- .../Formats/Astc/IntegerSequenceCodecTests.cs | 26 +++--- .../Formats/Astc/IntegrationTests.cs | 2 +- .../Formats/Astc/IntermediateBlockTests.cs | 2 +- .../Formats/Astc/LogicalAstcBlockTests.cs | 5 +- .../Formats/Astc/PartitionTests.cs | 86 +++++++++---------- .../Formats/Astc/PhysicalAstcBlockTests.cs | 2 +- .../Formats/Astc/QuantizationTests.cs | 2 +- .../Formats/Astc/WeightInfillTests.cs | 2 +- .../TextureProviders/TestTextureProvider.cs | 2 +- .../Decoding/AstcDecoderTests.cs | 1 - 23 files changed, 105 insertions(+), 99 deletions(-) diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs index 908d78cd..ccbbb97f 100644 --- a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs @@ -107,6 +107,7 @@ public void DecompressHdr_SolidColor_ShouldMatch(FootprintType footprintType) public void DecompressHdr_Gradient_ShouldMatch(FootprintType footprintType) { var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + // 2×2 blocks for HDR gradient int width = blockX * 2; int height = blockY * 2; @@ -143,6 +144,7 @@ public void DecompressHdr_Gradient_ShouldMatch(FootprintType footprintType) public void DecompressHdr_MixedLdrHdr_ShouldMatch(FootprintType footprintType) { var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + // 2×2 blocks int width = blockX * 2; int height = blockY * 2; @@ -170,6 +172,7 @@ public void DecompressHdr_MixedLdrHdr_ShouldMatch(FootprintType footprintType) pixels[idx + 1] = (Half)(0.5f + fraction * 2.0f); pixels[idx + 2] = (Half)(0.2f + fraction * 1.5f); } + pixels[idx + 3] = (Half)1.0f; } } diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs index 63eeffe2..822034b3 100644 --- a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; using SixLabors.ImageSharp.Textures.Astc.Reference.Tests.Utils; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Astc.Reference.Tests; @@ -118,6 +118,7 @@ public void DecompressLdr_SolidColor_ShouldMatch(FootprintType footprintType) public void DecompressLdr_Gradient_ShouldMatch(FootprintType footprintType) { var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + // 2×2 blocks for gradient int width = blockX * 2; int height = blockY * 2; @@ -149,6 +150,7 @@ public void DecompressLdr_Gradient_ShouldMatch(FootprintType footprintType) public void DecompressLdr_RandomNoise_ShouldMatch(FootprintType footprintType) { var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + // 2×2 blocks int width = blockX * 2; int height = blockY * 2; @@ -156,6 +158,7 @@ public void DecompressLdr_RandomNoise_ShouldMatch(FootprintType footprintType) var rng = new Random(42); // Fixed seed for reproducibility var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; rng.NextBytes(pixels); + // Force alpha to 255 so compression doesn't introduce alpha-related variance for (int index = 3; index < pixels.Length; index += RgbaColor.BytesPerPixel) pixels[index] = byte.MaxValue; @@ -206,10 +209,9 @@ public void DecompressLdr_VoidExtentBlock_ShouldMatch() // Bits [80..95] = G (UNORM16) // Bits [96..111] = B (UNORM16) // Bits [112..127]= A (UNORM16) - var block = new byte[16]; ulong low = 0xFFFFFFFFFFFFFDFC; - ulong high = ((ulong)0xFFFF << 48) | ((ulong)0xC000 << 32) | ((ulong)0x4000 << 16) | 0x8000; + ulong high = (0xFFFFUL << 48) | ((ulong)0xC000 << 32) | (0x4000UL << 16) | 0x8000; BitConverter.TryWriteBytes(block.AsSpan(0, 8), low); BitConverter.TryWriteBytes(block.AsSpan(8, 8), high); diff --git a/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs index 346c7cf7..76f66499 100644 --- a/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs +++ b/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs @@ -11,20 +11,20 @@ namespace SixLabors.ImageSharp.Textures.Benchmarks; [MemoryDiagnoser] public class AstcDecodingBenchmark { - private AstcFile? _astcFile; + private AstcFile? astcFile; [GlobalSetup] public void Setup() { string path = BenchmarkTestDataLocator.FindAstcTestData(Path.Combine("Input", "atlas_small_4x4.astc")); byte[] astcData = File.ReadAllBytes(path); - _astcFile = AstcFile.FromMemory(astcData); + this.astcFile = AstcFile.FromMemory(astcData); } [Benchmark] public bool ParseBlock() { - ReadOnlySpan blocks = _astcFile!.Blocks; + ReadOnlySpan blocks = this.astcFile!.Blocks; Span blockBytes = stackalloc byte[16]; blocks.Slice(0, 16).CopyTo(blockBytes); ulong low = BitConverter.ToUInt64(blockBytes); @@ -37,7 +37,7 @@ public bool ParseBlock() [Benchmark] public bool DecodeEndpoints() { - ReadOnlySpan blocks = _astcFile!.Blocks; + ReadOnlySpan blocks = this.astcFile!.Blocks; Span blockBytes = stackalloc byte[16]; blocks.Slice(0, 16).CopyTo(blockBytes); ulong low = BitConverter.ToUInt64(blockBytes); @@ -52,7 +52,7 @@ public bool DecodeEndpoints() [Benchmark] public bool Partitioning() { - ReadOnlySpan blocks = _astcFile!.Blocks; + ReadOnlySpan blocks = this.astcFile!.Blocks; Span blockBytes = stackalloc byte[16]; blocks.Slice(0, 16).CopyTo(blockBytes); ulong low = BitConverter.ToUInt64(blockBytes); diff --git a/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs index 1f167138..396b6b80 100644 --- a/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs +++ b/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs @@ -10,20 +10,20 @@ namespace SixLabors.ImageSharp.Textures.Benchmarks; [MemoryDiagnoser] public class AstcImageDecodeBenchmark { - private AstcFile? _astcFile; + private AstcFile? astcFile; [GlobalSetup] public void Setup() { string path = BenchmarkTestDataLocator.FindAstcTestData(Path.Combine("Input", "atlas_small_4x4.astc")); byte[] astcData = File.ReadAllBytes(path); - _astcFile = AstcFile.FromMemory(astcData); + this.astcFile = AstcFile.FromMemory(astcData); } [Benchmark] public void ImageDecode() { - ReadOnlySpan blocks = _astcFile!.Blocks; + ReadOnlySpan blocks = this.astcFile!.Blocks; int numBlocks = blocks.Length / 16; Span blockBytes = stackalloc byte[16]; for (int i = 0; i < numBlocks; ++i) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs index ed49da95..923d69b4 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; +using SixLabors.ImageSharp.Textures.Astc.Core; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs index c79e1d84..4e187a0e 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Textures.Astc.IO; using AwesomeAssertions; +using SixLabors.ImageSharp.Textures.Astc.IO; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -19,7 +19,7 @@ public void Constructor_WithBitsAndLength_ShouldInitializeCorrectly() [Fact] public void Constructor_WithoutParameters_ShouldInitializeEmpty() { - var stream = new BitStream(); + var stream = default(BitStream); stream.Bits.Should().Be(0); } @@ -121,7 +121,7 @@ public void TryGetBits_WithZeroBits_ShouldReturnZeroAndNotConsume() [Fact] public void PutBits_WithSmallValues_ShouldAccumulateCorrectly() { - var stream = new BitStream(); + var stream = default(BitStream); stream.PutBits(0U, 1); stream.PutBits(0b11U, 2); @@ -135,7 +135,7 @@ public void PutBits_WithSmallValues_ShouldAccumulateCorrectly() public void PutBits_With64BitsOfOnes_ShouldStoreCorrectly() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; - var stream = new BitStream(); + var stream = default(BitStream); stream.PutBits(allBits, 64); @@ -150,7 +150,7 @@ public void PutBits_With40BitsOfOnes_ShouldMaskTo40Bits() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; const ulong expected40Bits = 0x000000FFFFFFFFFFUL; - var stream = new BitStream(); + var stream = default(BitStream); stream.PutBits(allBits, 40); @@ -164,7 +164,7 @@ public void PutBits_WithZeroBitsInterspersed_ShouldReturnValue() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; const ulong expected40Bits = 0x000000FFFFFFFFFFUL; - var stream = new BitStream(); + var stream = default(BitStream); stream.PutBits(0U, 0); stream.PutBits((uint)(allBits & 0xFFFFFFFFUL), 32); @@ -178,7 +178,7 @@ public void PutBits_WithZeroBitsInterspersed_ShouldReturnValue() [Fact] public void PutBits_ThenGetBits_ShouldReturnValue() { - var stream = new BitStream(); + var stream = default(BitStream); const uint value1 = 0b101; const uint value2 = 0b11001100; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs index 190eb8d6..e063a664 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Textures.Astc; @@ -8,7 +9,6 @@ using SixLabors.ImageSharp.Textures.Astc.IO; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -133,5 +133,4 @@ public void DecompressToImage_WithAstcFile_ShouldMatchExpected( using Image expectedImage = Image.Load(expectedImagePath); ImageComparer.TolerantPercentage(0.1f).VerifySimilarity(expectedImage, actualImage); } - } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs index 6540678a..3a54151c 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs @@ -2,10 +2,10 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -185,10 +185,10 @@ public void EncodeDecodeColors_WithRgbDirectAndRandomColors_ShouldPreserveColors { var low = new RgbaColor(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); var high = new RgbaColor(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); - var (Low, High) = EncodeAndDecodeColors(low, high, 255, mode); + var (low1, high1) = EncodeAndDecodeColors(low, high, 255, mode); - (Low == low).Should().BeTrue(); - (High == high).Should().BeTrue(); + (low1 == low).Should().BeTrue(); + (high1 == high).Should().BeTrue(); } } @@ -277,7 +277,8 @@ public void DecodeColorsForMode_WithRgbBaseOffset_AndIdenticalColors_ShouldDecod int b = random.Next(0, 256); // Ensure even channels (reference test skips odd) - if (((r | g | b) & 1) != 0) continue; + if (((r | g | b) & 1) != 0) + continue; var color = new RgbaColor(r, g, b, 255); var values = EncodeRgbBaseOffset(color, color); @@ -296,9 +297,11 @@ private static int[] EncodeRgbBaseOffset(RgbaColor low, RgbaColor high) bool isLarge = low[i] >= 128; values.Add((low[i] * 2) & 0xFF); int diff = (high[i] - low[i]) * 2; - if (isLarge) diff |= 0x80; + if (isLarge) + diff |= 0x80; values.Add(diff); } + return values.ToArray(); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs index 15489ae5..d57c4acb 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; +using SixLabors.ImageSharp.Textures.Astc.Core; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs index 6faf43d0..7644c261 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs index 005e8dad..96a1a29e 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; @@ -27,6 +27,7 @@ public void DecompressToFloat16_WithValidBlock_ShouldProduceCorrectOutputSize() { float.IsNaN(value).Should().BeFalse(); float.IsInfinity(value).Should().BeFalse(); + // Values should be in reasonable range for normalized colors value.Should().BeGreaterThanOrEqualTo(0.0f); value.Should().BeLessThanOrEqualTo(1.1f); // Allow slight overshoot for HDR diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs index a07ca27e..9d5e15ee 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs @@ -2,10 +2,10 @@ // Licensed under the Six Labors Split License. using System.ComponentModel; +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; @@ -81,6 +81,7 @@ public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() if (v > 1.0f) valuesGreaterThanOne++; } + valuesGreaterThanOne.Should().Be(64); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs index 69dc3e88..225eb8d9 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Textures.Astc.Core; using AwesomeAssertions; +using SixLabors.ImageSharp.Textures.Astc.Core; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc.HDR; @@ -112,5 +112,4 @@ public void Empty_ShouldReturnBlackTransparent() empty.B.Should().Be(0); empty.A.Should().Be(0); } - } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs index fc410fc3..98b908cd 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs @@ -2,9 +2,9 @@ // Licensed under the Six Labors Split License. using System.ComponentModel; +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; using SixLabors.ImageSharp.Textures.Astc.IO; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -56,7 +56,7 @@ public void GetPackingModeBitCount_ForValidRange_ShouldMatchExpectedValues() (BiseEncodingMode.BitEncoding, 5), // Range 28 (BiseEncodingMode.BitEncoding, 5), // Range 29 (BiseEncodingMode.BitEncoding, 5), // Range 30 - (BiseEncodingMode.BitEncoding, 5) // Range 31 + (BiseEncodingMode.BitEncoding, 5) // Range 31 ]; for (int i = 1; i < 32; ++i) @@ -154,9 +154,9 @@ public void GetBitCount_WithQuintEncoding7Values_ShouldReturnExpectedBitCount() { const int valueCount = 7; const int bits = 3; - int expectedBitCount = 7 * 2 + // First two quint blocks - 6 * 3 + // First two blocks of bits - 3 + // Last quint block without high order four bits + int expectedBitCount = 7 * 2 + // First two quint blocks + 6 * 3 + // First two blocks of bits + 3 + // Last quint block without high order four bits 3; // Last block with one set of three bits var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.QuintEncoding, valueCount, bits); @@ -175,7 +175,7 @@ public void EncodeDecode_WithQuintValues_ShouldEncodeAndDecodeExpectedValues() encoder.AddValue(value); // Encode - var bitSink = new BitStream(); + var bitSink = default(BitStream); encoder.Encode(ref bitSink); // Verify encoded data @@ -208,7 +208,7 @@ public void DecodeThenEncode_WithQuintValues_ShouldPreserveEncoding() decoded.Should().Equal(expectedValues); // Re-encode - var bitSink = new BitStream(); + var bitSink = default(BitStream); var encoder = new BoundedIntegerSequenceEncoder(range); foreach (var value in expectedValues) encoder.AddValue(value); @@ -231,7 +231,7 @@ public void EncodeDecode_WithTritValues_ShouldEncodeAndDecodeExpectedValues() encoder.AddValue(value); // Encode - var bitSink = new BitStream(); + var bitSink = default(BitStream); encoder.Encode(ref bitSink); // Verify encoded data @@ -264,7 +264,7 @@ public void DecodeThenEncode_WithTritValues_ShouldPreserveEncoding() decoded.Should().Equal(expectedValues); // Re-encode - var bitSink = new BitStream(); + var bitSink = default(BitStream); var encoder = new BoundedIntegerSequenceEncoder(range); foreach (var value in expectedValues) encoder.AddValue(value); @@ -276,8 +276,6 @@ public void DecodeThenEncode_WithTritValues_ShouldPreserveEncoding() reencoded.Should().Be(encoding); } - - [Fact] public void EncodeDecode_WithRandomValues_ShouldAlwaysRoundTripCorrectly() { @@ -290,7 +288,8 @@ public void EncodeDecode_WithRandomValues_ShouldAlwaysRoundTripCorrectly() int range = 1 + random.Next(0, 256) % 63; int bitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, range); - if (bitCount >= 64) continue; + if (bitCount >= 64) + continue; // Generate random values var generated = new List(valueCount); @@ -298,7 +297,7 @@ public void EncodeDecode_WithRandomValues_ShouldAlwaysRoundTripCorrectly() generated.Add(random.Next(range + 1)); // Encode - var bitSink = new BitStream(); + var bitSink = default(BitStream); var encoder = new BoundedIntegerSequenceEncoder(range); foreach (var value in generated) encoder.AddValue(value); @@ -316,5 +315,4 @@ public void EncodeDecode_WithRandomValues_ShouldAlwaysRoundTripCorrectly() decoded.Should().Equal(generated); } } - } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs index 550d225d..7b2576e8 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc; using SixLabors.ImageSharp.Textures.Astc.IO; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs index 964dce18..bb107c6a 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs index 0019433a..570a4ca1 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; @@ -348,6 +348,7 @@ public void UnpackLogicalBlock_WithStandardBlock_ShouldReturnLogicalBlock() } [Theory] + // Synthetic test images [InlineData("footprint_4x4", false, FootprintType.Footprint4x4, 32, 32)] [InlineData("footprint_5x4", false, FootprintType.Footprint5x4, 32, 32)] @@ -363,12 +364,14 @@ public void UnpackLogicalBlock_WithStandardBlock_ShouldReturnLogicalBlock() [InlineData("footprint_10x10", false, FootprintType.Footprint10x10, 32, 32)] [InlineData("footprint_12x10", false, FootprintType.Footprint12x10, 32, 32)] [InlineData("footprint_12x12", false, FootprintType.Footprint12x12, 32, 32)] + // RGB without alpha images [InlineData("rgb_4x4", false, FootprintType.Footprint4x4, 224, 288)] [InlineData("rgb_5x4", false, FootprintType.Footprint5x4, 224, 288)] [InlineData("rgb_6x6", false, FootprintType.Footprint6x6, 224, 288)] [InlineData("rgb_8x8", false, FootprintType.Footprint8x8, 224, 288)] [InlineData("rgb_12x12", false, FootprintType.Footprint12x12, 224, 288)] + // RGB with alpha images [InlineData("atlas_small_4x4", true, FootprintType.Footprint4x4, 256, 256)] [InlineData("atlas_small_5x5", true, FootprintType.Footprint5x5, 256, 256)] diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs index eb386166..6807a6d2 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs @@ -1,15 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; public class PartitionTests { - [Fact] public void PartitionMetric_WithSimplePartitions_ShouldCalculateCorrectDistance() { @@ -17,12 +16,12 @@ public void PartitionMetric_WithSimplePartitions_ShouldCalculateCorrectDistance( { Assignment = [ - 0,0,0,0,0,0, - 0,0,0,0,0,0, - 0,0,0,0,0,0, - 0,0,0,0,0,0, - 0,0,0,0,0,0, - 0,0,0,0,0,1 + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1 ] }; @@ -30,12 +29,12 @@ public void PartitionMetric_WithSimplePartitions_ShouldCalculateCorrectDistance( { Assignment = [ - 1,0,0,0,0,0, - 0,0,0,0,0,0, - 0,0,0,0,0,0, - 0,0,0,0,0,0, - 0,0,0,0,0,0, - 0,0,0,0,0,0 + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 ] }; @@ -51,10 +50,10 @@ public void PartitionMetric_WithDifferentPartCounts_ShouldCalculateCorrectDistan { Assignment = [ - 2,2,2,0, - 0,0,0,0, - 0,0,0,0, - 0,0,0,1 + 2, 2, 2, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 1 ] }; @@ -62,10 +61,10 @@ public void PartitionMetric_WithDifferentPartCounts_ShouldCalculateCorrectDistan { Assignment = [ - 1,0,0,0, - 0,0,0,0, - 0,0,0,0, - 0,0,0,0 + 1, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 ] }; @@ -81,10 +80,10 @@ public void PartitionMetric_WithDifferentMapping_ShouldCalculateCorrectDistance( { Assignment = [ - 0,1,2,2, - 2,2,2,2, - 2,2,2,2, - 2,2,2,2 + 0, 1, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2 ] }; @@ -92,10 +91,10 @@ public void PartitionMetric_WithDifferentMapping_ShouldCalculateCorrectDistance( { Assignment = [ - 1,0,0,0, - 0,0,0,0, - 0,0,0,0, - 0,0,0,0 + 1, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 ] }; @@ -109,12 +108,12 @@ public void GetASTCPartition_WithSpecificParameters_ShouldReturnExpectedAssignme { int[] expected = [ - 0,0,0,0,1,1,1,2,2,2, - 0,0,0,0,1,1,1,2,2,2, - 0,0,0,0,1,1,1,2,2,2, - 0,0,0,0,1,1,1,2,2,2, - 0,0,0,0,1,1,1,2,2,2, - 0,0,0,0,1,1,1,2,2,2 + 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, + 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, + 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, + 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, + 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, + 0, 0, 0, 0, 1, 1, 1, 2, 2, 2 ]; var partition = Partition.GetASTCPartition(Footprint.Get10x6(), 3, 557); @@ -131,8 +130,6 @@ public void GetASTCPartition_WithDifferentIds_ShouldProduceUniqueAssignments() partition0.Assignment.Should().NotEqual(partition1.Assignment); } - - [Fact] public void FindClosestASTCPartition_ShouldPreservePartitionCount() { @@ -140,12 +137,12 @@ public void FindClosestASTCPartition_ShouldPreservePartitionCount() { Assignment = [ - 0,0,1,1,1,0, - 0,0,0,0,0,0, - 0,0,0,0,0,0, - 0,1,1,1,1,1, - 0,0,0,0,0,0, - 1,1,1,1,1,1 + 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1 ] }; @@ -210,6 +207,7 @@ public void FindClosestASTCPartition_WithRandomPartitions_ShouldReturnFewerOrEqu { assignment[j] = random.Next(numParts); } + var partition = new Partition(footprint, numParts) { Assignment = assignment diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs index d2183f85..9798f287 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.TexelBlock; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs index a8d87ae3..14e4a602 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; using SixLabors.ImageSharp.Textures.Astc.BiseEncoding.Quantize; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs index d0047d2e..2d224434 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; -using AwesomeAssertions; namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/TestTextureProvider.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/TestTextureProvider.cs index 372164db..1bcdd600 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/TestTextureProvider.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/TestTextureProvider.cs @@ -1,9 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Globalization; using System.IO; using System.Text; -using System.Globalization; using SixLabors.ImageSharp.Textures.Formats; using SixLabors.ImageSharp.Textures.Tests.Enums; using SixLabors.ImageSharp.Textures.TextureFormats; diff --git a/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs b/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs index f98efec7..8d885ef9 100644 --- a/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/TextureFormats/Decoding/AstcDecoderTests.cs @@ -327,5 +327,4 @@ public void DecompressImage_WithExtraBlockData_ReturnExpectedSize() Assert.Equal(256 * 256 * 4, result.Length); } - } From 517ec1aaf7d8d4e1d987c4f77df1b67627da805a Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:06:59 +0100 Subject: [PATCH 36/37] Fix more style warnings --- src/ImageSharp.Textures.Astc/AstcDecoder.cs | 60 +- .../BoundedIntegerSequenceCodec.cs | 31 +- .../BoundedIntegerSequenceDecoder.cs | 4 +- .../BoundedIntegerSequenceEncoder.cs | 10 +- .../BiseEncoding/Quantize/Quantization.cs | 28 +- .../Quantize/QuintQuantizationMap.cs | 4 +- .../Quantize/TritQuantizationMap.cs | 4 +- .../BlockDecoder/FusedBlockDecoder.cs | 24 +- .../BlockDecoder/FusedHdrBlockDecoder.cs | 4 +- .../BlockDecoder/FusedLdrBlockDecoder.cs | 8 +- .../ColorEncoding/EndpointCodec.cs | 28 +- .../ColorEncoding/EndpointEncoder.cs | 147 +- .../ColorEncoding/HdrEndpointDecoder.cs | 57 +- .../ColorEncoding/Partition.cs | 28 +- .../ColorEncoding/RgbaColorExtensions.cs | 2 +- .../Core/BitOperations.cs | 2 +- .../Core/DecimationInfo.cs | 17 +- .../Core/DecimationTable.cs | 11 +- src/ImageSharp.Textures.Astc/Core/RgbColor.cs | 2 +- .../Core/RgbaColor.cs | 2 +- .../Core/SimdHelpers.cs | 20 +- src/ImageSharp.Textures.Astc/IO/AstcFile.cs | 4 +- src/ImageSharp.Textures.Astc/IO/BitStream.cs | 4 +- .../TexelBlock/BlockInfo.cs | 8 +- .../TexelBlock/IntermediateBlock.cs | 39 +- .../TexelBlock/IntermediateBlockPacker.cs | 51 +- .../TexelBlock/LogicalBlock.cs | 61 +- .../TexelBlock/PhysicalBlock.cs | 2 +- .../Exceptions/TextureFormatException.cs | 47 +- .../Exceptions/TextureProcessingException.cs | 45 +- .../UnknownTextureFormatException.cs | 25 +- .../Common/Extensions/StreamExtensions.cs | 148 +- .../Common/Extensions/ToStringExtension.cs | 25 +- .../Common/Helpers/FloatHelper.cs | 113 +- .../Common/Helpers/PixelUtils.cs | 65 +- src/ImageSharp.Textures/Configuration.cs | 243 +- .../Formats/Dds/DdsConfigurationModule.cs | 21 +- .../Formats/Dds/DdsConstants.cs | 47 +- .../Formats/Dds/DdsDecoder.cs | 33 +- .../Formats/Dds/DdsDecoderCore.cs | 307 ++- .../Formats/Dds/DdsFormat.cs | 43 +- .../Formats/Dds/DdsFourCC.cs | 83 +- .../Formats/Dds/DdsHeader.cs | 416 ++-- .../Formats/Dds/DdsHeaderDxt10.cs | 186 +- .../Formats/Dds/DdsImageFormatDetector.cs | 36 +- .../Formats/Dds/DdsPixelFormat.cs | 512 ++-- .../Formats/Dds/DdsProcessor.cs | 821 ++++--- .../Formats/Dds/DdsSurfaceType.cs | 109 +- .../Formats/Dds/DdsTools.cs | 629 +++-- .../Dds/Enums/D3d10ResourceDimension.cs | 49 +- .../Dds/Enums/D3d10ResourceMiscFlags.cs | 95 +- .../Formats/Dds/Enums/D3dFormat.cs | 165 +- .../Formats/Dds/Enums/DdsCaps1.cs | 61 +- .../Formats/Dds/Enums/DdsCaps2.cs | 111 +- .../Formats/Dds/Enums/DdsFlags.cs | 109 +- .../Formats/Dds/Enums/DdsPixelFormatFlags.cs | 105 +- .../Formats/Dds/Enums/DxgiFormat.cs | 1283 +++++----- .../Dds/Extensions/DdsHeaderExtensions.cs | 313 ++- .../Formats/Dds/IDdsDecoderOptions.cs | 13 +- .../Formats/ITextureDecoder.cs | 27 +- .../Formats/ITextureEncoder.cs | 25 +- .../Formats/ITextureFormat.cs | 89 +- .../Formats/ITextureFormatDetector.cs | 31 +- .../Formats/ITextureFormatManager.cs | 321 ++- .../Formats/ITextureInfoDetector.cs | 23 +- .../Ktx/Enums/GlBaseInternalPixelFormat.cs | 33 +- .../Ktx/Enums/GlInternalPixelFormat.cs | 161 +- .../Formats/Ktx/Enums/GlPixelFormat.cs | 113 +- .../Formats/Ktx/Enums/GlType.cs | 53 +- .../Formats/Ktx/Enums/KtxEndianness.cs | 23 +- .../Formats/Ktx/IKtxDecoderOptions.cs | 13 +- .../Formats/Ktx/KtxConfigurationModule.cs | 21 +- .../Formats/Ktx/KtxConstants.cs | 68 +- .../Formats/Ktx/KtxDecoder.cs | 33 +- .../Formats/Ktx/KtxDecoderCore.cs | 166 +- .../Formats/Ktx/KtxFormat.cs | 43 +- .../Formats/Ktx/KtxHeader.cs | 322 ++- .../Formats/Ktx/KtxImageFormatDetector.cs | 35 +- .../Formats/Ktx/KtxProcessor.cs | 529 +++-- .../Formats/Ktx2/Enums/VkFormat.cs | 2085 ++++++++--------- .../Formats/Ktx2/IKtx2DecoderOptions.cs | 13 +- .../Formats/Ktx2/Ktx2ConfigurationModule.cs | 21 +- .../Formats/Ktx2/Ktx2Constants.cs | 70 +- .../Formats/Ktx2/Ktx2Decoder.cs | 33 +- .../Formats/Ktx2/Ktx2DecoderCore.cs | 235 +- .../Formats/Ktx2/Ktx2Format.cs | 43 +- .../Formats/Ktx2/Ktx2Header.cs | 308 ++- .../Formats/Ktx2/Ktx2ImageFormatDetector.cs | 35 +- .../Formats/Ktx2/Ktx2Processor.cs | 867 ++++--- .../Formats/Ktx2/LevelIndex.cs | 15 +- .../Formats/TextureFormatManager.cs | 4 +- .../Formats/TextureTypeInfo.cs | 30 +- .../IConfigurationModule.cs | 19 +- src/ImageSharp.Textures/IO/IFileSystem.cs | 37 +- src/ImageSharp.Textures/IO/LocalFileSystem.cs | 23 +- src/ImageSharp.Textures/ITexture.cs | 15 +- src/ImageSharp.Textures/ITextureInfo.cs | 35 +- src/ImageSharp.Textures/MipMap.cs | 19 +- src/ImageSharp.Textures/MipMap{TBlock}.cs | 85 +- src/ImageSharp.Textures/PixelFormats/Ayuv.cs | 229 +- .../PixelFormats/ColorSpaceConversion.cs | 122 +- src/ImageSharp.Textures/PixelFormats/Fp32.cs | 288 ++- .../PixelFormats/Generated/Bgr32.cs | 322 ++- .../PixelFormats/Generated/Bgr555.cs | 322 ++- .../Generated/D32_FLOAT_S8X24_UINT.cs | 325 ++- .../PixelFormats/Generated/R11G11B10Float.cs | 328 ++- .../PixelFormats/Generated/Rg32Float.cs | 318 ++- .../PixelFormats/Generated/Rg64.cs | 300 ++- .../PixelFormats/Generated/Rg64Float.cs | 300 ++- .../PixelFormats/Generated/Rgb32.cs | 322 ++- .../PixelFormats/Generated/Rgb565.cs | 325 ++- .../PixelFormats/Generated/Rgb96.cs | 321 ++- .../PixelFormats/Generated/Rgb96Float.cs | 318 ++- .../PixelFormats/Generated/Rgba128.cs | 336 ++- .../PixelFormats/Generated/Rgba128Float.cs | 336 ++- .../PixelFormats/Generated/Rgba4444.cs | 326 ++- .../PixelFormats/Generated/Rgba5551.cs | 326 ++- .../PixelFormats/Generated/Rgba64Float.cs | 329 ++- src/ImageSharp.Textures/PixelFormats/L32.cs | 499 ++-- .../PixelFormats/R16Float.cs | 278 ++- src/ImageSharp.Textures/PixelFormats/Rg16.cs | 320 ++- src/ImageSharp.Textures/PixelFormats/Rg32.cs | 320 ++- src/ImageSharp.Textures/PixelFormats/Y410.cs | 224 +- src/ImageSharp.Textures/PixelFormats/Y416.cs | 218 +- src/ImageSharp.Textures/Texture.FromStream.cs | 2 +- src/ImageSharp.Textures/Texture.cs | 41 +- .../TextureFormats/CubemapTexture.cs | 123 +- .../TextureFormats/Decoding/A8.cs | 49 +- .../TextureFormats/Decoding/Ayuv.cs | 49 +- .../TextureFormats/Decoding/Bc4.cs | 147 +- .../TextureFormats/Decoding/Bc4s.cs | 157 +- .../TextureFormats/Decoding/Bc5.cs | 164 +- .../TextureFormats/Decoding/Bc5s.cs | 125 +- .../TextureFormats/Decoding/Bc6h.cs | 1030 ++++---- .../TextureFormats/Decoding/Bc6hEField.cs | 37 +- .../Decoding/Bc6hModeDescriptor.cs | 19 +- .../TextureFormats/Decoding/Bc6hModeInfo.cs | 31 +- .../TextureFormats/Decoding/Bc6hs.cs | 1044 ++++----- .../Decoding/Bc6hsModeDescriptor.cs | 19 +- .../TextureFormats/Decoding/Bc6hsModeInfo.cs | 31 +- .../TextureFormats/Decoding/Bc7.cs | 550 +++-- .../TextureFormats/Decoding/Bc7ModeInfo.cs | 47 +- .../TextureFormats/Decoding/Bgr24.cs | 49 +- .../TextureFormats/Decoding/Bgr32.cs | 49 +- .../TextureFormats/Decoding/Bgr555.cs | 49 +- .../TextureFormats/Decoding/Bgr565.cs | 49 +- .../TextureFormats/Decoding/Bgra16.cs | 49 +- .../TextureFormats/Decoding/Bgra32.cs | 49 +- .../TextureFormats/Decoding/Bgra4444.cs | 49 +- .../TextureFormats/Decoding/Bgra5551.cs | 49 +- .../TextureFormats/Decoding/Dxt1.cs | 151 +- .../TextureFormats/Decoding/Dxt3.cs | 151 +- .../TextureFormats/Decoding/Dxt5.cs | 135 +- .../TextureFormats/Decoding/Etc1.cs | 99 +- .../TextureFormats/Decoding/Etc2.cs | 99 +- .../TextureFormats/Decoding/EtcDecoder.cs | 674 +++--- .../TextureFormats/Decoding/Fp32.cs | 49 +- .../TextureFormats/Decoding/Grgb32.cs | 90 +- .../TextureFormats/Decoding/Helper.cs | 158 +- .../TextureFormats/Decoding/IBlock.cs | 93 +- .../TextureFormats/Decoding/IBlock{TSelf}.cs | 17 +- .../TextureFormats/Decoding/L16.cs | 49 +- .../TextureFormats/Decoding/L32.cs | 49 +- .../TextureFormats/Decoding/L8.cs | 49 +- .../TextureFormats/Decoding/La16.cs | 49 +- .../Decoding/PixelFormats/Constants.cs | 599 +++-- .../Decoding/PixelFormats/Helpers.cs | 109 +- .../Decoding/PixelFormats/IntColor.cs | 134 +- .../Decoding/PixelFormats/IntEndPntPair.cs | 19 +- .../Decoding/PixelFormats/LdrColorA.cs | 288 ++- .../TextureFormats/Decoding/R16Float.cs | 49 +- .../TextureFormats/Decoding/Rg16.cs | 49 +- .../TextureFormats/Decoding/Rg32.cs | 49 +- .../TextureFormats/Decoding/Rg32Float.cs | 49 +- .../TextureFormats/Decoding/Rg64.cs | 49 +- .../TextureFormats/Decoding/Rg64Float.cs | 49 +- .../TextureFormats/Decoding/Rgb111110Float.cs | 49 +- .../TextureFormats/Decoding/Rgb24.cs | 49 +- .../TextureFormats/Decoding/Rgb32.cs | 49 +- .../TextureFormats/Decoding/Rgb48.cs | 49 +- .../TextureFormats/Decoding/Rgb565.cs | 49 +- .../TextureFormats/Decoding/Rgb96.cs | 49 +- .../TextureFormats/Decoding/Rgb96Float.cs | 49 +- .../TextureFormats/Decoding/Rgba1010102.cs | 49 +- .../TextureFormats/Decoding/Rgba128.cs | 49 +- .../TextureFormats/Decoding/Rgba128Float.cs | 49 +- .../TextureFormats/Decoding/Rgba32.cs | 49 +- .../TextureFormats/Decoding/Rgba4444.cs | 49 +- .../TextureFormats/Decoding/Rgba5551.cs | 49 +- .../TextureFormats/Decoding/Rgba64.cs | 49 +- .../TextureFormats/Decoding/Rgba64Float.cs | 49 +- .../TextureFormats/Decoding/Rgbg32.cs | 90 +- .../TextureFormats/Decoding/Y210.cs | 106 +- .../TextureFormats/Decoding/Y216.cs | 104 +- .../TextureFormats/Decoding/Y410.cs | 49 +- .../TextureFormats/Decoding/Y416.cs | 49 +- .../TextureFormats/Decoding/Yuy2.cs | 104 +- .../TextureFormats/FlatTexture.cs | 62 +- .../TextureFormats/VolumeTexture.cs | 62 +- src/ImageSharp.Textures/TextureInfo.cs | 43 +- .../ReferenceDecoderHdrTests.cs | 87 +- .../ReferenceDecoderTests.cs | 96 +- .../Utils/ReferenceDecoder.cs | 68 +- .../AstcDecodingBenchmark.cs | 12 +- .../AstcImageDecodeBenchmark.cs | 4 +- .../ImageSharp.Textures.Benchmarks/Config.cs | 25 +- .../ImageSharp.Textures.Benchmarks/Program.cs | 9 +- .../ApplicationManager.cs | 53 +- .../Extensions.cs | 18 +- .../Program.cs | 126 +- .../ResourceLoader.cs | 86 +- .../UI/Button.cs | 40 +- .../UI/MenuBar.cs | 73 +- .../UI/TitleBar.cs | 31 +- .../UI/Widgets.cs | 42 +- .../UI/Wizard.cs | 176 +- .../UI/WizardPage.cs | 39 +- .../UI/WizardPages/Preview.cs | 384 ++- .../UI/WizardPages/Welcome.cs | 35 +- .../UIManager.cs | 153 +- .../Enums/TestTextureFormat.cs | 31 +- .../Enums/TestTextureTool.cs | 39 +- .../Enums/TestTextureType.cs | 31 +- .../Formats/Astc/BitOperationsTests.cs | 28 +- .../Formats/Astc/BitStreamTests.cs | 60 +- .../Formats/Astc/CodecTests.cs | 33 +- .../Formats/Astc/EndpointCodecTests.cs | 205 +- .../Formats/Astc/FootprintTests.cs | 20 +- .../Formats/Astc/HDR/HdrComparisonTests.cs | 94 +- .../Formats/Astc/HDR/HdrDecoderTests.cs | 38 +- .../Formats/Astc/HDR/HdrImageTests.cs | 52 +- .../Formats/Astc/HDR/RgbaHdrColorTests.cs | 34 +- .../Formats/Astc/IntegerSequenceCodecTests.cs | 140 +- .../Formats/Astc/IntegrationTests.cs | 8 +- .../Formats/Astc/IntermediateBlockTests.cs | 147 +- .../Formats/Astc/LogicalAstcBlockTests.cs | 124 +- .../Formats/Astc/PartitionTests.cs | 43 +- .../Formats/Astc/PhysicalAstcBlockTests.cs | 210 +- .../Formats/Astc/QuantizationTests.cs | 90 +- .../Formats/Astc/WeightInfillTests.cs | 8 +- .../Formats/Dds/DdsDecoderCubemapTests.cs | 38 +- .../Formats/Dds/DdsDecoderFlatTests.cs | 683 +++--- .../Formats/Dds/DdsDecoderTexConvFlatTests.cs | 1213 +++++----- .../Formats/Dds/DdsDecoderVolumeTests.cs | 34 +- .../Formats/Ktx/KtxDecoderTests.cs | 70 +- .../Formats/PixelFormat/PixelFormatTests.cs | 168 +- tests/ImageSharp.Textures.Tests/TestFile.cs | 244 +- .../Attributes/GroupOutputAttribute.cs | 19 +- .../Attributes/WithFileAttribute.cs | 69 +- .../ImageComparison/ExactImageComparer.cs | 74 +- .../ImageDimensionsMismatchException.cs | 21 +- .../Exceptions/ImagesSimilarityException.cs | 15 +- .../ImageComparison/ImageComparer.cs | 160 +- .../ImageComparison/ImageSimilarityReport.cs | 147 +- .../ImageComparison/PixelDifference.cs | 75 +- .../ImageComparison/TolerantImageComparer.cs | 184 +- .../ImageProviders/FileProvider.cs | 293 ++- .../ImageProviders/ITestImageProvider.cs | 15 +- .../ImageProviders/TestImageProvider.cs | 177 +- .../TestUtilities/ImagingTestCaseUtility.cs | 507 ++-- .../TestUtilities/PixelTypes.cs | 81 +- .../TestUtilities/TestEnvironment.cs | 468 ++-- .../TestUtilities/TestImageExtensions.cs | 407 ++-- .../TestUtilities/TestUtils.cs | 142 +- .../TextureProviders/ITestTextureProvider.cs | 39 +- .../TextureProviders/TestTextureProvider.cs | 177 +- tests/Images/TestEnvironment.cs | 122 +- tests/Images/TestTextures.cs | 9 +- 268 files changed, 18830 insertions(+), 19322 deletions(-) diff --git a/src/ImageSharp.Textures.Astc/AstcDecoder.cs b/src/ImageSharp.Textures.Astc/AstcDecoder.cs index 6c101ba0..c243265e 100644 --- a/src/ImageSharp.Textures.Astc/AstcDecoder.cs +++ b/src/ImageSharp.Textures.Astc/AstcDecoder.cs @@ -30,7 +30,7 @@ public static class AstcDecoder /// If decompression fails for any block public static Span DecompressImage(ReadOnlySpan astcData, int width, int height, Footprint footprint) { - var imageBuffer = new byte[width * height * BytesPerPixelUnorm8]; + byte[] imageBuffer = new byte[width * height * BytesPerPixelUnorm8]; return DecompressImage(astcData, width, height, footprint, imageBuffer) ? imageBuffer @@ -54,13 +54,13 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h return false; } - var decodedBlock = Array.Empty(); + byte[] decodedBlock = []; try { // Create a buffer once for fallback blocks; fast path writes directly to image decodedBlock = ArrayPool.Rent(footprint.Width * footprint.Height * BytesPerPixelUnorm8); - var decodedPixels = decodedBlock.AsSpan(); + Span decodedPixels = decodedBlock.AsSpan(); int blockIndex = 0; int footprintWidth = footprint.Width; int footprintHeight = footprint.Height; @@ -75,16 +75,16 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h continue; } - ulong low = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset)); - ulong high = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset + 8)); - var blockBits = new UInt128(high, low); + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(astcData[blockDataOffset..]); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(astcData[(blockDataOffset + 8)..]); + UInt128 blockBits = new(high, low); int dstBaseX = blockX * footprintWidth; int dstBaseY = blockY * footprintHeight; int copyWidth = Math.Min(footprintWidth, width - dstBaseX); int copyHeight = Math.Min(footprintHeight, height - dstBaseY); - var info = BlockInfo.Decode(blockBits); + BlockInfo info = BlockInfo.Decode(blockBits); if (!info.IsValid) { continue; @@ -114,7 +114,7 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h } else { - var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); + LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); if (logicalBlock is null) { continue; @@ -150,11 +150,11 @@ public static bool DecompressImage(ReadOnlySpan astcData, int width, int h /// The decoded block of pixels as RGBA values public static Span DecompressBlock(ReadOnlySpan blockData, Footprint footprint) { - var decodedPixels = Array.Empty(); + byte[] decodedPixels = []; try { decodedPixels = ArrayPool.Rent(footprint.Width * footprint.Height * BytesPerPixelUnorm8); - var decodedPixelBuffer = decodedPixels.AsSpan(); + Span decodedPixelBuffer = decodedPixels.AsSpan(); DecompressBlock(blockData, footprint, decodedPixelBuffer); } @@ -176,10 +176,10 @@ public static void DecompressBlock(ReadOnlySpan blockData, Footprint footp { // Read the 16 bytes that make up the ASTC block as a 128-bit value ulong low = BinaryPrimitives.ReadUInt64LittleEndian(blockData); - ulong high = BinaryPrimitives.ReadUInt64LittleEndian(blockData.Slice(8)); - var blockBits = new UInt128(high, low); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(blockData[8..]); + UInt128 blockBits = new(high, low); - var info = BlockInfo.Decode(blockBits); + BlockInfo info = BlockInfo.Decode(blockBits); if (!info.IsValid) { return; @@ -194,7 +194,7 @@ public static void DecompressBlock(ReadOnlySpan blockData, Footprint footp } // Fallback for void extent, multi-partition, dual plane, HDR - var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); + LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); if (logicalBlock is null) { return; @@ -216,7 +216,7 @@ public static void DecompressBlock(ReadOnlySpan blockData, Footprint footp public static Span DecompressHdrImage(ReadOnlySpan astcData, int width, int height, Footprint footprint) { const int channelsPerPixel = 4; - var imageBuffer = new float[width * height * channelsPerPixel]; + float[] imageBuffer = new float[width * height * channelsPerPixel]; if (!DecompressHdrImage(astcData, width, height, footprint, imageBuffer)) { return []; @@ -243,13 +243,13 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in } const int channelsPerPixel = 4; - var decodedBlock = Array.Empty(); + float[] decodedBlock = []; try { // Create a buffer once for fallback blocks; fast path writes directly to image decodedBlock = ArrayPool.Shared.Rent(footprint.Width * footprint.Height * channelsPerPixel); - var decodedPixels = decodedBlock.AsSpan(); + Span decodedPixels = decodedBlock.AsSpan(); int blockIndex = 0; int footprintWidth = footprint.Width; int footprintHeight = footprint.Height; @@ -264,16 +264,16 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in continue; } - ulong low = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset)); - ulong high = BinaryPrimitives.ReadUInt64LittleEndian(astcData.Slice(blockDataOffset + 8)); - var blockBits = new UInt128(high, low); + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(astcData[blockDataOffset..]); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(astcData[(blockDataOffset + 8)..]); + UInt128 blockBits = new(high, low); int dstBaseX = blockX * footprintWidth; int dstBaseY = blockY * footprintHeight; int copyWidth = Math.Min(footprintWidth, width - dstBaseX); int copyHeight = Math.Min(footprintHeight, height - dstBaseY); - var info = BlockInfo.Decode(blockBits); + BlockInfo info = BlockInfo.Decode(blockBits); if (!info.IsValid) { continue; @@ -302,7 +302,7 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in else { // Fallback: LogicalBlock path for void extent, multi-partition, dual plane - var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); + LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); if (logicalBlock is null) { continue; @@ -312,7 +312,7 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in { for (int column = 0; column < footprintWidth; ++column) { - var pixelOffset = (footprintWidth * row * channelsPerPixel) + (column * channelsPerPixel); + int pixelOffset = (footprintWidth * row * channelsPerPixel) + (column * channelsPerPixel); logicalBlock.WriteHdrPixel(column, row, decodedPixels.Slice(pixelOffset, channelsPerPixel)); } } @@ -349,7 +349,7 @@ public static bool DecompressHdrImage(ReadOnlySpan astcData, int width, in /// public static Span DecompressHdrImage(ReadOnlySpan astcData, int width, int height, FootprintType footprint) { - var footPrint = Footprint.FromFootprintType(footprint); + Footprint footPrint = Footprint.FromFootprintType(footprint); return DecompressHdrImage(astcData, width, height, footPrint); } @@ -363,10 +363,10 @@ public static void DecompressHdrBlock(ReadOnlySpan blockData, Footprint fo { // Read the 16 bytes that make up the ASTC block as a 128-bit value ulong low = BinaryPrimitives.ReadUInt64LittleEndian(blockData); - ulong high = BinaryPrimitives.ReadUInt64LittleEndian(blockData.Slice(8)); - var blockBits = new UInt128(high, low); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(blockData[8..]); + UInt128 blockBits = new(high, low); - var info = BlockInfo.Decode(blockBits); + BlockInfo info = BlockInfo.Decode(blockBits); if (!info.IsValid) { return; @@ -380,7 +380,7 @@ public static void DecompressHdrBlock(ReadOnlySpan blockData, Footprint fo } // Fallback for void extent, multi-partition, dual plane - var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); + LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, blockBits, in info); if (logicalBlock is null) { return; @@ -391,7 +391,7 @@ public static void DecompressHdrBlock(ReadOnlySpan blockData, Footprint fo { for (int column = 0; column < footprint.Width; ++column) { - var pixelOffset = (footprint.Width * row * channelsPerPixel) + (column * channelsPerPixel); + int pixelOffset = (footprint.Width * row * channelsPerPixel) + (column * channelsPerPixel); logicalBlock.WriteHdrPixel(column, row, buffer.Slice(pixelOffset, channelsPerPixel)); } } @@ -406,7 +406,7 @@ internal static Span DecompressImage(AstcFile file) internal static Span DecompressImage(ReadOnlySpan astcData, int width, int height, FootprintType footprint) { - var footPrint = Footprint.FromFootprintType(footprint); + Footprint footPrint = Footprint.FromFootprintType(footprint); return DecompressImage(astcData, width, height, footPrint); } diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs index 0c244c7a..8ee2c9f9 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceCodec.cs @@ -116,9 +116,6 @@ internal partial class BoundedIntegerSequenceCodec private static readonly (BiseEncodingMode Mode, int BitCount)[] PackingModeCache = InitPackingModeCache(); - private BiseEncodingMode encoding; - private int bitCount; - /// /// Initializes a new instance of the class. /// operate on sequences of integers and produce bit patterns that pack the @@ -143,14 +140,14 @@ internal partial class BoundedIntegerSequenceCodec /// Creates a decoder that decodes values within [0, ] (inclusive). protected BoundedIntegerSequenceCodec(int range) { - var (encodingMode, bitCount) = GetPackingModeBitCount(range); - this.encoding = encodingMode; - this.bitCount = bitCount; + (BiseEncodingMode encodingMode, int bitCount) = GetPackingModeBitCount(range); + this.Encoding = encodingMode; + this.BitCount = bitCount; } - protected BiseEncodingMode Encoding => this.encoding; + protected BiseEncodingMode Encoding { get; } - protected int BitCount => this.bitCount; + protected int BitCount { get; } /// /// The number of bits needed to encode the given number of values with respect to the @@ -169,14 +166,14 @@ public static (BiseEncodingMode Mode, int BitCount) GetPackingModeBitCount(int r /// public static int GetBitCount(BiseEncodingMode encodingMode, int valuesCount, int bitCount) { - var encodingBitCount = encodingMode switch + int encodingBitCount = encodingMode switch { BiseEncodingMode.TritEncoding => ((valuesCount * 8) + 4) / 5, BiseEncodingMode.QuintEncoding => ((valuesCount * 7) + 2) / 3, BiseEncodingMode.BitEncoding => 0, _ => throw new ArgumentOutOfRangeException(nameof(encodingMode), "Invalid encoding mode"), }; - var baseBitCount = valuesCount * bitCount; + int baseBitCount = valuesCount * bitCount; return encodingBitCount + baseBitCount; } @@ -186,7 +183,7 @@ public static int GetBitCount(BiseEncodingMode encodingMode, int valuesCount, in /// public static int GetBitCountForRange(int valuesCount, int range) { - var (mode, bitCount) = GetPackingModeBitCount(range); + (BiseEncodingMode mode, int bitCount) = GetPackingModeBitCount(range); return GetBitCount(mode, valuesCount, bitCount); } @@ -196,7 +193,7 @@ public static int GetBitCountForRange(int valuesCount, int range) /// protected int GetEncodedBlockSize() { - var (blockSize, extraBlockSize) = this.encoding switch + (int blockSize, int extraBlockSize) = this.Encoding switch { BiseEncodingMode.TritEncoding => (5, 8), BiseEncodingMode.QuintEncoding => (3, 7), @@ -204,12 +201,12 @@ protected int GetEncodedBlockSize() _ => (0, 0), }; - return extraBlockSize + (blockSize * this.bitCount); + return extraBlockSize + (blockSize * this.BitCount); } private static int[] FlattenEncodings(int[][] jagged, int stride) { - var flat = new int[jagged.Length * stride]; + int[] flat = new int[jagged.Length * stride]; for (int i = 0; i < jagged.Length; i++) { for (int j = 0; j < stride; j++) @@ -223,7 +220,7 @@ private static int[] FlattenEncodings(int[][] jagged, int stride) private static (BiseEncodingMode, int)[] InitPackingModeCache() { - var cache = new (BiseEncodingMode, int)[1 << Log2MaxRangeForBits]; + (BiseEncodingMode, int)[] cache = new (BiseEncodingMode, int)[1 << Log2MaxRangeForBits]; // Precompute for all valid ranges [1, 255] for (int range = 1; range < cache.Length; range++) @@ -239,13 +236,13 @@ private static (BiseEncodingMode, int)[] InitPackingModeCache() } int maxValue = index < 0 - ? MaxRanges[MaxRanges.Length - 1] + 1 + ? MaxRanges[^1] + 1 : MaxRanges[index] + 1; // Check QuintEncoding (5), TritEncoding (3), BitEncoding (1) in descending order BiseEncodingMode encodingMode = BiseEncodingMode.Unknown; ReadOnlySpan modes = [BiseEncodingMode.QuintEncoding, BiseEncodingMode.TritEncoding, BiseEncodingMode.BitEncoding]; - foreach (var em in modes) + foreach (BiseEncodingMode em in modes) { if (maxValue % (int)em == 0 && int.IsPow2(maxValue / (int)em)) { diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs index 6c0464b7..bf9ec0f9 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceDecoder.cs @@ -16,7 +16,7 @@ public BoundedIntegerSequenceDecoder(int range) public static BoundedIntegerSequenceDecoder GetCached(int range) { - var decoder = Cache[range]; + BoundedIntegerSequenceDecoder? decoder = Cache[range]; if (decoder is null) { decoder = new BoundedIntegerSequenceDecoder(range); @@ -89,7 +89,7 @@ public void Decode(int valuesCount, ref BitStream bitSource, Span result) /// Thrown when there are not enough bits to decode. public int[] Decode(int valuesCount, ref BitStream bitSource) { - var result = new int[valuesCount]; + int[] result = new int[valuesCount]; this.Decode(valuesCount, ref bitSource, result); return result; } diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs index 36c011be..341e2c29 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/BoundedIntegerSequenceEncoder.cs @@ -33,7 +33,7 @@ public void Encode(ref BitStream bitSink) switch (this.Encoding) { case BiseEncodingMode.TritEncoding: - var trits = new List(); + List trits = []; for (int i = 0; i < 5; ++i) { if (index < this.values.Count) @@ -49,10 +49,10 @@ public void Encode(ref BitStream bitSink) EncodeISEBlock(trits, this.BitCount, ref bitSink, ref bitsWrittenCount, totalBitCount); break; case BiseEncodingMode.QuintEncoding: - var quints = new List(); + List quints = []; for (int i = 0; i < 3; ++i) { - var value = index < this.values.Count + int value = index < this.values.Count ? this.values[index++] : 0; quints.Add(value); @@ -82,8 +82,8 @@ private static void EncodeISEBlock(List values, int bitsPerValue, ref Bi ? InterleavedQuintBits : InterleavedTritBits; - var nonBitComponents = new int[valueCount]; - var bitComponents = new int[valueCount]; + int[] nonBitComponents = new int[valueCount]; + int[] bitComponents = new int[valueCount]; for (int i = 0; i < valueCount; ++i) { bitComponents[i] = values[i] & ((1 << bitsPerValue) - 1); diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs index f1153800..d97c2bea 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/Quantization.cs @@ -32,7 +32,7 @@ public static int QuantizeCEValueToRange(int value, int rangeMaxValue) ArgumentOutOfRangeException.ThrowIfLessThan(value, 0); ArgumentOutOfRangeException.ThrowIfGreaterThan(value, byte.MaxValue); - var map = GetQuantMapForValueRange(rangeMaxValue); + QuantizationMap? map = GetQuantMapForValueRange(rangeMaxValue); return map != null ? map.Quantize(value) : 0; } @@ -43,7 +43,7 @@ public static int UnquantizeCEValueFromRange(int value, int rangeMaxValue) ArgumentOutOfRangeException.ThrowIfLessThan(value, 0); ArgumentOutOfRangeException.ThrowIfGreaterThan(value, rangeMaxValue); - var map = GetQuantMapForValueRange(rangeMaxValue); + QuantizationMap? map = GetQuantMapForValueRange(rangeMaxValue); return map != null ? map.Unquantize(value) : 0; } @@ -59,7 +59,7 @@ public static int QuantizeWeightToRange(int weight, int rangeMaxValue) weight -= 1; } - var map = GetQuantMapForWeightRange(rangeMaxValue); + QuantizationMap? map = GetQuantMapForWeightRange(rangeMaxValue); return map != null ? map.Quantize(weight) : 0; } @@ -70,7 +70,7 @@ public static int UnquantizeWeightFromRange(int weight, int rangeMaxValue) ArgumentOutOfRangeException.ThrowIfLessThan(weight, 0); ArgumentOutOfRangeException.ThrowIfGreaterThan(weight, rangeMaxValue); - var map = GetQuantMapForWeightRange(rangeMaxValue); + QuantizationMap? map = GetQuantMapForWeightRange(rangeMaxValue); int dequantized = map != null ? map.Unquantize(weight) : 0; if (dequantized > 32) { @@ -118,7 +118,7 @@ internal static void UnquantizeCEValuesBatch(Span values, int count, int ra private static SortedDictionary InitEndpointMaps() { - var d = new SortedDictionary + SortedDictionary d = new() { { 5, new TritQuantizationMap(5, TritQuantizationMap.GetUnquantizedValue) }, { 7, new BitQuantizationMap(7, 8) }, @@ -143,7 +143,7 @@ private static SortedDictionary InitEndpointMaps() private static SortedDictionary InitWeightMaps() { - var d = new SortedDictionary + SortedDictionary d = new() { { 1, new BitQuantizationMap(1, 6) }, { 2, new TritQuantizationMap(2, TritQuantizationMap.GetUnquantizedWeight) }, @@ -163,11 +163,11 @@ private static SortedDictionary InitWeightMaps() private static QuantizationMap?[] BuildFlatLookup(SortedDictionary maps, int size) { - var flat = new QuantizationMap?[size]; + QuantizationMap?[] flat = new QuantizationMap?[size]; QuantizationMap? current = null; for (int i = 0; i < size; i++) { - if (maps.TryGetValue(i, out var map)) + if (maps.TryGetValue(i, out QuantizationMap? map)) { current = map; } @@ -206,12 +206,12 @@ private static SortedDictionary InitWeightMaps() private static int[]?[] InitializeUnquantizeWeightsFlat() { - var tables = new int[]?[WeightRangeMaxValue + 1]; + int[]?[] tables = new int[]?[WeightRangeMaxValue + 1]; foreach (KeyValuePair kvp in WeightMaps) { int range = kvp.Key; - var map = kvp.Value; - var table = new int[range + 1]; + QuantizationMap map = kvp.Value; + int[] table = new int[range + 1]; for (int i = 0; i <= range; i++) { int dequantized = map.Unquantize(i); @@ -226,12 +226,12 @@ private static SortedDictionary InitWeightMaps() private static int[]?[] InitializeUnquantizeEndpointsFlat() { - var tables = new int[]?[256]; + int[]?[] tables = new int[]?[256]; foreach (KeyValuePair kvp in EndpointMaps) { int range = kvp.Key; - var map = kvp.Value; - var table = new int[range + 1]; + QuantizationMap map = kvp.Value; + int[] table = new int[range + 1]; for (int i = 0; i <= range; i++) { table[i] = map.Unquantize(i); diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs index a3afec10..3c77263f 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/QuintQuantizationMap.cs @@ -27,7 +27,7 @@ public QuintQuantizationMap(int range, Func unquantFunc) internal static int GetUnquantizedValue(int quint, int bits, int range) { int a = (bits & 1) != 0 ? 0x1FF : 0; - var (b, c) = range switch + (int b, int c) = range switch { 9 => (0, 113), 19 => ((bits >> 1) & 0x1) is var x ? ((x << 2) | (x << 3) | (x << 8), 54) : default, @@ -51,7 +51,7 @@ internal static int GetUnquantizedWeight(int quint, int bits, int range) } int a = (bits & 1) != 0 ? 0x7F : 0; - var (b, c) = range switch + (int b, int c) = range switch { 9 => (0, 28), 19 => ((bits >> 1) & 0x1) is var x ? ((x << 1) | (x << 6), 13) : default, diff --git a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs index c9999390..9daa76d7 100644 --- a/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs +++ b/src/ImageSharp.Textures.Astc/BiseEncoding/Quantize/TritQuantizationMap.cs @@ -27,7 +27,7 @@ public TritQuantizationMap(int range, Func unquantFunc) internal static int GetUnquantizedValue(int trit, int bits, int range) { int a = (bits & 1) != 0 ? 0x1FF : 0; - var (b, c) = range switch + (int b, int c) = range switch { 5 => (0, 204), 11 => ((bits >> 1) & 0x1) is var x ? ((x << 1) | (x << 2) | (x << 4) | (x << 8), 93) : default, @@ -56,7 +56,7 @@ internal static int GetUnquantizedWeight(int trit, int bits, int range) } int a = (bits & 1) != 0 ? 0x7F : 0; - var (b, c) = range switch + (int b, int c) = range switch { 5 => (0, 50), 11 => ((bits >> 1) & 1) is var x diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs index 927f797a..8ca24c51 100644 --- a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedBlockDecoder.cs @@ -32,7 +32,7 @@ internal static ColorEndpointPair DecodeFusedCore( // 2. Batch unquantize color values, then decode endpoint pair Quantization.UnquantizeCEValuesBatch(colors, colorCount, info.ColorValuesRange); - var endpointPair = EndpointCodec.DecodeColorsForModePolymorphicUnquantized(colors, info.EndpointMode0); + ColorEndpointPair endpointPair = EndpointCodec.DecodeColorsForModePolymorphicUnquantized(colors, info.EndpointMode0); // 3. BISE decode weights int gridSize = info.GridWidth * info.GridHeight; @@ -49,7 +49,7 @@ internal static ColorEndpointPair DecodeFusedCore( } else { - var decimationInfo = DecimationTable.Get(footprint, info.GridWidth, info.GridHeight); + DecimationInfo decimationInfo = DecimationTable.Get(footprint, info.GridWidth, info.GridHeight); DecimationTable.InfillWeights(gridWeights, decimationInfo, texelWeights); } @@ -64,7 +64,7 @@ internal static ColorEndpointPair DecodeFusedCore( [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void DecodeBiseValues(UInt128 bits, int startBit, int bitCount, int range, int valuesCount, Span result) { - var (encMode, bitsPerValue) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(range); + (BiseEncodingMode encMode, int bitsPerValue) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(range); if (encMode == BiseEncodingMode.BitEncoding) { @@ -85,7 +85,7 @@ internal static void DecodeBiseValues(UInt128 bits, int startBit, int bitCount, else { // Spans both halves — use UInt128 shift then extract from low - var shifted = (bits >> startBit) & UInt128Extensions.OnesMask(totalBits); + UInt128 shifted = (bits >> startBit) & UInt128Extensions.OnesMask(totalBits); ulong lowBits = shifted.Low(); ulong highBits = shifted.High(); int bitPos = 0; @@ -114,10 +114,10 @@ internal static void DecodeBiseValues(UInt128 bits, int startBit, int bitCount, } // Trit/quint encoding: fall back to full BISE decoder - var colorBitMask = UInt128Extensions.OnesMask(bitCount); - var colorBits = (bits >> startBit) & colorBitMask; - var colorBitStream = new BitStream(colorBits, 128); - var decoder = BoundedIntegerSequenceDecoder.GetCached(range); + UInt128 colorBitMask = UInt128Extensions.OnesMask(bitCount); + UInt128 colorBits = (bits >> startBit) & colorBitMask; + BitStream colorBitStream = new(colorBits, 128); + BoundedIntegerSequenceDecoder decoder = BoundedIntegerSequenceDecoder.GetCached(range); decoder.Decode(valuesCount, ref colorBitStream, result); } @@ -128,8 +128,8 @@ internal static void DecodeBiseValues(UInt128 bits, int startBit, int bitCount, [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void DecodeBiseWeights(UInt128 bits, int weightBitCount, int weightRange, int count, Span result) { - var (encMode, bitsPerValue) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(weightRange); - var weightBits = UInt128Extensions.ReverseBits(bits) & UInt128Extensions.OnesMask(weightBitCount); + (BiseEncodingMode encMode, int bitsPerValue) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(weightRange); + UInt128 weightBits = UInt128Extensions.ReverseBits(bits) & UInt128Extensions.OnesMask(weightBitCount); if (encMode == BiseEncodingMode.BitEncoding) { @@ -176,8 +176,8 @@ internal static void DecodeBiseWeights(UInt128 bits, int weightBitCount, int wei } // Trit/quint encoding: fall back to full BISE decoder - var weightBitStream = new BitStream(weightBits, 128); - var decoder = BoundedIntegerSequenceDecoder.GetCached(weightRange); + BitStream weightBitStream = new(weightBits, 128); + BoundedIntegerSequenceDecoder decoder = BoundedIntegerSequenceDecoder.GetCached(weightRange); decoder.Decode(count, ref weightBitStream, result); } } diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs index 3164dbc4..d88b2eee 100644 --- a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedHdrBlockDecoder.cs @@ -22,7 +22,7 @@ internal static class FusedHdrBlockDecoder internal static void DecompressBlockFusedHdr(UInt128 bits, in BlockInfo info, Footprint footprint, Span buffer) { Span texelWeights = stackalloc int[footprint.PixelCount]; - var endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); + ColorEndpointPair endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); WriteHdrOutputPixels(buffer, footprint.PixelCount, in endpointPair, texelWeights); } @@ -41,7 +41,7 @@ internal static void DecompressBlockFusedHdrToImage( Span imageBuffer) { Span texelWeights = stackalloc int[footprint.PixelCount]; - var endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); + ColorEndpointPair endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); WriteHdrOutputPixelsToImage(imageBuffer, footprint, dstBaseX, dstBaseY, imageWidth, in endpointPair, texelWeights); } diff --git a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs index 1381db69..5391c4b3 100644 --- a/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs +++ b/src/ImageSharp.Textures.Astc/BlockDecoder/FusedLdrBlockDecoder.cs @@ -25,7 +25,7 @@ internal static class FusedLdrBlockDecoder internal static void DecompressBlockFusedLdr(UInt128 bits, in BlockInfo info, Footprint footprint, Span buffer) { Span texelWeights = stackalloc int[footprint.PixelCount]; - var endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); + ColorEndpointPair endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); WriteLdrPixels(buffer, footprint.PixelCount, in endpointPair, texelWeights); } @@ -44,7 +44,7 @@ internal static void DecompressBlockFusedLdrToImage( Span imageBuffer) { Span texelWeights = stackalloc int[footprint.PixelCount]; - var endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); + ColorEndpointPair endpointPair = FusedBlockDecoder.DecodeFusedCore(bits, in info, footprint, texelWeights); WriteLdrPixelsToImage(imageBuffer, footprint, dstBaseX, dstBaseY, imageWidth, in endpointPair, texelWeights); } @@ -63,7 +63,7 @@ private static void WriteLdrPixels(Span buffer, int pixelCount, in ColorEn int limit = pixelCount - 3; for (; i < limit; i += 4) { - var weights = Vector128.Create( + Vector128 weights = Vector128.Create( texelWeights[i], texelWeights[i + 1], texelWeights[i + 2], @@ -132,7 +132,7 @@ private static void WriteLdrPixelsToImage( for (; pixelX < limit; pixelX += 4) { int texelIndex = srcRowBase + pixelX; - var weights = Vector128.Create( + Vector128 weights = Vector128.Create( texelWeights[texelIndex], texelWeights[texelIndex + 1], texelWeights[texelIndex + 2], diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs index 215b3ef2..3263ac79 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/EndpointCodec.cs @@ -20,13 +20,13 @@ public static ColorEndpointPair DecodeColorsForModePolymorphic(ReadOnlySpan { if (mode.IsHdr()) { - var (low, high) = HdrEndpointDecoder.DecodeHdrMode(values, maxValue, mode); + (RgbaHdrColor low, RgbaHdrColor high) = HdrEndpointDecoder.DecodeHdrMode(values, maxValue, mode); bool alphaIsLdr = mode == ColorEndpointMode.HdrRgbDirectLdrAlpha; return ColorEndpointPair.Hdr(low, high, alphaIsLdr); } else { - var (low, high) = DecodeColorsForMode(values, maxValue, mode); + (RgbaColor low, RgbaColor high) = DecodeColorsForMode(values, maxValue, mode); return ColorEndpointPair.Ldr(low, high); } } @@ -42,7 +42,7 @@ public static (RgbaColor EndpointLowRgba, RgbaColor EndpointHighRgba) DecodeColo } UnquantizeInline(unquantizedValues, maxValue); - var pair = DecodeColorsForModeUnquantized(unquantizedValues, mode); + ColorEndpointPair pair = DecodeColorsForModeUnquantized(unquantizedValues, mode); return (pair.LdrLow, pair.LdrHigh); } @@ -55,7 +55,7 @@ internal static ColorEndpointPair DecodeColorsForModePolymorphicUnquantized(Read { if (mode.IsHdr()) { - var (low, high) = HdrEndpointDecoder.DecodeHdrModeUnquantized(unquantizedValues, mode); + (RgbaHdrColor low, RgbaHdrColor high) = HdrEndpointDecoder.DecodeHdrModeUnquantized(unquantizedValues, mode); bool alphaIsLdr = mode == ColorEndpointMode.HdrRgbDirectLdrAlpha; return ColorEndpointPair.Hdr(low, high, alphaIsLdr); } @@ -93,8 +93,8 @@ internal static ColorEndpointPair DecodeColorsForModeUnquantized(ReadOnlySpan s1; @@ -35,16 +35,16 @@ public static bool UsesBlueContract(int maxValue, ColorEndpointMode mode, List avg2) { needsWeightSwap = true; - var temp = avg1; - avg1 = avg2; - avg2 = temp; + (avg2, avg1) = (avg1, avg2); } int offset = Math.Min(avg2 - avg1, 0x3F); @@ -187,11 +183,11 @@ private static bool EncodeColorsLuma(RgbaColor endpointLow, RgbaColor endpointHi values[0] = quantOffLow; values[1] = quantOffHigh; - var (decLowOff, decHighOff) = EndpointCodec.DecodeColorsForMode(values.ToArray(), maxValue, ColorEndpointMode.LdrLumaBaseOffset); + (RgbaColor decLowOff, RgbaColor decHighOff) = EndpointCodec.DecodeColorsForMode(values.ToArray(), maxValue, ColorEndpointMode.LdrLumaBaseOffset); values[0] = quantLow; values[1] = quantHigh; - var (decLowDir, decHighDir) = EndpointCodec.DecodeColorsForMode(values.ToArray(), maxValue, ColorEndpointMode.LdrLumaDirect); + (RgbaColor decLowDir, RgbaColor decHighDir) = EndpointCodec.DecodeColorsForMode(values.ToArray(), maxValue, ColorEndpointMode.LdrLumaDirect); int calculateErrorOff = 0; int calculateErrorDir = 0; @@ -227,11 +223,11 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi astcMode = ColorEndpointMode.LdrRgbDirect; int numChannels = withAlpha ? 4 : 3; - var invertedBlueContractLow = endpointLowRgba.WithInvertedBlueContract(); - var invertedBlueContractHigh = endpointHighRgba.WithInvertedBlueContract(); + RgbaColor invertedBlueContractLow = endpointLowRgba.WithInvertedBlueContract(); + RgbaColor invertedBlueContractHigh = endpointHighRgba.WithInvertedBlueContract(); - var directBase = new int[4]; - var directOffset = new int[4]; + int[] directBase = new int[4]; + int[] directOffset = new int[4]; for (int i = 0; i < 4; ++i) { directBase[i] = endpointLowRgba[i]; @@ -239,8 +235,8 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi (directOffset[i], directBase[i]) = BitOperations.TransferPrecisionInverse(directOffset[i], directBase[i]); } - var invertedBlueContractBase = new int[4]; - var invertedBlueContractOffset = new int[4]; + int[] invertedBlueContractBase = new int[4]; + int[] invertedBlueContractOffset = new int[4]; for (int i = 0; i < 4; ++i) { invertedBlueContractBase[i] = invertedBlueContractHigh[i]; @@ -248,8 +244,8 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi (invertedBlueContractOffset[i], invertedBlueContractBase[i]) = BitOperations.TransferPrecisionInverse(invertedBlueContractOffset[i], invertedBlueContractBase[i]); } - var directBaseSwapped = new int[4]; - var directOffsetSwapped = new int[4]; + int[] directBaseSwapped = new int[4]; + int[] directOffsetSwapped = new int[4]; for (int i = 0; i < 4; ++i) { directBaseSwapped[i] = endpointHighRgba[i]; @@ -257,8 +253,8 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi (directOffsetSwapped[i], directBaseSwapped[i]) = BitOperations.TransferPrecisionInverse(directOffsetSwapped[i], directBaseSwapped[i]); } - var invertedBlueContractBaseSwapped = new int[4]; - var invertedBlueContractOffsetSwapped = new int[4]; + int[] invertedBlueContractBaseSwapped = new int[4]; + int[] invertedBlueContractOffsetSwapped = new int[4]; for (int i = 0; i < 4; ++i) { invertedBlueContractBaseSwapped[i] = invertedBlueContractLow[i]; @@ -266,24 +262,24 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi (invertedBlueContractOffsetSwapped[i], invertedBlueContractBaseSwapped[i]) = BitOperations.TransferPrecisionInverse(invertedBlueContractOffsetSwapped[i], invertedBlueContractBaseSwapped[i]); } - var directQuantized = new QuantizedEndpointPair(endpointLowRgba, endpointHighRgba, maxValue); - var bcQuantized = new QuantizedEndpointPair(invertedBlueContractLow, invertedBlueContractHigh, maxValue); + QuantizedEndpointPair directQuantized = new(endpointLowRgba, endpointHighRgba, maxValue); + QuantizedEndpointPair bcQuantized = new(invertedBlueContractLow, invertedBlueContractHigh, maxValue); - var offsetQuantized = new QuantizedEndpointPair(new RgbaColor(directBase[0], directBase[1], directBase[2], directBase[3]), new RgbaColor(directOffset[0], directOffset[1], directOffset[2], directOffset[3]), maxValue); - var bcOffsetQuantized = new QuantizedEndpointPair(new RgbaColor(invertedBlueContractBase[0], invertedBlueContractBase[1], invertedBlueContractBase[2], invertedBlueContractBase[3]), new RgbaColor(invertedBlueContractOffset[0], invertedBlueContractOffset[1], invertedBlueContractOffset[2], invertedBlueContractOffset[3]), maxValue); + QuantizedEndpointPair offsetQuantized = new(new RgbaColor(directBase[0], directBase[1], directBase[2], directBase[3]), new RgbaColor(directOffset[0], directOffset[1], directOffset[2], directOffset[3]), maxValue); + QuantizedEndpointPair bcOffsetQuantized = new(new RgbaColor(invertedBlueContractBase[0], invertedBlueContractBase[1], invertedBlueContractBase[2], invertedBlueContractBase[3]), new RgbaColor(invertedBlueContractOffset[0], invertedBlueContractOffset[1], invertedBlueContractOffset[2], invertedBlueContractOffset[3]), maxValue); - var offsetSwappedQuantized = new QuantizedEndpointPair(new RgbaColor(directBaseSwapped[0], directBaseSwapped[1], directBaseSwapped[2], directBaseSwapped[3]), new RgbaColor(directOffsetSwapped[0], directOffsetSwapped[1], directOffsetSwapped[2], directOffsetSwapped[3]), maxValue); - var bcOffsetSwappedQuantized = new QuantizedEndpointPair(new RgbaColor(invertedBlueContractBaseSwapped[0], invertedBlueContractBaseSwapped[1], invertedBlueContractBaseSwapped[2], invertedBlueContractBaseSwapped[3]), new RgbaColor(invertedBlueContractOffsetSwapped[0], invertedBlueContractOffsetSwapped[1], invertedBlueContractOffsetSwapped[2], invertedBlueContractOffsetSwapped[3]), maxValue); + QuantizedEndpointPair offsetSwappedQuantized = new(new RgbaColor(directBaseSwapped[0], directBaseSwapped[1], directBaseSwapped[2], directBaseSwapped[3]), new RgbaColor(directOffsetSwapped[0], directOffsetSwapped[1], directOffsetSwapped[2], directOffsetSwapped[3]), maxValue); + QuantizedEndpointPair bcOffsetSwappedQuantized = new(new RgbaColor(invertedBlueContractBaseSwapped[0], invertedBlueContractBaseSwapped[1], invertedBlueContractBaseSwapped[2], invertedBlueContractBaseSwapped[3]), new RgbaColor(invertedBlueContractOffsetSwapped[0], invertedBlueContractOffsetSwapped[1], invertedBlueContractOffsetSwapped[2], invertedBlueContractOffsetSwapped[3]), maxValue); - var errors = new List(6); + List errors = new(6); // 3.1 regular unquantized error { - var rgbaLow = directQuantized.UnquantizedLow(); - var rgbaHigh = directQuantized.UnquantizedHigh(); - var lowColor = new RgbaColor(rgbaLow[0], rgbaLow[1], rgbaLow[2], rgbaLow[3]); - var highColor = new RgbaColor(rgbaHigh[0], rgbaHigh[1], rgbaHigh[2], rgbaHigh[3]); - var squaredRgbError = withAlpha + int[] rgbaLow = directQuantized.UnquantizedLow(); + int[] rgbaHigh = directQuantized.UnquantizedHigh(); + RgbaColor lowColor = new(rgbaLow[0], rgbaLow[1], rgbaLow[2], rgbaLow[3]); + RgbaColor highColor = new(rgbaHigh[0], rgbaHigh[1], rgbaHigh[2], rgbaHigh[3]); + int squaredRgbError = withAlpha ? RgbaColor.SquaredError(lowColor, endpointLowRgba) + RgbaColor.SquaredError(highColor, endpointHighRgba) : RgbColor.SquaredError(lowColor, endpointLowRgba) + RgbColor.SquaredError(highColor, endpointHighRgba); errors.Add(new CEEncodingOption(squaredRgbError, directQuantized, false, false, false)); @@ -291,13 +287,13 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi // 3.2 blue-contract { - var blueContractUnquantizedLow = bcQuantized.UnquantizedLow(); - var blueContractUnquantizedHigh = bcQuantized.UnquantizedHigh(); - var blueContractLow = RgbaColorExtensions.WithBlueContract(blueContractUnquantizedLow[0], blueContractUnquantizedLow[1], blueContractUnquantizedLow[2], blueContractUnquantizedLow[3]); - var blueContractHigh = RgbaColorExtensions.WithBlueContract(blueContractUnquantizedHigh[0], blueContractUnquantizedHigh[1], blueContractUnquantizedHigh[2], blueContractUnquantizedHigh[3]); + int[] blueContractUnquantizedLow = bcQuantized.UnquantizedLow(); + int[] blueContractUnquantizedHigh = bcQuantized.UnquantizedHigh(); + RgbaColor blueContractLow = RgbaColorExtensions.WithBlueContract(blueContractUnquantizedLow[0], blueContractUnquantizedLow[1], blueContractUnquantizedLow[2], blueContractUnquantizedLow[3]); + RgbaColor blueContractHigh = RgbaColorExtensions.WithBlueContract(blueContractUnquantizedHigh[0], blueContractUnquantizedHigh[1], blueContractUnquantizedHigh[2], blueContractUnquantizedHigh[3]); // TODO: How to handle alpha for this entire functions?? - var blueContractSquaredError = withAlpha + int blueContractSquaredError = withAlpha ? RgbaColor.SquaredError(blueContractLow, endpointLowRgba) + RgbaColor.SquaredError(blueContractHigh, endpointHighRgba) : RgbColor.SquaredError(blueContractLow, endpointLowRgba) + RgbColor.SquaredError(blueContractHigh, endpointHighRgba); @@ -305,13 +301,13 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi } // 3.3 base/offset - Action computeBaseOffsetError = (pair, swapped) => + void ComputeBaseOffsetError(QuantizedEndpointPair pair, bool swapped) { - var baseArr = pair.UnquantizedLow(); - var offsetArr = pair.UnquantizedHigh(); + int[] baseArr = pair.UnquantizedLow(); + int[] offsetArr = pair.UnquantizedHigh(); - var baseColor = new RgbaColor(baseArr[0], baseArr[1], baseArr[2], baseArr[3]); - var offsetColor = new RgbaColor(offsetArr[0], offsetArr[1], offsetArr[2], offsetArr[3]).AsOffsetFrom(baseColor); + RgbaColor baseColor = new(baseArr[0], baseArr[1], baseArr[2], baseArr[3]); + RgbaColor offsetColor = new RgbaColor(offsetArr[0], offsetArr[1], offsetArr[2], offsetArr[3]).AsOffsetFrom(baseColor); int baseOffsetError = 0; if (swapped) @@ -328,17 +324,17 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi } errors.Add(new CEEncodingOption(baseOffsetError, pair, swapped, false, true)); - }; + } - computeBaseOffsetError(offsetQuantized, false); + ComputeBaseOffsetError(offsetQuantized, false); - Action computeBaseOffsetBlueContractError = (pair, swapped) => + void ComputeBaseOffsetBlueContractError(QuantizedEndpointPair pair, bool swapped) { - var baseArr = pair.UnquantizedLow(); - var offsetArr = pair.UnquantizedHigh(); + int[] baseArr = pair.UnquantizedLow(); + int[] offsetArr = pair.UnquantizedHigh(); - var baseColor = new RgbaColor(baseArr[0], baseArr[1], baseArr[2], baseArr[3]); - var offsetColor = new RgbaColor(offsetArr[0], offsetArr[1], offsetArr[2], offsetArr[3]).AsOffsetFrom(baseColor); + RgbaColor baseColor = new(baseArr[0], baseArr[1], baseArr[2], baseArr[3]); + RgbaColor offsetColor = new RgbaColor(offsetArr[0], offsetArr[1], offsetArr[2], offsetArr[3]).AsOffsetFrom(baseColor); baseColor = baseColor.WithBlueContract(); offsetColor = offsetColor.WithBlueContract(); @@ -358,19 +354,18 @@ private static bool EncodeColorsRGBA(RgbaColor endpointLowRgba, RgbaColor endpoi } errors.Add(new CEEncodingOption(squaredBlueContractError, pair, swapped, true, true)); - }; + } - computeBaseOffsetBlueContractError(bcOffsetQuantized, false); - computeBaseOffsetError(offsetSwappedQuantized, true); - computeBaseOffsetBlueContractError(bcOffsetSwappedQuantized, true); + ComputeBaseOffsetBlueContractError(bcOffsetQuantized, false); + ComputeBaseOffsetError(offsetSwappedQuantized, true); + ComputeBaseOffsetBlueContractError(bcOffsetSwappedQuantized, true); errors.Sort((a, b) => a.Error().CompareTo(b.Error())); - foreach (var measurement in errors) + foreach (CEEncodingOption measurement in errors) { bool needsWeightSwap = false; - ColorEndpointMode modeUnused; - if (measurement.Pack(withAlpha, out modeUnused, values, ref needsWeightSwap)) + if (measurement.Pack(withAlpha, out ColorEndpointMode modeUnused, values, ref needsWeightSwap)) { return needsWeightSwap; } @@ -436,11 +431,11 @@ public CEEncodingOption( public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List values, ref bool needsWeightSwap) { endpointMode = ColorEndpointMode.LdrLumaDirect; - var unquantizedLowOriginal = this.quantizedEndpoints.UnquantizedLow(); - var unquantizedHighOriginal = this.quantizedEndpoints.UnquantizedHigh(); + int[] unquantizedLowOriginal = this.quantizedEndpoints.UnquantizedLow(); + int[] unquantizedHighOriginal = this.quantizedEndpoints.UnquantizedHigh(); - var unquantizedLow = (int[])unquantizedLowOriginal.Clone(); - var unquantizedHigh = (int[])unquantizedHighOriginal.Clone(); + int[] unquantizedLow = (int[])unquantizedLowOriginal.Clone(); + int[] unquantizedHigh = (int[])unquantizedHighOriginal.Clone(); if (this.useOffsetMode) { @@ -492,11 +487,11 @@ public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List va } } - var quantizedLowOriginal = this.quantizedEndpoints.QuantizedLow(); - var quantizedHighOriginal = this.quantizedEndpoints.QuantizedHigh(); + int[] quantizedLowOriginal = this.quantizedEndpoints.QuantizedLow(); + int[] quantizedHighOriginal = this.quantizedEndpoints.QuantizedHigh(); - var quantizedLow = (int[])quantizedLowOriginal.Clone(); - var quantizedHigh = (int[])quantizedHighOriginal.Clone(); + int[] quantizedLow = (int[])quantizedLowOriginal.Clone(); + int[] quantizedHigh = (int[])quantizedHighOriginal.Clone(); if (swapVals) { @@ -505,9 +500,7 @@ public bool Pack(bool hasAlpha, out ColorEndpointMode endpointMode, List va throw new InvalidOperationException(); } - var tmp = quantizedLow; - quantizedLow = quantizedHigh; - quantizedHigh = tmp; + (quantizedHigh, quantizedLow) = (quantizedLow, quantizedHigh); needsWeightSwap = !needsWeightSwap; } diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs index 538883cd..68040c57 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/HdrEndpointDecoder.cs @@ -33,19 +33,16 @@ public static (RgbaHdrColor Low, RgbaHdrColor High) DecodeHdrMode(ReadOnlySpan - public static (RgbaHdrColor Low, RgbaHdrColor High) DecodeHdrModeUnquantized(ReadOnlySpan value, ColorEndpointMode mode) + public static (RgbaHdrColor Low, RgbaHdrColor High) DecodeHdrModeUnquantized(ReadOnlySpan value, ColorEndpointMode mode) => mode switch { - return mode switch - { - ColorEndpointMode.HdrLumaLargeRange => UnpackHdrLuminanceLargeRangeCore(value[0], value[1]), - ColorEndpointMode.HdrLumaSmallRange => UnpackHdrLuminanceSmallRangeCore(value[0], value[1]), - ColorEndpointMode.HdrRgbBaseScale => UnpackHdrRgbBaseScaleCore(value[0], value[1], value[2], value[3]), - ColorEndpointMode.HdrRgbDirect => UnpackHdrRgbDirectCore(value[0], value[1], value[2], value[3], value[4], value[5]), - ColorEndpointMode.HdrRgbDirectLdrAlpha => UnpackHdrRgbDirectLdrAlphaCore(value), - ColorEndpointMode.HdrRgbDirectHdrAlpha => UnpackHdrRgbDirectHdrAlphaCore(value), - _ => throw new InvalidOperationException($"Mode {mode} is not an HDR mode") - }; - } + ColorEndpointMode.HdrLumaLargeRange => UnpackHdrLuminanceLargeRangeCore(value[0], value[1]), + ColorEndpointMode.HdrLumaSmallRange => UnpackHdrLuminanceSmallRangeCore(value[0], value[1]), + ColorEndpointMode.HdrRgbBaseScale => UnpackHdrRgbBaseScaleCore(value[0], value[1], value[2], value[3]), + ColorEndpointMode.HdrRgbDirect => UnpackHdrRgbDirectCore(value[0], value[1], value[2], value[3], value[4], value[5]), + ColorEndpointMode.HdrRgbDirectLdrAlpha => UnpackHdrRgbDirectLdrAlphaCore(value), + ColorEndpointMode.HdrRgbDirectHdrAlpha => UnpackHdrRgbDirectHdrAlphaCore(value), + _ => throw new InvalidOperationException($"Mode {mode} is not an HDR mode") + }; /// /// Performs an unsigned left shift of a signed value, avoiding undefined behavior @@ -67,8 +64,8 @@ private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrLuminanceLargeRang y1 = (v0 << 4) - 8; } - var low = new RgbaHdrColor((ushort)(y0 << 4), (ushort)(y0 << 4), (ushort)(y0 << 4), 0x7800); - var high = new RgbaHdrColor((ushort)(y1 << 4), (ushort)(y1 << 4), (ushort)(y1 << 4), 0x7800); + RgbaHdrColor low = new((ushort)(y0 << 4), (ushort)(y0 << 4), (ushort)(y0 << 4), 0x7800); + RgbaHdrColor high = new((ushort)(y1 << 4), (ushort)(y1 << 4), (ushort)(y1 << 4), 0x7800); return (low, high); } @@ -92,8 +89,8 @@ private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrLuminanceSmallRang y1 = 0xFFF; } - var low = new RgbaHdrColor((ushort)(y0 << 4), (ushort)(y0 << 4), (ushort)(y0 << 4), 0x7800); - var high = new RgbaHdrColor((ushort)(y1 << 4), (ushort)(y1 << 4), (ushort)(y1 << 4), 0x7800); + RgbaHdrColor low = new((ushort)(y0 << 4), (ushort)(y0 << 4), (ushort)(y0 << 4), 0x7800); + RgbaHdrColor high = new((ushort)(y1 << 4), (ushort)(y1 << 4), (ushort)(y1 << 4), 0x7800); return (low, high); } @@ -247,8 +244,8 @@ _ when (modeValue & 0xC) != 0xC => (modeValue >> 2, modeValue & 3), green0 = Math.Max(green0, 0); blue0 = Math.Max(blue0, 0); - var low = new RgbaHdrColor((ushort)(red0 << 4), (ushort)(green0 << 4), (ushort)(blue0 << 4), 0x7800); - var high = new RgbaHdrColor((ushort)(red << 4), (ushort)(green << 4), (ushort)(blue << 4), 0x7800); + RgbaHdrColor low = new((ushort)(red0 << 4), (ushort)(green0 << 4), (ushort)(blue0 << 4), 0x7800); + RgbaHdrColor high = new((ushort)(red << 4), (ushort)(green << 4), (ushort)(blue << 4), 0x7800); return (low, high); } @@ -260,12 +257,12 @@ private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrRgbDirectCore(int // Special case: majorComponent == 3 (direct passthrough) if (majorComponent == 3) { - var low = new RgbaHdrColor( + RgbaHdrColor low = new( (ushort)(v0 << 8), (ushort)(v2 << 8), (ushort)((v4 & 0x7F) << 9), 0x7800); - var high = new RgbaHdrColor( + RgbaHdrColor high = new( (ushort)(v1 << 8), (ushort)(v3 << 8), (ushort)((v5 & 0x7F) << 9), @@ -404,31 +401,31 @@ private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrRgbDirectCore(int _ => (red0, green0, blue0, red1, green1, blue1) }; - var lowResult = new RgbaHdrColor((ushort)(red0 << 4), (ushort)(green0 << 4), (ushort)(blue0 << 4), 0x7800); - var highResult = new RgbaHdrColor((ushort)(red1 << 4), (ushort)(green1 << 4), (ushort)(blue1 << 4), 0x7800); + RgbaHdrColor lowResult = new((ushort)(red0 << 4), (ushort)(green0 << 4), (ushort)(blue0 << 4), 0x7800); + RgbaHdrColor highResult = new((ushort)(red1 << 4), (ushort)(green1 << 4), (ushort)(blue1 << 4), 0x7800); return (lowResult, highResult); } private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrRgbDirectLdrAlphaCore(ReadOnlySpan unquantizedValues) { - var (rgbLow, rgbHigh) = UnpackHdrRgbDirectCore(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[3], unquantizedValues[4], unquantizedValues[5]); + (RgbaHdrColor rgbLow, RgbaHdrColor rgbHigh) = UnpackHdrRgbDirectCore(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[3], unquantizedValues[4], unquantizedValues[5]); ushort alpha0 = (ushort)(unquantizedValues[6] * 257); ushort alpha1 = (ushort)(unquantizedValues[7] * 257); - var low = new RgbaHdrColor(rgbLow.R, rgbLow.G, rgbLow.B, alpha0); - var high = new RgbaHdrColor(rgbHigh.R, rgbHigh.G, rgbHigh.B, alpha1); + RgbaHdrColor low = new(rgbLow.R, rgbLow.G, rgbLow.B, alpha0); + RgbaHdrColor high = new(rgbHigh.R, rgbHigh.G, rgbHigh.B, alpha1); return (low, high); } private static (RgbaHdrColor Low, RgbaHdrColor High) UnpackHdrRgbDirectHdrAlphaCore(ReadOnlySpan unquantizedValues) { - var (rgbLow, rgbHigh) = UnpackHdrRgbDirectCore(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[3], unquantizedValues[4], unquantizedValues[5]); + (RgbaHdrColor rgbLow, RgbaHdrColor rgbHigh) = UnpackHdrRgbDirectCore(unquantizedValues[0], unquantizedValues[1], unquantizedValues[2], unquantizedValues[3], unquantizedValues[4], unquantizedValues[5]); - var (alpha0, alpha1) = UnpackHdrAlpha(unquantizedValues[6], unquantizedValues[7]); + (ushort alpha0, ushort alpha1) = UnpackHdrAlpha(unquantizedValues[6], unquantizedValues[7]); - var low = new RgbaHdrColor(rgbLow.R, rgbLow.G, rgbLow.B, alpha0); - var high = new RgbaHdrColor(rgbHigh.R, rgbHigh.G, rgbHigh.B, alpha1); + RgbaHdrColor low = new(rgbLow.R, rgbLow.G, rgbLow.B, alpha0); + RgbaHdrColor high = new(rgbHigh.R, rgbHigh.G, rgbHigh.B, alpha1); return (low, high); } @@ -452,7 +449,7 @@ private static (ushort Low, ushort High) UnpackHdrAlpha(int v6, int v7) else { // Complex mode: base + sign-extended offset - v6 |= v7 << (selector + 1) & 0x780; + v6 |= (v7 << (selector + 1)) & 0x780; v7 &= 0x3F >> selector; v7 ^= 32 >> selector; v7 -= 32 >> selector; diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs index d92d907a..1ea3df1d 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/Partition.cs @@ -41,11 +41,11 @@ public static int PartitionMetric(Partition a, Partition b) { ArgumentOutOfRangeException.ThrowIfNotEqual(a.Footprint, b.Footprint); - const int MaxNumSubsets = 4; + const int maxNumSubsets = 4; int width = a.Footprint.Width; int height = a.Footprint.Height; - var pairCounts = new List<(int A, int B, int Count)>(); + List<(int A, int B, int Count)> pairCounts = []; for (int y = 0; y < 4; ++y) { for (int x = 0; x < 4; ++x) @@ -65,15 +65,15 @@ public static int PartitionMetric(Partition a, Partition b) } } - var sorted = pairCounts.OrderByDescending(p => p.Count).ToList(); - var assigned = new bool[MaxNumSubsets, MaxNumSubsets]; + List<(int A, int B, int Count)> sorted = [.. pairCounts.OrderByDescending(p => p.Count)]; + bool[,] assigned = new bool[maxNumSubsets, maxNumSubsets]; int pixelsMatched = 0; - foreach (var pairCount in sorted) + foreach ((int pairA, int pairB, int count) in sorted) { bool isAssigned = false; - for (int i = 0; i < MaxNumSubsets; ++i) + for (int i = 0; i < maxNumSubsets; ++i) { - if (assigned[pairCount.A, i] || assigned[i, pairCount.B]) + if (assigned[pairA, i] || assigned[i, pairB]) { isAssigned = true; break; @@ -82,8 +82,8 @@ public static int PartitionMetric(Partition a, Partition b) if (!isAssigned) { - assigned[pairCount.A, pairCount.B] = true; - pixelsMatched += pairCount.Count; + assigned[pairA, pairB] = true; + pixelsMatched += count; } } @@ -93,16 +93,16 @@ public static int PartitionMetric(Partition a, Partition b) // Basic GetASTCPartition implementation using selection function from C++ public static Partition GetASTCPartition(Footprint footprint, int partitionCount, int partitionId) { - var key = (footprint, partitionCount, partitionId); - if (PartitionCache.TryGetValue(key, out var cached)) + (Footprint Footprint, int PartitionCount, int PartitionId) key = (footprint, partitionCount, partitionId); + if (PartitionCache.TryGetValue(key, out Partition? cached)) { return cached; } - var part = new Partition(footprint, partitionCount, partitionId); + Partition part = new(footprint, partitionCount, partitionId); int w = footprint.Width; int h = footprint.Height; - var assignment = new int[w * h]; + int[] assignment = new int[w * h]; int idx = 0; for (int y = 0; y < h; ++y) { @@ -123,7 +123,7 @@ public static Partition GetASTCPartition(Footprint footprint, int partitionCount public static Partition FindClosestASTCPartition(Partition candidate) { // Search a few partitions and pick the one with minimal PartitionMetric - var best = GetASTCPartition(candidate.Footprint, Math.Max(1, candidate.PartitionCount), 0); + Partition best = GetASTCPartition(candidate.Footprint, Math.Max(1, candidate.PartitionCount), 0); int bestDist = PartitionMetric(best, candidate); return best; } diff --git a/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs b/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs index 08cd9e81..c8b7740e 100644 --- a/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs +++ b/src/ImageSharp.Textures.Astc/ColorEncoding/RgbaColorExtensions.cs @@ -45,7 +45,7 @@ public static RgbaColor AsOffsetFrom(this RgbaColor color, RgbaColor baseColor) for (int i = 0; i < RgbaColor.BytesPerPixel; ++i) { - var (a, b) = BitOperations.TransferPrecision(offset[i], baseColor[i]); + (int a, int b) = BitOperations.TransferPrecision(offset[i], baseColor[i]); offset[i] = Math.Clamp(baseColor[i] + a, byte.MinValue, byte.MaxValue); } diff --git a/src/ImageSharp.Textures.Astc/Core/BitOperations.cs b/src/ImageSharp.Textures.Astc/Core/BitOperations.cs index f59628cc..4f6f8fbc 100644 --- a/src/ImageSharp.Textures.Astc/Core/BitOperations.cs +++ b/src/ImageSharp.Textures.Astc/Core/BitOperations.cs @@ -15,7 +15,7 @@ public static UInt128 GetBits(UInt128 value, int start, int length) return UInt128.Zero; } - var shifted = value >> start; + UInt128 shifted = value >> start; if (length >= 128) { return shifted; diff --git a/src/ImageSharp.Textures.Astc/Core/DecimationInfo.cs b/src/ImageSharp.Textures.Astc/Core/DecimationInfo.cs index 4449c939..d513b353 100644 --- a/src/ImageSharp.Textures.Astc/Core/DecimationInfo.cs +++ b/src/ImageSharp.Textures.Astc/Core/DecimationInfo.cs @@ -9,24 +9,19 @@ namespace SixLabors.ImageSharp.Textures.Astc.Core; /// internal sealed class DecimationInfo { - private readonly int texelCount; - // Transposed layout: [contribution * TexelCount + texel] // 4 contributions per texel (bilinear interpolation from weight grid). // For edge texels where some grid points are out of bounds, factor is 0 and index is 0. - private readonly int[] weightIndices; // size: 4 * TexelCount - private readonly int[] weightFactors; // size: 4 * TexelCount - public DecimationInfo(int texelCount, int[] weightIndices, int[] weightFactors) { - this.texelCount = texelCount; - this.weightIndices = weightIndices; - this.weightFactors = weightFactors; + this.TexelCount = texelCount; + this.WeightIndices = weightIndices; + this.WeightFactors = weightFactors; } - public int TexelCount => this.texelCount; + public int TexelCount { get; } - public int[] WeightIndices => this.weightIndices; + public int[] WeightIndices { get; } - public int[] WeightFactors => this.weightFactors; + public int[] WeightFactors { get; } } diff --git a/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs b/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs index 55f37183..5be931ff 100644 --- a/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs +++ b/src/ImageSharp.Textures.Astc/Core/DecimationTable.cs @@ -23,7 +23,7 @@ internal static class DecimationTable public static DecimationInfo Get(Footprint footprint, int gridX, int gridY) { int index = ((int)footprint.Type * GridRange * GridRange) + ((gridX - GridMin) * GridRange) + (gridY - GridMin); - var decimationInfo = Table[index]; + DecimationInfo? decimationInfo = Table[index]; if (decimationInfo is null) { decimationInfo = Compute(footprint.Width, footprint.Height, gridX, gridY); @@ -56,17 +56,14 @@ public static void InfillWeights(ReadOnlySpan gridWeights, DecimationInfo d } } - private static int GetScaleFactorD(int blockDimensions) - { - return (int)((1024f + (blockDimensions >> 1)) / (blockDimensions - 1)); - } + private static int GetScaleFactorD(int blockDimensions) => (int)((1024f + (blockDimensions >> 1)) / (blockDimensions - 1)); private static DecimationInfo Compute(int footprintWidth, int footprintHeight, int gridWidth, int gridHeight) { int texelCount = footprintWidth * footprintHeight; - var indices = new int[4 * texelCount]; - var factors = new int[4 * texelCount]; + int[] indices = new int[4 * texelCount]; + int[] factors = new int[4 * texelCount]; int scaleHorizontal = GetScaleFactorD(footprintWidth); int scaleVertical = GetScaleFactorD(footprintHeight); diff --git a/src/ImageSharp.Textures.Astc/Core/RgbColor.cs b/src/ImageSharp.Textures.Astc/Core/RgbColor.cs index 5756fd26..19d5e4c4 100644 --- a/src/ImageSharp.Textures.Astc/Core/RgbColor.cs +++ b/src/ImageSharp.Textures.Astc/Core/RgbColor.cs @@ -24,7 +24,7 @@ public byte Average { get { - var sum = this.R + this.G + this.B; + int sum = this.R + this.G + this.B; return (byte)(((sum * 256) + 384) / 768); } } diff --git a/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs b/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs index b6125911..69824fdf 100644 --- a/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs +++ b/src/ImageSharp.Textures.Astc/Core/RgbaColor.cs @@ -25,7 +25,7 @@ public byte Average { get { - var sum = this.R + this.G + this.B; + int sum = this.R + this.G + this.B; return (byte)(((sum * 256) + 384) / 768); } } diff --git a/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs b/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs index 32a49ce0..bc6f7448 100644 --- a/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs +++ b/src/ImageSharp.Textures.Astc/Core/SimdHelpers.cs @@ -22,17 +22,17 @@ internal static class SimdHelpers public static Vector128 Interpolate4ChannelPixels(int p0, int p1, Vector128 weights) { // Bit-replicate endpoint bytes to 16-bit - var c0 = Vector128.Create((p0 << 8) | p0); - var c1 = Vector128.Create((p1 << 8) | p1); + Vector128 c0 = Vector128.Create((p0 << 8) | p0); + Vector128 c1 = Vector128.Create((p1 << 8) | p1); // c = (c0 * (64 - w) + c1 * w + 32) >> 6 // NOTE: Using >> 6 instead of / 64 because Vector128 division // has no hardware support and decomposes to scalar operations. - var w64 = Vec64 - weights; - var c = ((c0 * w64) + (c1 * weights) + Vec32) >> 6; + Vector128 w64 = Vec64 - weights; + Vector128 c = ((c0 * w64) + (c1 * weights) + Vec32) >> 6; // Quantize: (c * 255 + 32767) >> 16, clamped to [0, 255] - var result = ((c * Vec255) + Vec32767) >>> 16; + Vector128 result = ((c * Vec255) + Vec32767) >>> 16; return Vector128.Min(Vector128.Max(result, Vector128.Zero), Vec255); } @@ -54,16 +54,16 @@ public static void Write4PixelLdr( int highA, Vector128 weights) { - var r = Interpolate4ChannelPixels(lowR, highR, weights); - var g = Interpolate4ChannelPixels(lowG, highG, weights); - var b = Interpolate4ChannelPixels(lowB, highB, weights); - var a = Interpolate4ChannelPixels(lowA, highA, weights); + Vector128 r = Interpolate4ChannelPixels(lowR, highR, weights); + Vector128 g = Interpolate4ChannelPixels(lowG, highG, weights); + Vector128 b = Interpolate4ChannelPixels(lowB, highB, weights); + Vector128 a = Interpolate4ChannelPixels(lowA, highA, weights); // Pack 4 RGBA pixels into 16 bytes via vector OR+shift. // Each int element has its channel value in bits [0:7]. // Combine: element[i] = R[i] | (G[i] << 8) | (B[i] << 16) | (A[i] << 24) // On little-endian, storing this int32 writes bytes [R, G, B, A]. - var rgba = r | (g << 8) | (b << 16) | (a << 24); + Vector128 rgba = r | (g << 8) | (b << 16) | (a << 24); rgba.AsByte().CopyTo(output.Slice(offset, 16)); } diff --git a/src/ImageSharp.Textures.Astc/IO/AstcFile.cs b/src/ImageSharp.Textures.Astc/IO/AstcFile.cs index 7cb7cd82..84a46481 100644 --- a/src/ImageSharp.Textures.Astc/IO/AstcFile.cs +++ b/src/ImageSharp.Textures.Astc/IO/AstcFile.cs @@ -36,10 +36,10 @@ internal AstcFile(AstcFileHeader header, byte[] blocks) public static AstcFile FromMemory(byte[] data) { - var header = AstcFileHeader.FromMemory(data.AsSpan(0, AstcFileHeader.SizeInBytes)); + AstcFileHeader header = AstcFileHeader.FromMemory(data.AsSpan(0, AstcFileHeader.SizeInBytes)); // Remaining bytes are blocks; C++ reference keeps them as string; here we keep as byte[] - var blocks = new byte[data.Length - AstcFileHeader.SizeInBytes]; + byte[] blocks = new byte[data.Length - AstcFileHeader.SizeInBytes]; Array.Copy(data, AstcFileHeader.SizeInBytes, blocks, 0, blocks.Length); return new AstcFile(header, blocks); diff --git a/src/ImageSharp.Textures.Astc/IO/BitStream.cs b/src/ImageSharp.Textures.Astc/IO/BitStream.cs index 900eda3a..e111b1eb 100644 --- a/src/ImageSharp.Textures.Astc/IO/BitStream.cs +++ b/src/ImageSharp.Textures.Astc/IO/BitStream.cs @@ -29,7 +29,7 @@ public BitStream(UInt128 data, uint dataSize) this.dataSize = dataSize; } - public uint Bits => this.dataSize; + public readonly uint Bits => this.dataSize; public void PutBits(T x, int size) where T : unmanaged @@ -170,7 +170,7 @@ private void ShiftBuffer(int count) if (count < 64) { this.low = (this.low >> count) | (this.high << (64 - count)); - this.high = this.high >> count; + this.high >>= count; } else if (count == 64) { diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs b/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs index a76cdf24..61e847f5 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/BlockInfo.cs @@ -191,7 +191,7 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), // ---- Step 7: Weight bit count ---- int weightBitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(numWeights, weightRange); - if (weightBitCount < 24 || weightBitCount > 96) + if (weightBitCount is < 24 or > 96) { return default; } @@ -214,7 +214,7 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), if (sharedCemMarker == 0) { // Shared CEM: all partitions use the same mode - var sharedCem = (ColorEndpointMode)((lowBits >> 25) & 0xF); + ColorEndpointMode sharedCem = (ColorEndpointMode)((lowBits >> 25) & 0xF); cem0 = cem1 = cem2 = cem3 = sharedCem; for (int i = 0; i < partitionCount; i++) { @@ -227,7 +227,7 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), numExtraCEMBits = ExtraCemBitsForPartition[partitionCount - 1]; int extraCemStartPos = 128 - numExtraCEMBits - weightBitCount; - var extraCem = BitOperations.GetBits(bits, extraCemStartPos, numExtraCEMBits); + UInt128 extraCem = BitOperations.GetBits(bits, extraCemStartPos, numExtraCEMBits); ulong cemval = (lowBits >> 23) & 0x3F; // 6 bits starting at bit 23 int baseCem = (int)(((cemval & 0x3) - 1) * 4); @@ -249,7 +249,7 @@ 3 when ((lowBits >> 8) & 1) != 0 => ((int)((lowBits >> 7) & 0x1) + 2, a + 2), { int m = (int)(cembits & 0x3); cembits >>= 2; - var mode = (ColorEndpointMode)(baseCem + (4 * c[i]) + m); + ColorEndpointMode mode = (ColorEndpointMode)(baseCem + (4 * c[i]) + m); switch (i) { case 0: diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs index c1e423b5..086aa7ed 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlock.cs @@ -24,7 +24,7 @@ internal static class IntermediateBlock return null; } - var info = BlockInfo.Decode(physicalBlock.BlockBits); + BlockInfo info = BlockInfo.Decode(physicalBlock.BlockBits); if (!info.IsValid || info.IsVoidExtent) { return null; @@ -43,14 +43,14 @@ internal static class IntermediateBlock return null; } - var data = default(IntermediateBlockData); + IntermediateBlockData data = default; // Use cached values from BlockInfo instead of PhysicalBlock getters - var colorBitMask = UInt128Extensions.OnesMask(info.ColorBitCount); - var colorBits = (bits >> info.ColorStartBit) & colorBitMask; - var colorBitStream = new BitStream(colorBits, 128); + UInt128 colorBitMask = UInt128Extensions.OnesMask(info.ColorBitCount); + UInt128 colorBits = (bits >> info.ColorStartBit) & colorBitMask; + BitStream colorBitStream = new(colorBits, 128); - var colorDecoder = BoundedIntegerSequenceDecoder.GetCached(info.ColorValuesRange); + BoundedIntegerSequenceDecoder colorDecoder = BoundedIntegerSequenceDecoder.GetCached(info.ColorValuesRange); Span colors = stackalloc int[info.ColorValuesCount]; colorDecoder.Decode(info.ColorValuesCount, ref colorBitStream, colors); @@ -70,9 +70,10 @@ internal static class IntermediateBlock data.EndpointCount = info.PartitionCount; for (int i = 0; i < info.PartitionCount; ++i) { - var mode = info.GetEndpointMode(i); + ColorEndpointMode mode = info.GetEndpointMode(i); int colorCount = mode.GetColorValuesCount(); - var ep = new IntermediateEndpointData { Mode = mode, ColorCount = colorCount }; + IntermediateEndpointData ep = new() + { Mode = mode, ColorCount = colorCount }; for (int j = 0; j < colorCount; ++j) { ep.Colors[j] = colors[colorIndex++]; @@ -83,10 +84,10 @@ internal static class IntermediateBlock data.EndpointRange = info.ColorValuesRange; - var weightBits = UInt128Extensions.ReverseBits(bits) & UInt128Extensions.OnesMask(info.WeightBitCount); - var weightBitStream = new BitStream(weightBits, 128); + UInt128 weightBits = UInt128Extensions.ReverseBits(bits) & UInt128Extensions.OnesMask(info.WeightBitCount); + BitStream weightBitStream = new(weightBits, 128); - var weightDecoder = BoundedIntegerSequenceDecoder.GetCached(data.WeightRange); + BoundedIntegerSequenceDecoder weightDecoder = BoundedIntegerSequenceDecoder.GetCached(data.WeightRange); int weightsCount = data.WeightGridX * data.WeightGridY; if (info.IsDualPlane) { @@ -143,19 +144,19 @@ public static int EndpointRangeForBlock(in IntermediateBlockData data) public static VoidExtentData? UnpackVoidExtent(PhysicalBlock physicalBlock) { - var colorStartBit = physicalBlock.GetColorStartBit(); - var colorBitCount = physicalBlock.GetColorBitCount(); + int? colorStartBit = physicalBlock.GetColorStartBit(); + int? colorBitCount = physicalBlock.GetColorBitCount(); if (physicalBlock.IsIllegalEncoding || !physicalBlock.IsVoidExtent || colorStartBit is null || colorBitCount is null) { return null; } - var colorBits = (physicalBlock.BlockBits >> colorStartBit.Value) & UInt128Extensions.OnesMask(colorBitCount.Value); + UInt128 colorBits = (physicalBlock.BlockBits >> colorStartBit.Value) & UInt128Extensions.OnesMask(colorBitCount.Value); // We expect low 64 bits contain the 4x16-bit channels - var low = colorBits.Low(); + ulong low = colorBits.Low(); - var data = default(VoidExtentData); + VoidExtentData data = default; // Bit 9 of the block mode indicates HDR (1) vs LDR (0) void extent data.IsHdr = (physicalBlock.BlockBits.Low() & (1UL << 9)) != 0; @@ -164,7 +165,7 @@ public static int EndpointRangeForBlock(in IntermediateBlockData data) data.B = (ushort)((low >> 32) & 0xFFFF); data.A = (ushort)((low >> 48) & 0xFFFF); - var coords = physicalBlock.GetVoidExtentCoordinates(); + int[]? coords = physicalBlock.GetVoidExtentCoordinates(); data.Coords = new ushort[4]; if (coords != null) { @@ -175,7 +176,7 @@ public static int EndpointRangeForBlock(in IntermediateBlockData data) } else { - ushort allOnes = (ushort)((1 << 13) - 1); + ushort allOnes = (1 << 13) - 1; for (int i = 0; i < 4; ++i) { data.Coords[i] = allOnes; @@ -195,7 +196,7 @@ internal static bool SharedEndpointModes(in IntermediateBlockData data) return true; } - var first = data.Endpoints[0].Mode; + ColorEndpointMode first = data.Endpoints[0].Mode; for (int i = 1; i < data.EndpointCount; i++) { if (data.Endpoints[i].Mode != first) diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs index c05cc800..df157e29 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/IntermediateBlockPacker.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Textures.Astc.BiseEncoding; -using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; using SixLabors.ImageSharp.Textures.Astc.IO; @@ -38,10 +37,10 @@ public static (string? Error, UInt128 PhysicalBlockBits) Pack(in IntermediateBlo return ("Incorrect number of weights!", 0); } - var bitSink = new BitStream(0UL, 0); + BitStream bitSink = new(0UL, 0); // First we need to encode the block mode. - var errorMessage = PackBlockMode(data.WeightGridX, data.WeightGridY, data.WeightRange, data.DualPlaneChannel.HasValue, ref bitSink); + string? errorMessage = PackBlockMode(data.WeightGridX, data.WeightGridY, data.WeightRange, data.DualPlaneChannel.HasValue, ref bitSink); if (errorMessage != null) { return (errorMessage, 0); @@ -58,17 +57,15 @@ public static (string? Error, UInt128 PhysicalBlockBits) Pack(in IntermediateBlo bitSink.PutBits((uint)id, 10); } - var (weightSink, weightBitsCount) = EncodeWeights(data); + (BitStream weightSink, int weightBitsCount) = EncodeWeights(data); - var (error, extraConfig) = EncodeColorEndpointModes(data, partitionCount, ref bitSink); + (string? error, int extraConfig) = EncodeColorEndpointModes(data, partitionCount, ref bitSink); if (error != null) { return (error, 0); } - int colorValueRange = data.EndpointRange.HasValue - ? data.EndpointRange.Value - : IntermediateBlock.EndpointRangeForBlock(data); + int colorValueRange = data.EndpointRange ?? IntermediateBlock.EndpointRangeForBlock(data); if (colorValueRange == -1) { throw new InvalidOperationException($"{nameof(colorValueRange)} must not be EndpointRangeInvalidWeightDimensions"); @@ -79,10 +76,10 @@ public static (string? Error, UInt128 PhysicalBlockBits) Pack(in IntermediateBlo return ("Intermediate block emits illegal color range", 0); } - var colorEncoder = new BoundedIntegerSequenceEncoder(colorValueRange); + BoundedIntegerSequenceEncoder colorEncoder = new(colorValueRange); for (int i = 0; i < data.EndpointCount; i++) { - var ep = data.Endpoints[i]; + IntermediateBlock.IntermediateEndpointData ep = data.Endpoints[i]; for (int j = 0; j < ep.ColorCount; j++) { int color = ep.Colors[j]; @@ -120,21 +117,21 @@ public static (string? Error, UInt128 PhysicalBlockBits) Pack(in IntermediateBlo ArgumentOutOfRangeException.ThrowIfNotEqual(bitSink.Bits, 128u - weightBitsCount); // Flush out the bit writer - if (!bitSink.TryGetBits(128 - weightBitsCount, out var astcBits)) + if (!bitSink.TryGetBits(128 - weightBitsCount, out UInt128 astcBits)) { throw new InvalidOperationException(); } - if (!weightSink.TryGetBits(weightBitsCount, out var revWeightBits)) + if (!weightSink.TryGetBits(weightBitsCount, out UInt128 revWeightBits)) { throw new InvalidOperationException(); } - var combined = astcBits | UInt128Extensions.ReverseBits(revWeightBits); + UInt128 combined = astcBits | UInt128Extensions.ReverseBits(revWeightBits); physicalBlockBits = combined; - var block = PhysicalBlock.Create(physicalBlockBits); - var illegal = block.IdentifyInvalidEncodingIssues(); + PhysicalBlock block = PhysicalBlock.Create(physicalBlockBits); + string? illegal = block.IdentifyInvalidEncodingIssues(); return (illegal, physicalBlockBits); } @@ -144,7 +141,7 @@ public static (string? Error, UInt128 PhysicalBlockBits) Pack(IntermediateBlock. // Pack void extent // Assemble the 128-bit value explicitly: low 64 bits = RGBA (4x16) // high 64 bits = 12-bit header (0xDFC) followed by four 13-bit coords. - ulong high64 = ((ulong)data.A << 48) | ((ulong)data.B << 32) | ((ulong)data.G << 16) | (ulong)data.R; + ulong high64 = ((ulong)data.A << 48) | ((ulong)data.B << 32) | ((ulong)data.G << 16) | data.R; ulong low64 = 0UL; // Header occupies lowest 12 bits of the high word @@ -173,8 +170,8 @@ public static (string? Error, UInt128 PhysicalBlockBits) Pack(IntermediateBlock. // using full void extent representation } - var block = PhysicalBlock.Create(physicalBlockBits); - var illegal = block.IdentifyInvalidEncodingIssues(); + PhysicalBlock block = PhysicalBlock.Create(physicalBlockBits); + string? illegal = block.IdentifyInvalidEncodingIssues(); if (illegal is not null) { throw new InvalidOperationException($"{nameof(Pack)}(void extent) produced illegal encoding"); @@ -203,14 +200,14 @@ private static (string? Error, int[] Range) GetEncodedWeightRange(int range) index = IntermediateBlock.ValidWeightRanges.Length - 1; } - var encoding = validRangeEncodings[index]; + int[] encoding = validRangeEncodings[index]; return (null, [encoding[0], encoding[1], encoding[2]]); } private static string? PackBlockMode(int dimX, int dimY, int range, bool dualPlane, ref BitStream bitSink) { bool highPrec = range > 7; - var (maybeErr, rangeValues) = GetEncodedWeightRange(range); + (string? maybeErr, int[]? rangeValues) = GetEncodedWeightRange(range); if (maybeErr != null) { return maybeErr; @@ -224,7 +221,7 @@ private static (string? Error, int[] Range) GetEncodedWeightRange(int range) for (int mode = 0; mode < BlockModeInfoTable.Length; ++mode) { - var blockMode = BlockModeInfoTable[mode]; + BlockModeInfo blockMode = BlockModeInfoTable[mode]; bool isValidMode = true; isValidMode &= blockMode.MinWeightGridDimX <= dimX; isValidMode &= dimX <= blockMode.MaxWeightGridDimX; @@ -276,8 +273,8 @@ void SetBit(uint value, int offset) if (!blockMode.RequireSinglePlaneLowPrec) { - SetBit((uint)(highPrec ? 1u : 0u), 9); - SetBit((uint)(dualPlane ? 1u : 0u), 10); + SetBit(highPrec ? 1u : 0u, 9); + SetBit(dualPlane ? 1u : 0u, 10); } if (bitSink.Bits != 0) @@ -294,8 +291,8 @@ void SetBit(uint value, int offset) private static (BitStream WeightSink, int WeightBitsCount) EncodeWeights(in IntermediateBlock.IntermediateBlockData data) { - var weightSink = new BitStream(0UL, 0); - var weightsEncoder = new BoundedIntegerSequenceEncoder(data.WeightRange); + BitStream weightSink = new(0UL, 0); + BoundedIntegerSequenceEncoder weightsEncoder = new(data.WeightRange); int weightCount = data.WeightsCount > 0 ? data.WeightsCount : (data.Weights?.Length ?? 0); @@ -304,7 +301,7 @@ private static (BitStream WeightSink, int WeightBitsCount) EncodeWeights(in Inte throw new InvalidOperationException($"{nameof(data.Weights)} is null in {nameof(EncodeWeights)}"); } - for (var i = 0; i < weightCount; i++) + for (int i = 0; i < weightCount; i++) { weightsEncoder.AddValue(data.Weights[i]); } @@ -351,7 +348,7 @@ private static (string? Error, int ExtraConfig) EncodeColorEndpointModes(in Inte return ("Endpoint modes are invalid", 0); } - var cemEncoder = new BitStream(0UL, 0); + BitStream cemEncoder = new(0UL, 0); cemEncoder.PutBits((uint)(minClass + 1), 2); for (int i = 0; i < data.EndpointCount; i++) diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs index 0ef85ab5..e9cc92e7 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/LogicalBlock.cs @@ -12,7 +12,7 @@ internal sealed class LogicalBlock { private ColorEndpointPair[] endpoints; private int endpointCount; - private int[] weights; + private readonly int[] weights; private Partition partition; private DualPlaneData? dualPlane; @@ -40,7 +40,7 @@ public LogicalBlock(Footprint footprint, IntermediateBlock.VoidExtentData block) { this.endpoints = new ColorEndpointPair[1]; this.endpointCount = DecodeEndpoints(block, this.endpoints); - this.partition = ComputePartition(footprint, block); + this.partition = ComputePartition(footprint); this.weights = new int[footprint.PixelCount]; } @@ -68,7 +68,7 @@ private LogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) int colorIndex = 0; for (int i = 0; i < this.endpointCount; i++) { - var mode = info.GetEndpointMode(i); + ColorEndpointMode mode = info.GetEndpointMode(i); int colorCount = mode.GetColorValuesCount(); ReadOnlySpan slice = colors.Slice(colorIndex, colorCount); this.endpoints[i] = EndpointCodec.DecodeColorsForModePolymorphicUnquantized(slice, mode); @@ -96,7 +96,7 @@ private LogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) totalWeights, rawWeights); - var decimationInfo = DecimationTable.Get(footprint, info.GridWidth, info.GridHeight); + DecimationInfo decimationInfo = DecimationTable.Get(footprint, info.GridWidth, info.GridHeight); this.weights = new int[footprint.PixelCount]; if (!isDualPlane) @@ -133,7 +133,7 @@ private LogicalBlock(Footprint footprint, UInt128 bits, in BlockInfo info) public void SetWeightAt(int x, int y, int weight) { - if (weight < 0 || weight > 64) + if (weight is < 0 or > 64) { throw new ArgumentOutOfRangeException(nameof(weight)); } @@ -177,7 +177,7 @@ public int DualPlaneWeightAt(int channel, int x, int y) public RgbaColor ColorAt(int x, int y) { - var footprint = this.GetFootprint(); + Footprint footprint = this.GetFootprint(); ArgumentOutOfRangeException.ThrowIfNegative(x); ArgumentOutOfRangeException.ThrowIfNegative(y); @@ -186,7 +186,7 @@ public RgbaColor ColorAt(int x, int y) int index = (y * footprint.Width) + x; int part = this.partition.Assignment[index]; - ref var endpoint = ref this.endpoints[part]; + ref ColorEndpointPair endpoint = ref this.endpoints[part]; int weight = this.weights[index]; if (!endpoint.IsHdr) @@ -233,7 +233,7 @@ public RgbaColor ColorAt(int x, int y) /// public RgbaHdrColor ColorAtHdr(int x, int y) { - var footprint = this.GetFootprint(); + Footprint footprint = this.GetFootprint(); ArgumentOutOfRangeException.ThrowIfNegative(x); ArgumentOutOfRangeException.ThrowIfNegative(y); @@ -242,7 +242,7 @@ public RgbaHdrColor ColorAtHdr(int x, int y) int index = (y * footprint.Width) + x; int part = this.partition.Assignment[index]; - ref var endpoint = ref this.endpoints[part]; + ref ColorEndpointPair endpoint = ref this.endpoints[part]; int weight = this.weights[index]; if (endpoint.IsHdr) @@ -304,7 +304,7 @@ public RgbaHdrColor ColorAtHdr(int x, int y) /// public void WriteHdrPixel(int x, int y, Span output) { - var footprint = this.GetFootprint(); + Footprint footprint = this.GetFootprint(); ArgumentOutOfRangeException.ThrowIfNegative(x); ArgumentOutOfRangeException.ThrowIfNegative(y); @@ -313,7 +313,7 @@ public void WriteHdrPixel(int x, int y, Span output) int index = (y * footprint.Width) + x; int part = this.partition.Assignment[index]; - ref var endpoint = ref this.endpoints[part]; + ref ColorEndpointPair endpoint = ref this.endpoints[part]; int weight = this.weights[index]; int dualPlaneChannel = this.dualPlane?.Channel ?? -1; @@ -367,7 +367,7 @@ public void WriteHdrPixel(int x, int y, Span output) /// public void WriteAllPixelsLdr(Footprint footprint, Span buffer) { - ref var endpoint0 = ref this.endpoints[0]; + ref ColorEndpointPair endpoint0 = ref this.endpoints[0]; if (!endpoint0.IsHdr && this.partition.PartitionCount == 1) { @@ -382,7 +382,7 @@ public void WriteAllPixelsLdr(Footprint footprint, Span buffer) else { int dualPlaneChannel = this.dualPlane.Channel; - var dpWeights = this.dualPlane.Weights; + int[] dpWeights = this.dualPlane.Weights; int pixelCount = footprint.PixelCount; for (int i = 0; i < pixelCount; i++) { @@ -420,7 +420,7 @@ public void SetPartition(Partition p) this.partition = p; if (this.endpointCount < p.PartitionCount) { - var newEndpoints = new ColorEndpointPair[p.PartitionCount]; + ColorEndpointPair[] newEndpoints = new ColorEndpointPair[p.PartitionCount]; Array.Copy(this.endpoints, newEndpoints, this.endpointCount); for (int i = this.endpointCount; i < p.PartitionCount; i++) { @@ -469,8 +469,8 @@ public void SetDualPlaneChannel(int channel) if (info.IsVoidExtent) { // Void extent blocks are rare; fall back to existing PhysicalBlock path - var pb = PhysicalBlock.Create(bits); - var voidExtentData = IntermediateBlock.UnpackVoidExtent(pb); + PhysicalBlock pb = PhysicalBlock.Create(bits); + IntermediateBlock.VoidExtentData? voidExtentData = IntermediateBlock.UnpackVoidExtent(pb); if (voidExtentData is null) { return null; @@ -525,7 +525,7 @@ private static int DecodeEndpoints(in IntermediateBlock.IntermediateBlockData bl for (int i = 0; i < block.EndpointCount; i++) { - var ed = block.Endpoints[i]; + IntermediateBlock.IntermediateEndpointData ed = block.Endpoints[i]; ReadOnlySpan colorSpan = ((ReadOnlySpan)ed.Colors)[..ed.ColorCount]; endpointPair[i] = EndpointCodec.DecodeColorsForModePolymorphic(colorSpan, endpointRange, ed.Mode); } @@ -538,13 +538,13 @@ private static int DecodeEndpoints(IntermediateBlock.VoidExtentData block, Color if (block.IsHdr) { // HDR void extent: ushort values are FP16 bit patterns (not LNS) - var hdrColor = new RgbaHdrColor(block.R, block.G, block.B, block.A); + RgbaHdrColor hdrColor = new(block.R, block.G, block.B, block.A); endpointPair[0] = ColorEndpointPair.Hdr(hdrColor, hdrColor, valuesAreLns: false); } else { // LDR void extent: ushort values are UNORM16, convert to byte range - var ldrColor = new RgbaColor( + RgbaColor ldrColor = new( (byte)(block.R >> 8), (byte)(block.G >> 8), (byte)(block.B >> 8), @@ -555,20 +555,17 @@ private static int DecodeEndpoints(IntermediateBlock.VoidExtentData block, Color return 1; } - private static Partition GenerateSinglePartition(Footprint footprint) + private static Partition GenerateSinglePartition(Footprint footprint) => new(footprint, 1, 0) { - return new Partition(footprint, 1, 0) - { - Assignment = new int[footprint.PixelCount] - }; - } + Assignment = new int[footprint.PixelCount] + }; private static Partition ComputePartition(Footprint footprint, in IntermediateBlock.IntermediateBlockData block) => block.PartitionId.HasValue ? Partition.GetASTCPartition(footprint, block.EndpointCount, block.PartitionId.Value) : GenerateSinglePartition(footprint); - private static Partition ComputePartition(Footprint footprint, IntermediateBlock.VoidExtentData block) + private static Partition ComputePartition(Footprint footprint) => GenerateSinglePartition(footprint); private void CalculateWeights(Footprint footprint, in IntermediateBlock.IntermediateBlockData block) @@ -577,7 +574,7 @@ private void CalculateWeights(Footprint footprint, in IntermediateBlock.Intermed int weightFrequency = block.DualPlaneChannel.HasValue ? 2 : 1; // Get decimation info once for both planes - var decimationInfo = DecimationTable.Get(footprint, block.WeightGridX, block.WeightGridY); + DecimationInfo decimationInfo = DecimationTable.Get(footprint, block.WeightGridX, block.WeightGridY); // stackalloc avoids per-block heap allocation (max 12×12 = 144 ints = 576 bytes) Span unquantized = stackalloc int[gridSize]; @@ -591,9 +588,11 @@ private void CalculateWeights(Footprint footprint, in IntermediateBlock.Intermed if (block.DualPlaneChannel.HasValue) { - var dualPlane = new DualPlaneData(); - dualPlane.Channel = block.DualPlaneChannel.Value; - dualPlane.Weights = new int[footprint.PixelCount]; + DualPlaneData dualPlane = new() + { + Channel = block.DualPlaneChannel.Value, + Weights = new int[footprint.PixelCount] + }; this.dualPlane = dualPlane; for (int i = 0; i < gridSize; ++i) { @@ -676,7 +675,7 @@ private void WriteAllPixelsGeneral(Footprint footprint, Span buffer) for (int i = 0; i < pixelCount; i++) { int part = this.partition.Assignment[i]; - ref var endpoint = ref this.endpoints[part]; + ref ColorEndpointPair endpoint = ref this.endpoints[part]; int weight = this.weights[i]; if (!endpoint.IsHdr) diff --git a/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs b/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs index eafc738f..d7b6e2c6 100644 --- a/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs +++ b/src/ImageSharp.Textures.Astc/TexelBlock/PhysicalBlock.cs @@ -178,7 +178,7 @@ public static PhysicalBlock Create(UInt128 bits) internal static int[] DecodeVoidExtentCoordinates(UInt128 astcBits) { ulong lowBits = astcBits.Low(); - var coords = new int[4]; + int[] coords = new int[4]; for (int i = 0; i < 4; ++i) { coords[i] = (int)BitOperations.GetBits(lowBits, 12 + (13 * i), 13); diff --git a/src/ImageSharp.Textures/Common/Exceptions/TextureFormatException.cs b/src/ImageSharp.Textures/Common/Exceptions/TextureFormatException.cs index 8fd3c014..df73308e 100644 --- a/src/ImageSharp.Textures/Common/Exceptions/TextureFormatException.cs +++ b/src/ImageSharp.Textures/Common/Exceptions/TextureFormatException.cs @@ -1,36 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Common.Exceptions; -namespace SixLabors.ImageSharp.Textures.Common.Exceptions +/// +/// The exception that is thrown when the library tries to load +/// an image, which has an invalid format. +/// +public class TextureFormatException : Exception { /// - /// The exception that is thrown when the library tries to load - /// an image, which has an invalid format. + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. /// - public class TextureFormatException : Exception + /// The error message that explains the reason for this exception. + internal TextureFormatException(string errorMessage) + : base(errorMessage) { - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - internal TextureFormatException(string errorMessage) - : base(errorMessage) - { - } + } - /// - /// Initializes a new instance of the class with a specified - /// error message and the exception that is the cause of this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - internal TextureFormatException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { - } + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + internal TextureFormatException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { } } diff --git a/src/ImageSharp.Textures/Common/Exceptions/TextureProcessingException.cs b/src/ImageSharp.Textures/Common/Exceptions/TextureProcessingException.cs index ba360be0..e714fb18 100644 --- a/src/ImageSharp.Textures/Common/Exceptions/TextureProcessingException.cs +++ b/src/ImageSharp.Textures/Common/Exceptions/TextureProcessingException.cs @@ -1,35 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Common.Exceptions; -namespace SixLabors.ImageSharp.Textures.Common.Exceptions +/// +/// The exception that is thrown when an error occurs when applying a process to an image. +/// +public sealed class TextureProcessingException : Exception { /// - /// The exception that is thrown when an error occurs when applying a process to an image. + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. /// - public sealed class TextureProcessingException : Exception + /// The error message that explains the reason for this exception. + public TextureProcessingException(string errorMessage) + : base(errorMessage) { - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public TextureProcessingException(string errorMessage) - : base(errorMessage) - { - } + } - /// - /// Initializes a new instance of the class with a specified - /// error message and the exception that is the cause of this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - public TextureProcessingException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { - } + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public TextureProcessingException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { } } diff --git a/src/ImageSharp.Textures/Common/Exceptions/UnknownTextureFormatException.cs b/src/ImageSharp.Textures/Common/Exceptions/UnknownTextureFormatException.cs index 05bce3f2..80b093f9 100644 --- a/src/ImageSharp.Textures/Common/Exceptions/UnknownTextureFormatException.cs +++ b/src/ImageSharp.Textures/Common/Exceptions/UnknownTextureFormatException.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Common.Exceptions +namespace SixLabors.ImageSharp.Textures.Common.Exceptions; + +/// +/// The exception that is thrown when the library tries to load +/// an image which has an unknown format. +/// +public sealed class UnknownTextureFormatException : TextureFormatException { /// - /// The exception that is thrown when the library tries to load - /// an image which has an unknown format. + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. /// - public sealed class UnknownTextureFormatException : TextureFormatException + /// The error message that explains the reason for this exception. + public UnknownTextureFormatException(string errorMessage) + : base(errorMessage) { - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public UnknownTextureFormatException(string errorMessage) - : base(errorMessage) - { - } } } diff --git a/src/ImageSharp.Textures/Common/Extensions/StreamExtensions.cs b/src/ImageSharp.Textures/Common/Extensions/StreamExtensions.cs index 274a0227..723927e9 100644 --- a/src/ImageSharp.Textures/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp.Textures/Common/Extensions/StreamExtensions.cs @@ -1,105 +1,101 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; -using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Textures.Common.Extensions +namespace SixLabors.ImageSharp.Textures.Common.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class StreamExtensions { /// - /// Extension methods for the type. + /// Reads data from a stream into the provided buffer. /// - internal static class StreamExtensions - { - /// - /// Reads data from a stream into the provided buffer. - /// - /// The stream. - /// The buffer.. - /// The offset within the buffer where the bytes are read into. - /// The number of bytes, if available, to read. - /// The actual number of bytes read. - public static int Read(this Stream stream, Span buffer, int offset, int count) => stream.Read(buffer.Slice(offset, count)); + /// The stream. + /// The buffer.. + /// The offset within the buffer where the bytes are read into. + /// The number of bytes, if available, to read. + /// The actual number of bytes read. + public static int Read(this Stream stream, Span buffer, int offset, int count) => stream.Read(buffer.Slice(offset, count)); - /// - /// Skips the number of bytes in the given stream. - /// - /// The stream. - /// The count. - public static void Skip(this Stream stream, int count) + /// + /// Skips the number of bytes in the given stream. + /// + /// The stream. + /// The count. + public static void Skip(this Stream stream, int count) + { + if (count < 1) { - if (count < 1) - { - return; - } + return; + } - if (stream.CanSeek) - { - stream.Seek(count, SeekOrigin.Current); // Position += count; - } - else + if (stream.CanSeek) + { + stream.Seek(count, SeekOrigin.Current); // Position += count; + } + else + { + byte[] foo = new byte[count]; + while (count > 0) { - byte[] foo = new byte[count]; - while (count > 0) + int bytesRead = stream.Read(foo, 0, count); + if (bytesRead == 0) { - int bytesRead = stream.Read(foo, 0, count); - if (bytesRead == 0) - { - break; - } - - count -= bytesRead; + break; } + + count -= bytesRead; } } + } #if NET472 || NETSTANDARD1_3 || NETSTANDARD2_0 - // This is a port of the CoreFX implementation and is MIT Licensed: https://github.com/dotnet/coreclr/blob/c4dca1072d15bdda64c754ad1ea474b1580fa554/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L770 - public static void Write(this Stream stream, ReadOnlySpan buffer) + // This is a port of the CoreFX implementation and is MIT Licensed: https://github.com/dotnet/coreclr/blob/c4dca1072d15bdda64c754ad1ea474b1580fa554/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L770 + public static void Write(this Stream stream, ReadOnlySpan buffer) + { + // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, + // in order to match the signature of the framework method that exists in + // .NET Core. + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try { - // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, - // in order to match the signature of the framework method that exists in - // .NET Core. - byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); - try - { - buffer.CopyTo(sharedBuffer); - stream.Write(sharedBuffer, 0, buffer.Length); - } - finally - { - ArrayPool.Shared.Return(sharedBuffer); - } + buffer.CopyTo(sharedBuffer); + stream.Write(sharedBuffer, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); } + } #endif #if !SUPPORTS_SPAN_STREAM - // This is a port of the CoreFX implementation and is MIT Licensed: - // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 - public static int Read(this Stream stream, Span buffer) + // This is a port of the CoreFX implementation and is MIT Licensed: + // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 + public static int Read(this Stream stream, Span buffer) + { + // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, + // in order to match the signature of the framework method that exists in + // .NET Core. + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try { - // This uses ArrayPool.Shared, rather than taking a MemoryAllocator, - // in order to match the signature of the framework method that exists in - // .NET Core. - byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); - try + int numRead = stream.Read(sharedBuffer, 0, buffer.Length); + if ((uint)numRead > (uint)buffer.Length) { - int numRead = stream.Read(sharedBuffer, 0, buffer.Length); - if ((uint)numRead > (uint)buffer.Length) - { - throw new IOException("Stream was too long."); - } - - new Span(sharedBuffer, 0, numRead).CopyTo(buffer); - return numRead; - } - finally - { - ArrayPool.Shared.Return(sharedBuffer); + throw new IOException("Stream was too long."); } + + new Span(sharedBuffer, 0, numRead).CopyTo(buffer); + return numRead; + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); } -#endif } +#endif } diff --git a/src/ImageSharp.Textures/Common/Extensions/ToStringExtension.cs b/src/ImageSharp.Textures/Common/Extensions/ToStringExtension.cs index da8ec122..07e0d106 100644 --- a/src/ImageSharp.Textures/Common/Extensions/ToStringExtension.cs +++ b/src/ImageSharp.Textures/Common/Extensions/ToStringExtension.cs @@ -1,24 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Text; -namespace SixLabors.ImageSharp.Textures.Common.Extensions +namespace SixLabors.ImageSharp.Textures.Common.Extensions; + +/// +/// To string extension methods. +/// +public static class ToStringExtension { /// - /// To string extension methods. + /// Converts a FourCC value to a string. /// - public static class ToStringExtension - { - /// - /// Converts a FourCC value to a string. - /// - /// The FourCC. - /// A string for the FourCC. - public static string FourCcToString(this uint value) - { - return Encoding.UTF8.GetString(BitConverter.GetBytes(value)); - } - } + /// The FourCC. + /// A string for the FourCC. + public static string FourCcToString(this uint value) => Encoding.UTF8.GetString(BitConverter.GetBytes(value)); } diff --git a/src/ImageSharp.Textures/Common/Helpers/FloatHelper.cs b/src/ImageSharp.Textures/Common/Helpers/FloatHelper.cs index 98b8de50..63e86b8d 100644 --- a/src/ImageSharp.Textures/Common/Helpers/FloatHelper.cs +++ b/src/ImageSharp.Textures/Common/Helpers/FloatHelper.cs @@ -3,70 +3,69 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Textures.Common.Helpers +namespace SixLabors.ImageSharp.Textures.Common.Helpers; + +internal static class FloatHelper { - internal static class FloatHelper - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float UnpackFloat32ToFloat(uint value) => Unsafe.As(ref value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UnpackFloat32ToFloat(uint value) => Unsafe.As(ref value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint PackFloatToFloat32(float value) => Unsafe.As(ref value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint PackFloatToFloat32(float value) => Unsafe.As(ref value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float UnpackFloat16ToFloat(uint value) - { - uint result = - ((value >> 15) << 31) | - ((((value >> 10) & 0x1f) - 15 + 127) << 23) | - ((value & 0x3ff) << 13); - return Unsafe.As(ref result); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UnpackFloat16ToFloat(uint value) + { + uint result = + ((value >> 15) << 31) | + ((((value >> 10) & 0x1f) - 15 + 127) << 23) | + ((value & 0x3ff) << 13); + return Unsafe.As(ref result); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint PackFloatToFloat16(float value) - { - uint temp = Unsafe.As(ref value); - return - ((temp >> 31) << 15) | - ((((temp >> 23) & 0xff) - 127 + 15) << 10) | - ((temp & 0x7fffff) >> 13); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint PackFloatToFloat16(float value) + { + uint temp = Unsafe.As(ref value); + return + ((temp >> 31) << 15) | + ((((temp >> 23) & 0xff) - 127 + 15) << 10) | + ((temp & 0x7fffff) >> 13); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float UnpackFloat10ToFloat(uint value) - { - uint result = - ((((value >> 5) & 0x1f) - 10 + 127) << 23) | - ((value & 0x1f) << 18); - return Unsafe.As(ref result); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UnpackFloat10ToFloat(uint value) + { + uint result = + ((((value >> 5) & 0x1f) - 10 + 127) << 23) | + ((value & 0x1f) << 18); + return Unsafe.As(ref result); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint PackFloatToFloat10(float value) - { - uint temp = Unsafe.As(ref value); - return - ((((temp >> 23) & 0xff) - 127 + 10) << 5) | - ((temp & 0x7fffff) >> 18); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint PackFloatToFloat10(float value) + { + uint temp = Unsafe.As(ref value); + return + ((((temp >> 23) & 0xff) - 127 + 10) << 5) | + ((temp & 0x7fffff) >> 18); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float UnpackFloat11ToFloat(uint value) - { - uint result = - ((((value >> 6) & 0x1f) - 11 + 127) << 23) | - ((value & 0x3f) << 17); - return Unsafe.As(ref result); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UnpackFloat11ToFloat(uint value) + { + uint result = + ((((value >> 6) & 0x1f) - 11 + 127) << 23) | + ((value & 0x3f) << 17); + return Unsafe.As(ref result); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint PackFloatToFloat11(float value) - { - uint temp = Unsafe.As(ref value); - return - ((((temp >> 23) & 0xff) - 127 + 11) << 6) | - ((temp & 0x7fffff) >> 17); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint PackFloatToFloat11(float value) + { + uint temp = Unsafe.As(ref value); + return + ((((temp >> 23) & 0xff) - 127 + 11) << 6) | + ((temp & 0x7fffff) >> 17); } } diff --git a/src/ImageSharp.Textures/Common/Helpers/PixelUtils.cs b/src/ImageSharp.Textures/Common/Helpers/PixelUtils.cs index ed7907b2..d9c789bb 100644 --- a/src/ImageSharp.Textures/Common/Helpers/PixelUtils.cs +++ b/src/ImageSharp.Textures/Common/Helpers/PixelUtils.cs @@ -4,43 +4,42 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.Common.Helpers +namespace SixLabors.ImageSharp.Textures.Common.Helpers; + +/// +/// Provides methods for calculating pixel values. +/// +internal static class PixelUtils { /// - /// Provides methods for calculating pixel values. + /// Performs final shifting from a 5bit value to an 8bit one. /// - internal static class PixelUtils - { - /// - /// Performs final shifting from a 5bit value to an 8bit one. - /// - /// The masked and shifted value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); + /// The masked and shifted value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); - /// - /// Performs final shifting from a 6bit value to an 8bit one. - /// - /// The masked and shifted value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4)); + /// + /// Performs final shifting from a 6bit value to an 8bit one. + /// + /// The masked and shifted value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4)); - /// - /// Extracts the R5G6B5 values from a packed ushort pixel in that order. - /// - /// The packed color. - /// The extracted color. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ExtractR5G6B5(ushort color, ref Rgb24 dest) - { - var r = (color & 0xF800) >> 11; - var g = (color & 0x7E0) >> 5; - var b = color & 0x1f; - dest.R = PixelUtils.GetBytesFrom5BitValue(r); - dest.G = PixelUtils.GetBytesFrom6BitValue(g); - dest.B = PixelUtils.GetBytesFrom5BitValue(b); - } + /// + /// Extracts the R5G6B5 values from a packed ushort pixel in that order. + /// + /// The packed color. + /// The extracted color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ExtractR5G6B5(ushort color, ref Rgb24 dest) + { + int r = (color & 0xF800) >> 11; + int g = (color & 0x7E0) >> 5; + int b = color & 0x1f; + dest.R = PixelUtils.GetBytesFrom5BitValue(r); + dest.G = PixelUtils.GetBytesFrom6BitValue(g); + dest.B = PixelUtils.GetBytesFrom5BitValue(b); } } diff --git a/src/ImageSharp.Textures/Configuration.cs b/src/ImageSharp.Textures/Configuration.cs index d68b4726..9e4e9be8 100644 --- a/src/ImageSharp.Textures/Configuration.cs +++ b/src/ImageSharp.Textures/Configuration.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Textures.Formats; using SixLabors.ImageSharp.Textures.Formats.Dds; @@ -10,147 +8,146 @@ using SixLabors.ImageSharp.Textures.Formats.Ktx2; using SixLabors.ImageSharp.Textures.IO; -namespace SixLabors.ImageSharp.Textures +namespace SixLabors.ImageSharp.Textures; + +/// +/// Provides configuration code which allows altering default behaviour or extending the library. +/// +public sealed class Configuration { /// - /// Provides configuration code which allows altering default behaviour or extending the library. + /// A lazily initialized configuration default instance. /// - public sealed class Configuration - { - /// - /// A lazily initialized configuration default instance. - /// - private static readonly Lazy Lazy = new(CreateDefaultInstance); + private static readonly Lazy Lazy = new(CreateDefaultInstance); - private int maxDegreeOfParallelism = Environment.ProcessorCount; + private int maxDegreeOfParallelism = Environment.ProcessorCount; - private MemoryAllocator memoryAllocator = MemoryAllocator.Default; + private MemoryAllocator memoryAllocator = MemoryAllocator.Default; - /// - /// Initializes a new instance of the class. - /// - public Configuration() - { - } + /// + /// Initializes a new instance of the class. + /// + public Configuration() + { + } - /// - /// Initializes a new instance of the class. - /// - /// A collection of configuration modules to register - public Configuration(params IConfigurationModule[] configurationModules) + /// + /// Initializes a new instance of the class. + /// + /// A collection of configuration modules to register + public Configuration(params IConfigurationModule[] configurationModules) + { + if (configurationModules != null) { - if (configurationModules != null) + foreach (IConfigurationModule p in configurationModules) { - foreach (IConfigurationModule p in configurationModules) - { - p.Configure(this); - } + p.Configure(this); } } + } - /// - /// Gets the default instance. - /// - public static Configuration Default { get; } = Lazy.Value; - - /// - /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms - /// configured with this instance. - /// Initialized with by default. - /// - public int MaxDegreeOfParallelism - { - get => this.maxDegreeOfParallelism; - set - { - if (value == 0 || value < -1) - { - throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); - } - - this.maxDegreeOfParallelism = value; - } - } + /// + /// Gets the default instance. + /// + public static Configuration Default { get; } = Lazy.Value; - /// - /// Gets the currently registered s. - /// - public IEnumerable ImageFormats => this.ImageFormatsManager.ImageFormats; - - /// - /// Gets or sets the position in a stream to use for reading when using a seekable stream as an image data source. - /// - public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current; - - /// - /// Gets or sets the that is currently in use. - /// - public TextureFormatManager ImageFormatsManager { get; set; } = new(); - - /// - /// Gets or sets the that is currently in use. - /// - public MemoryAllocator MemoryAllocator + /// + /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms + /// configured with this instance. + /// Initialized with by default. + /// + public int MaxDegreeOfParallelism + { + get => this.maxDegreeOfParallelism; + set { - get => this.memoryAllocator; - set + if (value is 0 or < (-1)) { - Guard.NotNull(value, nameof(this.MemoryAllocator)); - this.memoryAllocator = value; + throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); } + + this.maxDegreeOfParallelism = value; } + } - /// - /// Gets the maximum header size of all the formats. - /// - internal int MaxHeaderSize => this.ImageFormatsManager.MaxHeaderSize; - - /// - /// Gets or sets the filesystem helper for accessing the local file system. - /// - internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); - - /// - /// Gets or sets the working buffer size hint for image processors. - /// The default value is 1MB. - /// - /// - /// Currently only used by Resize. - /// - internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; - - /// - /// Registers a new format provider. - /// - /// The configuration provider to call configure on. - public void Configure(IConfigurationModule configuration) + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.ImageFormatsManager.ImageFormats; + + /// + /// Gets or sets the position in a stream to use for reading when using a seekable stream as an image data source. + /// + public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current; + + /// + /// Gets or sets the that is currently in use. + /// + public TextureFormatManager ImageFormatsManager { get; set; } = new(); + + /// + /// Gets or sets the that is currently in use. + /// + public MemoryAllocator MemoryAllocator + { + get => this.memoryAllocator; + set { - Guard.NotNull(configuration, nameof(configuration)); - configuration.Configure(this); + Guard.NotNull(value, nameof(this.MemoryAllocator)); + this.memoryAllocator = value; } + } - /// - /// Creates a shallow copy of the . - /// - /// A new configuration instance. - public Configuration Clone() => new() - { - MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, - ImageFormatsManager = this.ImageFormatsManager, - MemoryAllocator = this.MemoryAllocator, - ReadOrigin = this.ReadOrigin, - FileSystem = this.FileSystem, - WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, - }; - - /// - /// Creates the default instance with the following s preregistered: - /// - /// - /// The default configuration of . - internal static Configuration CreateDefaultInstance() => new( - new DdsConfigurationModule(), - new KtxConfigurationModule(), - new Ktx2ConfigurationModule()); + /// + /// Gets the maximum header size of all the formats. + /// + internal int MaxHeaderSize => this.ImageFormatsManager.MaxHeaderSize; + + /// + /// Gets or sets the filesystem helper for accessing the local file system. + /// + internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); + + /// + /// Gets or sets the working buffer size hint for image processors. + /// The default value is 1MB. + /// + /// + /// Currently only used by Resize. + /// + internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; + + /// + /// Registers a new format provider. + /// + /// The configuration provider to call configure on. + public void Configure(IConfigurationModule configuration) + { + Guard.NotNull(configuration, nameof(configuration)); + configuration.Configure(this); } + + /// + /// Creates a shallow copy of the . + /// + /// A new configuration instance. + public Configuration Clone() => new() + { + MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, + ImageFormatsManager = this.ImageFormatsManager, + MemoryAllocator = this.MemoryAllocator, + ReadOrigin = this.ReadOrigin, + FileSystem = this.FileSystem, + WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, + }; + + /// + /// Creates the default instance with the following s preregistered: + /// + /// + /// The default configuration of . + internal static Configuration CreateDefaultInstance() => new( + new DdsConfigurationModule(), + new KtxConfigurationModule(), + new Ktx2ConfigurationModule()); } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsConfigurationModule.cs b/src/ImageSharp.Textures/Formats/Dds/DdsConfigurationModule.cs index c81d20b7..88085655 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsConfigurationModule.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsConfigurationModule.cs @@ -1,18 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// Registers the image encoders, decoders and mime type detectors for texture formats. +/// +public sealed class DdsConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for texture formats. - /// - public sealed class DdsConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetDecoder(DdsFormat.Instance, new DdsDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new DdsImageFormatDetector()); - } + configuration.ImageFormatsManager.SetDecoder(DdsFormat.Instance, new DdsDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new DdsImageFormatDetector()); } } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsConstants.cs b/src/ImageSharp.Textures/Formats/Dds/DdsConstants.cs index 0caac846..c7ec94dd 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsConstants.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsConstants.cs @@ -1,35 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.Formats.Dds; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +internal static class DdsConstants { - internal static class DdsConstants - { - /// - /// The list of mimetypes that equate to a dds file. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/vnd.ms-dds" }; + /// + /// The list of mimetypes that equate to a dds file. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/vnd.ms-dds" }; - /// - /// The list of file extensions that equate to a dds file. - /// - public static readonly IEnumerable FileExtensions = new[] { "dds" }; + /// + /// The list of file extensions that equate to a dds file. + /// + public static readonly IEnumerable FileExtensions = new[] { "dds" }; - /// - /// The dds header size in bytes. - /// - public const int DdsHeaderSize = 124; + /// + /// The dds header size in bytes. + /// + public const int DdsHeaderSize = 124; - /// - /// The dds pixel format size in bytes. - /// - public const int DdsPixelFormatSize = 32; + /// + /// The dds pixel format size in bytes. + /// + public const int DdsPixelFormatSize = 32; - /// - /// The dds dxt10 header size in bytes. - /// - public const int DdsDxt10HeaderSize = 20; - } + /// + /// The dds dxt10 header size in bytes. + /// + public const int DdsDxt10HeaderSize = 20; } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsDecoder.cs b/src/ImageSharp.Textures/Formats/Dds/DdsDecoder.cs index 6291bf5d..c4983089 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsDecoder.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsDecoder.cs @@ -1,29 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; +namespace SixLabors.ImageSharp.Textures.Formats.Dds; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +/// +/// Image decoder for DDS textures. +/// +public sealed class DdsDecoder : ITextureDecoder, IDdsDecoderOptions, ITextureInfoDetector { - /// - /// Image decoder for DDS textures. - /// - public sealed class DdsDecoder : ITextureDecoder, IDdsDecoderOptions, ITextureInfoDetector + /// + public Texture DecodeTexture(Configuration configuration, Stream stream) { - /// - public Texture DecodeTexture(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(stream, nameof(stream)); - return new DdsDecoderCore(configuration, this).DecodeTexture(stream); - } + return new DdsDecoderCore(configuration, this).DecodeTexture(stream); + } - /// - public ITextureInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); + /// + public ITextureInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); - return new DdsDecoderCore(configuration, this).Identify(stream); - } + return new DdsDecoderCore(configuration, this).Identify(stream); } } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsDecoderCore.cs b/src/ImageSharp.Textures/Formats/Dds/DdsDecoderCore.cs index 1bb1d0b5..f99bde8e 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsDecoderCore.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsDecoderCore.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Textures.Common.Exceptions; using SixLabors.ImageSharp.Textures.Common.Extensions; @@ -12,199 +10,198 @@ using SixLabors.ImageSharp.Textures.Formats.Dds.Processing; using SixLabors.ImageSharp.Textures.TextureFormats; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// Performs the dds decoding operation. +/// +internal sealed class DdsDecoderCore { /// - /// Performs the dds decoding operation. + /// The file header containing general information about the texture. + /// + private DdsHeader ddsHeader; + + /// + /// The dxt10 header if available /// - internal sealed class DdsDecoderCore + private DdsHeaderDxt10 ddsDxt10header; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The texture decoder options. + /// + private readonly IDdsDecoderOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public DdsDecoderCore(Configuration configuration, IDdsDecoderOptions options) { - /// - /// The file header containing general information about the texture. - /// - private DdsHeader ddsHeader; - - /// - /// The dxt10 header if available - /// - private DdsHeaderDxt10 ddsDxt10header; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The texture decoder options. - /// - private readonly IDdsDecoderOptions options; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The options. - public DdsDecoderCore(Configuration configuration, IDdsDecoderOptions options) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; - } + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; + } - /// - /// Decodes the texture from the specified stream. - /// - /// The stream, where the texture should be decoded from. Cannot be null. - /// The decoded image. - public Texture DecodeTexture(Stream stream) + /// + /// Decodes the texture from the specified stream. + /// + /// The stream, where the texture should be decoded from. Cannot be null. + /// The decoded image. + public Texture DecodeTexture(Stream stream) + { + try { - try - { - this.ReadFileHeader(stream); + this.ReadFileHeader(stream); - if (this.ddsHeader.Width == 0 || this.ddsHeader.Height == 0) - { - throw new UnknownTextureFormatException("Width or height cannot be 0"); - } + if (this.ddsHeader.Width == 0 || this.ddsHeader.Height == 0) + { + throw new UnknownTextureFormatException("Width or height cannot be 0"); + } - var ddsProcessor = new DdsProcessor(this.ddsHeader, this.ddsDxt10header); + DdsProcessor ddsProcessor = new DdsProcessor(this.ddsHeader, this.ddsDxt10header); - int width = (int)this.ddsHeader.Width; - int height = (int)this.ddsHeader.Height; - int count = this.ddsHeader.TextureCount(); + int width = (int)this.ddsHeader.Width; + int height = (int)this.ddsHeader.Height; + int count = this.ddsHeader.TextureCount(); - if (this.ddsHeader.IsVolumeTexture()) - { - int depths = this.ddsHeader.ComputeDepth(); + if (this.ddsHeader.IsVolumeTexture()) + { + int depths = this.ddsHeader.ComputeDepth(); - var texture = new VolumeTexture(); - var surfaces = new FlatTexture[depths]; + VolumeTexture texture = new VolumeTexture(); + FlatTexture[] surfaces = new FlatTexture[depths]; - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++) + { + for (int depth = 0; depth < depths; depth++) { - for (int depth = 0; depth < depths; depth++) + if (i == 0) { - if (i == 0) - { - surfaces[depth] = new FlatTexture(); - } - - MipMap[] mipMaps = ddsProcessor.DecodeDds(stream, width, height, 1); - surfaces[depth].MipMaps.AddRange(mipMaps); + surfaces[depth] = new FlatTexture(); } - depths >>= 1; - width >>= 1; - height >>= 1; + MipMap[] mipMaps = ddsProcessor.DecodeDds(stream, width, height, 1); + surfaces[depth].MipMaps.AddRange(mipMaps); } - texture.Slices.AddRange(surfaces); - return texture; + depths >>= 1; + width >>= 1; + height >>= 1; } - else if (this.ddsHeader.IsCubemap()) - { - DdsSurfaceType[] faces = this.ddsHeader.GetExistingCubemapFaces() ?? Array.Empty(); - var texture = new CubemapTexture(); - for (int face = 0; face < faces.Length; face++) - { - MipMap[] mipMaps = ddsProcessor.DecodeDds(stream, width, height, count); - if (faces[face] == DdsSurfaceType.CubemapPositiveX) - { - texture.PositiveX.MipMaps.AddRange(mipMaps); - } + texture.Slices.AddRange(surfaces); + return texture; + } + else if (this.ddsHeader.IsCubemap()) + { + DdsSurfaceType[] faces = this.ddsHeader.GetExistingCubemapFaces() ?? Array.Empty(); - if (faces[face] == DdsSurfaceType.CubemapNegativeX) - { - texture.NegativeX.MipMaps.AddRange(mipMaps); - } + CubemapTexture texture = new CubemapTexture(); + for (int face = 0; face < faces.Length; face++) + { + MipMap[] mipMaps = ddsProcessor.DecodeDds(stream, width, height, count); + if (faces[face] == DdsSurfaceType.CubemapPositiveX) + { + texture.PositiveX.MipMaps.AddRange(mipMaps); + } - if (faces[face] == DdsSurfaceType.CubemapPositiveY) - { - texture.PositiveY.MipMaps.AddRange(mipMaps); - } + if (faces[face] == DdsSurfaceType.CubemapNegativeX) + { + texture.NegativeX.MipMaps.AddRange(mipMaps); + } - if (faces[face] == DdsSurfaceType.CubemapNegativeY) - { - texture.NegativeY.MipMaps.AddRange(mipMaps); - } + if (faces[face] == DdsSurfaceType.CubemapPositiveY) + { + texture.PositiveY.MipMaps.AddRange(mipMaps); + } - if (faces[face] == DdsSurfaceType.CubemapPositiveZ) - { - texture.PositiveZ.MipMaps.AddRange(mipMaps); - } + if (faces[face] == DdsSurfaceType.CubemapNegativeY) + { + texture.NegativeY.MipMaps.AddRange(mipMaps); + } - if (faces[face] == DdsSurfaceType.CubemapNegativeZ) - { - texture.NegativeZ.MipMaps.AddRange(mipMaps); - } + if (faces[face] == DdsSurfaceType.CubemapPositiveZ) + { + texture.PositiveZ.MipMaps.AddRange(mipMaps); } - return texture; - } - else - { - var texture = new FlatTexture(); - MipMap[] mipMaps = ddsProcessor.DecodeDds(stream, width, height, count); - texture.MipMaps.AddRange(mipMaps); - return texture; + if (faces[face] == DdsSurfaceType.CubemapNegativeZ) + { + texture.NegativeZ.MipMaps.AddRange(mipMaps); + } } + + return texture; } - catch (IndexOutOfRangeException e) + else { - throw new TextureFormatException("Dds image does not have a valid format.", e); + FlatTexture texture = new FlatTexture(); + MipMap[] mipMaps = ddsProcessor.DecodeDds(stream, width, height, count); + texture.MipMaps.AddRange(mipMaps); + return texture; } } - - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - public ITextureInfo Identify(Stream stream) + catch (IndexOutOfRangeException e) { - this.ReadFileHeader(stream); + throw new TextureFormatException("Dds image does not have a valid format.", e); + } + } - D3dFormat d3dFormat = this.ddsHeader.PixelFormat.GetD3DFormat(); - DxgiFormat dxgiFormat = this.ddsHeader.ShouldHaveDxt10Header() ? this.ddsDxt10header.DxgiFormat : DxgiFormat.Unknown; - int bitsPerPixel = DdsTools.GetBitsPerPixel(d3dFormat, dxgiFormat); + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public ITextureInfo Identify(Stream stream) + { + this.ReadFileHeader(stream); - return new TextureInfo( - new TextureTypeInfo(bitsPerPixel), - (int)this.ddsHeader.Width, - (int)this.ddsHeader.Height); - } + D3dFormat d3dFormat = this.ddsHeader.PixelFormat.GetD3DFormat(); + DxgiFormat dxgiFormat = this.ddsHeader.ShouldHaveDxt10Header() ? this.ddsDxt10header.DxgiFormat : DxgiFormat.Unknown; + int bitsPerPixel = DdsTools.GetBitsPerPixel(d3dFormat, dxgiFormat); - /// - /// Reads the dds file header from the stream. - /// - /// The containing texture data. - private void ReadFileHeader(Stream stream) + return new TextureInfo( + new TextureTypeInfo(bitsPerPixel), + (int)this.ddsHeader.Width, + (int)this.ddsHeader.Height); + } + + /// + /// Reads the dds file header from the stream. + /// + /// The containing texture data. + private void ReadFileHeader(Stream stream) + { + Span magicBuffer = stackalloc byte[4]; + stream.Read(magicBuffer, 0, 4); + uint magicValue = BinaryPrimitives.ReadUInt32LittleEndian(magicBuffer); + if (magicValue != DdsFourCc.DdsMagicWord) { - Span magicBuffer = stackalloc byte[4]; - stream.Read(magicBuffer, 0, 4); - uint magicValue = BinaryPrimitives.ReadUInt32LittleEndian(magicBuffer); - if (magicValue != DdsFourCc.DdsMagicWord) - { - throw new NotSupportedException("Invalid DDS magic value."); - } + throw new NotSupportedException("Invalid DDS magic value."); + } - byte[] ddsHeaderBuffer = new byte[DdsConstants.DdsHeaderSize]; + byte[] ddsHeaderBuffer = new byte[DdsConstants.DdsHeaderSize]; - stream.Read(ddsHeaderBuffer, 0, DdsConstants.DdsHeaderSize); - this.ddsHeader = DdsHeader.Parse(ddsHeaderBuffer); - this.ddsHeader.Validate(); + stream.Read(ddsHeaderBuffer, 0, DdsConstants.DdsHeaderSize); + this.ddsHeader = DdsHeader.Parse(ddsHeaderBuffer); + this.ddsHeader.Validate(); - if (this.ddsHeader.ShouldHaveDxt10Header()) - { - byte[] ddsDxt10headerBuffer = new byte[DdsConstants.DdsDxt10HeaderSize]; - stream.Read(ddsDxt10headerBuffer, 0, DdsConstants.DdsDxt10HeaderSize); - this.ddsDxt10header = DdsHeaderDxt10.Parse(ddsDxt10headerBuffer); - } + if (this.ddsHeader.ShouldHaveDxt10Header()) + { + byte[] ddsDxt10headerBuffer = new byte[DdsConstants.DdsDxt10HeaderSize]; + stream.Read(ddsDxt10headerBuffer, 0, DdsConstants.DdsDxt10HeaderSize); + this.ddsDxt10header = DdsHeaderDxt10.Parse(ddsDxt10headerBuffer); } } } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsFormat.cs b/src/ImageSharp.Textures/Formats/Dds/DdsFormat.cs index 7028ce09..b19c0ab0 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsFormat.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsFormat.cs @@ -1,37 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.Formats.Dds; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +/// +/// Registers the texture decoders and mime type detectors for the dds format. +/// +public sealed class DdsFormat : ITextureFormat { /// - /// Registers the texture decoders and mime type detectors for the dds format. + /// Prevents a default instance of the class from being created. /// - public sealed class DdsFormat : ITextureFormat + private DdsFormat() { - /// - /// Prevents a default instance of the class from being created. - /// - private DdsFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static DdsFormat Instance { get; } = new DdsFormat(); + /// + /// Gets the current instance. + /// + public static DdsFormat Instance { get; } = new DdsFormat(); - /// - public string Name => "DDS"; + /// + public string Name => "DDS"; - /// - public string DefaultMimeType => "image/vnd.ms-dds"; + /// + public string DefaultMimeType => "image/vnd.ms-dds"; - /// - public IEnumerable MimeTypes => DdsConstants.MimeTypes; + /// + public IEnumerable MimeTypes => DdsConstants.MimeTypes; - /// - public IEnumerable FileExtensions => DdsConstants.FileExtensions; - } + /// + public IEnumerable FileExtensions => DdsConstants.FileExtensions; } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsFourCC.cs b/src/ImageSharp.Textures/Formats/Dds/DdsFourCC.cs index 2f922330..e549205b 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsFourCC.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsFourCC.cs @@ -2,73 +2,72 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// Four character codes constants used in DDS files. +/// +internal static class DdsFourCc { - /// - /// Four character codes constants used in DDS files. - /// - internal static class DdsFourCc - { - public const uint DdsMagicWord = 'D' | ('D' << 8) | ('S' << 16) | (' ' << 24); + public const uint DdsMagicWord = 'D' | ('D' << 8) | ('S' << 16) | (' ' << 24); - public const uint None = 0; + public const uint None = 0; - public const uint DX10 = 'D' | ('X' << 8) | ('1' << 16) | ('0' << 24); + public const uint DX10 = 'D' | ('X' << 8) | ('1' << 16) | ('0' << 24); - public const uint UYVY = 'U' | ('Y' << 8) | ('V' << 16) | ('Y' << 24); + public const uint UYVY = 'U' | ('Y' << 8) | ('V' << 16) | ('Y' << 24); - public const uint RGBG = 'R' | ('G' << 8) | ('B' << 16) | ('G' << 24); + public const uint RGBG = 'R' | ('G' << 8) | ('B' << 16) | ('G' << 24); - public const uint YUY2 = 'Y' | ('U' << 8) | ('Y' << 16) | ('2' << 24); + public const uint YUY2 = 'Y' | ('U' << 8) | ('Y' << 16) | ('2' << 24); - public const uint GRGB = 'G' | ('R' << 8) | ('G' << 16) | ('B' << 24); + public const uint GRGB = 'G' | ('R' << 8) | ('G' << 16) | ('B' << 24); - public const uint DXT1 = 'D' | ('X' << 8) | ('T' << 16) | ('1' << 24); + public const uint DXT1 = 'D' | ('X' << 8) | ('T' << 16) | ('1' << 24); - public const uint DXT2 = 'D' | ('X' << 8) | ('T' << 16) | ('2' << 24); + public const uint DXT2 = 'D' | ('X' << 8) | ('T' << 16) | ('2' << 24); - public const uint DXT3 = 'D' | ('X' << 8) | ('T' << 16) | ('3' << 24); + public const uint DXT3 = 'D' | ('X' << 8) | ('T' << 16) | ('3' << 24); - public const uint DXT4 = 'D' | ('X' << 8) | ('T' << 16) | ('4' << 24); + public const uint DXT4 = 'D' | ('X' << 8) | ('T' << 16) | ('4' << 24); - public const uint DXT5 = 'D' | ('X' << 8) | ('T' << 16) | ('5' << 24); + public const uint DXT5 = 'D' | ('X' << 8) | ('T' << 16) | ('5' << 24); - public const uint MET1 = 'M' | ('E' << 8) | ('T' << 16) | ('1' << 24); + public const uint MET1 = 'M' | ('E' << 8) | ('T' << 16) | ('1' << 24); - public const uint BC4U = 'B' | ('C' << 8) | ('4' << 16) | ('U' << 24); + public const uint BC4U = 'B' | ('C' << 8) | ('4' << 16) | ('U' << 24); - public const uint BC4S = 'B' | ('C' << 8) | ('4' << 16) | ('S' << 24); + public const uint BC4S = 'B' | ('C' << 8) | ('4' << 16) | ('S' << 24); - public const uint BC5U = 'B' | ('C' << 8) | ('5' << 16) | ('U' << 24); + public const uint BC5U = 'B' | ('C' << 8) | ('5' << 16) | ('U' << 24); - public const uint BC5S = 'B' | ('C' << 8) | ('5' << 16) | ('S' << 24); + public const uint BC5S = 'B' | ('C' << 8) | ('5' << 16) | ('S' << 24); - public const uint ATI1 = 'A' | ('T' << 8) | ('I' << 16) | ('1' << 24); + public const uint ATI1 = 'A' | ('T' << 8) | ('I' << 16) | ('1' << 24); - public const uint ATI2 = 'A' | ('T' << 8) | ('I' << 16) | ('2' << 24); + public const uint ATI2 = 'A' | ('T' << 8) | ('I' << 16) | ('2' << 24); - // DXGI_FORMAT_R16G16B16A16_UNORM - public const uint R16G16B16A16UNORM = 36; + // DXGI_FORMAT_R16G16B16A16_UNORM + public const uint R16G16B16A16UNORM = 36; - // DXGI_FORMAT_R16G16B16A16_SNORM - public const uint R16G16B16A16SNORM = 110; + // DXGI_FORMAT_R16G16B16A16_SNORM + public const uint R16G16B16A16SNORM = 110; - // DXGI_FORMAT_R16_FLOAT - public const uint R16FLOAT = 111; + // DXGI_FORMAT_R16_FLOAT + public const uint R16FLOAT = 111; - // DXGI_FORMAT_R16G16_FLOAT - public const uint R16G16FLOAT = 112; + // DXGI_FORMAT_R16G16_FLOAT + public const uint R16G16FLOAT = 112; - // D3DFMT_A16B16G16R16F - public const uint R16G16B16A16FLOAT = 113; + // D3DFMT_A16B16G16R16F + public const uint R16G16B16A16FLOAT = 113; - // DXGI_FORMAT_R32_FLOAT - public const uint R32FLOAT = 114; + // DXGI_FORMAT_R32_FLOAT + public const uint R32FLOAT = 114; - // DXGI_FORMAT_R32G32_FLOAT - public const uint R32G32FLOAT = 115; + // DXGI_FORMAT_R32G32_FLOAT + public const uint R32G32FLOAT = 115; - // DXGI_FORMAT_R32G32B32A32_FLOAT - public const uint R32G32B32A32FLOAT = 116; - } + // DXGI_FORMAT_R32G32B32A32_FLOAT + public const uint R32G32B32A32FLOAT = 116; } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsHeader.cs b/src/ImageSharp.Textures/Formats/Dds/DdsHeader.cs index 27e1271e..4a4280ba 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsHeader.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsHeader.cs @@ -1,236 +1,234 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// Describes a DDS file header. +/// +/// +/// Note When you write .dds files, you should set the and +/// flags, and for mipmapped textures you should also set the +/// flag. +/// However, when you read a .dds file, you should not rely on the , +/// , and +/// flags being set because some writers of such a file might not set these flags. +/// Include flags in for the members of the structure that contain valid data. Use this +/// structure in combination with a to store a resource array in a DDS file. +/// For more information, see texture arrays. +/// is identical to the DirectDraw DDSURFACEDESC2 structure without DirectDraw +/// dependencies. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal struct DdsHeader { + public DdsHeader( + uint size, + DdsFlags flags, + uint height, + uint width, + uint pitchOrLinearSize, + uint depth, + uint mipMapCount, + uint[] reserved1, + DdsPixelFormat pixelFormat, + DdsCaps1 caps1, + DdsCaps2 caps2, + uint caps3, + uint caps4, + uint reserved2) + { + this.Size = size; + this.Flags = flags; + this.Height = height; + this.Width = width; + this.PitchOrLinearSize = pitchOrLinearSize; + this.Depth = depth; + this.MipMapCount = mipMapCount; + this.Reserved1 = reserved1; + this.PixelFormat = pixelFormat; + this.Caps1 = caps1; + this.Caps2 = caps2; + this.Caps3 = caps3; + this.Caps4 = caps4; + this.Reserved2 = reserved2; + } + /// - /// Describes a DDS file header. + /// Gets size of structure. This member must be set to 124. + /// + public uint Size { get; } + + /// + /// Gets flags to indicate which members contain valid data. /// /// - /// Note When you write .dds files, you should set the and - /// flags, and for mipmapped textures you should also set the - /// flag. + /// When you write .dds files, you should set the and + /// flags, + /// and for mipmapped textures you should also set the flag. /// However, when you read a .dds file, you should not rely on the , /// , and /// flags being set because some writers of such a file might not set these flags. - /// Include flags in for the members of the structure that contain valid data. Use this - /// structure in combination with a to store a resource array in a DDS file. - /// For more information, see texture arrays. - /// is identical to the DirectDraw DDSURFACEDESC2 structure without DirectDraw - /// dependencies. /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct DdsHeader + public DdsFlags Flags { get; } + + /// + /// Gets surface height (in pixels). + /// + public uint Height { get; } + + /// + /// Gets surface width (in pixels). + /// + public uint Width { get; } + + /// + /// Gets the pitch or number of bytes per scan line in an uncompressed texture; + /// the total number of bytes in the top level texture for a compressed texture. + /// + public uint PitchOrLinearSize { get; } + + /// + /// Gets depth of a volume texture (in pixels), otherwise unused. + /// + public uint Depth { get; } + + /// + /// Gets number of mipmap levels, otherwise unused. + /// + public uint MipMapCount { get; } + + /// + /// Gets unused. + /// + public uint[] Reserved1 { get; } + + /// + /// Gets the pixel format. + /// + public DdsPixelFormat PixelFormat { get; } + + /// + /// Gets specifies the complexity of the surfaces stored. + /// + /// + /// When you write .dds files, you should set the flag, + /// and for multiple surfaces you should also set the flag. + /// However, when you read a .dds file, you should not rely on the and + /// flags being set because some file writers might not set these flags. + /// + public DdsCaps1 Caps1 { get; } + + /// + /// Gets defines additional capabilities of the surface. + /// + public DdsCaps2 Caps2 { get; } + + /// + /// Gets unused. + /// + public uint Caps3 { get; } + + /// + /// Gets unused. + /// + public uint Caps4 { get; } + + /// + /// Gets unused. + /// + public uint Reserved2 { get; } + + /// + /// Validates the dds header. + /// + /// + /// Thrown if the image does not pass validation. + /// + public readonly void Validate() { - public DdsHeader( - uint size, - DdsFlags flags, - uint height, - uint width, - uint pitchOrLinearSize, - uint depth, - uint mipMapCount, - uint[] reserved1, - DdsPixelFormat pixelFormat, - DdsCaps1 caps1, - DdsCaps2 caps2, - uint caps3, - uint caps4, - uint reserved2) + bool incorrectSize = (this.Size != DdsConstants.DdsHeaderSize) || (this.PixelFormat.Size != DdsConstants.DdsPixelFormatSize); + if (incorrectSize) { - this.Size = size; - this.Flags = flags; - this.Height = height; - this.Width = width; - this.PitchOrLinearSize = pitchOrLinearSize; - this.Depth = depth; - this.MipMapCount = mipMapCount; - this.Reserved1 = reserved1; - this.PixelFormat = pixelFormat; - this.Caps1 = caps1; - this.Caps2 = caps2; - this.Caps3 = caps3; - this.Caps4 = caps4; - this.Reserved2 = reserved2; + throw new NotSupportedException("Invalid structure size."); } - /// - /// Gets size of structure. This member must be set to 124. - /// - public uint Size { get; } - - /// - /// Gets flags to indicate which members contain valid data. - /// - /// - /// When you write .dds files, you should set the and - /// flags, - /// and for mipmapped textures you should also set the flag. - /// However, when you read a .dds file, you should not rely on the , - /// , and - /// flags being set because some writers of such a file might not set these flags. - /// - public DdsFlags Flags { get; } - - /// - /// Gets surface height (in pixels). - /// - public uint Height { get; } - - /// - /// Gets surface width (in pixels). - /// - public uint Width { get; } - - /// - /// Gets the pitch or number of bytes per scan line in an uncompressed texture; - /// the total number of bytes in the top level texture for a compressed texture. - /// - public uint PitchOrLinearSize { get; } - - /// - /// Gets depth of a volume texture (in pixels), otherwise unused. - /// - public uint Depth { get; } - - /// - /// Gets number of mipmap levels, otherwise unused. - /// - public uint MipMapCount { get; } - - /// - /// Gets unused. - /// - public uint[] Reserved1 { get; } - - /// - /// Gets the pixel format. - /// - public DdsPixelFormat PixelFormat { get; } - - /// - /// Gets specifies the complexity of the surfaces stored. - /// - /// - /// When you write .dds files, you should set the flag, - /// and for multiple surfaces you should also set the flag. - /// However, when you read a .dds file, you should not rely on the and - /// flags being set because some file writers might not set these flags. - /// - public DdsCaps1 Caps1 { get; } - - /// - /// Gets defines additional capabilities of the surface. - /// - public DdsCaps2 Caps2 { get; } - - /// - /// Gets unused. - /// - public uint Caps3 { get; } - - /// - /// Gets unused. - /// - public uint Caps4 { get; } - - /// - /// Gets unused. - /// - public uint Reserved2 { get; } - - /// - /// Validates the dds header. - /// - /// - /// Thrown if the image does not pass validation. - /// - public void Validate() + bool requiredFlagsMissing = (this.Flags & DdsFlags.Caps) == 0 || (this.Flags & DdsFlags.PixelFormat) == 0 || (this.Caps1 & DdsCaps1.Texture) == 0; + if (requiredFlagsMissing) { - bool incorrectSize = (this.Size != DdsConstants.DdsHeaderSize) || (this.PixelFormat.Size != DdsConstants.DdsPixelFormatSize); - if (incorrectSize) - { - throw new NotSupportedException("Invalid structure size."); - } - - bool requiredFlagsMissing = (this.Flags & DdsFlags.Caps) == 0 || (this.Flags & DdsFlags.PixelFormat) == 0 || (this.Caps1 & DdsCaps1.Texture) == 0; - if (requiredFlagsMissing) - { - throw new NotSupportedException("Required flags missing."); - } - - bool hasInvalidCompression = (this.Flags & DdsFlags.Pitch) != 0 && (this.Flags & DdsFlags.LinearSize) != 0; - if (hasInvalidCompression) - { - throw new NotSupportedException("Invalid compression."); - } + throw new NotSupportedException("Required flags missing."); } - public void WriteTo(Span buffer) + bool hasInvalidCompression = (this.Flags & DdsFlags.Pitch) != 0 && (this.Flags & DdsFlags.LinearSize) != 0; + if (hasInvalidCompression) { - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(0, 4), this.Size); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(4, 4), (uint)this.Flags); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8, 4), this.Height); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(12, 4), this.Width); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(16, 4), this.PitchOrLinearSize); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(20, 4), this.Depth); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(24, 4), this.MipMapCount); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(28, 4), this.Reserved1[0]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(32, 4), this.Reserved1[1]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(36, 4), this.Reserved1[2]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(40, 4), this.Reserved1[3]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(44, 4), this.Reserved1[4]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(48, 4), this.Reserved1[5]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(52, 4), this.Reserved1[6]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(56, 4), this.Reserved1[7]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(60, 4), this.Reserved1[8]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(64, 4), this.Reserved1[9]); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(68, 4), this.Reserved1[10]); - this.PixelFormat.WriteTo(buffer, 72); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(104, 4), (uint)this.Caps1); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(108, 4), (uint)this.Caps2); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(112, 4), this.Caps3); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(116, 4), this.Caps4); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(120, 4), this.Reserved2); + throw new NotSupportedException("Invalid compression."); } + } - public static DdsHeader Parse(Span data) - { - uint[] reserved1 = - { - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(28, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(32, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(36, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(40, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(44, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(48, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(52, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(56, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(60, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(64, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(68, 4)) - }; - - return new DdsHeader( - size: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(0, 4)), - flags: (DdsFlags)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(8, 4)), - width: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(12, 4)), - pitchOrLinearSize: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(16, 4)), - depth: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(20, 4)), - mipMapCount: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(24, 4)), - reserved1: reserved1, - pixelFormat: DdsPixelFormat.Parse(data, 72), - caps1: (DdsCaps1)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(104, 4)), - caps2: (DdsCaps2)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(108, 4)), - caps3: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(112, 4)), - caps4: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(116, 4)), - reserved2: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(120, 4))); - } + public readonly void WriteTo(Span buffer) + { + BinaryPrimitives.WriteUInt32LittleEndian(buffer[..4], this.Size); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(4, 4), (uint)this.Flags); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8, 4), this.Height); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(12, 4), this.Width); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(16, 4), this.PitchOrLinearSize); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(20, 4), this.Depth); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(24, 4), this.MipMapCount); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(28, 4), this.Reserved1[0]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(32, 4), this.Reserved1[1]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(36, 4), this.Reserved1[2]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(40, 4), this.Reserved1[3]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(44, 4), this.Reserved1[4]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(48, 4), this.Reserved1[5]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(52, 4), this.Reserved1[6]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(56, 4), this.Reserved1[7]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(60, 4), this.Reserved1[8]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(64, 4), this.Reserved1[9]); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(68, 4), this.Reserved1[10]); + this.PixelFormat.WriteTo(buffer, 72); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(104, 4), (uint)this.Caps1); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(108, 4), (uint)this.Caps2); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(112, 4), this.Caps3); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(116, 4), this.Caps4); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(120, 4), this.Reserved2); + } + + public static DdsHeader Parse(Span data) + { + uint[] reserved1 = + [ + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(28, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(32, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(36, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(40, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(44, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(48, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(52, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(56, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(60, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(64, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(68, 4)) + ]; + + return new DdsHeader( + size: BinaryPrimitives.ReadUInt32LittleEndian(data[..4]), + flags: (DdsFlags)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(8, 4)), + width: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(12, 4)), + pitchOrLinearSize: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(16, 4)), + depth: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(20, 4)), + mipMapCount: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(24, 4)), + reserved1: reserved1, + pixelFormat: DdsPixelFormat.Parse(data, 72), + caps1: (DdsCaps1)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(104, 4)), + caps2: (DdsCaps2)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(108, 4)), + caps3: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(112, 4)), + caps4: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(116, 4)), + reserved2: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(120, 4))); } } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsHeaderDxt10.cs b/src/ImageSharp.Textures/Formats/Dds/DdsHeaderDxt10.cs index 25494921..c52fa550 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsHeaderDxt10.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsHeaderDxt10.cs @@ -1,111 +1,109 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// DDS header extension to handle resource arrays. +/// +/// +/// Use this structure together with a to store a resource array in a DDS file. +/// For more information, see texture arrays. +/// This header is present if the member of the +/// structure is set to 'DX10'. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal struct DdsHeaderDxt10 { - /// - /// DDS header extension to handle resource arrays. - /// - /// - /// Use this structure together with a to store a resource array in a DDS file. - /// For more information, see texture arrays. - /// This header is present if the member of the - /// structure is set to 'DX10'. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct DdsHeaderDxt10 + public DdsHeaderDxt10( + DxgiFormat dxgiFormat, + D3d10ResourceDimension resourceDimension, + D3d10ResourceMiscFlags miscFlag, + uint arraySize, + uint reserved) { - public DdsHeaderDxt10( - DxgiFormat dxgiFormat, - D3d10ResourceDimension resourceDimension, - D3d10ResourceMiscFlags miscFlag, - uint arraySize, - uint reserved) - { - this.DxgiFormat = dxgiFormat; - this.ResourceDimension = resourceDimension; - this.MiscFlag = miscFlag; - this.ArraySize = arraySize; - this.Reserved = reserved; - } - - /// - /// Gets the surface pixel format. - /// - public DxgiFormat DxgiFormat { get; } + this.DxgiFormat = dxgiFormat; + this.ResourceDimension = resourceDimension; + this.MiscFlag = miscFlag; + this.ArraySize = arraySize; + this.Reserved = reserved; + } - /// - /// Gets identifies the type of resource. - /// The following values for this member are a subset of the values in the - /// enumeration: - /// : - /// Resource is a 1D texture. The member of - /// specifies the size of the texture. Typically, you set the member of - /// to 1; you also must set the flag in - /// the member of . - /// : - /// Resource is a 2D texture with an area specified by the and - /// members of . - /// You can also use this type to identify a cube-map texture. For more information about how to identify a - /// cube-map texture, see and members. - /// : - /// Resource is a 3D texture with a volume specified by the , - /// , and members of - /// . You also must set the flag - /// in the member of . - /// - public D3d10ResourceDimension ResourceDimension { get; } + /// + /// Gets the surface pixel format. + /// + public DxgiFormat DxgiFormat { get; } - /// - /// Gets identifies other, less common options for resources. The following value for this member is a - /// subset of the values in the enumeration: - /// DDS_RESOURCE_MISC_TEXTURECUBE Indicates a 2D texture is a cube-map texture. - /// - public D3d10ResourceMiscFlags MiscFlag { get; } + /// + /// Gets identifies the type of resource. + /// The following values for this member are a subset of the values in the + /// enumeration: + /// : + /// Resource is a 1D texture. The member of + /// specifies the size of the texture. Typically, you set the member of + /// to 1; you also must set the flag in + /// the member of . + /// : + /// Resource is a 2D texture with an area specified by the and + /// members of . + /// You can also use this type to identify a cube-map texture. For more information about how to identify a + /// cube-map texture, see and members. + /// : + /// Resource is a 3D texture with a volume specified by the , + /// , and members of + /// . You also must set the flag + /// in the member of . + /// + public D3d10ResourceDimension ResourceDimension { get; } - /// - /// Gets the number of elements in the array. - /// For a 2D texture that is also a cube-map texture, this number represents the number of cubes. - /// This number is the same as the number in the NumCubes member of D3D10_TEXCUBE_ARRAY_SRV1 or - /// D3D11_TEXCUBE_ARRAY_SRV). In this case, the DDS file contains arraySize*6 2D textures. - /// For more information about this case, see the description. - /// For a 3D texture, you must set this number to 1. - /// - public uint ArraySize { get; } + /// + /// Gets identifies other, less common options for resources. The following value for this member is a + /// subset of the values in the enumeration: + /// DDS_RESOURCE_MISC_TEXTURECUBE Indicates a 2D texture is a cube-map texture. + /// + public D3d10ResourceMiscFlags MiscFlag { get; } - /// - /// Gets reserved for future use. - /// - public uint Reserved { get; } + /// + /// Gets the number of elements in the array. + /// For a 2D texture that is also a cube-map texture, this number represents the number of cubes. + /// This number is the same as the number in the NumCubes member of D3D10_TEXCUBE_ARRAY_SRV1 or + /// D3D11_TEXCUBE_ARRAY_SRV). In this case, the DDS file contains arraySize*6 2D textures. + /// For more information about this case, see the description. + /// For a 3D texture, you must set this number to 1. + /// + public uint ArraySize { get; } - /// - /// Writes the header to the given buffer. - /// - /// The buffer to write to. - public void WriteTo(Span buffer) - { - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(0, 4), (uint)this.DxgiFormat); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(4, 4), (uint)this.ResourceDimension); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8, 4), (uint)this.MiscFlag); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(12, 4), this.ArraySize); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(16, 4), this.Reserved); - } + /// + /// Gets reserved for future use. + /// + public uint Reserved { get; } - /// - /// Parses the DDS_HEADER_DXT10 from the given data buffer. - /// - /// The data to parse. - /// The parsed DDS_HEADER_DXT10. - public static DdsHeaderDxt10 Parse(Span data) => new DdsHeaderDxt10( - dxgiFormat: (DxgiFormat)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(0, 4)), - resourceDimension: (D3d10ResourceDimension)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(4, 4)), - miscFlag: (D3d10ResourceMiscFlags)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(8, 4)), - arraySize: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(12, 4)), - reserved: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(16, 4))); + /// + /// Writes the header to the given buffer. + /// + /// The buffer to write to. + public readonly void WriteTo(Span buffer) + { + BinaryPrimitives.WriteUInt32LittleEndian(buffer[..4], (uint)this.DxgiFormat); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(4, 4), (uint)this.ResourceDimension); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8, 4), (uint)this.MiscFlag); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(12, 4), this.ArraySize); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(16, 4), this.Reserved); } + + /// + /// Parses the DDS_HEADER_DXT10 from the given data buffer. + /// + /// The data to parse. + /// The parsed DDS_HEADER_DXT10. + public static DdsHeaderDxt10 Parse(Span data) => new( + dxgiFormat: (DxgiFormat)BinaryPrimitives.ReadUInt32LittleEndian(data[..4]), + resourceDimension: (D3d10ResourceDimension)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(4, 4)), + miscFlag: (D3d10ResourceMiscFlags)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(8, 4)), + arraySize: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(12, 4)), + reserved: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(16, 4))); } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsImageFormatDetector.cs b/src/ImageSharp.Textures/Formats/Dds/DdsImageFormatDetector.cs index 0a352438..b4248348 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsImageFormatDetector.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsImageFormatDetector.cs @@ -1,31 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// Detects dds file headers. +/// +public sealed class DdsImageFormatDetector : ITextureFormatDetector { - /// - /// Detects dds file headers. - /// - public sealed class DdsImageFormatDetector : ITextureFormatDetector - { - /// - public int HeaderSize => 8; + /// + public int HeaderSize => 8; - /// - public ITextureFormat? DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? DdsFormat.Instance : null; + /// + public ITextureFormat? DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? DdsFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) { - if (header.Length >= this.HeaderSize) - { - uint magicValue = BinaryPrimitives.ReadUInt32LittleEndian(header); - return magicValue == DdsFourCc.DdsMagicWord; - } - - return false; + uint magicValue = BinaryPrimitives.ReadUInt32LittleEndian(header); + return magicValue == DdsFourCc.DdsMagicWord; } + + return false; } } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsPixelFormat.cs b/src/ImageSharp.Textures/Formats/Dds/DdsPixelFormat.cs index 76c4c46c..4d969a89 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsPixelFormat.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsPixelFormat.cs @@ -1,320 +1,318 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// Surface pixel format. +/// +/// To store DXGI formats such as floating-point data, use a of +/// and set to +/// 'D','X','1','0'. Use the extension header to store the DXGI format in the +/// member. +/// Note that there are non-standard variants of DDS files where has +/// and the value +/// is set directly to a D3DFORMAT or enumeration value. It is not possible to +/// disambiguate the D3DFORMAT versus values using this non-standard scheme, so the +/// DX10 extension header is recommended instead. +/// +[StructLayout(LayoutKind.Sequential, Size = 32, Pack = 1)] +internal struct DdsPixelFormat { - /// Surface pixel format. - /// - /// To store DXGI formats such as floating-point data, use a of - /// and set to - /// 'D','X','1','0'. Use the extension header to store the DXGI format in the - /// member. - /// Note that there are non-standard variants of DDS files where has - /// and the value - /// is set directly to a D3DFORMAT or enumeration value. It is not possible to - /// disambiguate the D3DFORMAT versus values using this non-standard scheme, so the - /// DX10 extension header is recommended instead. - /// - [StructLayout(LayoutKind.Sequential, Size = 32, Pack = 1)] - internal struct DdsPixelFormat + public DdsPixelFormat( + uint size, + DdsPixelFormatFlags flags, + uint fourCC, + uint rgbBitCount, + uint rBitMask, + uint gBitMask, + uint bBitMask, + uint aBitMask) { - public DdsPixelFormat( - uint size, - DdsPixelFormatFlags flags, - uint fourCC, - uint rgbBitCount, - uint rBitMask, - uint gBitMask, - uint bBitMask, - uint aBitMask) - { - this.Size = size; - this.Flags = flags; - this.FourCC = fourCC; - this.RGBBitCount = rgbBitCount; - this.RBitMask = rBitMask; - this.GBitMask = gBitMask; - this.BBitMask = bBitMask; - this.ABitMask = aBitMask; - } + this.Size = size; + this.Flags = flags; + this.FourCC = fourCC; + this.RGBBitCount = rgbBitCount; + this.RBitMask = rBitMask; + this.GBitMask = gBitMask; + this.BBitMask = bBitMask; + this.ABitMask = aBitMask; + } - /// - /// Gets Structure size; set to 32 (bytes). - /// - public uint Size { get; } - - /// - /// Gets Values which indicate what type of data is in the surface. - /// - public DdsPixelFormatFlags Flags { get; } - - /// - /// Gets Four-character codes for specifying compressed or custom formats. - /// Possible values include: DXT1, DXT2, DXT3, DXT4, or DXT5. A FOURCC of DX10 indicates the prescense of - /// the extended header, and the - /// member of that structure indicates the - /// true format. When using a four-character code, must include - /// . - /// - public uint FourCC { get; } - - /// - /// Gets Number of bits in an RGB (possibly including alpha) format. Valid when - /// includes , or , - /// or . - /// - public uint RGBBitCount { get; } - - /// - /// Gets Red (or lumiannce or Y) mask for reading color data. For instance, given the A8R8G8B8 format, - /// the red mask would be 0x00ff0000. - /// - public uint RBitMask { get; } - - /// - /// Gets Green (or U) mask for reading color data. For instance, given the A8R8G8B8 format, - /// the green mask would be 0x0000ff00. - /// - public uint GBitMask { get; } - - /// - /// Gets Blue (or V) mask for reading color data. For instance, given the A8R8G8B8 format, - /// the blue mask would be 0x000000ff. - /// - public uint BBitMask { get; } - - /// - /// Gets Alpha mask for reading alpha data. dwFlags must include - /// or . - /// For instance, given the A8R8G8B8 format, the alpha mask would be 0xff000000. - /// - public uint ABitMask { get; } - - /// - /// Writes the header to the given buffer. - /// - /// The buffer to write to. - /// Offset in the buffer. - public void WriteTo(Span buffer, int offset) - { - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset, 4), this.Size); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 4, 4), (uint)this.Flags); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8, 4), this.FourCC); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12, 4), this.RGBBitCount); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16, 4), this.RBitMask); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20, 4), this.GBitMask); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 24, 4), this.BBitMask); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 28, 4), this.ABitMask); - } + /// + /// Gets Structure size; set to 32 (bytes). + /// + public uint Size { get; } + + /// + /// Gets Values which indicate what type of data is in the surface. + /// + public DdsPixelFormatFlags Flags { get; } + + /// + /// Gets Four-character codes for specifying compressed or custom formats. + /// Possible values include: DXT1, DXT2, DXT3, DXT4, or DXT5. A FOURCC of DX10 indicates the prescense of + /// the extended header, and the + /// member of that structure indicates the + /// true format. When using a four-character code, must include + /// . + /// + public uint FourCC { get; } + + /// + /// Gets Number of bits in an RGB (possibly including alpha) format. Valid when + /// includes , or , + /// or . + /// + public uint RGBBitCount { get; } + + /// + /// Gets Red (or lumiannce or Y) mask for reading color data. For instance, given the A8R8G8B8 format, + /// the red mask would be 0x00ff0000. + /// + public uint RBitMask { get; } + + /// + /// Gets Green (or U) mask for reading color data. For instance, given the A8R8G8B8 format, + /// the green mask would be 0x0000ff00. + /// + public uint GBitMask { get; } + + /// + /// Gets Blue (or V) mask for reading color data. For instance, given the A8R8G8B8 format, + /// the blue mask would be 0x000000ff. + /// + public uint BBitMask { get; } + + /// + /// Gets Alpha mask for reading alpha data. dwFlags must include + /// or . + /// For instance, given the A8R8G8B8 format, the alpha mask would be 0xff000000. + /// + public uint ABitMask { get; } + + /// + /// Writes the header to the given buffer. + /// + /// The buffer to write to. + /// Offset in the buffer. + public readonly void WriteTo(Span buffer, int offset) + { + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset, 4), this.Size); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 4, 4), (uint)this.Flags); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8, 4), this.FourCC); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12, 4), this.RGBBitCount); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16, 4), this.RBitMask); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20, 4), this.GBitMask); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 24, 4), this.BBitMask); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 28, 4), this.ABitMask); + } - /// - /// Parses the DdsPixelFormat from the given data buffer. - /// - /// The data to parse. - /// Offset in the buffer. - /// The parsed DdsPixelFormat. - public static DdsPixelFormat Parse(Span data, int offset) => - new DdsPixelFormat( - size: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset, 4)), - flags: (DdsPixelFormatFlags)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 4, 4)), - fourCC: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 8, 4)), - rgbBitCount: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 12, 4)), - rBitMask: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 16, 4)), - gBitMask: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 20, 4)), - bBitMask: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 24, 4)), - aBitMask: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 28, 4))); - - /// - /// Gets the represented by this structure. - /// - /// - /// The represented by this structure. - /// - public D3dFormat GetD3DFormat() + /// + /// Parses the DdsPixelFormat from the given data buffer. + /// + /// The data to parse. + /// Offset in the buffer. + /// The parsed DdsPixelFormat. + public static DdsPixelFormat Parse(Span data, int offset) => + new( + size: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset, 4)), + flags: (DdsPixelFormatFlags)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 4, 4)), + fourCC: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 8, 4)), + rgbBitCount: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 12, 4)), + rBitMask: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 16, 4)), + gBitMask: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 20, 4)), + bBitMask: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 24, 4)), + aBitMask: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset + 28, 4))); + + /// + /// Gets the represented by this structure. + /// + /// + /// The represented by this structure. + /// + public D3dFormat GetD3DFormat() + { + if ((this.Flags & DdsPixelFormatFlags.RGBA) == DdsPixelFormatFlags.RGBA) { - if ((this.Flags & DdsPixelFormatFlags.RGBA) == DdsPixelFormatFlags.RGBA) + switch (this.RGBBitCount) { - switch (this.RGBBitCount) - { - case 32: - return this.GetRgba32(); - case 16: - return this.GetRgba16(); - } - } - else if ((this.Flags & DdsPixelFormatFlags.RGB) == DdsPixelFormatFlags.RGB) - { - switch (this.RGBBitCount) - { - case 32: - return this.GetRgb32(); - case 24: - return this.GetRgb24(); - case 16: - return this.GetRgb16(); - } + case 32: + return this.GetRgba32(); + case 16: + return this.GetRgba16(); } - else if ((this.Flags & DdsPixelFormatFlags.Alpha) == DdsPixelFormatFlags.Alpha) + } + else if ((this.Flags & DdsPixelFormatFlags.RGB) == DdsPixelFormatFlags.RGB) + { + switch (this.RGBBitCount) { - if (this.RGBBitCount == 8) - { - if (this.ABitMask == 0xff) - { - return D3dFormat.A8; - } - } + case 32: + return this.GetRgb32(); + case 24: + return this.GetRgb24(); + case 16: + return this.GetRgb16(); } - else if ((this.Flags & DdsPixelFormatFlags.Luminance) == DdsPixelFormatFlags.Luminance) + } + else if ((this.Flags & DdsPixelFormatFlags.Alpha) == DdsPixelFormatFlags.Alpha) + { + if (this.RGBBitCount == 8) { - switch (this.RGBBitCount) + if (this.ABitMask == 0xff) { - case 16: - return this.GetLumi16(); - case 8: - return this.GetLumi8(); + return D3dFormat.A8; } } - else if ((this.Flags & DdsPixelFormatFlags.FourCC) == DdsPixelFormatFlags.FourCC) - { - return (D3dFormat)this.FourCC; - } - - return D3dFormat.Unknown; } - - private D3dFormat GetRgba32() + else if ((this.Flags & DdsPixelFormatFlags.Luminance) == DdsPixelFormatFlags.Luminance) { - if (this.RBitMask == 0xff && this.GBitMask == 0xff00 && this.BBitMask == 0xff0000 && this.ABitMask == 0xff000000) - { - return D3dFormat.A8B8G8R8; - } - - if (this.RBitMask == 0xffff && this.GBitMask == 0xffff0000) + switch (this.RGBBitCount) { - return D3dFormat.G16R16; + case 16: + return this.GetLumi16(); + case 8: + return this.GetLumi8(); } + } + else if ((this.Flags & DdsPixelFormatFlags.FourCC) == DdsPixelFormatFlags.FourCC) + { + return (D3dFormat)this.FourCC; + } - if (this.RBitMask == 0x3ff && this.GBitMask == 0xffc00 && this.BBitMask == 0x3ff00000) - { - return D3dFormat.A2B10G10R10; - } + return D3dFormat.Unknown; + } - if (this.RBitMask == 0xff0000 && this.GBitMask == 0xff00 && this.BBitMask == 0xff && this.ABitMask == 0xff000000) - { - return D3dFormat.A8R8G8B8; - } + private readonly D3dFormat GetRgba32() + { + if (this.RBitMask == 0xff && this.GBitMask == 0xff00 && this.BBitMask == 0xff0000 && this.ABitMask == 0xff000000) + { + return D3dFormat.A8B8G8R8; + } - if (this.RBitMask == 0x3ff00000 && this.GBitMask == 0xffc00 && this.BBitMask == 0x3ff && this.ABitMask == 0xc0000000) - { - return D3dFormat.A2R10G10B10; - } + if (this.RBitMask == 0xffff && this.GBitMask == 0xffff0000) + { + return D3dFormat.G16R16; + } - return D3dFormat.Unknown; + if (this.RBitMask == 0x3ff && this.GBitMask == 0xffc00 && this.BBitMask == 0x3ff00000) + { + return D3dFormat.A2B10G10R10; } - private D3dFormat GetRgba16() + if (this.RBitMask == 0xff0000 && this.GBitMask == 0xff00 && this.BBitMask == 0xff && this.ABitMask == 0xff000000) { - if (this.RBitMask == 0x7c00 && this.GBitMask == 0x3e0 && this.BBitMask == 0x1f && this.ABitMask == 0x8000) - { - return D3dFormat.A1R5G5B5; - } + return D3dFormat.A8R8G8B8; + } - if (this.RBitMask == 0xf00 && this.GBitMask == 0xf0 && this.BBitMask == 0xf && this.ABitMask == 0xf000) - { - return D3dFormat.A4R4G4B4; - } + if (this.RBitMask == 0x3ff00000 && this.GBitMask == 0xffc00 && this.BBitMask == 0x3ff && this.ABitMask == 0xc0000000) + { + return D3dFormat.A2R10G10B10; + } - if (this.RBitMask == 0xe0 && this.GBitMask == 0x1c && this.BBitMask == 0x3 && this.ABitMask == 0xff00) - { - return D3dFormat.A8R3G3B2; - } + return D3dFormat.Unknown; + } - return D3dFormat.Unknown; + private readonly D3dFormat GetRgba16() + { + if (this.RBitMask == 0x7c00 && this.GBitMask == 0x3e0 && this.BBitMask == 0x1f && this.ABitMask == 0x8000) + { + return D3dFormat.A1R5G5B5; } - private D3dFormat GetRgb32() + if (this.RBitMask == 0xf00 && this.GBitMask == 0xf0 && this.BBitMask == 0xf && this.ABitMask == 0xf000) { - if (this.RBitMask == 0xffff && this.GBitMask == 0xffff0000) - { - return D3dFormat.G16R16; - } + return D3dFormat.A4R4G4B4; + } - if (this.RBitMask == 0xff0000 && this.GBitMask == 0xff00 && this.BBitMask == 0xff) - { - return D3dFormat.X8R8G8B8; - } + if (this.RBitMask == 0xe0 && this.GBitMask == 0x1c && this.BBitMask == 0x3 && this.ABitMask == 0xff00) + { + return D3dFormat.A8R3G3B2; + } - if (this.RBitMask == 0xff && this.GBitMask == 0xff00 && this.BBitMask == 0xff0000) - { - return D3dFormat.X8B8G8R8; - } + return D3dFormat.Unknown; + } - return D3dFormat.Unknown; + private readonly D3dFormat GetRgb32() + { + if (this.RBitMask == 0xffff && this.GBitMask == 0xffff0000) + { + return D3dFormat.G16R16; } - private D3dFormat GetRgb24() + if (this.RBitMask == 0xff0000 && this.GBitMask == 0xff00 && this.BBitMask == 0xff) { - if (this.RBitMask == 0xff0000 && this.GBitMask == 0xff00 && this.BBitMask == 0xff) - { - return D3dFormat.R8G8B8; - } + return D3dFormat.X8R8G8B8; + } - return D3dFormat.Unknown; + if (this.RBitMask == 0xff && this.GBitMask == 0xff00 && this.BBitMask == 0xff0000) + { + return D3dFormat.X8B8G8R8; } - private D3dFormat GetRgb16() + return D3dFormat.Unknown; + } + + private readonly D3dFormat GetRgb24() + { + if (this.RBitMask == 0xff0000 && this.GBitMask == 0xff00 && this.BBitMask == 0xff) { - if (this.RBitMask == 0xf800 && this.GBitMask == 0x7e0 && this.BBitMask == 0x1f) - { - return D3dFormat.R5G6B5; - } + return D3dFormat.R8G8B8; + } - if (this.RBitMask == 0x7c00 && this.GBitMask == 0x3e0 && this.BBitMask == 0x1f) - { - return D3dFormat.X1R5G5B5; - } + return D3dFormat.Unknown; + } - if (this.RBitMask == 0xf00 && this.GBitMask == 0xf0 && this.BBitMask == 0xf) - { - return D3dFormat.X4R4G4B4; - } + private readonly D3dFormat GetRgb16() + { + if (this.RBitMask == 0xf800 && this.GBitMask == 0x7e0 && this.BBitMask == 0x1f) + { + return D3dFormat.R5G6B5; + } - return D3dFormat.Unknown; + if (this.RBitMask == 0x7c00 && this.GBitMask == 0x3e0 && this.BBitMask == 0x1f) + { + return D3dFormat.X1R5G5B5; } - private D3dFormat GetLumi16() + if (this.RBitMask == 0xf00 && this.GBitMask == 0xf0 && this.BBitMask == 0xf) { - if (this.RBitMask == 0xff && this.ABitMask == 0xff00) - { - return D3dFormat.A8L8; - } + return D3dFormat.X4R4G4B4; + } - if (this.RBitMask == 0xffff) - { - return D3dFormat.L16; - } + return D3dFormat.Unknown; + } - return D3dFormat.Unknown; + private readonly D3dFormat GetLumi16() + { + if (this.RBitMask == 0xff && this.ABitMask == 0xff00) + { + return D3dFormat.A8L8; } - private D3dFormat GetLumi8() + if (this.RBitMask == 0xffff) { - if (this.RBitMask == 0xf && this.ABitMask == 0xf0) - { - return D3dFormat.A4L4; - } + return D3dFormat.L16; + } - if (this.RBitMask == 0xff) - { - return D3dFormat.L8; - } + return D3dFormat.Unknown; + } - return D3dFormat.Unknown; + private readonly D3dFormat GetLumi8() + { + if (this.RBitMask == 0xf && this.ABitMask == 0xf0) + { + return D3dFormat.A4L4; + } + + if (this.RBitMask == 0xff) + { + return D3dFormat.L8; } + + return D3dFormat.Unknown; } } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsProcessor.cs b/src/ImageSharp.Textures/Formats/Dds/DdsProcessor.cs index 2c976ff0..15d6a2c4 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsProcessor.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Textures.Common.Exceptions; using SixLabors.ImageSharp.Textures.Common.Extensions; using SixLabors.ImageSharp.Textures.Formats.Dds.Emums; @@ -10,489 +8,488 @@ using Fp32 = SixLabors.ImageSharp.Textures.TextureFormats.Decoding.Fp32; using L32 = SixLabors.ImageSharp.Textures.TextureFormats.Decoding.L32; -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// Decodes direct draw surfaces. +/// +internal class DdsProcessor { /// - /// Decodes direct draw surfaces. + /// Initializes a new instance of the class. /// - internal class DdsProcessor + /// The DDS header. + /// The DDS header DXT10. + public DdsProcessor(DdsHeader ddsHeader, DdsHeaderDxt10 ddsHeaderDxt10) { - /// - /// Initializes a new instance of the class. - /// - /// The DDS header. - /// The DDS header DXT10. - public DdsProcessor(DdsHeader ddsHeader, DdsHeaderDxt10 ddsHeaderDxt10) - { - this.DdsHeader = ddsHeader; - this.DdsHeaderDxt10 = ddsHeaderDxt10; - } + this.DdsHeader = ddsHeader; + this.DdsHeaderDxt10 = ddsHeaderDxt10; + } + + /// + /// Gets the dds header. + /// + public DdsHeader DdsHeader { get; } + + /// + /// Gets the dxt 10 header. + /// + public DdsHeaderDxt10 DdsHeaderDxt10 { get; } + + /// + /// Decodes the mipmaps of a DDS textures. + /// + /// The stream to read the texture data from. + /// The width of the texture at level 0. + /// The height of the texture at level 0. + /// The mipmap count. + /// The decoded mipmaps. + public MipMap[] DecodeDds(Stream stream, int width, int height, int count) + { + Guard.MustBeGreaterThan(count, 0, nameof(count)); - /// - /// Gets the dds header. - /// - public DdsHeader DdsHeader { get; } - - /// - /// Gets the dxt 10 header. - /// - public DdsHeaderDxt10 DdsHeaderDxt10 { get; } - - /// - /// Decodes the mipmaps of a DDS textures. - /// - /// The stream to read the texture data from. - /// The width of the texture at level 0. - /// The height of the texture at level 0. - /// The mipmap count. - /// The decoded mipmaps. - public MipMap[] DecodeDds(Stream stream, int width, int height, int count) + return this.DdsHeader.PixelFormat.FourCC switch { - Guard.MustBeGreaterThan(count, 0, nameof(count)); + DdsFourCc.None + or DdsFourCc.R16FLOAT + or DdsFourCc.R16G16FLOAT + or DdsFourCc.R16G16B16A16SNORM + or DdsFourCc.R16G16B16A16UNORM + or DdsFourCc.R16G16B16A16FLOAT + or DdsFourCc.R32FLOAT + or DdsFourCc.R32G32FLOAT + or DdsFourCc.R32G32B32A32FLOAT + or DdsFourCc.YUY2 + or DdsFourCc.RGBG + or DdsFourCc.GRGB => this.ProcessUncompressed(stream, width, height, count), + DdsFourCc.DXT1 => AllocateMipMaps(stream, width, height, count), + DdsFourCc.DXT2 or DdsFourCc.DXT4 => throw new NotSupportedException("Due to patents Can, DXT2 or DXT4 cannot be supported."), + DdsFourCc.DXT3 => AllocateMipMaps(stream, width, height, count), + DdsFourCc.DXT5 => AllocateMipMaps(stream, width, height, count), + DdsFourCc.DX10 => this.GetDx10Dds(stream, width, height, count), + DdsFourCc.ATI1 or DdsFourCc.BC4U => AllocateMipMaps(stream, width, height, count), + DdsFourCc.BC4S => AllocateMipMaps(stream, width, height, count), + DdsFourCc.ATI2 or DdsFourCc.BC5U => AllocateMipMaps(stream, width, height, count), + DdsFourCc.BC5S => AllocateMipMaps(stream, width, height, count), + _ => throw new NotSupportedException($"FourCC: {this.DdsHeader.PixelFormat.FourCC.FourCcToString()} not supported."), + }; + } - return this.DdsHeader.PixelFormat.FourCC switch + public MipMap[] ProcessUncompressed(Stream stream, int width, int height, int count) + { + uint bitsPerPixel = this.DdsHeader.PixelFormat.RGBBitCount; + return bitsPerPixel switch + { + 8 => this.EightBitImageFormat(stream, width, height, count), + 16 => this.SixteenBitImageFormat(stream, width, height, count), + 24 => this.TwentyFourBitImageFormat(stream, width, height, count), + 32 => this.ThirtyTwoBitImageFormat(stream, width, height, count), + _ => this.DdsHeader.PixelFormat.FourCC switch { - DdsFourCc.None - or DdsFourCc.R16FLOAT + DdsFourCc.R16FLOAT => this.SixteenBitImageFormat(stream, width, height, count), + DdsFourCc.R32FLOAT or DdsFourCc.R16G16FLOAT - or DdsFourCc.R16G16B16A16SNORM - or DdsFourCc.R16G16B16A16UNORM - or DdsFourCc.R16G16B16A16FLOAT - or DdsFourCc.R32FLOAT - or DdsFourCc.R32G32FLOAT - or DdsFourCc.R32G32B32A32FLOAT or DdsFourCc.YUY2 or DdsFourCc.RGBG - or DdsFourCc.GRGB => this.ProcessUncompressed(stream, width, height, count), - DdsFourCc.DXT1 => AllocateMipMaps(stream, width, height, count), - DdsFourCc.DXT2 or DdsFourCc.DXT4 => throw new NotSupportedException("Due to patents Can, DXT2 or DXT4 cannot be supported."), - DdsFourCc.DXT3 => AllocateMipMaps(stream, width, height, count), - DdsFourCc.DXT5 => AllocateMipMaps(stream, width, height, count), - DdsFourCc.DX10 => this.GetDx10Dds(stream, width, height, count), - DdsFourCc.ATI1 or DdsFourCc.BC4U => AllocateMipMaps(stream, width, height, count), - DdsFourCc.BC4S => AllocateMipMaps(stream, width, height, count), - DdsFourCc.ATI2 or DdsFourCc.BC5U => AllocateMipMaps(stream, width, height, count), - DdsFourCc.BC5S => AllocateMipMaps(stream, width, height, count), - _ => throw new NotSupportedException($"FourCC: {this.DdsHeader.PixelFormat.FourCC.FourCcToString()} not supported."), - }; - } + or DdsFourCc.GRGB => this.ThirtyTwoBitImageFormat(stream, width, height, count), + DdsFourCc.R16G16B16A16SNORM + or DdsFourCc.R16G16B16A16UNORM + or DdsFourCc.R16G16B16A16FLOAT + or DdsFourCc.R32G32FLOAT => this.SixtyFourBitImageFormat(stream, width, height, count), + DdsFourCc.R32G32B32A32FLOAT => this.HundredTwentyEightBitImageFormat(stream, width, height, count), + _ => throw new ArgumentOutOfRangeException($"Unrecognized rgb bit count: {this.DdsHeader.PixelFormat.RGBBitCount}"), + }, // For unknown reason some formats do not have the bitsPerPixel set in the header (its zero). + }; + } - public MipMap[] ProcessUncompressed(Stream stream, int width, int height, int count) - { - uint bitsPerPixel = this.DdsHeader.PixelFormat.RGBBitCount; - return bitsPerPixel switch - { - 8 => this.EightBitImageFormat(stream, width, height, count), - 16 => this.SixteenBitImageFormat(stream, width, height, count), - 24 => this.TwentyFourBitImageFormat(stream, width, height, count), - 32 => this.ThirtyTwoBitImageFormat(stream, width, height, count), - _ => this.DdsHeader.PixelFormat.FourCC switch - { - DdsFourCc.R16FLOAT => this.SixteenBitImageFormat(stream, width, height, count), - DdsFourCc.R32FLOAT - or DdsFourCc.R16G16FLOAT - or DdsFourCc.YUY2 - or DdsFourCc.RGBG - or DdsFourCc.GRGB => this.ThirtyTwoBitImageFormat(stream, width, height, count), - DdsFourCc.R16G16B16A16SNORM - or DdsFourCc.R16G16B16A16UNORM - or DdsFourCc.R16G16B16A16FLOAT - or DdsFourCc.R32G32FLOAT => this.SixtyFourBitImageFormat(stream, width, height, count), - DdsFourCc.R32G32B32A32FLOAT => this.HundredTwentyEightBitImageFormat(stream, width, height, count), - _ => throw new ArgumentOutOfRangeException($"Unrecognized rgb bit count: {this.DdsHeader.PixelFormat.RGBBitCount}"), - }, // For unknown reason some formats do not have the bitsPerPixel set in the header (its zero). - }; - } + /// + /// Allocates and decodes all mipmap levels of a DDS texture. + /// + /// The stream to read the texture data from. + /// The width of the texture at level 0. + /// The height of the texture at level 0. + /// The mipmap count. + /// The decoded mipmaps. + private static MipMap[] AllocateMipMaps(Stream stream, int width, int height, int count) + where TBlock : struct, IBlock + { + TBlock blockFormat = default(TBlock); + + MipMap[] mipMaps = new MipMap[count]; - /// - /// Allocates and decodes all mipmap levels of a DDS texture. - /// - /// The stream to read the texture data from. - /// The width of the texture at level 0. - /// The height of the texture at level 0. - /// The mipmap count. - /// The decoded mipmaps. - private static MipMap[] AllocateMipMaps(Stream stream, int width, int height, int count) - where TBlock : struct, IBlock + for (int i = 0; i < count; i++) { - var blockFormat = default(TBlock); + int widthBlocks = blockFormat.Compressed ? Helper.CalcBlocks(width) : width; + int heightBlocks = blockFormat.Compressed ? Helper.CalcBlocks(height) : height; + int bytesToRead = heightBlocks * widthBlocks * blockFormat.CompressedBytesPerBlock; - var mipMaps = new MipMap[count]; + // Special case for yuv formats with a single pixel. + if (bytesToRead < blockFormat.BitsPerPixel / 8) + { + bytesToRead = blockFormat.BitsPerPixel / 8; + } - for (int i = 0; i < count; i++) + byte[] mipData = new byte[bytesToRead]; + int read = stream.Read(mipData, 0, bytesToRead); + if (read != bytesToRead) { - int widthBlocks = blockFormat.Compressed ? Helper.CalcBlocks(width) : width; - int heightBlocks = blockFormat.Compressed ? Helper.CalcBlocks(height) : height; - int bytesToRead = heightBlocks * widthBlocks * blockFormat.CompressedBytesPerBlock; - - // Special case for yuv formats with a single pixel. - if (bytesToRead < blockFormat.BitsPerPixel / 8) - { - bytesToRead = blockFormat.BitsPerPixel / 8; - } - - byte[] mipData = new byte[bytesToRead]; - int read = stream.Read(mipData, 0, bytesToRead); - if (read != bytesToRead) - { - throw new TextureFormatException("could not read enough texture data from the stream"); - } - - mipMaps[i] = new MipMap(blockFormat, mipData, width, height); - - width >>= 1; - height >>= 1; + throw new TextureFormatException("could not read enough texture data from the stream"); } - return mipMaps; - } + mipMaps[i] = new MipMap(blockFormat, mipData, width, height); - private MipMap[] EightBitImageFormat(Stream stream, int width, int height, int count) - { - DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; + width >>= 1; + height >>= 1; + } - bool hasAlpha = pixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels); + return mipMaps; + } - if (pixelFormat.RBitMask == 0x0 && pixelFormat.GBitMask == 0x0 && pixelFormat.BBitMask == 0x0) - { - return AllocateMipMaps(stream, width, height, count); - } + private MipMap[] EightBitImageFormat(Stream stream, int width, int height, int count) + { + DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; - if (!hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0x0 && pixelFormat.BBitMask == 0x0) - { - return AllocateMipMaps(stream, width, height, count); - } + bool hasAlpha = pixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels); - throw new NotSupportedException("Unsupported 8 bit format"); + if (pixelFormat.RBitMask == 0x0 && pixelFormat.GBitMask == 0x0 && pixelFormat.BBitMask == 0x0) + { + return AllocateMipMaps(stream, width, height, count); } - private MipMap[] SixteenBitImageFormat(Stream stream, int width, int height, int count) + if (!hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0x0 && pixelFormat.BBitMask == 0x0) { - DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; + return AllocateMipMaps(stream, width, height, count); + } - bool hasAlpha = pixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels); + throw new NotSupportedException("Unsupported 8 bit format"); + } - if (hasAlpha && pixelFormat.RBitMask == 0xF00 && pixelFormat.GBitMask == 0xF0 && pixelFormat.BBitMask == 0xF) - { - return AllocateMipMaps(stream, width, height, count); - } + private MipMap[] SixteenBitImageFormat(Stream stream, int width, int height, int count) + { + DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; - if (!hasAlpha && pixelFormat.RBitMask == 0x7C00 && pixelFormat.GBitMask == 0x3E0 && pixelFormat.BBitMask == 0x1F) - { - return AllocateMipMaps(stream, width, height, count); - } + bool hasAlpha = pixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels); - if (hasAlpha && pixelFormat.RBitMask == 0x7C00 && pixelFormat.GBitMask == 0x3E0 && pixelFormat.BBitMask == 0x1F) - { - return AllocateMipMaps(stream, width, height, count); - } + if (hasAlpha && pixelFormat.RBitMask == 0xF00 && pixelFormat.GBitMask == 0xF0 && pixelFormat.BBitMask == 0xF) + { + return AllocateMipMaps(stream, width, height, count); + } - if (!hasAlpha && pixelFormat.RBitMask == 0xF800 && pixelFormat.GBitMask == 0x7E0 && pixelFormat.BBitMask == 0x1F) - { - return AllocateMipMaps(stream, width, height, count); - } + if (!hasAlpha && pixelFormat.RBitMask == 0x7C00 && pixelFormat.GBitMask == 0x3E0 && pixelFormat.BBitMask == 0x1F) + { + return AllocateMipMaps(stream, width, height, count); + } - if (hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0x0 && pixelFormat.BBitMask == 0x0) - { - return AllocateMipMaps(stream, width, height, count); - } + if (hasAlpha && pixelFormat.RBitMask == 0x7C00 && pixelFormat.GBitMask == 0x3E0 && pixelFormat.BBitMask == 0x1F) + { + return AllocateMipMaps(stream, width, height, count); + } - if (!hasAlpha && pixelFormat.RBitMask == 0xFFFF && pixelFormat.GBitMask == 0x0 && pixelFormat.BBitMask == 0x0) - { - return AllocateMipMaps(stream, width, height, count); - } + if (!hasAlpha && pixelFormat.RBitMask == 0xF800 && pixelFormat.GBitMask == 0x7E0 && pixelFormat.BBitMask == 0x1F) + { + return AllocateMipMaps(stream, width, height, count); + } - if (!hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0x0) - { - return AllocateMipMaps(stream, width, height, count); - } + if (hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0x0 && pixelFormat.BBitMask == 0x0) + { + return AllocateMipMaps(stream, width, height, count); + } - if (pixelFormat.FourCC == DdsFourCc.R16FLOAT) - { - return AllocateMipMaps(stream, width, height, count); - } + if (!hasAlpha && pixelFormat.RBitMask == 0xFFFF && pixelFormat.GBitMask == 0x0 && pixelFormat.BBitMask == 0x0) + { + return AllocateMipMaps(stream, width, height, count); + } - throw new NotSupportedException("Unsupported 16 bit format"); + if (!hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0x0) + { + return AllocateMipMaps(stream, width, height, count); } - private MipMap[] TwentyFourBitImageFormat(Stream stream, int width, int height, int count) + if (pixelFormat.FourCC == DdsFourCc.R16FLOAT) { - DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; + return AllocateMipMaps(stream, width, height, count); + } - bool hasAlpha = pixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels); + throw new NotSupportedException("Unsupported 16 bit format"); + } - if (!hasAlpha && pixelFormat.RBitMask == 0xFF0000 && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF) - { - return AllocateMipMaps(stream, width, height, count); - } + private MipMap[] TwentyFourBitImageFormat(Stream stream, int width, int height, int count) + { + DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; - throw new NotSupportedException("Unsupported 24 bit format"); - } + bool hasAlpha = pixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels); - private MipMap[] ThirtyTwoBitImageFormat(Stream stream, int width, int height, int count) + if (!hasAlpha && pixelFormat.RBitMask == 0xFF0000 && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF) { - DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; + return AllocateMipMaps(stream, width, height, count); + } - bool hasAlpha = pixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels); + throw new NotSupportedException("Unsupported 24 bit format"); + } - if (hasAlpha && pixelFormat.RBitMask == 0xFF0000 && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF) - { - return AllocateMipMaps(stream, width, height, count); - } + private MipMap[] ThirtyTwoBitImageFormat(Stream stream, int width, int height, int count) + { + DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; - if (hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF0000) - { - return AllocateMipMaps(stream, width, height, count); - } + bool hasAlpha = pixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels); - if (!hasAlpha && pixelFormat.RBitMask == 0xFF0000 && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF) - { - return AllocateMipMaps(stream, width, height, count); - } + if (hasAlpha && pixelFormat.RBitMask == 0xFF0000 && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF) + { + return AllocateMipMaps(stream, width, height, count); + } - if (!hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF0000) - { - return AllocateMipMaps(stream, width, height, count); - } + if (hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF0000) + { + return AllocateMipMaps(stream, width, height, count); + } - if (!hasAlpha && pixelFormat.RBitMask == 0xFFFF && pixelFormat.GBitMask == 0xFFFF0000 && pixelFormat.BBitMask == 0x0) - { - return AllocateMipMaps(stream, width, height, count); - } + if (!hasAlpha && pixelFormat.RBitMask == 0xFF0000 && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF) + { + return AllocateMipMaps(stream, width, height, count); + } - if (pixelFormat.FourCC == DdsFourCc.R32FLOAT) - { - return AllocateMipMaps(stream, width, height, count); - } + if (!hasAlpha && pixelFormat.RBitMask == 0xFF && pixelFormat.GBitMask == 0xFF00 && pixelFormat.BBitMask == 0xFF0000) + { + return AllocateMipMaps(stream, width, height, count); + } - if (pixelFormat.FourCC == DdsFourCc.R16G16FLOAT) - { - return AllocateMipMaps(stream, width, height, count); - } + if (!hasAlpha && pixelFormat.RBitMask == 0xFFFF && pixelFormat.GBitMask == 0xFFFF0000 && pixelFormat.BBitMask == 0x0) + { + return AllocateMipMaps(stream, width, height, count); + } - if (pixelFormat.FourCC == DdsFourCc.YUY2) - { - return AllocateMipMaps(stream, width, height, count); - } + if (pixelFormat.FourCC == DdsFourCc.R32FLOAT) + { + return AllocateMipMaps(stream, width, height, count); + } - if (pixelFormat.FourCC == DdsFourCc.RGBG) - { - return AllocateMipMaps(stream, width, height, count); - } + if (pixelFormat.FourCC == DdsFourCc.R16G16FLOAT) + { + return AllocateMipMaps(stream, width, height, count); + } - if (pixelFormat.FourCC == DdsFourCc.GRGB) - { - return AllocateMipMaps(stream, width, height, count); - } + if (pixelFormat.FourCC == DdsFourCc.YUY2) + { + return AllocateMipMaps(stream, width, height, count); + } - throw new NotSupportedException("Unsupported 32 bit format"); + if (pixelFormat.FourCC == DdsFourCc.RGBG) + { + return AllocateMipMaps(stream, width, height, count); } - private MipMap[] SixtyFourBitImageFormat(Stream stream, int width, int height, int count) + if (pixelFormat.FourCC == DdsFourCc.GRGB) { - DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; + return AllocateMipMaps(stream, width, height, count); + } - if (pixelFormat.FourCC == DdsFourCc.R16G16B16A16SNORM || pixelFormat.FourCC == DdsFourCc.R16G16B16A16UNORM) - { - return AllocateMipMaps(stream, width, height, count); - } + throw new NotSupportedException("Unsupported 32 bit format"); + } - if (pixelFormat.FourCC == DdsFourCc.R32G32FLOAT) - { - return AllocateMipMaps(stream, width, height, count); - } + private MipMap[] SixtyFourBitImageFormat(Stream stream, int width, int height, int count) + { + DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; - if (pixelFormat.FourCC == DdsFourCc.R16G16B16A16FLOAT) - { - return AllocateMipMaps(stream, width, height, count); - } + if (pixelFormat.FourCC is DdsFourCc.R16G16B16A16SNORM or DdsFourCc.R16G16B16A16UNORM) + { + return AllocateMipMaps(stream, width, height, count); + } - throw new NotSupportedException("Unsupported 64 bit format"); + if (pixelFormat.FourCC == DdsFourCc.R32G32FLOAT) + { + return AllocateMipMaps(stream, width, height, count); } - private MipMap[] HundredTwentyEightBitImageFormat(Stream stream, int width, int height, int count) + if (pixelFormat.FourCC == DdsFourCc.R16G16B16A16FLOAT) { - DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; + return AllocateMipMaps(stream, width, height, count); + } - if (pixelFormat.FourCC == DdsFourCc.R32G32B32A32FLOAT || pixelFormat.FourCC == DdsFourCc.R32FLOAT) - { - return AllocateMipMaps(stream, width, height, count); - } + throw new NotSupportedException("Unsupported 64 bit format"); + } + + private MipMap[] HundredTwentyEightBitImageFormat(Stream stream, int width, int height, int count) + { + DdsPixelFormat pixelFormat = this.DdsHeader.PixelFormat; - throw new NotSupportedException("Unsupported 128 bit format"); + if (pixelFormat.FourCC is DdsFourCc.R32G32B32A32FLOAT or DdsFourCc.R32FLOAT) + { + return AllocateMipMaps(stream, width, height, count); } - /* - https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide - https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format - */ + throw new NotSupportedException("Unsupported 128 bit format"); + } + + /* + https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide + https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format + */ - private MipMap[] GetDx10Dds(Stream stream, int width, int height, int count) + private MipMap[] GetDx10Dds(Stream stream, int width, int height, int count) + { + switch (this.DdsHeaderDxt10.DxgiFormat) { - switch (this.DdsHeaderDxt10.DxgiFormat) - { - case DxgiFormat.BC1_Typeless: - case DxgiFormat.BC1_UNorm_SRGB: - case DxgiFormat.BC1_UNorm: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC2_Typeless: - case DxgiFormat.BC2_UNorm: - case DxgiFormat.BC2_UNorm_SRGB: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC3_Typeless: - case DxgiFormat.BC3_UNorm: - case DxgiFormat.BC3_UNorm_SRGB: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC4_Typeless: - case DxgiFormat.BC4_UNorm: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC4_SNorm: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC5_Typeless: - case DxgiFormat.BC5_UNorm: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC5_SNorm: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC6H_Typeless: - case DxgiFormat.BC6H_UF16: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC6H_SF16: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.BC7_Typeless: - case DxgiFormat.BC7_UNorm: - case DxgiFormat.BC7_UNorm_SRGB: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R8G8B8A8_Typeless: - case DxgiFormat.R8G8B8A8_UNorm: - case DxgiFormat.R8G8B8A8_UNorm_SRGB: - case DxgiFormat.R8G8B8A8_UInt: - case DxgiFormat.R8G8B8A8_SNorm: - case DxgiFormat.R8G8B8A8_SInt: - case DxgiFormat.B8G8R8X8_Typeless: - case DxgiFormat.B8G8R8X8_UNorm: - case DxgiFormat.B8G8R8X8_UNorm_SRGB: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.B8G8R8A8_Typeless: - case DxgiFormat.B8G8R8A8_UNorm: - case DxgiFormat.B8G8R8A8_UNorm_SRGB: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32G32B32A32_Float: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32G32B32A32_Typeless: - case DxgiFormat.R32G32B32A32_UInt: - case DxgiFormat.R32G32B32A32_SInt: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32G32B32_Float: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32G32B32_Typeless: - case DxgiFormat.R32G32B32_UInt: - case DxgiFormat.R32G32B32_SInt: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R16G16B16A16_Typeless: - case DxgiFormat.R16G16B16A16_Float: - case DxgiFormat.R16G16B16A16_UNorm: - case DxgiFormat.R16G16B16A16_UInt: - case DxgiFormat.R16G16B16A16_SNorm: - case DxgiFormat.R16G16B16A16_SInt: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32G32_Float: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32G32_Typeless: - case DxgiFormat.R32G32_UInt: - case DxgiFormat.R32G32_SInt: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R10G10B10A2_Typeless: - case DxgiFormat.R10G10B10A2_UNorm: - case DxgiFormat.R10G10B10A2_UInt: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R16G16_Float: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R16G16_Typeless: - case DxgiFormat.R16G16_UNorm: - case DxgiFormat.R16G16_UInt: - case DxgiFormat.R16G16_SNorm: - case DxgiFormat.R16G16_SInt: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32_Float: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32_Typeless: - case DxgiFormat.R32_UInt: - case DxgiFormat.R32_SInt: - // Treating single channel format as 32 bit gray image. - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R8G8_Typeless: - case DxgiFormat.R8G8_UNorm: - case DxgiFormat.R8G8_UInt: - case DxgiFormat.R8G8_SNorm: - case DxgiFormat.R8G8_SInt: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R16_Float: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R16_Typeless: - case DxgiFormat.R16_UNorm: - case DxgiFormat.R16_UInt: - case DxgiFormat.R16_SNorm: - case DxgiFormat.R16_SInt: - // Treating single channel format as 16 bit gray image. - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R8_Typeless: - case DxgiFormat.R8_UNorm: - case DxgiFormat.R8_UInt: - case DxgiFormat.R8_SNorm: - case DxgiFormat.R8_SInt: - // Treating single channel format as 8 bit gray image. - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.A8_UNorm: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R1_UNorm: - throw new NotImplementedException($"{nameof(DxgiFormat.R1_UNorm)} is currently not implemented"); - case DxgiFormat.R11G11B10_Float: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.Y410: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.Y416: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.Y210: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.Y216: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.AYUV: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.YUY2: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R8G8_B8G8_UNorm: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.G8R8_G8B8_UNorm: - return AllocateMipMaps(stream, width, height, count); - case DxgiFormat.R32G8X24_Typeless: - case DxgiFormat.D32_Float_S8X24_UInt: - case DxgiFormat.R32_Float_X8X24_Typeless: - case DxgiFormat.X32_Typeless_G8X24_UInt: - case DxgiFormat.D32_Float: - case DxgiFormat.R24G8_Typeless: - case DxgiFormat.D24_UNorm_S8_UInt: - case DxgiFormat.R24_UNorm_X8_Typeless: - case DxgiFormat.X24_Typeless_G8_UInt: - case DxgiFormat.D16_UNorm: - case DxgiFormat.R9G9B9E5_SharedExp: - case DxgiFormat.R10G10B10_XR_BIAS_A2_UNorm: - case DxgiFormat.NV12: - case DxgiFormat.P010: - case DxgiFormat.P016: - case DxgiFormat.Opaque_420: - case DxgiFormat.NV11: - case DxgiFormat.AI44: - case DxgiFormat.IA44: - case DxgiFormat.P8: - case DxgiFormat.A8P8: - case DxgiFormat.B4G4R4A4_UNorm: - case DxgiFormat.P208: - case DxgiFormat.V208: - case DxgiFormat.V408: - case DxgiFormat.Unknown: - default: - throw new NotSupportedException($"Unsupported format {this.DdsHeaderDxt10.DxgiFormat}"); - } + case DxgiFormat.BC1_Typeless: + case DxgiFormat.BC1_UNorm_SRGB: + case DxgiFormat.BC1_UNorm: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC2_Typeless: + case DxgiFormat.BC2_UNorm: + case DxgiFormat.BC2_UNorm_SRGB: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC3_Typeless: + case DxgiFormat.BC3_UNorm: + case DxgiFormat.BC3_UNorm_SRGB: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC4_Typeless: + case DxgiFormat.BC4_UNorm: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC4_SNorm: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC5_Typeless: + case DxgiFormat.BC5_UNorm: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC5_SNorm: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC6H_Typeless: + case DxgiFormat.BC6H_UF16: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC6H_SF16: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.BC7_Typeless: + case DxgiFormat.BC7_UNorm: + case DxgiFormat.BC7_UNorm_SRGB: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R8G8B8A8_Typeless: + case DxgiFormat.R8G8B8A8_UNorm: + case DxgiFormat.R8G8B8A8_UNorm_SRGB: + case DxgiFormat.R8G8B8A8_UInt: + case DxgiFormat.R8G8B8A8_SNorm: + case DxgiFormat.R8G8B8A8_SInt: + case DxgiFormat.B8G8R8X8_Typeless: + case DxgiFormat.B8G8R8X8_UNorm: + case DxgiFormat.B8G8R8X8_UNorm_SRGB: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.B8G8R8A8_Typeless: + case DxgiFormat.B8G8R8A8_UNorm: + case DxgiFormat.B8G8R8A8_UNorm_SRGB: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32G32B32A32_Float: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32G32B32A32_Typeless: + case DxgiFormat.R32G32B32A32_UInt: + case DxgiFormat.R32G32B32A32_SInt: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32G32B32_Float: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32G32B32_Typeless: + case DxgiFormat.R32G32B32_UInt: + case DxgiFormat.R32G32B32_SInt: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R16G16B16A16_Typeless: + case DxgiFormat.R16G16B16A16_Float: + case DxgiFormat.R16G16B16A16_UNorm: + case DxgiFormat.R16G16B16A16_UInt: + case DxgiFormat.R16G16B16A16_SNorm: + case DxgiFormat.R16G16B16A16_SInt: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32G32_Float: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32G32_Typeless: + case DxgiFormat.R32G32_UInt: + case DxgiFormat.R32G32_SInt: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R10G10B10A2_Typeless: + case DxgiFormat.R10G10B10A2_UNorm: + case DxgiFormat.R10G10B10A2_UInt: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R16G16_Float: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R16G16_Typeless: + case DxgiFormat.R16G16_UNorm: + case DxgiFormat.R16G16_UInt: + case DxgiFormat.R16G16_SNorm: + case DxgiFormat.R16G16_SInt: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32_Float: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32_Typeless: + case DxgiFormat.R32_UInt: + case DxgiFormat.R32_SInt: + // Treating single channel format as 32 bit gray image. + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R8G8_Typeless: + case DxgiFormat.R8G8_UNorm: + case DxgiFormat.R8G8_UInt: + case DxgiFormat.R8G8_SNorm: + case DxgiFormat.R8G8_SInt: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R16_Float: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R16_Typeless: + case DxgiFormat.R16_UNorm: + case DxgiFormat.R16_UInt: + case DxgiFormat.R16_SNorm: + case DxgiFormat.R16_SInt: + // Treating single channel format as 16 bit gray image. + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R8_Typeless: + case DxgiFormat.R8_UNorm: + case DxgiFormat.R8_UInt: + case DxgiFormat.R8_SNorm: + case DxgiFormat.R8_SInt: + // Treating single channel format as 8 bit gray image. + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.A8_UNorm: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R1_UNorm: + throw new NotImplementedException($"{nameof(DxgiFormat.R1_UNorm)} is currently not implemented"); + case DxgiFormat.R11G11B10_Float: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.Y410: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.Y416: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.Y210: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.Y216: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.AYUV: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.YUY2: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R8G8_B8G8_UNorm: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.G8R8_G8B8_UNorm: + return AllocateMipMaps(stream, width, height, count); + case DxgiFormat.R32G8X24_Typeless: + case DxgiFormat.D32_Float_S8X24_UInt: + case DxgiFormat.R32_Float_X8X24_Typeless: + case DxgiFormat.X32_Typeless_G8X24_UInt: + case DxgiFormat.D32_Float: + case DxgiFormat.R24G8_Typeless: + case DxgiFormat.D24_UNorm_S8_UInt: + case DxgiFormat.R24_UNorm_X8_Typeless: + case DxgiFormat.X24_Typeless_G8_UInt: + case DxgiFormat.D16_UNorm: + case DxgiFormat.R9G9B9E5_SharedExp: + case DxgiFormat.R10G10B10_XR_BIAS_A2_UNorm: + case DxgiFormat.NV12: + case DxgiFormat.P010: + case DxgiFormat.P016: + case DxgiFormat.Opaque_420: + case DxgiFormat.NV11: + case DxgiFormat.AI44: + case DxgiFormat.IA44: + case DxgiFormat.P8: + case DxgiFormat.A8P8: + case DxgiFormat.B4G4R4A4_UNorm: + case DxgiFormat.P208: + case DxgiFormat.V208: + case DxgiFormat.V408: + case DxgiFormat.Unknown: + default: + throw new NotSupportedException($"Unsupported format {this.DdsHeaderDxt10.DxgiFormat}"); } } } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsSurfaceType.cs b/src/ImageSharp.Textures/Formats/Dds/DdsSurfaceType.cs index 187359e6..e949526a 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsSurfaceType.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsSurfaceType.cs @@ -1,61 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// Shows what kind of surface we're dealing with. +/// +public enum DdsSurfaceType { /// - /// Shows what kind of surface we're dealing with. - /// - public enum DdsSurfaceType - { - /// - /// Default value. - /// - Unknown, - - /// - /// Positive X cube map face. - /// - CubemapPositiveX, - - /// - /// Negative X cube map face. - /// - CubemapNegativeX, - - /// - /// Positive Y cube map face. - /// - CubemapPositiveY, - - /// - /// Negative Y cube map face. - /// - CubemapNegativeY, - - /// - /// Positive Z cube map face. - /// - CubemapPositiveZ, - - /// - /// Negative Z cube map face. - /// - CubemapNegativeZ, - - /// - /// Represents a one-dimensional texture (height == 1). - /// - Texture1D, - - /// - /// Represents a usual two-dimensional texture. - /// - Texture2D, - - /// - /// Represents slices of a volume texture for a one mip-map level. - /// - Texture3D - } + /// Default value. + /// + Unknown, + + /// + /// Positive X cube map face. + /// + CubemapPositiveX, + + /// + /// Negative X cube map face. + /// + CubemapNegativeX, + + /// + /// Positive Y cube map face. + /// + CubemapPositiveY, + + /// + /// Negative Y cube map face. + /// + CubemapNegativeY, + + /// + /// Positive Z cube map face. + /// + CubemapPositiveZ, + + /// + /// Negative Z cube map face. + /// + CubemapNegativeZ, + + /// + /// Represents a one-dimensional texture (height == 1). + /// + Texture1D, + + /// + /// Represents a usual two-dimensional texture. + /// + Texture2D, + + /// + /// Represents slices of a volume texture for a one mip-map level. + /// + Texture3D } diff --git a/src/ImageSharp.Textures/Formats/Dds/DdsTools.cs b/src/ImageSharp.Textures/Formats/Dds/DdsTools.cs index 7402f524..17f590bd 100644 --- a/src/ImageSharp.Textures/Formats/Dds/DdsTools.cs +++ b/src/ImageSharp.Textures/Formats/Dds/DdsTools.cs @@ -1,362 +1,351 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Processing +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Processing; + +internal class DdsTools { - internal class DdsTools + public static int GetBitsPerPixel(D3dFormat d3dFormat) { - public static int GetBitsPerPixel(D3dFormat d3dFormat) + switch (d3dFormat) { - switch (d3dFormat) - { - case D3dFormat.A1: - return 1; - case D3dFormat.DXT1: - case D3dFormat.BC4S: - case D3dFormat.BC4U: - case D3dFormat.ATI1: - return 4; - case D3dFormat.R3G3B2: - case D3dFormat.A8: - case D3dFormat.P8: - case D3dFormat.L8: - case D3dFormat.A4L4: - case D3dFormat.S8_Lockable: - case D3dFormat.DXT2: - case D3dFormat.DXT3: - case D3dFormat.DXT4: - case D3dFormat.DXT5: - case D3dFormat.BC5S: - case D3dFormat.BC5U: - case D3dFormat.ATI2: - return 8; - case D3dFormat.R5G6B5: - case D3dFormat.X1R5G5B5: - case D3dFormat.A1R5G5B5: - case D3dFormat.A4R4G4B4: - case D3dFormat.A8R3G3B2: - case D3dFormat.X4R4G4B4: - case D3dFormat.A8P8: - case D3dFormat.A8L8: - case D3dFormat.V8U8: - case D3dFormat.L6V5U5: - case D3dFormat.UYVY: - case D3dFormat.YUY2: - case D3dFormat.R8G8_B8G8: - case D3dFormat.G8R8_G8B8: - case D3dFormat.D16: - case D3dFormat.D16_Lockable: - case D3dFormat.D15S1: - case D3dFormat.L16: - case D3dFormat.Index16: - case D3dFormat.R16F: - case D3dFormat.CxV8U8: - return 16; - case D3dFormat.R8G8B8: - return 24; - case D3dFormat.A8R8G8B8: - case D3dFormat.X8R8G8B8: - case D3dFormat.A2B10G10R10: - case D3dFormat.A8B8G8R8: - case D3dFormat.X8B8G8R8: - case D3dFormat.G16R16: - case D3dFormat.A2R10G10B10: - case D3dFormat.X8L8V8U8: - case D3dFormat.DQ8W8V8U8: - case D3dFormat.V16U16: - case D3dFormat.A2W10V10U10: - case D3dFormat.D32: - case D3dFormat.D32_Lockable: - case D3dFormat.D32F_Lockable: - case D3dFormat.D24S8: - case D3dFormat.D24X8: - case D3dFormat.D24X4S4: - case D3dFormat.D24FS8: - case D3dFormat.Index32: - case D3dFormat.G16R16F: - case D3dFormat.R32F: - case D3dFormat.A2B10G10R10_XR_Bias: - case D3dFormat.Multi2_ARGB8: - return 32; - case D3dFormat.A16B16G16R16: - case D3dFormat.A16B16G16R16F: - case D3dFormat.Q16W16V16U16: - case D3dFormat.G32R32F: - return 64; - case D3dFormat.A32B32G32R32F: - return 128; - case D3dFormat.Unknown: - case D3dFormat.VertexData: - case D3dFormat.BinaryBuffer: - return 0; - default: - return 0; - } + case D3dFormat.A1: + return 1; + case D3dFormat.DXT1: + case D3dFormat.BC4S: + case D3dFormat.BC4U: + case D3dFormat.ATI1: + return 4; + case D3dFormat.R3G3B2: + case D3dFormat.A8: + case D3dFormat.P8: + case D3dFormat.L8: + case D3dFormat.A4L4: + case D3dFormat.S8_Lockable: + case D3dFormat.DXT2: + case D3dFormat.DXT3: + case D3dFormat.DXT4: + case D3dFormat.DXT5: + case D3dFormat.BC5S: + case D3dFormat.BC5U: + case D3dFormat.ATI2: + return 8; + case D3dFormat.R5G6B5: + case D3dFormat.X1R5G5B5: + case D3dFormat.A1R5G5B5: + case D3dFormat.A4R4G4B4: + case D3dFormat.A8R3G3B2: + case D3dFormat.X4R4G4B4: + case D3dFormat.A8P8: + case D3dFormat.A8L8: + case D3dFormat.V8U8: + case D3dFormat.L6V5U5: + case D3dFormat.UYVY: + case D3dFormat.YUY2: + case D3dFormat.R8G8_B8G8: + case D3dFormat.G8R8_G8B8: + case D3dFormat.D16: + case D3dFormat.D16_Lockable: + case D3dFormat.D15S1: + case D3dFormat.L16: + case D3dFormat.Index16: + case D3dFormat.R16F: + case D3dFormat.CxV8U8: + return 16; + case D3dFormat.R8G8B8: + return 24; + case D3dFormat.A8R8G8B8: + case D3dFormat.X8R8G8B8: + case D3dFormat.A2B10G10R10: + case D3dFormat.A8B8G8R8: + case D3dFormat.X8B8G8R8: + case D3dFormat.G16R16: + case D3dFormat.A2R10G10B10: + case D3dFormat.X8L8V8U8: + case D3dFormat.DQ8W8V8U8: + case D3dFormat.V16U16: + case D3dFormat.A2W10V10U10: + case D3dFormat.D32: + case D3dFormat.D32_Lockable: + case D3dFormat.D32F_Lockable: + case D3dFormat.D24S8: + case D3dFormat.D24X8: + case D3dFormat.D24X4S4: + case D3dFormat.D24FS8: + case D3dFormat.Index32: + case D3dFormat.G16R16F: + case D3dFormat.R32F: + case D3dFormat.A2B10G10R10_XR_Bias: + case D3dFormat.Multi2_ARGB8: + return 32; + case D3dFormat.A16B16G16R16: + case D3dFormat.A16B16G16R16F: + case D3dFormat.Q16W16V16U16: + case D3dFormat.G32R32F: + return 64; + case D3dFormat.A32B32G32R32F: + return 128; + case D3dFormat.Unknown: + case D3dFormat.VertexData: + case D3dFormat.BinaryBuffer: + return 0; + default: + return 0; } + } - public static int GetBitsPerPixel(DxgiFormat dxgiFormat) + public static int GetBitsPerPixel(DxgiFormat dxgiFormat) + { + switch (dxgiFormat) { - switch (dxgiFormat) - { - case DxgiFormat.R32G32B32A32_Typeless: - case DxgiFormat.R32G32B32A32_Float: - case DxgiFormat.R32G32B32A32_UInt: - case DxgiFormat.R32G32B32A32_SInt: - return 128; - case DxgiFormat.R32G32B32_Typeless: - case DxgiFormat.R32G32B32_Float: - case DxgiFormat.R32G32B32_UInt: - case DxgiFormat.R32G32B32_SInt: - return 96; - case DxgiFormat.R16G16B16A16_Typeless: - case DxgiFormat.R16G16B16A16_Float: - case DxgiFormat.R16G16B16A16_UNorm: - case DxgiFormat.R16G16B16A16_UInt: - case DxgiFormat.R16G16B16A16_SNorm: - case DxgiFormat.R16G16B16A16_SInt: - case DxgiFormat.R32G32_Typeless: - case DxgiFormat.R32G32_Float: - case DxgiFormat.R32G32_UInt: - case DxgiFormat.R32G32_SInt: - case DxgiFormat.R32G8X24_Typeless: - case DxgiFormat.D32_Float_S8X24_UInt: - case DxgiFormat.R32_Float_X8X24_Typeless: - case DxgiFormat.X32_Typeless_G8X24_UInt: - return 64; - case DxgiFormat.R10G10B10A2_Typeless: - case DxgiFormat.R10G10B10A2_UNorm: - case DxgiFormat.R10G10B10A2_UInt: - case DxgiFormat.R11G11B10_Float: - case DxgiFormat.R8G8B8A8_Typeless: - case DxgiFormat.R8G8B8A8_UNorm: - case DxgiFormat.R8G8B8A8_UNorm_SRGB: - case DxgiFormat.R8G8B8A8_UInt: - case DxgiFormat.R8G8B8A8_SNorm: - case DxgiFormat.R8G8B8A8_SInt: - case DxgiFormat.R16G16_Typeless: - case DxgiFormat.R16G16_Float: - case DxgiFormat.R16G16_UNorm: - case DxgiFormat.R16G16_UInt: - case DxgiFormat.R16G16_SNorm: - case DxgiFormat.R16G16_SInt: - case DxgiFormat.R32_Typeless: - case DxgiFormat.D32_Float: - case DxgiFormat.R32_Float: - case DxgiFormat.R32_UInt: - case DxgiFormat.R32_SInt: - case DxgiFormat.R24G8_Typeless: - case DxgiFormat.D24_UNorm_S8_UInt: - case DxgiFormat.R24_UNorm_X8_Typeless: - case DxgiFormat.X24_Typeless_G8_UInt: - case DxgiFormat.R9G9B9E5_SharedExp: - case DxgiFormat.R8G8_B8G8_UNorm: - case DxgiFormat.G8R8_G8B8_UNorm: - case DxgiFormat.B8G8R8A8_UNorm: - case DxgiFormat.B8G8R8X8_UNorm: - case DxgiFormat.R10G10B10_XR_BIAS_A2_UNorm: - case DxgiFormat.B8G8R8A8_Typeless: - case DxgiFormat.B8G8R8A8_UNorm_SRGB: - case DxgiFormat.B8G8R8X8_Typeless: - case DxgiFormat.B8G8R8X8_UNorm_SRGB: - return 32; - case DxgiFormat.R8G8_Typeless: - case DxgiFormat.R8G8_UNorm: - case DxgiFormat.R8G8_UInt: - case DxgiFormat.R8G8_SNorm: - case DxgiFormat.R8G8_SInt: - case DxgiFormat.R16_Typeless: - case DxgiFormat.R16_Float: - case DxgiFormat.D16_UNorm: - case DxgiFormat.R16_UNorm: - case DxgiFormat.R16_UInt: - case DxgiFormat.R16_SNorm: - case DxgiFormat.R16_SInt: - case DxgiFormat.B5G6R5_UNorm: - case DxgiFormat.B5G5R5A1_UNorm: - case DxgiFormat.B4G4R4A4_UNorm: - return 16; - case DxgiFormat.R8_Typeless: - case DxgiFormat.R8_UNorm: - case DxgiFormat.R8_UInt: - case DxgiFormat.R8_SNorm: - case DxgiFormat.R8_SInt: - case DxgiFormat.A8_UNorm: - return 8; - case DxgiFormat.R1_UNorm: - return 1; - case DxgiFormat.BC1_Typeless: - case DxgiFormat.BC1_UNorm: - case DxgiFormat.BC1_UNorm_SRGB: - case DxgiFormat.BC4_Typeless: - case DxgiFormat.BC4_UNorm: - case DxgiFormat.BC4_SNorm: - return 4; - case DxgiFormat.BC2_Typeless: - case DxgiFormat.BC2_UNorm: - case DxgiFormat.BC2_UNorm_SRGB: - case DxgiFormat.BC3_Typeless: - case DxgiFormat.BC3_UNorm: - case DxgiFormat.BC3_UNorm_SRGB: - case DxgiFormat.BC5_Typeless: - case DxgiFormat.BC5_UNorm: - case DxgiFormat.BC5_SNorm: - case DxgiFormat.BC6H_Typeless: - case DxgiFormat.BC6H_UF16: - case DxgiFormat.BC6H_SF16: - case DxgiFormat.BC7_Typeless: - case DxgiFormat.BC7_UNorm: - case DxgiFormat.BC7_UNorm_SRGB: - return 8; - default: - return 0; - } + case DxgiFormat.R32G32B32A32_Typeless: + case DxgiFormat.R32G32B32A32_Float: + case DxgiFormat.R32G32B32A32_UInt: + case DxgiFormat.R32G32B32A32_SInt: + return 128; + case DxgiFormat.R32G32B32_Typeless: + case DxgiFormat.R32G32B32_Float: + case DxgiFormat.R32G32B32_UInt: + case DxgiFormat.R32G32B32_SInt: + return 96; + case DxgiFormat.R16G16B16A16_Typeless: + case DxgiFormat.R16G16B16A16_Float: + case DxgiFormat.R16G16B16A16_UNorm: + case DxgiFormat.R16G16B16A16_UInt: + case DxgiFormat.R16G16B16A16_SNorm: + case DxgiFormat.R16G16B16A16_SInt: + case DxgiFormat.R32G32_Typeless: + case DxgiFormat.R32G32_Float: + case DxgiFormat.R32G32_UInt: + case DxgiFormat.R32G32_SInt: + case DxgiFormat.R32G8X24_Typeless: + case DxgiFormat.D32_Float_S8X24_UInt: + case DxgiFormat.R32_Float_X8X24_Typeless: + case DxgiFormat.X32_Typeless_G8X24_UInt: + return 64; + case DxgiFormat.R10G10B10A2_Typeless: + case DxgiFormat.R10G10B10A2_UNorm: + case DxgiFormat.R10G10B10A2_UInt: + case DxgiFormat.R11G11B10_Float: + case DxgiFormat.R8G8B8A8_Typeless: + case DxgiFormat.R8G8B8A8_UNorm: + case DxgiFormat.R8G8B8A8_UNorm_SRGB: + case DxgiFormat.R8G8B8A8_UInt: + case DxgiFormat.R8G8B8A8_SNorm: + case DxgiFormat.R8G8B8A8_SInt: + case DxgiFormat.R16G16_Typeless: + case DxgiFormat.R16G16_Float: + case DxgiFormat.R16G16_UNorm: + case DxgiFormat.R16G16_UInt: + case DxgiFormat.R16G16_SNorm: + case DxgiFormat.R16G16_SInt: + case DxgiFormat.R32_Typeless: + case DxgiFormat.D32_Float: + case DxgiFormat.R32_Float: + case DxgiFormat.R32_UInt: + case DxgiFormat.R32_SInt: + case DxgiFormat.R24G8_Typeless: + case DxgiFormat.D24_UNorm_S8_UInt: + case DxgiFormat.R24_UNorm_X8_Typeless: + case DxgiFormat.X24_Typeless_G8_UInt: + case DxgiFormat.R9G9B9E5_SharedExp: + case DxgiFormat.R8G8_B8G8_UNorm: + case DxgiFormat.G8R8_G8B8_UNorm: + case DxgiFormat.B8G8R8A8_UNorm: + case DxgiFormat.B8G8R8X8_UNorm: + case DxgiFormat.R10G10B10_XR_BIAS_A2_UNorm: + case DxgiFormat.B8G8R8A8_Typeless: + case DxgiFormat.B8G8R8A8_UNorm_SRGB: + case DxgiFormat.B8G8R8X8_Typeless: + case DxgiFormat.B8G8R8X8_UNorm_SRGB: + return 32; + case DxgiFormat.R8G8_Typeless: + case DxgiFormat.R8G8_UNorm: + case DxgiFormat.R8G8_UInt: + case DxgiFormat.R8G8_SNorm: + case DxgiFormat.R8G8_SInt: + case DxgiFormat.R16_Typeless: + case DxgiFormat.R16_Float: + case DxgiFormat.D16_UNorm: + case DxgiFormat.R16_UNorm: + case DxgiFormat.R16_UInt: + case DxgiFormat.R16_SNorm: + case DxgiFormat.R16_SInt: + case DxgiFormat.B5G6R5_UNorm: + case DxgiFormat.B5G5R5A1_UNorm: + case DxgiFormat.B4G4R4A4_UNorm: + return 16; + case DxgiFormat.R8_Typeless: + case DxgiFormat.R8_UNorm: + case DxgiFormat.R8_UInt: + case DxgiFormat.R8_SNorm: + case DxgiFormat.R8_SInt: + case DxgiFormat.A8_UNorm: + return 8; + case DxgiFormat.R1_UNorm: + return 1; + case DxgiFormat.BC1_Typeless: + case DxgiFormat.BC1_UNorm: + case DxgiFormat.BC1_UNorm_SRGB: + case DxgiFormat.BC4_Typeless: + case DxgiFormat.BC4_UNorm: + case DxgiFormat.BC4_SNorm: + return 4; + case DxgiFormat.BC2_Typeless: + case DxgiFormat.BC2_UNorm: + case DxgiFormat.BC2_UNorm_SRGB: + case DxgiFormat.BC3_Typeless: + case DxgiFormat.BC3_UNorm: + case DxgiFormat.BC3_UNorm_SRGB: + case DxgiFormat.BC5_Typeless: + case DxgiFormat.BC5_UNorm: + case DxgiFormat.BC5_SNorm: + case DxgiFormat.BC6H_Typeless: + case DxgiFormat.BC6H_UF16: + case DxgiFormat.BC6H_SF16: + case DxgiFormat.BC7_Typeless: + case DxgiFormat.BC7_UNorm: + case DxgiFormat.BC7_UNorm_SRGB: + return 8; + default: + return 0; } + } - public static int GetBitsPerPixel(D3dFormat d3dFormat, DxgiFormat dxgiFormat) + public static int GetBitsPerPixel(D3dFormat d3dFormat, DxgiFormat dxgiFormat) + { + if (dxgiFormat != DxgiFormat.Unknown) { - if (dxgiFormat != DxgiFormat.Unknown) - { - return GetBitsPerPixel(dxgiFormat); - } - - return GetBitsPerPixel(d3dFormat); + return GetBitsPerPixel(dxgiFormat); } - public static long ComputePitch(uint dimension, D3dFormat formatD3D, DxgiFormat formatDxgi, int defaultPitchOrLinearSize = 0) - { - int bitsPerPixel = GetBitsPerPixel(formatD3D, formatDxgi); - if (IsBlockCompressedFormat(formatD3D) || IsBlockCompressedFormat(formatDxgi)) - { - return ComputeBCPitch(dimension, bitsPerPixel * 2); - } - - if (formatD3D == D3dFormat.R8G8_B8G8 || formatD3D == D3dFormat.G8R8_G8B8 || formatD3D == D3dFormat.UYVY || formatD3D == D3dFormat.YUY2) - { - return Math.Max(1, (dimension + 1) >> 1) * 4; - } - - if (IsPackedFormat(formatDxgi)) - { - return defaultPitchOrLinearSize; - } - - return ComputeUncompressedPitch(dimension, bitsPerPixel); - } + return GetBitsPerPixel(d3dFormat); + } - public static bool IsBlockCompressedFormat(D3dFormat format) + public static long ComputePitch(uint dimension, D3dFormat formatD3D, DxgiFormat formatDxgi, int defaultPitchOrLinearSize = 0) + { + int bitsPerPixel = GetBitsPerPixel(formatD3D, formatDxgi); + if (IsBlockCompressedFormat(formatD3D) || IsBlockCompressedFormat(formatDxgi)) { - switch (format) - { - case D3dFormat.DXT1: - case D3dFormat.DXT2: - case D3dFormat.DXT3: - case D3dFormat.DXT4: - case D3dFormat.DXT5: - case D3dFormat.BC4U: - case D3dFormat.BC4S: - case D3dFormat.BC5S: - case D3dFormat.BC5U: - return true; - default: - return false; - } + return ComputeBCPitch(dimension, bitsPerPixel * 2); } - public static bool IsBlockCompressedFormat(DxgiFormat format) + if (formatD3D is D3dFormat.R8G8_B8G8 or D3dFormat.G8R8_G8B8 or D3dFormat.UYVY or D3dFormat.YUY2) { - switch (format) - { - case DxgiFormat.BC1_Typeless: - case DxgiFormat.BC1_UNorm: - case DxgiFormat.BC1_UNorm_SRGB: - case DxgiFormat.BC2_Typeless: - case DxgiFormat.BC2_UNorm: - case DxgiFormat.BC2_UNorm_SRGB: - case DxgiFormat.BC3_Typeless: - case DxgiFormat.BC3_UNorm: - case DxgiFormat.BC3_UNorm_SRGB: - case DxgiFormat.BC4_Typeless: - case DxgiFormat.BC4_UNorm: - case DxgiFormat.BC4_SNorm: - case DxgiFormat.BC5_Typeless: - case DxgiFormat.BC5_UNorm: - case DxgiFormat.BC5_SNorm: - case DxgiFormat.BC6H_Typeless: - case DxgiFormat.BC6H_SF16: - case DxgiFormat.BC6H_UF16: - case DxgiFormat.BC7_Typeless: - case DxgiFormat.BC7_UNorm: - case DxgiFormat.BC7_UNorm_SRGB: - return true; - default: - return false; - } + return Math.Max(1, (dimension + 1) >> 1) * 4; } - internal static int ComputeBCPitch(uint dimension, int bytesPerBlock) + if (IsPackedFormat(formatDxgi)) { - return (int)Math.Max(1, (dimension + 3) / 4) * bytesPerBlock; + return defaultPitchOrLinearSize; } - public static bool IsPackedFormat(DxgiFormat format) + return ComputeUncompressedPitch(dimension, bitsPerPixel); + } + + public static bool IsBlockCompressedFormat(D3dFormat format) + { + switch (format) { - switch (format) - { - case DxgiFormat.YUY2: - case DxgiFormat.Y210: - case DxgiFormat.Y216: - case DxgiFormat.Y410: - case DxgiFormat.Y416: - case DxgiFormat.Opaque_420: - case DxgiFormat.AI44: - case DxgiFormat.AYUV: - case DxgiFormat.IA44: - case DxgiFormat.NV11: - case DxgiFormat.NV12: - case DxgiFormat.P010: - case DxgiFormat.P016: - case DxgiFormat.R8G8_B8G8_UNorm: - case DxgiFormat.G8R8_G8B8_UNorm: - return true; - default: - return false; - } + case D3dFormat.DXT1: + case D3dFormat.DXT2: + case D3dFormat.DXT3: + case D3dFormat.DXT4: + case D3dFormat.DXT5: + case D3dFormat.BC4U: + case D3dFormat.BC4S: + case D3dFormat.BC5S: + case D3dFormat.BC5U: + return true; + default: + return false; } + } - internal static int ComputeUncompressedPitch(uint dimension, int bitsPerPixel) + public static bool IsBlockCompressedFormat(DxgiFormat format) + { + switch (format) { - return (int)Math.Max(1, ((dimension * bitsPerPixel) + 7) / 8); + case DxgiFormat.BC1_Typeless: + case DxgiFormat.BC1_UNorm: + case DxgiFormat.BC1_UNorm_SRGB: + case DxgiFormat.BC2_Typeless: + case DxgiFormat.BC2_UNorm: + case DxgiFormat.BC2_UNorm_SRGB: + case DxgiFormat.BC3_Typeless: + case DxgiFormat.BC3_UNorm: + case DxgiFormat.BC3_UNorm_SRGB: + case DxgiFormat.BC4_Typeless: + case DxgiFormat.BC4_UNorm: + case DxgiFormat.BC4_SNorm: + case DxgiFormat.BC5_Typeless: + case DxgiFormat.BC5_UNorm: + case DxgiFormat.BC5_SNorm: + case DxgiFormat.BC6H_Typeless: + case DxgiFormat.BC6H_SF16: + case DxgiFormat.BC6H_UF16: + case DxgiFormat.BC7_Typeless: + case DxgiFormat.BC7_UNorm: + case DxgiFormat.BC7_UNorm_SRGB: + return true; + default: + return false; } + } - public static long ComputeLinearSize(uint width, uint height, D3dFormat formatD3D, DxgiFormat formatDxgi, int defaultPitchOrLinearSize = 0) + internal static int ComputeBCPitch(uint dimension, int bytesPerBlock) => (int)Math.Max(1, (dimension + 3) / 4) * bytesPerBlock; + + public static bool IsPackedFormat(DxgiFormat format) + { + switch (format) { - int bitsPerPixel = GetBitsPerPixel(formatD3D, formatDxgi); - if (IsBlockCompressedFormat(formatD3D) || IsBlockCompressedFormat(formatDxgi)) - { - return ComputeBCLinearSize(width, height, bitsPerPixel * 2); - } + case DxgiFormat.YUY2: + case DxgiFormat.Y210: + case DxgiFormat.Y216: + case DxgiFormat.Y410: + case DxgiFormat.Y416: + case DxgiFormat.Opaque_420: + case DxgiFormat.AI44: + case DxgiFormat.AYUV: + case DxgiFormat.IA44: + case DxgiFormat.NV11: + case DxgiFormat.NV12: + case DxgiFormat.P010: + case DxgiFormat.P016: + case DxgiFormat.R8G8_B8G8_UNorm: + case DxgiFormat.G8R8_G8B8_UNorm: + return true; + default: + return false; + } + } - // These packed formats get a special handling... - if (formatD3D == D3dFormat.R8G8_B8G8 || formatD3D == D3dFormat.G8R8_G8B8 || - formatD3D == D3dFormat.UYVY || formatD3D == D3dFormat.YUY2) - { - return ((width + 1) >> 1) * 4 * height; - } + internal static int ComputeUncompressedPitch(uint dimension, int bitsPerPixel) => (int)Math.Max(1, ((dimension * bitsPerPixel) + 7) / 8); - if (IsPackedFormat(formatDxgi)) - { - return defaultPitchOrLinearSize * height; - } + public static long ComputeLinearSize(uint width, uint height, D3dFormat formatD3D, DxgiFormat formatDxgi, int defaultPitchOrLinearSize = 0) + { + int bitsPerPixel = GetBitsPerPixel(formatD3D, formatDxgi); + if (IsBlockCompressedFormat(formatD3D) || IsBlockCompressedFormat(formatDxgi)) + { + return ComputeBCLinearSize(width, height, bitsPerPixel * 2); + } - return ComputeUncompressedPitch(width, bitsPerPixel) * height; + // These packed formats get a special handling... + if (formatD3D is D3dFormat.R8G8_B8G8 or D3dFormat.G8R8_G8B8 or + D3dFormat.UYVY or D3dFormat.YUY2) + { + return ((width + 1) >> 1) * 4 * height; } - internal static long ComputeBCLinearSize(uint width, uint height, int bytesPerBlock) + if (IsPackedFormat(formatDxgi)) { - return ComputeBCPitch(width, bytesPerBlock) * Math.Max(1, (height + 3) / 4); + return defaultPitchOrLinearSize * height; } + + return ComputeUncompressedPitch(width, bitsPerPixel) * height; } + + internal static long ComputeBCLinearSize(uint width, uint height, int bytesPerBlock) => ComputeBCPitch(width, bytesPerBlock) * Math.Max(1, (height + 3) / 4); } diff --git a/src/ImageSharp.Textures/Formats/Dds/Enums/D3d10ResourceDimension.cs b/src/ImageSharp.Textures/Formats/Dds/Enums/D3d10ResourceDimension.cs index 056fb472..478fd72e 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Enums/D3d10ResourceDimension.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Enums/D3d10ResourceDimension.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums; + +/// +/// Identifies the type of resource being used. +/// +internal enum D3d10ResourceDimension : uint { /// - /// Identifies the type of resource being used. + /// Resource is of unknown type. /// - internal enum D3d10ResourceDimension : uint - { - /// - /// Resource is of unknown type. - /// - Unknown = 0, + Unknown = 0, - /// - /// Resource is a buffer. - /// - Buffer = 1, + /// + /// Resource is a buffer. + /// + Buffer = 1, - /// - /// Resource is a 1D Texture. - /// - Texture1D = 2, + /// + /// Resource is a 1D Texture. + /// + Texture1D = 2, - /// - /// Resource is a 2D Texture. - /// - Texture2D = 3, + /// + /// Resource is a 2D Texture. + /// + Texture2D = 3, - /// - /// Resource is a 3D Texture. - /// - Texture3D = 4 - } + /// + /// Resource is a 3D Texture. + /// + Texture3D = 4 } diff --git a/src/ImageSharp.Textures/Formats/Dds/Enums/D3d10ResourceMiscFlags.cs b/src/ImageSharp.Textures/Formats/Dds/Enums/D3d10ResourceMiscFlags.cs index 76dad602..ef155a96 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Enums/D3d10ResourceMiscFlags.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Enums/D3d10ResourceMiscFlags.cs @@ -1,62 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums +/// +/// Identifies other, less common options for resources. +/// +/// +/// and +/// are mutually exclusive flags: either one may be set in the resource creation calls but not both simultaneously. +/// +[Flags] +internal enum D3d10ResourceMiscFlags : uint { /// - /// Identifies other, less common options for resources. + /// Unknown. /// - /// - /// and - /// are mutually exclusive flags: either one may be set in the resource creation calls but not both simultaneously. - /// - [Flags] - internal enum D3d10ResourceMiscFlags : uint - { - /// - /// Unknown. - /// - Unknown = 0, + Unknown = 0, - /// - /// Enables an application to call ID3D10Device::GenerateMips on a texture resource. - /// The resource must be created with the bind flags that specify that the resource is a render target and a - /// shader resource. - /// - GenerateMips = 0x1, + /// + /// Enables an application to call ID3D10Device::GenerateMips on a texture resource. + /// The resource must be created with the bind flags that specify that the resource is a render target and a + /// shader resource. + /// + GenerateMips = 0x1, - /// - /// Enables the sharing of resource data between two or more Direct3D devices. - /// The only resources that can be shared are 2D non-mipmapped textures. - /// WARP and REF devices do not support shared resources. Attempting to create a resource with this flag on - /// either a WARP or REF device will cause the create method to return an E_OUTOFMEMORY error code. - /// - Shared = 0x2, + /// + /// Enables the sharing of resource data between two or more Direct3D devices. + /// The only resources that can be shared are 2D non-mipmapped textures. + /// WARP and REF devices do not support shared resources. Attempting to create a resource with this flag on + /// either a WARP or REF device will cause the create method to return an E_OUTOFMEMORY error code. + /// + Shared = 0x2, - /// - /// Enables an application to create a cube texture from a Texture2DArray that contains 6 textures. - /// - TextureCube = 0x4, + /// + /// Enables an application to create a cube texture from a Texture2DArray that contains 6 textures. + /// + TextureCube = 0x4, - /// - /// Enables the resource created to be synchronized using the IDXGIKeyedMutex::AcquireSync and ReleaseSync APIs. - /// The following resource creation D3D10 APIs, that all take a D3D10_RESOURCE_MISC_FLAG parameter, have been extended to support the new flag. - /// ID3D10Device1::CreateTexture1D, ID3D10Device1::CreateTexture2D, ID3D10Device1::CreateTexture3D, ID3D10Device1::CreateBuffer - /// If any of the listed functions are called with the D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX flag set, the interface returned can be - /// queried for an IDXGIKeyedMutex interface, which implements AcquireSync and ReleaseSync APIs to synchronize access to the surface. - /// The device creating the surface, and any other device opening the surface (using OpenSharedResource) is required to - /// call IDXGIKeyedMutex::AcquireSync before any rendering commands to the surface, and IDXGIKeyedMutex::ReleaseSync when it is done rendering. - /// WARP and REF devices do not support shared resources. Attempting to create a resource with this flag on either a WARP or REF device will cause the - /// create method to return an E_OUTOFMEMORY error code. - /// - SharedKeyedMutex = 0x10, + /// + /// Enables the resource created to be synchronized using the IDXGIKeyedMutex::AcquireSync and ReleaseSync APIs. + /// The following resource creation D3D10 APIs, that all take a D3D10_RESOURCE_MISC_FLAG parameter, have been extended to support the new flag. + /// ID3D10Device1::CreateTexture1D, ID3D10Device1::CreateTexture2D, ID3D10Device1::CreateTexture3D, ID3D10Device1::CreateBuffer + /// If any of the listed functions are called with the D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX flag set, the interface returned can be + /// queried for an IDXGIKeyedMutex interface, which implements AcquireSync and ReleaseSync APIs to synchronize access to the surface. + /// The device creating the surface, and any other device opening the surface (using OpenSharedResource) is required to + /// call IDXGIKeyedMutex::AcquireSync before any rendering commands to the surface, and IDXGIKeyedMutex::ReleaseSync when it is done rendering. + /// WARP and REF devices do not support shared resources. Attempting to create a resource with this flag on either a WARP or REF device will cause the + /// create method to return an E_OUTOFMEMORY error code. + /// + SharedKeyedMutex = 0x10, - /// - /// Enables a surface to be used for GDI interoperability. Setting this flag enables rendering on - /// the surface via IDXGISurface1::GetDC. - /// - GdiCompatible = 0x20 - } + /// + /// Enables a surface to be used for GDI interoperability. Setting this flag enables rendering on + /// the surface via IDXGISurface1::GetDC. + /// + GdiCompatible = 0x20 } diff --git a/src/ImageSharp.Textures/Formats/Dds/Enums/D3dFormat.cs b/src/ImageSharp.Textures/Formats/Dds/Enums/D3dFormat.cs index 37d63eae..37b544de 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Enums/D3dFormat.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Enums/D3dFormat.cs @@ -1,88 +1,87 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums; + +/// +/// Defines the various types of surface formats. +/// +/// +/// Values taken from http://msdn.microsoft.com/en-us/library/windows/desktop/bb172558%28v=vs.85%29.aspx +/// +internal enum D3dFormat : uint { - /// - /// Defines the various types of surface formats. - /// - /// - /// Values taken from http://msdn.microsoft.com/en-us/library/windows/desktop/bb172558%28v=vs.85%29.aspx - /// - internal enum D3dFormat : uint - { - Unknown = 0, - R8G8B8 = 20, - A8R8G8B8 = 21, - X8R8G8B8 = 22, - R5G6B5 = 23, - X1R5G5B5 = 24, - A1R5G5B5 = 25, - A4R4G4B4 = 26, - R3G3B2 = 27, - A8 = 28, - A8R3G3B2 = 29, - X4R4G4B4 = 30, - A2B10G10R10 = 31, - A8B8G8R8 = 32, - X8B8G8R8 = 33, - G16R16 = 34, - A2R10G10B10 = 35, - A16B16G16R16 = 36, - A8P8 = 40, - P8 = 41, - L8 = 50, - A8L8 = 51, - A4L4 = 52, - V8U8 = 60, - L6V5U5 = 61, - X8L8V8U8 = 62, - DQ8W8V8U8 = 63, - V16U16 = 64, - A2W10V10U10 = 67, - UYVY = (int)DdsFourCc.UYVY, - YUY2 = (int)DdsFourCc.YUY2, - R8G8_B8G8 = (int)DdsFourCc.RGBG, - G8R8_G8B8 = (int)DdsFourCc.GRGB, - RGBG = (int)DdsFourCc.RGBG, - GRGB = (int)DdsFourCc.GRGB, - Multi2_ARGB8 = (int)DdsFourCc.MET1, - DXT1 = (int)DdsFourCc.DXT1, - DXT2 = (int)DdsFourCc.DXT2, - DXT3 = (int)DdsFourCc.DXT3, - DXT4 = (int)DdsFourCc.DXT4, - DXT5 = (int)DdsFourCc.DXT5, - BC4U = (int)DdsFourCc.BC4U, - BC4S = (int)DdsFourCc.BC4S, - BC5U = (int)DdsFourCc.BC5U, - BC5S = (int)DdsFourCc.BC5S, - ATI1 = (int)DdsFourCc.ATI1, - ATI2 = (int)DdsFourCc.ATI2, - D16_Lockable = 70, - D32 = 71, - D15S1 = 73, - D24S8 = 75, - D24X8 = 77, - D24X4S4 = 79, - D16 = 80, - L16 = 81, - D32F_Lockable = 82, - D24FS8 = 83, - D32_Lockable = 84, - S8_Lockable = 85, - VertexData = 100, - Index16 = 101, - Index32 = 102, - Q16W16V16U16 = 110, - R16F = 111, - G16R16F = 112, - A16B16G16R16F = 113, - R32F = 114, - G32R32F = 115, - A32B32G32R32F = 116, - CxV8U8 = 117, - A1 = 118, - A2B10G10R10_XR_Bias = 119, - BinaryBuffer = 199, - } + Unknown = 0, + R8G8B8 = 20, + A8R8G8B8 = 21, + X8R8G8B8 = 22, + R5G6B5 = 23, + X1R5G5B5 = 24, + A1R5G5B5 = 25, + A4R4G4B4 = 26, + R3G3B2 = 27, + A8 = 28, + A8R3G3B2 = 29, + X4R4G4B4 = 30, + A2B10G10R10 = 31, + A8B8G8R8 = 32, + X8B8G8R8 = 33, + G16R16 = 34, + A2R10G10B10 = 35, + A16B16G16R16 = 36, + A8P8 = 40, + P8 = 41, + L8 = 50, + A8L8 = 51, + A4L4 = 52, + V8U8 = 60, + L6V5U5 = 61, + X8L8V8U8 = 62, + DQ8W8V8U8 = 63, + V16U16 = 64, + A2W10V10U10 = 67, + UYVY = (int)DdsFourCc.UYVY, + YUY2 = (int)DdsFourCc.YUY2, + R8G8_B8G8 = (int)DdsFourCc.RGBG, + G8R8_G8B8 = (int)DdsFourCc.GRGB, + RGBG = (int)DdsFourCc.RGBG, + GRGB = (int)DdsFourCc.GRGB, + Multi2_ARGB8 = (int)DdsFourCc.MET1, + DXT1 = (int)DdsFourCc.DXT1, + DXT2 = (int)DdsFourCc.DXT2, + DXT3 = (int)DdsFourCc.DXT3, + DXT4 = (int)DdsFourCc.DXT4, + DXT5 = (int)DdsFourCc.DXT5, + BC4U = (int)DdsFourCc.BC4U, + BC4S = (int)DdsFourCc.BC4S, + BC5U = (int)DdsFourCc.BC5U, + BC5S = (int)DdsFourCc.BC5S, + ATI1 = (int)DdsFourCc.ATI1, + ATI2 = (int)DdsFourCc.ATI2, + D16_Lockable = 70, + D32 = 71, + D15S1 = 73, + D24S8 = 75, + D24X8 = 77, + D24X4S4 = 79, + D16 = 80, + L16 = 81, + D32F_Lockable = 82, + D24FS8 = 83, + D32_Lockable = 84, + S8_Lockable = 85, + VertexData = 100, + Index16 = 101, + Index32 = 102, + Q16W16V16U16 = 110, + R16F = 111, + G16R16F = 112, + A16B16G16R16F = 113, + R32F = 114, + G32R32F = 115, + A32B32G32R32F = 116, + CxV8U8 = 117, + A1 = 118, + A2B10G10R10_XR_Bias = 119, + BinaryBuffer = 199, } diff --git a/src/ImageSharp.Textures/Formats/Dds/Enums/DdsCaps1.cs b/src/ImageSharp.Textures/Formats/Dds/Enums/DdsCaps1.cs index abd4d7ea..6efa6831 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Enums/DdsCaps1.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Enums/DdsCaps1.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums +/// Specifies the complexity of the surfaces stored. +/// +/// The DDS_SURFACE_FLAGS_MIPMAP flag, which is defined in Dds.h, is a bitwise-OR combination of the +/// and flags. +/// The DDS_SURFACE_FLAGS_TEXTURE flag, which is defined in Dds.h, is equal to the +/// flag. +/// The DDS_SURFACE_FLAGS_CUBEMAP flag, which is defined in Dds.h, is equal to the +/// flag. +/// +[Flags] +internal enum DdsCaps1 : uint { - /// Specifies the complexity of the surfaces stored. - /// - /// The DDS_SURFACE_FLAGS_MIPMAP flag, which is defined in Dds.h, is a bitwise-OR combination of the - /// and flags. - /// The DDS_SURFACE_FLAGS_TEXTURE flag, which is defined in Dds.h, is equal to the - /// flag. - /// The DDS_SURFACE_FLAGS_CUBEMAP flag, which is defined in Dds.h, is equal to the - /// flag. - /// - [Flags] - internal enum DdsCaps1 : uint - { - /// - /// Unknown. - /// - Unknown = 0, + /// + /// Unknown. + /// + Unknown = 0, - /// - /// Optional; must be used on any file that contains more than one surface - /// (a mipmap, a cubic environment map, or mipmapped volume texture). - /// - Complex = 0x8, + /// + /// Optional; must be used on any file that contains more than one surface + /// (a mipmap, a cubic environment map, or mipmapped volume texture). + /// + Complex = 0x8, - /// - /// Optional; should be used for a mipmap. - /// - MipMap = 0x400000, + /// + /// Optional; should be used for a mipmap. + /// + MipMap = 0x400000, - /// - /// Required. - /// - Texture = 0x1000 - } + /// + /// Required. + /// + Texture = 0x1000 } diff --git a/src/ImageSharp.Textures/Formats/Dds/Enums/DdsCaps2.cs b/src/ImageSharp.Textures/Formats/Dds/Enums/DdsCaps2.cs index ae88ac0f..a46bdf59 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Enums/DdsCaps2.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Enums/DdsCaps2.cs @@ -1,63 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums; + +/// +/// Additional detail about the surfaces stored. +/// +/// +/// Although Direct3D 9 supports partial cube-maps, Direct3D 10, 10.1, and 11 require that you +/// define all six cube-map faces (that is, you must set DDS_Cubemap_ALLFACES). +/// +[Flags] +internal enum DdsCaps2 : uint { /// - /// Additional detail about the surfaces stored. - /// - /// - /// Although Direct3D 9 supports partial cube-maps, Direct3D 10, 10.1, and 11 require that you - /// define all six cube-map faces (that is, you must set DDS_Cubemap_ALLFACES). - /// - [Flags] - internal enum DdsCaps2 : uint - { - /// - /// Unknown. - /// - Unknown = 0, - - /// - /// Required for a cube map. - /// - Cubemap = 0x200, - - /// - /// Required when these surfaces are stored in a cube map. - /// - CubemapPositiveX = 0x400, - - /// - /// Required when these surfaces are stored in a cube map. - /// - CubemapNegativeX = 0x800, - - /// - /// Required when these surfaces are stored in a cube map. - /// - CubemapPositiveY = 0x1000, - - /// - /// Required when these surfaces are stored in a cube map. - /// - CubemapNegativeY = 0x2000, - - /// - /// Required when these surfaces are stored in a cube map. - /// - CubemapPositiveZ = 0x4000, - - /// - /// Required when these surfaces are stored in a cube map. - /// - CubemapNegativeZ = 0x8000, - - /// - /// Required for a volume texture. - /// - Volume = 0x200000 - } + /// Unknown. + /// + Unknown = 0, + + /// + /// Required for a cube map. + /// + Cubemap = 0x200, + + /// + /// Required when these surfaces are stored in a cube map. + /// + CubemapPositiveX = 0x400, + + /// + /// Required when these surfaces are stored in a cube map. + /// + CubemapNegativeX = 0x800, + + /// + /// Required when these surfaces are stored in a cube map. + /// + CubemapPositiveY = 0x1000, + + /// + /// Required when these surfaces are stored in a cube map. + /// + CubemapNegativeY = 0x2000, + + /// + /// Required when these surfaces are stored in a cube map. + /// + CubemapPositiveZ = 0x4000, + + /// + /// Required when these surfaces are stored in a cube map. + /// + CubemapNegativeZ = 0x8000, + + /// + /// Required for a volume texture. + /// + Volume = 0x200000 } diff --git a/src/ImageSharp.Textures/Formats/Dds/Enums/DdsFlags.cs b/src/ImageSharp.Textures/Formats/Dds/Enums/DdsFlags.cs index ae311d09..78be2076 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Enums/DdsFlags.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Enums/DdsFlags.cs @@ -1,72 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums +/// +/// Flags to indicate which members contain valid data. +/// +/// +/// The DDS_HEADER_FLAGS_TEXTURE flag, which is defined in Dds.h, is a bitwise-OR combination of the +/// , , +/// , and flags. +/// The DDS_HEADER_FLAGS_MIPMAP flag, which is defined in Dds.h, is equal to the +/// flag. +/// The DDS_HEADER_FLAGS_VOLUME flag, which is defined in Dds.h, is equal to the +/// flag. +/// The DDS_HEADER_FLAGS_PITCH flag, which is defined in Dds.h, is equal to the +/// flag. +/// The DDS_HEADER_FLAGS_LINEARSIZE flag, which is defined in Dds.h, is equal to the +/// flag. +/// +[Flags] +internal enum DdsFlags : uint { /// - /// Flags to indicate which members contain valid data. + /// Unknown. /// - /// - /// The DDS_HEADER_FLAGS_TEXTURE flag, which is defined in Dds.h, is a bitwise-OR combination of the - /// , , - /// , and flags. - /// The DDS_HEADER_FLAGS_MIPMAP flag, which is defined in Dds.h, is equal to the - /// flag. - /// The DDS_HEADER_FLAGS_VOLUME flag, which is defined in Dds.h, is equal to the - /// flag. - /// The DDS_HEADER_FLAGS_PITCH flag, which is defined in Dds.h, is equal to the - /// flag. - /// The DDS_HEADER_FLAGS_LINEARSIZE flag, which is defined in Dds.h, is equal to the - /// flag. - /// - [Flags] - internal enum DdsFlags : uint - { - /// - /// Unknown. - /// - Unknown = 0, + Unknown = 0, - /// - /// Required in every .dds file. - /// - Caps = 0x1, + /// + /// Required in every .dds file. + /// + Caps = 0x1, - /// - /// Required in every .dds file. - /// - Height = 0x2, + /// + /// Required in every .dds file. + /// + Height = 0x2, - /// - /// Required in every .dds file. - /// - Width = 0x4, + /// + /// Required in every .dds file. + /// + Width = 0x4, - /// - /// Required when pitch is provided for an uncompressed texture. - /// - Pitch = 0x8, + /// + /// Required when pitch is provided for an uncompressed texture. + /// + Pitch = 0x8, - /// - /// Required in every .dds file. - /// - PixelFormat = 0x1000, + /// + /// Required in every .dds file. + /// + PixelFormat = 0x1000, - /// - /// Required in a mipmapped texture. - /// - MipMapCount = 0x20000, + /// + /// Required in a mipmapped texture. + /// + MipMapCount = 0x20000, - /// - /// Required when pitch is provided for a compressed texture. - /// - LinearSize = 0x80000, + /// + /// Required when pitch is provided for a compressed texture. + /// + LinearSize = 0x80000, - /// - /// Required in a depth texture. - /// - Depth = 0x800000 - } + /// + /// Required in a depth texture. + /// + Depth = 0x800000 } diff --git a/src/ImageSharp.Textures/Formats/Dds/Enums/DdsPixelFormatFlags.cs b/src/ImageSharp.Textures/Formats/Dds/Enums/DdsPixelFormatFlags.cs index 4fc301c9..c49cdd63 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Enums/DdsPixelFormatFlags.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Enums/DdsPixelFormatFlags.cs @@ -1,61 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums +/// +/// Values which indicate what type of data is in the surface. +/// +[Flags] +internal enum DdsPixelFormatFlags : uint { /// - /// Values which indicate what type of data is in the surface. - /// - [Flags] - internal enum DdsPixelFormatFlags : uint - { - /// - /// Unknown. - /// - Unknown = 0, - - /// - /// Texture contains alpha data; dwRGBAlphaBitMask contains valid data. - /// - AlphaPixels = 0x1, - - /// - /// Used in some older DDS files for alpha channel only uncompressed data - /// (dwRGBBitCount contains the alpha channel bitcount; dwABitMask contains valid data). - /// - Alpha = 0x2, - - /// - /// Texture contains compressed RGB data; dwFourCC contains valid data. - /// - FourCC = 0x4, - - /// - /// Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks - /// (dwRBitMask, dwRBitMask, dwRBitMask) contain valid data. - /// - RGB = 0x40, - - /// - /// Texture contains uncompressed RGB and alpha data; dwRGBBitCount and all of the masks - /// (dwRBitMask, dwRBitMask, dwRBitMask, dwRGBAlphaBitMask) contain valid data. - /// - RGBA = RGB | AlphaPixels, - - /// - /// Used in some older DDS files for YUV uncompressed data - /// (dwRGBBitCount contains the YUV bit count; dwRBitMask contains the Y mask, dwGBitMask contains the U mask, - /// dwBBitMask contains the V mask). - /// - YUV = 0x200, - - /// - /// Used in some older DDS files for single channel color uncompressed data - /// (dwRGBBitCount contains the luminance channel bit count; dwRBitMask contains the channel mask). - /// Can be combined with DDPF_ALPHAPIXELS for a two channel DDS file. - /// - Luminance = 0x20000 - } + /// Unknown. + /// + Unknown = 0, + + /// + /// Texture contains alpha data; dwRGBAlphaBitMask contains valid data. + /// + AlphaPixels = 0x1, + + /// + /// Used in some older DDS files for alpha channel only uncompressed data + /// (dwRGBBitCount contains the alpha channel bitcount; dwABitMask contains valid data). + /// + Alpha = 0x2, + + /// + /// Texture contains compressed RGB data; dwFourCC contains valid data. + /// + FourCC = 0x4, + + /// + /// Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks + /// (dwRBitMask, dwRBitMask, dwRBitMask) contain valid data. + /// + RGB = 0x40, + + /// + /// Texture contains uncompressed RGB and alpha data; dwRGBBitCount and all of the masks + /// (dwRBitMask, dwRBitMask, dwRBitMask, dwRGBAlphaBitMask) contain valid data. + /// + RGBA = RGB | AlphaPixels, + + /// + /// Used in some older DDS files for YUV uncompressed data + /// (dwRGBBitCount contains the YUV bit count; dwRBitMask contains the Y mask, dwGBitMask contains the U mask, + /// dwBBitMask contains the V mask). + /// + YUV = 0x200, + + /// + /// Used in some older DDS files for single channel color uncompressed data + /// (dwRGBBitCount contains the luminance channel bit count; dwRBitMask contains the channel mask). + /// Can be combined with DDPF_ALPHAPIXELS for a two channel DDS file. + /// + Luminance = 0x20000 } diff --git a/src/ImageSharp.Textures/Formats/Dds/Enums/DxgiFormat.cs b/src/ImageSharp.Textures/Formats/Dds/Enums/DxgiFormat.cs index dc4be9ac..37db737f 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Enums/DxgiFormat.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Enums/DxgiFormat.cs @@ -2,648 +2,647 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Emums; + +/// +/// Resource data formats which includes fully-typed and typeless formats. +/// +/// +/// Values taken from http://msdn.microsoft.com/en-us/library/windows/desktop/bb173059%28v=vs.85%29.aspx +/// +internal enum DxgiFormat : uint { + Unknown = 0, + + /// + /// A four-component, 128-bit typeless format that supports 32 bits per channel including alpha. + /// + R32G32B32A32_Typeless = 1, + + /// + /// A four-component, 128-bit floating-point format that supports 32 bits per channel including alpha. + /// + R32G32B32A32_Float = 2, + + /// + /// A four-component, 128-bit unsigned-integer format that supports 32 bits per channel including alpha. + /// + R32G32B32A32_UInt = 3, + /// - /// Resource data formats which includes fully-typed and typeless formats. - /// - /// - /// Values taken from http://msdn.microsoft.com/en-us/library/windows/desktop/bb173059%28v=vs.85%29.aspx - /// - internal enum DxgiFormat : uint - { - Unknown = 0, - - /// - /// A four-component, 128-bit typeless format that supports 32 bits per channel including alpha. - /// - R32G32B32A32_Typeless = 1, - - /// - /// A four-component, 128-bit floating-point format that supports 32 bits per channel including alpha. - /// - R32G32B32A32_Float = 2, - - /// - /// A four-component, 128-bit unsigned-integer format that supports 32 bits per channel including alpha. - /// - R32G32B32A32_UInt = 3, - - /// - /// A four-component, 128-bit signed-integer format that supports 32 bits per channel including alpha. - /// - R32G32B32A32_SInt = 4, - - /// - /// A three-component, 96-bit typeless format that supports 32 bits per color channel. - /// - R32G32B32_Typeless = 5, - - /// - /// A three-component, 96-bit floating-point format that supports 32 bits per color channel. - /// - R32G32B32_Float = 6, - - /// - /// A three-component, 96-bit unsigned-integer format that supports 32 bits per color channel. - /// - R32G32B32_UInt = 7, - - /// - /// A three-component, 96-bit signed-integer format that supports 32 bits per color channel. - /// - R32G32B32_SInt = 8, - - /// - /// A four-component, 64-bit typeless format that supports 16 bits per channel including alpha. - /// - R16G16B16A16_Typeless = 9, - - /// - /// A four-component, 64-bit floating-point format that supports 16 bits per channel including alpha. - /// - R16G16B16A16_Float = 10, - - /// - /// A four-component, 64-bit unsigned-normalized-integer format that supports 16 bits per channel including alpha. - /// - R16G16B16A16_UNorm = 11, - - /// - /// A four-component, 64-bit unsigned-integer format that supports 16 bits per channel including alpha. - /// - R16G16B16A16_UInt = 12, - - /// - /// A four-component, 64-bit signed-normalized-integer format that supports 16 bits per channel including alpha. - /// - R16G16B16A16_SNorm = 13, - - /// - /// A four-component, 64-bit signed-integer format that supports 16 bits per channel including alpha. - /// - R16G16B16A16_SInt = 14, - - /// - /// A two-component, 64-bit typeless format that supports 32 bits for the red channel and 32 bits for the green channel. - /// - R32G32_Typeless = 15, - - /// - /// A two-component, 64-bit floating-point format that supports 32 bits for the red channel and 32 bits for the green channel. - /// - R32G32_Float = 16, - - /// - /// A two-component, 64-bit unsigned-integer format that supports 32 bits for the red channel and 32 bits for the green channel. - /// - R32G32_UInt = 17, - - /// - /// A two-component, 64-bit signed-integer format that supports 32 bits for the red channel and 32 bits for the green channel. - /// - R32G32_SInt = 18, - - /// - /// A two-component, 64-bit typeless format that supports 32 bits for the red channel, 8 bits for the green channel, and 24 bits are unused. - /// - R32G8X24_Typeless = 19, - - /// - /// A 32-bit floating-point component, and two unsigned-integer components (with an additional 32 bits). This format supports 32-bit depth, 8-bit stencil, and 24 bits are unused. - /// - D32_Float_S8X24_UInt = 20, - - /// - /// A 32-bit floating-point component, and two typeless components (with an additional 32 bits). This format supports 32-bit red channel, 8 bits are unused, and 24 bits are unused. - /// - R32_Float_X8X24_Typeless = 21, - - /// - /// A 32-bit typeless component, and two unsigned-integer components (with an additional 32 bits). This format has 32 bits unused, 8 bits for green channel, and 24 bits are unused. - /// - X32_Typeless_G8X24_UInt = 22, - - /// - /// A four-component, 32-bit typeless format that supports 10 bits for each color and 2 bits for alpha. - /// - R10G10B10A2_Typeless = 23, - - /// - /// A four-component, 32-bit unsigned-normalized-integer format that supports 10 bits for each color and 2 bits for alpha. - /// - R10G10B10A2_UNorm = 24, - - /// - /// A four-component, 32-bit unsigned-integer format that supports 10 bits for each color and 2 bits for alpha. - /// - R10G10B10A2_UInt = 25, - - /// - /// Three partial-precision floating-point numbers encoded into a single 32-bit value (a variant of s10e5, which is sign bit, 10-bit mantissa, and 5-bit biased (15) exponent). - /// There are no sign bits, and there is a 5-bit biased (15) exponent for each channel, 6-bit mantissa for R and G, and a 5-bit mantissa for B. - /// - R11G11B10_Float = 26, - - /// - /// A four-component, 32-bit typeless format that supports 8 bits per channel including alpha. - /// - R8G8B8A8_Typeless = 27, - - /// - /// A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits per channel including alpha. - /// - R8G8B8A8_UNorm = 28, - - /// - /// A four-component, 32-bit unsigned-normalized integer sRGB format that supports 8 bits per channel including alpha. - /// - R8G8B8A8_UNorm_SRGB = 29, - - /// - /// A four-component, 32-bit unsigned-integer format that supports 8 bits per channel including alpha. - /// - R8G8B8A8_UInt = 30, - - /// - /// A four-component, 32-bit signed-normalized-integer format that supports 8 bits per channel including alpha. - /// - R8G8B8A8_SNorm = 31, - - /// - /// A four-component, 32-bit signed-integer format that supports 8 bits per channel including alpha. - /// - R8G8B8A8_SInt = 32, - - /// - /// A two-component, 32-bit typeless format that supports 16 bits for the red channel and 16 bits for the green channel. - /// - R16G16_Typeless = 33, - - /// - /// A two-component, 32-bit floating-point format that supports 16 bits for the red channel and 16 bits for the green channel. - /// - R16G16_Float = 34, - - /// - /// A two-component, 32-bit unsigned-normalized-integer format that supports 16 bits each for the green and red channels. - /// - R16G16_UNorm = 35, - - /// - /// A two-component, 32-bit unsigned-integer format that supports 16 bits for the red channel and 16 bits for the green channel. - /// - R16G16_UInt = 36, - - /// - /// A two-component, 32-bit signed-normalized-integer format that supports 16 bits for the red channel and 16 bits for the green channel. - /// - R16G16_SNorm = 37, - - /// - /// A two-component, 32-bit signed-integer format that supports 16 bits for the red channel and 16 bits for the green channel. - /// - R16G16_SInt = 38, - - /// - /// A single-component, 32-bit typeless format that supports 32 bits for the red channel. - /// - R32_Typeless = 39, - - /// - /// A single-component, 32-bit floating-point format that supports 32 bits for depth. - /// - D32_Float = 40, - - /// - /// A single-component, 32-bit floating-point format that supports 32 bits for the red channel. - /// - R32_Float = 41, - - /// - /// A single-component, 32-bit unsigned-integer format that supports 32 bits for the red channel. - /// - R32_UInt = 42, - - /// - /// A single-component, 32-bit signed-integer format that supports 32 bits for the red channel. - /// - R32_SInt = 43, - - /// - /// A two-component, 32-bit typeless format that supports 24 bits for the red channel and 8 bits for the green channel. - /// - R24G8_Typeless = 44, - - /// - /// A 32-bit z-buffer format that supports 24 bits for depth and 8 bits for stencil. - /// - D24_UNorm_S8_UInt = 45, - - /// - /// A 32-bit format, that contains a 24 bit, single-component, unsigned-normalized integer, with an additional typeless 8 bits. This format has 24 bits red channel and 8 bits unused. - /// - R24_UNorm_X8_Typeless = 46, - - /// - /// A 32-bit format, that contains a 24 bit, single-component, typeless format, with an additional 8 bit unsigned integer component. This format has 24 bits unused and 8 bits green channel. - /// - X24_Typeless_G8_UInt = 47, - - /// - /// A two-component, 16-bit typeless format that supports 8 bits for the red channel and 8 bits for the green channel. - /// - R8G8_Typeless = 48, - - /// - /// A two-component, 16-bit unsigned-normalized-integer format that supports 8 bits for the red channel and 8 bits for the green channel. - /// - R8G8_UNorm = 49, - - /// - /// A two-component, 16-bit unsigned-integer format that supports 8 bits for the red channel and 8 bits for the green channel. - /// - R8G8_UInt = 50, - - /// - /// A two-component, 16-bit signed-normalized-integer format that supports 8 bits for the red channel and 8 bits for the green channel. - /// - R8G8_SNorm = 51, - - /// - /// A two-component, 16-bit signed-integer format that supports 8 bits for the red channel and 8 bits for the green channel. - /// - R8G8_SInt = 52, - - /// - /// A single-component, 16-bit typeless format that supports 16 bits for the red channel. - /// - R16_Typeless = 53, - - /// - /// A single-component, 16-bit floating-point format that supports 16 bits for the red channel. - /// - R16_Float = 54, - - /// - /// A single-component, 16-bit unsigned-normalized-integer format that supports 16 bits for depth. - /// - D16_UNorm = 55, - - /// - /// A single-component, 16-bit unsigned-normalized-integer format that supports 16 bits for the red channel. - /// - R16_UNorm = 56, - - /// - /// A single-component, 16-bit unsigned-integer format that supports 16 bits for the red channel. - /// - R16_UInt = 57, - - /// - /// A single-component, 16-bit signed-normalized-integer format that supports 16 bits for the red channel. - /// - R16_SNorm = 58, - - /// - /// A single-component, 16-bit signed-integer format that supports 16 bits for the red channel. - /// - R16_SInt = 59, - - /// - /// A single-component, 8-bit typeless format that supports 8 bits for the red channel. - /// - R8_Typeless = 60, - - /// - /// A single-component, 8-bit unsigned-normalized-integer format that supports 8 bits for the red channel. - /// - R8_UNorm = 61, - - /// - /// A single-component, 8-bit unsigned-integer format that supports 8 bits for the red channel. - /// - R8_UInt = 62, - - /// - /// A single-component, 8-bit signed-normalized-integer format that supports 8 bits for the red channel. - /// - R8_SNorm = 63, - - /// - /// A single-component, 8-bit signed-integer format that supports 8 bits for the red channel. - /// - R8_SInt = 64, - - /// - /// A single-component, 8-bit unsigned-normalized-integer format for alpha only. - /// - A8_UNorm = 65, - - /// - /// A single-component, 1-bit unsigned-normalized integer format that supports 1 bit for the red channel. - /// - R1_UNorm = 66, - - /// - /// Three partial-precision floating-point numbers encoded into a single 32-bit value all sharing the same 5-bit exponent (variant of s10e5, which is sign bit, 10-bit mantissa, and 5-bit biased (15) exponent). - /// There is no sign bit, and there is a shared 5-bit biased (15) exponent and a 9-bit mantissa for each channel. - /// - R9G9B9E5_SharedExp = 67, - - /// - /// A four-component, 32-bit unsigned-normalized-integer format. This packed RGB format is analogous to the UYVY format. Each 32-bit block describes a pair of pixels: (R8, G8, B8) and (R8, G8, B8) where the R8/B8 values are repeated, - /// and the G8 values are unique to each pixel. Width must be even. - /// - R8G8_B8G8_UNorm = 68, - - /// - /// A four-component, 32-bit unsigned-normalized-integer format. This packed RGB format is analogous to the YUY2 format. Each 32-bit block describes a pair of pixels: (R8, G8, B8) and (R8, G8, B8) where the R8/B8 values are repeated, - /// and the G8 values are unique to each pixel. Width must be even. - /// - G8R8_G8B8_UNorm = 69, - - /// - /// Four-component typeless block-compression format. - /// - BC1_Typeless = 70, - - /// - /// Four-component block-compression format. - /// - BC1_UNorm = 71, - - /// - /// Four-component block-compression format for sRGB data. - /// - BC1_UNorm_SRGB = 72, - - /// - /// Four-component typeless block-compression format. - /// - BC2_Typeless = 73, - - /// - /// Four-component block-compression format. - /// - BC2_UNorm = 74, - - /// - /// Four-component block-compression format for sRGB data. - /// - BC2_UNorm_SRGB = 75, - - /// - /// Four-component typeless block-compression format. - /// - BC3_Typeless = 76, - - /// - /// Four-component block-compression format. - /// - BC3_UNorm = 77, - - /// - /// Four-component block-compression format for sRGB data. - /// - BC3_UNorm_SRGB = 78, - - /// - /// One-component typeless block-compression format. - /// - BC4_Typeless = 79, - - /// - /// One-component block-compression format. - /// - BC4_UNorm = 80, - - /// - /// One-component block-compression format. - /// - BC4_SNorm = 81, - - /// - /// Two-component typeless block-compression format. - /// - BC5_Typeless = 82, - - /// - /// Two-component block-compression format. - /// - BC5_UNorm = 83, - - /// - /// Two-component block-compression format. - /// - BC5_SNorm = 84, - - /// - /// A three-component, 16-bit unsigned-normalized-integer format that supports 5 bits for blue, 6 bits for green, and 5 bits for red. - /// Direct3D 10 through Direct3D 11: This value is defined for DXGI. However, Direct3D 10, 10.1, or 11 devices do not support this format. - /// Direct3D 11.1: This value is not supported until Windows 8. - /// - B5G6R5_UNorm = 85, - - /// - /// A four-component, 16-bit unsigned-normalized-integer format that supports 5 bits for each color channel and 1-bit alpha. - /// Direct3D 10 through Direct3D 11: This value is defined for DXGI. However, Direct3D 10, 10.1, or 11 devices do not support this format. - /// Direct3D 11.1: This value is not supported until Windows 8. - /// - B5G5R5A1_UNorm = 86, - - /// - /// A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits for each color channel and 8-bit alpha. - /// - B8G8R8A8_UNorm = 87, - - /// - /// A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits for each color channel and 8 bits unused. - /// - B8G8R8X8_UNorm = 88, - - /// - /// A four-component, 32-bit 2.8-biased fixed-point format that supports 10 bits for each color channel and 2-bit alpha. - /// - R10G10B10_XR_BIAS_A2_UNorm = 89, - - /// - /// A four-component, 32-bit typeless format that supports 8 bits for each channel including alpha. - /// - B8G8R8A8_Typeless = 90, - - /// - /// A four-component, 32-bit unsigned-normalized standard RGB format that supports 8 bits for each channel including alpha. - /// - B8G8R8A8_UNorm_SRGB = 91, - - /// - /// A four-component, 32-bit typeless format that supports 8 bits for each color channel, and 8 bits are unused. - /// - B8G8R8X8_Typeless = 92, - - /// - /// A four-component, 32-bit unsigned-normalized standard RGB format that supports 8 bits for each color channel, and 8 bits are unused. - /// - B8G8R8X8_UNorm_SRGB = 93, - - /// - /// A typeless block-compression format. - /// - BC6H_Typeless = 94, - - /// - /// A block-compression format. - /// - BC6H_UF16 = 95, - - /// - /// A block-compression format. - /// - BC6H_SF16 = 96, - - /// - /// A typeless block-compression format. - /// - BC7_Typeless = 97, - - /// - /// A block-compression format. - /// - BC7_UNorm = 98, - - /// - /// A block-compression format. - /// - BC7_UNorm_SRGB = 99, - - /// - /// Most common YUV 4:4:4 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_R8G8B8A8_UINT. - /// For UAVs, an additional valid view format is DXGI_FORMAT_R32_UINT. - /// By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_R8G8B8A8_UINT. - /// Supported view types are SRV, RTV, and UAV. One view provides a straightforward mapping of the entire surface. - /// The mapping to the view channel is V->R8, U->G8, Y->B8, and A->A8. - /// - AYUV = 100, - - /// - /// 10-bit per channel packed YUV 4:4:4 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R10G10B10A2_UNORM and DXGI_FORMAT_R10G10B10A2_UINT. - /// For UAVs, an additional valid view format is DXGI_FORMAT_R32_UINT. - /// By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R10G10B10A2_UNORM and DXGI_FORMAT_R10G10B10A2_UINT. - /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. - /// The mapping to the view channel is U->R10, Y->G10, V->B10, and A->A2. - /// - Y410 = 101, - - /// - /// 16-bit per channel packed YUV 4:4:4 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R16G16B16A16_UNORM and DXGI_FORMAT_R16G16B16A16_UINT. - /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. - /// The mapping to the view channel is U->R16, Y->G16, V->B16, and A->A16. - /// - Y416 = 102, - - /// - /// Most common YUV 4:2:0 video resource format. Valid luminance data view formats for this video resource format are DXGI_FORMAT_R8_UNORM and DXGI_FORMAT_R8_UINT. - /// Valid chrominance data view formats (width and height are each 1/2 of luminance view) for this video resource format are DXGI_FORMAT_R8G8_UNORM and DXGI_FORMAT_R8G8_UINT. - /// Supported view types are SRV, RTV, and UAV. For luminance data view, the mapping to the view channel is Y->R8. - /// For chrominance data view, the mapping to the view channel is U->R8 and V->G8. - /// - NV12 = 103, - - /// - /// 10-bit per channel planar YUV 4:2:0 video resource format. Valid luminance data view formats for this video resource format are DXGI_FORMAT_R16_UNORM and DXGI_FORMAT_R16_UINT. - /// The runtime does not enforce whether the lowest 6 bits are 0 (given that this video resource format is a 10-bit format that uses 16 bits). - /// If required, application shader code would have to enforce this manually. From the runtime's point of view, DXGI_FORMAT_P010 is no different than DXGI_FORMAT_P016. - /// Valid chrominance data view formats (width and height are each 1/2 of luminance view) for this video resource format are DXGI_FORMAT_R16G16_UNORM and DXGI_FORMAT_R16G16_UINT. - /// For UAVs, an additional valid chrominance data view format is DXGI_FORMAT_R32_UINT. By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R16G16_UNORM and DXGI_FORMAT_R16G16_UINT. Supported view types are SRV, RTV, and UAV. For luminance data view, the mapping to the view channel is Y->R16. - /// For chrominance data view, the mapping to the view channel is U->R16 and V->G16. - /// - P010 = 104, - - /// - /// 16-bit per channel planar YUV 4:2:0 video resource format. Valid luminance data view formats for this video resource format are DXGI_FORMAT_R16_UNORM and DXGI_FORMAT_R16_UINT. - /// Valid chrominance data view formats (width and height are each 1/2 of luminance view) for this video resource format are DXGI_FORMAT_R16G16_UNORM and DXGI_FORMAT_R16G16_UINT. - /// For UAVs, an additional valid chrominance data view format is DXGI_FORMAT_R32_UINT. By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R16G16_UNORM and DXGI_FORMAT_R16G16_UINT. Supported view types are SRV, RTV, and UAV. For luminance data view, the mapping to the view channel is Y->R16. - /// For chrominance data view, the mapping to the view channel is U->R16 and V->G16. - /// - P016 = 105, - - /// - /// 8-bit per channel planar YUV 4:2:0 video resource format. This format is subsampled where each pixel has its own Y value, but each 2x2 pixel block shares a single U and V value. - /// The runtime requires that the width and height of all resources that are created with this format are multiples of 2. The runtime also requires that the left, right, top, and bottom members of any RECT that are used for this format are multiples of 2. - /// This format differs from DXGI_FORMAT_NV12 in that the layout of the data within the resource is completely opaque to applications. - /// Applications cannot use the CPU to map the resource and then access the data within the resource. You cannot use shaders with this format. - /// Because of this behavior, legacy hardware that supports a non-NV12 4:2:0 layout (for example, YV12, and so on) can be used. - /// Also, new hardware that has a 4:2:0 implementation better than NV12 can be used when the application does not need the data to be in a standard layout. - /// - Opaque_420 = 106, - - /// - /// Most common YUV 4:2:2 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_R8G8B8A8_UINT. - /// For UAVs, an additional valid view format is DXGI_FORMAT_R32_UINT. By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_R8G8B8A8_UINT. - /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. The mapping to the view channel is Y0->R8, U0->G8,Y1->B8,and V0->A8. - /// - YUY2 = 107, - - /// - /// 10-bit per channel packed YUV 4:2:2 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R16G16B16A16_UNORM and DXGI_FORMAT_R16G16B16A16_UINT. - /// The runtime does not enforce whether the lowest 6 bits are 0 (given that this video resource format is a 10-bit format that uses 16 bits). - /// If required, application shader code would have to enforce this manually. From the runtime's point of view, DXGI_FORMAT_Y210 is no different than DXGI_FORMAT_Y216. - /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. The mapping to the view channel is Y0->R16, U->G16, Y1->B16, and V->A16. - /// - Y210 = 108, - - /// - /// 16-bit per channel packed YUV 4:2:2 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R16G16B16A16_UNORM and DXGI_FORMAT_R16G16B16A16_UINT. - /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. The mapping to the view channel is Y0->R16, U->G16, Y1->B16, and V->A16. - /// - Y216 = 109, - - /// - /// Most common planar YUV 4:1:1 video resource format. Valid luminance data view formats for this video resource format are DXGI_FORMAT_R8_UNORM and DXGI_FORMAT_R8_UINT. - /// Valid chrominance data view formats (width and height are each 1/4 of luminance view) for this video resource format are DXGI_FORMAT_R8G8_UNORM and DXGI_FORMAT_R8G8_UINT. - /// Supported view types are SRV, RTV, and UAV. For luminance data view, the mapping to the view channel is Y->R8. For chrominance data view, the mapping to the view channel is U->R8 and V->G8. - /// - NV11 = 110, - - /// - /// 4-bit palletized YUV format that is commonly used for DVD subpicture. - /// - AI44 = 111, - - /// - /// 4-bit palletized YUV format that is commonly used for DVD subpicture. - /// - IA44 = 112, - - /// - /// 8-bit palletized format that is used for palletized RGB data when the processor processes ISDB-T data and for palletized YUV data when the processor processes BluRay data. - /// - P8 = 113, - - /// - /// 8-bit palletized format with 8 bits of alpha that is used for palletized YUV data when the processor processes BluRay data. - /// - A8P8 = 114, - - /// - /// A four-component, 16-bit unsigned-normalized integer format that supports 4 bits for each channel including alpha. - /// - B4G4R4A4_UNorm = 115, - - /// - /// A video format; an 8-bit version of a hybrid planar 4:2:2 format. - /// - P208 = 130, - - /// - /// An 8 bit YCbCrA 4:4 rendering format. - /// - V208 = 131, - - /// - /// An 8 bit YCbCrA 4:4:4:4 rendering format. - /// - V408 = 132, - } + /// A four-component, 128-bit signed-integer format that supports 32 bits per channel including alpha. + /// + R32G32B32A32_SInt = 4, + + /// + /// A three-component, 96-bit typeless format that supports 32 bits per color channel. + /// + R32G32B32_Typeless = 5, + + /// + /// A three-component, 96-bit floating-point format that supports 32 bits per color channel. + /// + R32G32B32_Float = 6, + + /// + /// A three-component, 96-bit unsigned-integer format that supports 32 bits per color channel. + /// + R32G32B32_UInt = 7, + + /// + /// A three-component, 96-bit signed-integer format that supports 32 bits per color channel. + /// + R32G32B32_SInt = 8, + + /// + /// A four-component, 64-bit typeless format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_Typeless = 9, + + /// + /// A four-component, 64-bit floating-point format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_Float = 10, + + /// + /// A four-component, 64-bit unsigned-normalized-integer format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_UNorm = 11, + + /// + /// A four-component, 64-bit unsigned-integer format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_UInt = 12, + + /// + /// A four-component, 64-bit signed-normalized-integer format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_SNorm = 13, + + /// + /// A four-component, 64-bit signed-integer format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_SInt = 14, + + /// + /// A two-component, 64-bit typeless format that supports 32 bits for the red channel and 32 bits for the green channel. + /// + R32G32_Typeless = 15, + + /// + /// A two-component, 64-bit floating-point format that supports 32 bits for the red channel and 32 bits for the green channel. + /// + R32G32_Float = 16, + + /// + /// A two-component, 64-bit unsigned-integer format that supports 32 bits for the red channel and 32 bits for the green channel. + /// + R32G32_UInt = 17, + + /// + /// A two-component, 64-bit signed-integer format that supports 32 bits for the red channel and 32 bits for the green channel. + /// + R32G32_SInt = 18, + + /// + /// A two-component, 64-bit typeless format that supports 32 bits for the red channel, 8 bits for the green channel, and 24 bits are unused. + /// + R32G8X24_Typeless = 19, + + /// + /// A 32-bit floating-point component, and two unsigned-integer components (with an additional 32 bits). This format supports 32-bit depth, 8-bit stencil, and 24 bits are unused. + /// + D32_Float_S8X24_UInt = 20, + + /// + /// A 32-bit floating-point component, and two typeless components (with an additional 32 bits). This format supports 32-bit red channel, 8 bits are unused, and 24 bits are unused. + /// + R32_Float_X8X24_Typeless = 21, + + /// + /// A 32-bit typeless component, and two unsigned-integer components (with an additional 32 bits). This format has 32 bits unused, 8 bits for green channel, and 24 bits are unused. + /// + X32_Typeless_G8X24_UInt = 22, + + /// + /// A four-component, 32-bit typeless format that supports 10 bits for each color and 2 bits for alpha. + /// + R10G10B10A2_Typeless = 23, + + /// + /// A four-component, 32-bit unsigned-normalized-integer format that supports 10 bits for each color and 2 bits for alpha. + /// + R10G10B10A2_UNorm = 24, + + /// + /// A four-component, 32-bit unsigned-integer format that supports 10 bits for each color and 2 bits for alpha. + /// + R10G10B10A2_UInt = 25, + + /// + /// Three partial-precision floating-point numbers encoded into a single 32-bit value (a variant of s10e5, which is sign bit, 10-bit mantissa, and 5-bit biased (15) exponent). + /// There are no sign bits, and there is a 5-bit biased (15) exponent for each channel, 6-bit mantissa for R and G, and a 5-bit mantissa for B. + /// + R11G11B10_Float = 26, + + /// + /// A four-component, 32-bit typeless format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_Typeless = 27, + + /// + /// A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_UNorm = 28, + + /// + /// A four-component, 32-bit unsigned-normalized integer sRGB format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_UNorm_SRGB = 29, + + /// + /// A four-component, 32-bit unsigned-integer format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_UInt = 30, + + /// + /// A four-component, 32-bit signed-normalized-integer format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_SNorm = 31, + + /// + /// A four-component, 32-bit signed-integer format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_SInt = 32, + + /// + /// A two-component, 32-bit typeless format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_Typeless = 33, + + /// + /// A two-component, 32-bit floating-point format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_Float = 34, + + /// + /// A two-component, 32-bit unsigned-normalized-integer format that supports 16 bits each for the green and red channels. + /// + R16G16_UNorm = 35, + + /// + /// A two-component, 32-bit unsigned-integer format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_UInt = 36, + + /// + /// A two-component, 32-bit signed-normalized-integer format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_SNorm = 37, + + /// + /// A two-component, 32-bit signed-integer format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_SInt = 38, + + /// + /// A single-component, 32-bit typeless format that supports 32 bits for the red channel. + /// + R32_Typeless = 39, + + /// + /// A single-component, 32-bit floating-point format that supports 32 bits for depth. + /// + D32_Float = 40, + + /// + /// A single-component, 32-bit floating-point format that supports 32 bits for the red channel. + /// + R32_Float = 41, + + /// + /// A single-component, 32-bit unsigned-integer format that supports 32 bits for the red channel. + /// + R32_UInt = 42, + + /// + /// A single-component, 32-bit signed-integer format that supports 32 bits for the red channel. + /// + R32_SInt = 43, + + /// + /// A two-component, 32-bit typeless format that supports 24 bits for the red channel and 8 bits for the green channel. + /// + R24G8_Typeless = 44, + + /// + /// A 32-bit z-buffer format that supports 24 bits for depth and 8 bits for stencil. + /// + D24_UNorm_S8_UInt = 45, + + /// + /// A 32-bit format, that contains a 24 bit, single-component, unsigned-normalized integer, with an additional typeless 8 bits. This format has 24 bits red channel and 8 bits unused. + /// + R24_UNorm_X8_Typeless = 46, + + /// + /// A 32-bit format, that contains a 24 bit, single-component, typeless format, with an additional 8 bit unsigned integer component. This format has 24 bits unused and 8 bits green channel. + /// + X24_Typeless_G8_UInt = 47, + + /// + /// A two-component, 16-bit typeless format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_Typeless = 48, + + /// + /// A two-component, 16-bit unsigned-normalized-integer format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_UNorm = 49, + + /// + /// A two-component, 16-bit unsigned-integer format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_UInt = 50, + + /// + /// A two-component, 16-bit signed-normalized-integer format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_SNorm = 51, + + /// + /// A two-component, 16-bit signed-integer format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_SInt = 52, + + /// + /// A single-component, 16-bit typeless format that supports 16 bits for the red channel. + /// + R16_Typeless = 53, + + /// + /// A single-component, 16-bit floating-point format that supports 16 bits for the red channel. + /// + R16_Float = 54, + + /// + /// A single-component, 16-bit unsigned-normalized-integer format that supports 16 bits for depth. + /// + D16_UNorm = 55, + + /// + /// A single-component, 16-bit unsigned-normalized-integer format that supports 16 bits for the red channel. + /// + R16_UNorm = 56, + + /// + /// A single-component, 16-bit unsigned-integer format that supports 16 bits for the red channel. + /// + R16_UInt = 57, + + /// + /// A single-component, 16-bit signed-normalized-integer format that supports 16 bits for the red channel. + /// + R16_SNorm = 58, + + /// + /// A single-component, 16-bit signed-integer format that supports 16 bits for the red channel. + /// + R16_SInt = 59, + + /// + /// A single-component, 8-bit typeless format that supports 8 bits for the red channel. + /// + R8_Typeless = 60, + + /// + /// A single-component, 8-bit unsigned-normalized-integer format that supports 8 bits for the red channel. + /// + R8_UNorm = 61, + + /// + /// A single-component, 8-bit unsigned-integer format that supports 8 bits for the red channel. + /// + R8_UInt = 62, + + /// + /// A single-component, 8-bit signed-normalized-integer format that supports 8 bits for the red channel. + /// + R8_SNorm = 63, + + /// + /// A single-component, 8-bit signed-integer format that supports 8 bits for the red channel. + /// + R8_SInt = 64, + + /// + /// A single-component, 8-bit unsigned-normalized-integer format for alpha only. + /// + A8_UNorm = 65, + + /// + /// A single-component, 1-bit unsigned-normalized integer format that supports 1 bit for the red channel. + /// + R1_UNorm = 66, + + /// + /// Three partial-precision floating-point numbers encoded into a single 32-bit value all sharing the same 5-bit exponent (variant of s10e5, which is sign bit, 10-bit mantissa, and 5-bit biased (15) exponent). + /// There is no sign bit, and there is a shared 5-bit biased (15) exponent and a 9-bit mantissa for each channel. + /// + R9G9B9E5_SharedExp = 67, + + /// + /// A four-component, 32-bit unsigned-normalized-integer format. This packed RGB format is analogous to the UYVY format. Each 32-bit block describes a pair of pixels: (R8, G8, B8) and (R8, G8, B8) where the R8/B8 values are repeated, + /// and the G8 values are unique to each pixel. Width must be even. + /// + R8G8_B8G8_UNorm = 68, + + /// + /// A four-component, 32-bit unsigned-normalized-integer format. This packed RGB format is analogous to the YUY2 format. Each 32-bit block describes a pair of pixels: (R8, G8, B8) and (R8, G8, B8) where the R8/B8 values are repeated, + /// and the G8 values are unique to each pixel. Width must be even. + /// + G8R8_G8B8_UNorm = 69, + + /// + /// Four-component typeless block-compression format. + /// + BC1_Typeless = 70, + + /// + /// Four-component block-compression format. + /// + BC1_UNorm = 71, + + /// + /// Four-component block-compression format for sRGB data. + /// + BC1_UNorm_SRGB = 72, + + /// + /// Four-component typeless block-compression format. + /// + BC2_Typeless = 73, + + /// + /// Four-component block-compression format. + /// + BC2_UNorm = 74, + + /// + /// Four-component block-compression format for sRGB data. + /// + BC2_UNorm_SRGB = 75, + + /// + /// Four-component typeless block-compression format. + /// + BC3_Typeless = 76, + + /// + /// Four-component block-compression format. + /// + BC3_UNorm = 77, + + /// + /// Four-component block-compression format for sRGB data. + /// + BC3_UNorm_SRGB = 78, + + /// + /// One-component typeless block-compression format. + /// + BC4_Typeless = 79, + + /// + /// One-component block-compression format. + /// + BC4_UNorm = 80, + + /// + /// One-component block-compression format. + /// + BC4_SNorm = 81, + + /// + /// Two-component typeless block-compression format. + /// + BC5_Typeless = 82, + + /// + /// Two-component block-compression format. + /// + BC5_UNorm = 83, + + /// + /// Two-component block-compression format. + /// + BC5_SNorm = 84, + + /// + /// A three-component, 16-bit unsigned-normalized-integer format that supports 5 bits for blue, 6 bits for green, and 5 bits for red. + /// Direct3D 10 through Direct3D 11: This value is defined for DXGI. However, Direct3D 10, 10.1, or 11 devices do not support this format. + /// Direct3D 11.1: This value is not supported until Windows 8. + /// + B5G6R5_UNorm = 85, + + /// + /// A four-component, 16-bit unsigned-normalized-integer format that supports 5 bits for each color channel and 1-bit alpha. + /// Direct3D 10 through Direct3D 11: This value is defined for DXGI. However, Direct3D 10, 10.1, or 11 devices do not support this format. + /// Direct3D 11.1: This value is not supported until Windows 8. + /// + B5G5R5A1_UNorm = 86, + + /// + /// A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits for each color channel and 8-bit alpha. + /// + B8G8R8A8_UNorm = 87, + + /// + /// A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits for each color channel and 8 bits unused. + /// + B8G8R8X8_UNorm = 88, + + /// + /// A four-component, 32-bit 2.8-biased fixed-point format that supports 10 bits for each color channel and 2-bit alpha. + /// + R10G10B10_XR_BIAS_A2_UNorm = 89, + + /// + /// A four-component, 32-bit typeless format that supports 8 bits for each channel including alpha. + /// + B8G8R8A8_Typeless = 90, + + /// + /// A four-component, 32-bit unsigned-normalized standard RGB format that supports 8 bits for each channel including alpha. + /// + B8G8R8A8_UNorm_SRGB = 91, + + /// + /// A four-component, 32-bit typeless format that supports 8 bits for each color channel, and 8 bits are unused. + /// + B8G8R8X8_Typeless = 92, + + /// + /// A four-component, 32-bit unsigned-normalized standard RGB format that supports 8 bits for each color channel, and 8 bits are unused. + /// + B8G8R8X8_UNorm_SRGB = 93, + + /// + /// A typeless block-compression format. + /// + BC6H_Typeless = 94, + + /// + /// A block-compression format. + /// + BC6H_UF16 = 95, + + /// + /// A block-compression format. + /// + BC6H_SF16 = 96, + + /// + /// A typeless block-compression format. + /// + BC7_Typeless = 97, + + /// + /// A block-compression format. + /// + BC7_UNorm = 98, + + /// + /// A block-compression format. + /// + BC7_UNorm_SRGB = 99, + + /// + /// Most common YUV 4:4:4 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_R8G8B8A8_UINT. + /// For UAVs, an additional valid view format is DXGI_FORMAT_R32_UINT. + /// By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_R8G8B8A8_UINT. + /// Supported view types are SRV, RTV, and UAV. One view provides a straightforward mapping of the entire surface. + /// The mapping to the view channel is V->R8, U->G8, Y->B8, and A->A8. + /// + AYUV = 100, + + /// + /// 10-bit per channel packed YUV 4:4:4 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R10G10B10A2_UNORM and DXGI_FORMAT_R10G10B10A2_UINT. + /// For UAVs, an additional valid view format is DXGI_FORMAT_R32_UINT. + /// By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R10G10B10A2_UNORM and DXGI_FORMAT_R10G10B10A2_UINT. + /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. + /// The mapping to the view channel is U->R10, Y->G10, V->B10, and A->A2. + /// + Y410 = 101, + + /// + /// 16-bit per channel packed YUV 4:4:4 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R16G16B16A16_UNORM and DXGI_FORMAT_R16G16B16A16_UINT. + /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. + /// The mapping to the view channel is U->R16, Y->G16, V->B16, and A->A16. + /// + Y416 = 102, + + /// + /// Most common YUV 4:2:0 video resource format. Valid luminance data view formats for this video resource format are DXGI_FORMAT_R8_UNORM and DXGI_FORMAT_R8_UINT. + /// Valid chrominance data view formats (width and height are each 1/2 of luminance view) for this video resource format are DXGI_FORMAT_R8G8_UNORM and DXGI_FORMAT_R8G8_UINT. + /// Supported view types are SRV, RTV, and UAV. For luminance data view, the mapping to the view channel is Y->R8. + /// For chrominance data view, the mapping to the view channel is U->R8 and V->G8. + /// + NV12 = 103, + + /// + /// 10-bit per channel planar YUV 4:2:0 video resource format. Valid luminance data view formats for this video resource format are DXGI_FORMAT_R16_UNORM and DXGI_FORMAT_R16_UINT. + /// The runtime does not enforce whether the lowest 6 bits are 0 (given that this video resource format is a 10-bit format that uses 16 bits). + /// If required, application shader code would have to enforce this manually. From the runtime's point of view, DXGI_FORMAT_P010 is no different than DXGI_FORMAT_P016. + /// Valid chrominance data view formats (width and height are each 1/2 of luminance view) for this video resource format are DXGI_FORMAT_R16G16_UNORM and DXGI_FORMAT_R16G16_UINT. + /// For UAVs, an additional valid chrominance data view format is DXGI_FORMAT_R32_UINT. By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R16G16_UNORM and DXGI_FORMAT_R16G16_UINT. Supported view types are SRV, RTV, and UAV. For luminance data view, the mapping to the view channel is Y->R16. + /// For chrominance data view, the mapping to the view channel is U->R16 and V->G16. + /// + P010 = 104, + + /// + /// 16-bit per channel planar YUV 4:2:0 video resource format. Valid luminance data view formats for this video resource format are DXGI_FORMAT_R16_UNORM and DXGI_FORMAT_R16_UINT. + /// Valid chrominance data view formats (width and height are each 1/2 of luminance view) for this video resource format are DXGI_FORMAT_R16G16_UNORM and DXGI_FORMAT_R16G16_UINT. + /// For UAVs, an additional valid chrominance data view format is DXGI_FORMAT_R32_UINT. By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R16G16_UNORM and DXGI_FORMAT_R16G16_UINT. Supported view types are SRV, RTV, and UAV. For luminance data view, the mapping to the view channel is Y->R16. + /// For chrominance data view, the mapping to the view channel is U->R16 and V->G16. + /// + P016 = 105, + + /// + /// 8-bit per channel planar YUV 4:2:0 video resource format. This format is subsampled where each pixel has its own Y value, but each 2x2 pixel block shares a single U and V value. + /// The runtime requires that the width and height of all resources that are created with this format are multiples of 2. The runtime also requires that the left, right, top, and bottom members of any RECT that are used for this format are multiples of 2. + /// This format differs from DXGI_FORMAT_NV12 in that the layout of the data within the resource is completely opaque to applications. + /// Applications cannot use the CPU to map the resource and then access the data within the resource. You cannot use shaders with this format. + /// Because of this behavior, legacy hardware that supports a non-NV12 4:2:0 layout (for example, YV12, and so on) can be used. + /// Also, new hardware that has a 4:2:0 implementation better than NV12 can be used when the application does not need the data to be in a standard layout. + /// + Opaque_420 = 106, + + /// + /// Most common YUV 4:2:2 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_R8G8B8A8_UINT. + /// For UAVs, an additional valid view format is DXGI_FORMAT_R32_UINT. By using DXGI_FORMAT_R32_UINT for UAVs, you can both read and write as opposed to just write for DXGI_FORMAT_R8G8B8A8_UNORM and DXGI_FORMAT_R8G8B8A8_UINT. + /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. The mapping to the view channel is Y0->R8, U0->G8,Y1->B8,and V0->A8. + /// + YUY2 = 107, + + /// + /// 10-bit per channel packed YUV 4:2:2 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R16G16B16A16_UNORM and DXGI_FORMAT_R16G16B16A16_UINT. + /// The runtime does not enforce whether the lowest 6 bits are 0 (given that this video resource format is a 10-bit format that uses 16 bits). + /// If required, application shader code would have to enforce this manually. From the runtime's point of view, DXGI_FORMAT_Y210 is no different than DXGI_FORMAT_Y216. + /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. The mapping to the view channel is Y0->R16, U->G16, Y1->B16, and V->A16. + /// + Y210 = 108, + + /// + /// 16-bit per channel packed YUV 4:2:2 video resource format. Valid view formats for this video resource format are DXGI_FORMAT_R16G16B16A16_UNORM and DXGI_FORMAT_R16G16B16A16_UINT. + /// Supported view types are SRV and UAV. One view provides a straightforward mapping of the entire surface. The mapping to the view channel is Y0->R16, U->G16, Y1->B16, and V->A16. + /// + Y216 = 109, + + /// + /// Most common planar YUV 4:1:1 video resource format. Valid luminance data view formats for this video resource format are DXGI_FORMAT_R8_UNORM and DXGI_FORMAT_R8_UINT. + /// Valid chrominance data view formats (width and height are each 1/4 of luminance view) for this video resource format are DXGI_FORMAT_R8G8_UNORM and DXGI_FORMAT_R8G8_UINT. + /// Supported view types are SRV, RTV, and UAV. For luminance data view, the mapping to the view channel is Y->R8. For chrominance data view, the mapping to the view channel is U->R8 and V->G8. + /// + NV11 = 110, + + /// + /// 4-bit palletized YUV format that is commonly used for DVD subpicture. + /// + AI44 = 111, + + /// + /// 4-bit palletized YUV format that is commonly used for DVD subpicture. + /// + IA44 = 112, + + /// + /// 8-bit palletized format that is used for palletized RGB data when the processor processes ISDB-T data and for palletized YUV data when the processor processes BluRay data. + /// + P8 = 113, + + /// + /// 8-bit palletized format with 8 bits of alpha that is used for palletized YUV data when the processor processes BluRay data. + /// + A8P8 = 114, + + /// + /// A four-component, 16-bit unsigned-normalized integer format that supports 4 bits for each channel including alpha. + /// + B4G4R4A4_UNorm = 115, + + /// + /// A video format; an 8-bit version of a hybrid planar 4:2:2 format. + /// + P208 = 130, + + /// + /// An 8 bit YCbCrA 4:4 rendering format. + /// + V208 = 131, + + /// + /// An 8 bit YCbCrA 4:4:4:4 rendering format. + /// + V408 = 132, } diff --git a/src/ImageSharp.Textures/Formats/Dds/Extensions/DdsHeaderExtensions.cs b/src/ImageSharp.Textures/Formats/Dds/Extensions/DdsHeaderExtensions.cs index faf92f89..91777dc5 100644 --- a/src/ImageSharp.Textures/Formats/Dds/Extensions/DdsHeaderExtensions.cs +++ b/src/ImageSharp.Textures/Formats/Dds/Extensions/DdsHeaderExtensions.cs @@ -3,199 +3,174 @@ using SixLabors.ImageSharp.Textures.Formats.Dds.Emums; -namespace SixLabors.ImageSharp.Textures.Formats.Dds.Extensions +namespace SixLabors.ImageSharp.Textures.Formats.Dds.Extensions; + +internal static class DdsHeaderExtensions { - internal static class DdsHeaderExtensions + /// + /// Gets a value indicating whether determines whether this resource is a cubemap. + /// + /// + /// true if this resource is a cubemap; otherwise, false. + /// + public static bool IsCubemap(this DdsHeader ddsHeader) => (ddsHeader.Caps2 & DdsCaps2.Cubemap) != 0; + + /// + /// Gets a value indicating whether determines whether this resource is a volume texture. + /// + /// + /// true if this resource is a volume texture; otherwise, false. + /// + public static bool IsVolumeTexture(this DdsHeader ddsHeader) => (ddsHeader.Caps2 & DdsCaps2.Volume) != 0; + + /// + /// Gets a value indicating whether determines whether this resource contains compressed surface data. + /// + /// + /// true if this resource contains compressed surface data; otherwise, false. + /// + public static bool IsCompressed(this DdsHeader ddsHeader) => (ddsHeader.Flags & DdsFlags.LinearSize) != 0; + + /// + /// Gets a value indicating whether determines whether this resource contains alpha data. + /// + /// + /// true if this resource contains alpha data; otherwise, false. + /// + public static bool HasAlpha(this DdsHeader ddsHeader) => (ddsHeader.PixelFormat.Flags & DdsPixelFormatFlags.AlphaPixels) != 0; + + /// + /// Gets a value indicating whether determines whether this resource contains mipmap data. + /// + /// + /// true if this resource contains mipmap data; otherwise, false. + /// + public static bool HasMipmaps(this DdsHeader ddsHeader) => (ddsHeader.Caps1 & DdsCaps1.MipMap) != 0 && (ddsHeader.Flags & DdsFlags.MipMapCount) != 0; + + /// + /// Gets the number of textures. + /// + /// + /// Number of textures. + public static int TextureCount(this DdsHeader ddsHeader) => ddsHeader.HasMipmaps() ? (int)ddsHeader.MipMapCount : 1; + + /// + /// Checks if dds resource should have the header. + /// + /// + /// true if dds resource should have the header; + /// otherwise false. + /// + public static bool ShouldHaveDxt10Header(this DdsHeader ddsHeader) => (ddsHeader.PixelFormat.Flags == DdsPixelFormatFlags.FourCC) && (ddsHeader.PixelFormat.FourCC == DdsFourCc.DX10); + + /// + /// Determines whether width and height flags are set, so and + /// contain valid values. + /// + /// + /// true if dimensions flags are set; otherwise, false. + /// + public static bool AreDimensionsSet(this DdsHeader ddsHeader) => (ddsHeader.Flags & DdsFlags.Width) != 0 && (ddsHeader.Flags & DdsFlags.Height) != 0; + + /// + /// Returns either depth of a volume texture in pixels, amount of faces in a cubemap or + /// a 1 for a flat resource. + /// + /// + /// Actual depth of a resource. + /// + public static int ComputeDepth(this DdsHeader ddsHeader) { - /// - /// Gets a value indicating whether determines whether this resource is a cubemap. - /// - /// - /// true if this resource is a cubemap; otherwise, false. - /// - public static bool IsCubemap(this DdsHeader ddsHeader) + int result = 1; + if (ddsHeader.IsVolumeTexture()) { - return (ddsHeader.Caps2 & DdsCaps2.Cubemap) != 0; + result = (int)ddsHeader.Depth; } - - /// - /// Gets a value indicating whether determines whether this resource is a volume texture. - /// - /// - /// true if this resource is a volume texture; otherwise, false. - /// - public static bool IsVolumeTexture(this DdsHeader ddsHeader) + else if (ddsHeader.IsCubemap()) { - return (ddsHeader.Caps2 & DdsCaps2.Volume) != 0; - } + result = 0; - /// - /// Gets a value indicating whether determines whether this resource contains compressed surface data. - /// - /// - /// true if this resource contains compressed surface data; otherwise, false. - /// - public static bool IsCompressed(this DdsHeader ddsHeader) - { - return (ddsHeader.Flags & DdsFlags.LinearSize) != 0; - } + // Partial cubemaps are not supported by Direct3D >= 11, but lets support them for the legacy sake. + // So cubemaps can store up to 6 faces: + if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveX) != 0) + { + result++; + } - /// - /// Gets a value indicating whether determines whether this resource contains alpha data. - /// - /// - /// true if this resource contains alpha data; otherwise, false. - /// - public static bool HasAlpha(this DdsHeader ddsHeader) - { - return (ddsHeader.PixelFormat.Flags & DdsPixelFormatFlags.AlphaPixels) != 0; - } + if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeX) != 0) + { + result++; + } - /// - /// Gets a value indicating whether determines whether this resource contains mipmap data. - /// - /// - /// true if this resource contains mipmap data; otherwise, false. - /// - public static bool HasMipmaps(this DdsHeader ddsHeader) - { - return (ddsHeader.Caps1 & DdsCaps1.MipMap) != 0 && (ddsHeader.Flags & DdsFlags.MipMapCount) != 0; - } + if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveY) != 0) + { + result++; + } - /// - /// Gets the number of textures. - /// - /// - /// Number of textures. - public static int TextureCount(this DdsHeader ddsHeader) - { - return ddsHeader.HasMipmaps() ? (int)ddsHeader.MipMapCount : 1; - } + if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeY) != 0) + { + result++; + } - /// - /// Checks if dds resource should have the header. - /// - /// - /// true if dds resource should have the header; - /// otherwise false. - /// - public static bool ShouldHaveDxt10Header(this DdsHeader ddsHeader) - { - return (ddsHeader.PixelFormat.Flags == DdsPixelFormatFlags.FourCC) && (ddsHeader.PixelFormat.FourCC == DdsFourCc.DX10); - } + if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveZ) != 0) + { + result++; + } - /// - /// Determines whether width and height flags are set, so and - /// contain valid values. - /// - /// - /// true if dimensions flags are set; otherwise, false. - /// - public static bool AreDimensionsSet(this DdsHeader ddsHeader) - { - return (ddsHeader.Flags & DdsFlags.Width) != 0 && (ddsHeader.Flags & DdsFlags.Height) != 0; + if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeZ) != 0) + { + result++; + } } - /// - /// Returns either depth of a volume texture in pixels, amount of faces in a cubemap or - /// a 1 for a flat resource. - /// - /// - /// Actual depth of a resource. - /// - public static int ComputeDepth(this DdsHeader ddsHeader) + return result; + } + + /// + /// Gets the existing cube map faces, if this header represents a cube map. + /// + /// + /// Types of cube map faces stored in this cube map or null if this is not a cubemap. + /// + public static DdsSurfaceType[]? GetExistingCubemapFaces(this DdsHeader ddsHeader) + { + int depth = ddsHeader.ComputeDepth(); + DdsSurfaceType[] result = new DdsSurfaceType[depth]; + int index = 0; + + if (depth > 0) { - int result = 1; - if (ddsHeader.IsVolumeTexture()) + if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveX) != 0) { - result = (int)ddsHeader.Depth; + result[index++] = DdsSurfaceType.CubemapPositiveX; } - else if (ddsHeader.IsCubemap()) + + if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeX) != 0) { - result = 0; - - // Partial cubemaps are not supported by Direct3D >= 11, but lets support them for the legacy sake. - // So cubemaps can store up to 6 faces: - if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveX) != 0) - { - result++; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeX) != 0) - { - result++; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveY) != 0) - { - result++; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeY) != 0) - { - result++; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveZ) != 0) - { - result++; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeZ) != 0) - { - result++; - } + result[index++] = DdsSurfaceType.CubemapNegativeX; } - return result; - } + if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveY) != 0) + { + result[index++] = DdsSurfaceType.CubemapPositiveY; + } - /// - /// Gets the existing cube map faces, if this header represents a cube map. - /// - /// - /// Types of cube map faces stored in this cube map or null if this is not a cubemap. - /// - public static DdsSurfaceType[]? GetExistingCubemapFaces(this DdsHeader ddsHeader) - { - int depth = ddsHeader.ComputeDepth(); - var result = new DdsSurfaceType[depth]; - int index = 0; + if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeY) != 0) + { + result[index++] = DdsSurfaceType.CubemapNegativeY; + } - if (depth > 0) + if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveZ) != 0) { - if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveX) != 0) - { - result[index++] = DdsSurfaceType.CubemapPositiveX; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeX) != 0) - { - result[index++] = DdsSurfaceType.CubemapNegativeX; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveY) != 0) - { - result[index++] = DdsSurfaceType.CubemapPositiveY; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeY) != 0) - { - result[index++] = DdsSurfaceType.CubemapNegativeY; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapPositiveZ) != 0) - { - result[index++] = DdsSurfaceType.CubemapPositiveZ; - } - - if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeZ) != 0) - { - result[index++] = DdsSurfaceType.CubemapNegativeZ; - } + result[index++] = DdsSurfaceType.CubemapPositiveZ; } - return (index > 0) ? result : null; + if ((ddsHeader.Caps2 & DdsCaps2.CubemapNegativeZ) != 0) + { + result[index++] = DdsSurfaceType.CubemapNegativeZ; + } } + + return (index > 0) ? result : null; } } diff --git a/src/ImageSharp.Textures/Formats/Dds/IDdsDecoderOptions.cs b/src/ImageSharp.Textures/Formats/Dds/IDdsDecoderOptions.cs index 40a9b0cf..d48326d7 100644 --- a/src/ImageSharp.Textures/Formats/Dds/IDdsDecoderOptions.cs +++ b/src/ImageSharp.Textures/Formats/Dds/IDdsDecoderOptions.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Formats.Dds; + +/// +/// The options for decoding dds textures. Currently empty, but this may change in the future. +/// +internal interface IDdsDecoderOptions { - /// - /// The options for decoding dds textures. Currently empty, but this may change in the future. - /// - internal interface IDdsDecoderOptions - { - } } diff --git a/src/ImageSharp.Textures/Formats/ITextureDecoder.cs b/src/ImageSharp.Textures/Formats/ITextureDecoder.cs index c1e34985..cdf05b4f 100644 --- a/src/ImageSharp.Textures/Formats/ITextureDecoder.cs +++ b/src/ImageSharp.Textures/Formats/ITextureDecoder.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats -{ - using System.IO; +namespace SixLabors.ImageSharp.Textures.Formats; + +using System.IO; +/// +/// Encapsulates properties and methods required for decoding an image from a stream. +/// +public interface ITextureDecoder +{ /// - /// Encapsulates properties and methods required for decoding an image from a stream. + /// Decodes the image from the specified stream to an . /// - public interface ITextureDecoder - { - /// - /// Decodes the image from the specified stream to an . - /// - /// The configuration for the image. - /// The containing image data. - /// The . - Texture DecodeTexture(Configuration configuration, Stream stream); - } + /// The configuration for the image. + /// The containing image data. + /// The . + Texture DecodeTexture(Configuration configuration, Stream stream); } diff --git a/src/ImageSharp.Textures/Formats/ITextureEncoder.cs b/src/ImageSharp.Textures/Formats/ITextureEncoder.cs index a58ad860..a3ff2022 100644 --- a/src/ImageSharp.Textures/Formats/ITextureEncoder.cs +++ b/src/ImageSharp.Textures/Formats/ITextureEncoder.cs @@ -1,20 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats -{ - using System.IO; +namespace SixLabors.ImageSharp.Textures.Formats; + +using System.IO; +/// +/// Encapsulates properties and methods required for encoding an image to a stream. +/// +public interface ITextureEncoder +{ /// - /// Encapsulates properties and methods required for encoding an image to a stream. + /// Encodes the image to the specified stream from the . /// - public interface ITextureEncoder - { - /// - /// Encodes the image to the specified stream from the . - /// - /// The to encode from. - /// The to encode the image data to. - void Encode(Texture texture, Stream stream); - } + /// The to encode from. + /// The to encode the image data to. + void Encode(Texture texture, Stream stream); } diff --git a/src/ImageSharp.Textures/Formats/ITextureFormat.cs b/src/ImageSharp.Textures/Formats/ITextureFormat.cs index bfd52970..81253715 100644 --- a/src/ImageSharp.Textures/Formats/ITextureFormat.cs +++ b/src/ImageSharp.Textures/Formats/ITextureFormat.cs @@ -1,63 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.Formats; -namespace SixLabors.ImageSharp.Textures.Formats +/// +/// Defines the contract for an image format. +/// +public interface ITextureFormat { /// - /// Defines the contract for an image format. + /// Gets the name that describes this image format. /// - public interface ITextureFormat - { - /// - /// Gets the name that describes this image format. - /// - string Name { get; } + string Name { get; } - /// - /// Gets the default mimetype that the image format uses - /// - string DefaultMimeType { get; } + /// + /// Gets the default mimetype that the image format uses + /// + string DefaultMimeType { get; } - /// - /// Gets all the mimetypes that have been used by this image format. - /// - IEnumerable MimeTypes { get; } + /// + /// Gets all the mimetypes that have been used by this image format. + /// + IEnumerable MimeTypes { get; } - /// - /// Gets the file extensions this image format commonly uses. - /// - IEnumerable FileExtensions { get; } - } + /// + /// Gets the file extensions this image format commonly uses. + /// + IEnumerable FileExtensions { get; } +} +/// +/// Defines the contract for an image format containing metadata. +/// +/// The type of format metadata. +public interface IImageFormat : ITextureFormat + where TFormatMetadata : class +{ /// - /// Defines the contract for an image format containing metadata. + /// Creates a default instance of the format metadata. /// - /// The type of format metadata. - public interface IImageFormat : ITextureFormat - where TFormatMetadata : class - { - /// - /// Creates a default instance of the format metadata. - /// - /// The . - TFormatMetadata CreateDefaultFormatMetadata(); - } + /// The . + TFormatMetadata CreateDefaultFormatMetadata(); +} +/// +/// Defines the contract for an image format containing metadata with multiple frames. +/// +/// The type of format metadata. +/// The type of format frame metadata. +public interface IImageFormat : IImageFormat + where TFormatMetadata : class + where TFormatFrameMetadata : class +{ /// - /// Defines the contract for an image format containing metadata with multiple frames. + /// Creates a default instance of the format frame metadata. /// - /// The type of format metadata. - /// The type of format frame metadata. - public interface IImageFormat : IImageFormat - where TFormatMetadata : class - where TFormatFrameMetadata : class - { - /// - /// Creates a default instance of the format frame metadata. - /// - /// The . - TFormatFrameMetadata CreateDefaultFormatFrameMetadata(); - } + /// The . + TFormatFrameMetadata CreateDefaultFormatFrameMetadata(); } diff --git a/src/ImageSharp.Textures/Formats/ITextureFormatDetector.cs b/src/ImageSharp.Textures/Formats/ITextureFormatDetector.cs index 3e24a6f2..c1b3ffed 100644 --- a/src/ImageSharp.Textures/Formats/ITextureFormatDetector.cs +++ b/src/ImageSharp.Textures/Formats/ITextureFormatDetector.cs @@ -1,26 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Formats; -namespace SixLabors.ImageSharp.Textures.Formats +/// +/// Used for detecting mime types from a file header +/// +public interface ITextureFormatDetector { /// - /// Used for detecting mime types from a file header + /// Gets the size of the header for this image type. /// - public interface ITextureFormatDetector - { - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - int HeaderSize { get; } + /// The size of the header. + int HeaderSize { get; } - /// - /// Detect mimetype - /// - /// The containing the file header. - /// returns the mime type of detected otherwise returns null - ITextureFormat? DetectFormat(ReadOnlySpan header); - } + /// + /// Detect mimetype + /// + /// The containing the file header. + /// returns the mime type of detected otherwise returns null + ITextureFormat? DetectFormat(ReadOnlySpan header); } diff --git a/src/ImageSharp.Textures/Formats/ITextureFormatManager.cs b/src/ImageSharp.Textures/Formats/ITextureFormatManager.cs index f551e7cd..e8b30604 100644 --- a/src/ImageSharp.Textures/Formats/ITextureFormatManager.cs +++ b/src/ImageSharp.Textures/Formats/ITextureFormatManager.cs @@ -1,192 +1,185 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -namespace SixLabors.ImageSharp.Textures.Formats +namespace SixLabors.ImageSharp.Textures.Formats; + +/// +/// Collection of Image Formats to be used in class. +/// +public class ITextureFormatManager { /// - /// Collection of Image Formats to be used in class. + /// Used for locking against as there is no ConcurrentSet type. + /// + /// + private static readonly object HashLock = new(); + + /// + /// The list of supported keyed to mime types. /// - public class ITextureFormatManager + private readonly ConcurrentDictionary mimeTypeEncoders = new(); + + /// + /// The list of supported keyed to mime types. + /// + private readonly ConcurrentDictionary mimeTypeDecoders = new(); + + /// + /// The list of supported s. + /// + private readonly HashSet imageFormats = []; + + /// + /// The list of supported s. + /// + private ConcurrentBag imageFormatDetectors = []; + + /// + /// Initializes a new instance of the class. + /// + public ITextureFormatManager() { - /// - /// Used for locking against as there is no ConcurrentSet type. - /// - /// - private static readonly object HashLock = new object(); - - /// - /// The list of supported keyed to mime types. - /// - private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(); - - /// - /// The list of supported keyed to mime types. - /// - private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); - - /// - /// The list of supported s. - /// - private readonly HashSet imageFormats = new HashSet(); - - /// - /// The list of supported s. - /// - private ConcurrentBag imageFormatDetectors = new ConcurrentBag(); - - /// - /// Initializes a new instance of the class. - /// - public ITextureFormatManager() - { - } + } - /// - /// Gets the maximum header size of all the formats. - /// - internal int MaxHeaderSize { get; private set; } - - /// - /// Gets the currently registered s. - /// - public IEnumerable ImageFormats => this.imageFormats; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable FormatDetectors => this.imageFormatDetectors; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; - - /// - /// Registers a new format provider. - /// - /// The format to register as a known format. - public void AddImageFormat(ITextureFormat format) - { - Guard.NotNull(format, nameof(format)); - Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); - Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); - - lock (HashLock) - { - _ = this.imageFormats.Add(format); - } - } + /// + /// Gets the maximum header size of all the formats. + /// + internal int MaxHeaderSize { get; private set; } - /// - /// For the specified file extensions type find the e . - /// - /// The extension to discover - /// The if found otherwise null - public ITextureFormat? FindFormatByFileExtension(string extension) - { - Guard.NotNullOrWhiteSpace(extension, nameof(extension)); + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.imageFormats; - if (extension[0] == '.') - { - extension = extension.Substring(1); - } + /// + /// Gets the currently registered s. + /// + internal IEnumerable FormatDetectors => this.imageFormatDetectors; - return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); - } + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; - /// - /// For the specified mime type find the . - /// - /// The mime-type to discover - /// The if found; otherwise null - public ITextureFormat? FindFormatByMimeType(string mimeType) => this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); - - /// - /// Sets a specific image encoder as the encoder for a specific image format. - /// - /// The image format to register the encoder for. - /// The encoder to use, - public void SetEncoder(ITextureFormat imageFormat, ITextureEncoder encoder) - { - Guard.NotNull(imageFormat, nameof(imageFormat)); - Guard.NotNull(encoder, nameof(encoder)); - this.AddImageFormat(imageFormat); - this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); - } + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; + + /// + /// Registers a new format provider. + /// + /// The format to register as a known format. + public void AddImageFormat(ITextureFormat format) + { + Guard.NotNull(format, nameof(format)); + Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); + Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); - /// - /// Sets a specific image decoder as the decoder for a specific image format. - /// - /// The image format to register the encoder for. - /// The decoder to use, - public void SetDecoder(ITextureFormat imageFormat, ITextureDecoder decoder) + lock (HashLock) { - Guard.NotNull(imageFormat, nameof(imageFormat)); - Guard.NotNull(decoder, nameof(decoder)); - this.AddImageFormat(imageFormat); - this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); + _ = this.imageFormats.Add(format); } + } - /// - /// Removes all the registered image format detectors. - /// - public void ClearImageFormatDetectors() => this.imageFormatDetectors = new ConcurrentBag(); + /// + /// For the specified file extensions type find the e . + /// + /// The extension to discover + /// The if found otherwise null + public ITextureFormat? FindFormatByFileExtension(string extension) + { + Guard.NotNullOrWhiteSpace(extension, nameof(extension)); - /// - /// Adds a new detector for detecting mime types. - /// - /// The detector to add - public void AddImageFormatDetector(ITextureFormatDetector detector) + if (extension[0] == '.') { - Guard.NotNull(detector, nameof(detector)); - this.imageFormatDetectors.Add(detector); - this.SetMaxHeaderSize(); + extension = extension[1..]; } - /// - /// For the specified mime type find the decoder. - /// - /// The format to discover - /// The if found otherwise null - public ITextureDecoder? FindDecoder(ITextureFormat format) - { - Guard.NotNull(format, nameof(format)); + return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); + } - return this.mimeTypeDecoders.TryGetValue(format, out ITextureDecoder? decoder) - ? decoder - : null; - } + /// + /// For the specified mime type find the . + /// + /// The mime-type to discover + /// The if found; otherwise null + public ITextureFormat? FindFormatByMimeType(string mimeType) => this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); - /// - /// For the specified mime type find the encoder. - /// - /// The format to discover - /// The if found otherwise null - public ITextureEncoder? FindEncoder(ITextureFormat format) - { - Guard.NotNull(format, nameof(format)); + /// + /// Sets a specific image encoder as the encoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The encoder to use, + public void SetEncoder(ITextureFormat imageFormat, ITextureEncoder encoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(encoder, nameof(encoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); + } - return this.mimeTypeEncoders.TryGetValue(format, out ITextureEncoder? encoder) - ? encoder - : null; - } + /// + /// Sets a specific image decoder as the decoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The decoder to use, + public void SetDecoder(ITextureFormat imageFormat, ITextureDecoder decoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(decoder, nameof(decoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); + } - /// - /// Sets the max header size. - /// - private void SetMaxHeaderSize() - { - this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); - } + /// + /// Removes all the registered image format detectors. + /// + public void ClearImageFormatDetectors() => this.imageFormatDetectors = []; + + /// + /// Adds a new detector for detecting mime types. + /// + /// The detector to add + public void AddImageFormatDetector(ITextureFormatDetector detector) + { + Guard.NotNull(detector, nameof(detector)); + this.imageFormatDetectors.Add(detector); + this.SetMaxHeaderSize(); + } + + /// + /// For the specified mime type find the decoder. + /// + /// The format to discover + /// The if found otherwise null + public ITextureDecoder? FindDecoder(ITextureFormat format) + { + Guard.NotNull(format, nameof(format)); + + return this.mimeTypeDecoders.TryGetValue(format, out ITextureDecoder? decoder) + ? decoder + : null; + } + + /// + /// For the specified mime type find the encoder. + /// + /// The format to discover + /// The if found otherwise null + public ITextureEncoder? FindEncoder(ITextureFormat format) + { + Guard.NotNull(format, nameof(format)); + + return this.mimeTypeEncoders.TryGetValue(format, out ITextureEncoder? encoder) + ? encoder + : null; } + + /// + /// Sets the max header size. + /// + private void SetMaxHeaderSize() => this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); } diff --git a/src/ImageSharp.Textures/Formats/ITextureInfoDetector.cs b/src/ImageSharp.Textures/Formats/ITextureInfoDetector.cs index 3865e173..0a582987 100644 --- a/src/ImageSharp.Textures/Formats/ITextureInfoDetector.cs +++ b/src/ImageSharp.Textures/Formats/ITextureInfoDetector.cs @@ -1,21 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; +namespace SixLabors.ImageSharp.Textures.Formats; -namespace SixLabors.ImageSharp.Textures.Formats +/// +/// Encapsulates methods used for detecting the raw image information without fully decoding it. +/// +public interface ITextureInfoDetector { /// - /// Encapsulates methods used for detecting the raw image information without fully decoding it. + /// Reads the raw image information from the specified stream. /// - public interface ITextureInfoDetector - { - /// - /// Reads the raw image information from the specified stream. - /// - /// The configuration for the image. - /// The containing image data. - /// The object - ITextureInfo Identify(Configuration configuration, Stream stream); - } + /// The configuration for the image. + /// The containing image data. + /// The object + ITextureInfo Identify(Configuration configuration, Stream stream); } diff --git a/src/ImageSharp.Textures/Formats/Ktx/Enums/GlBaseInternalPixelFormat.cs b/src/ImageSharp.Textures/Formats/Ktx/Enums/GlBaseInternalPixelFormat.cs index bd585fe3..946ceff9 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/Enums/GlBaseInternalPixelFormat.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/Enums/GlBaseInternalPixelFormat.cs @@ -1,34 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx.Enums +namespace SixLabors.ImageSharp.Textures.Formats.Ktx.Enums; + +internal enum GlBaseInternalPixelFormat : uint { - internal enum GlBaseInternalPixelFormat : uint - { - Red = 0x1903, + Red = 0x1903, - Green = 0x1904, + Green = 0x1904, - Blue = 0x1905, + Blue = 0x1905, - Alpha = 0x1906, + Alpha = 0x1906, - Rgb = 0x1907, + Rgb = 0x1907, - Rgba = 0x1908, + Rgba = 0x1908, - Luminance = 0x1909, + Luminance = 0x1909, - LuminanceAlpha = 0x190A, + LuminanceAlpha = 0x190A, - Intensity = 0x8049, + Intensity = 0x8049, - RedGreen = 0x8227, + RedGreen = 0x8227, - Bgr = 0x80E0, + Bgr = 0x80E0, - Bgra = 0x80E1, + Bgra = 0x80E1, - Rg = 0x8228 - } + Rg = 0x8228 } diff --git a/src/ImageSharp.Textures/Formats/Ktx/Enums/GlInternalPixelFormat.cs b/src/ImageSharp.Textures/Formats/Ktx/Enums/GlInternalPixelFormat.cs index 0597c744..18f65a78 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/Enums/GlInternalPixelFormat.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/Enums/GlInternalPixelFormat.cs @@ -1,162 +1,161 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; + +internal enum GlInternalPixelFormat : uint { - internal enum GlInternalPixelFormat : uint - { - Luminance4 = 0x803F, + Luminance4 = 0x803F, - Luminance8 = 0x8040, + Luminance8 = 0x8040, - Luminance4Alpha4 = 0x8043, + Luminance4Alpha4 = 0x8043, - Luminance6Alpha2 = 0x8044, + Luminance6Alpha2 = 0x8044, - Luminance7Alpha8 = 0x8045, + Luminance7Alpha8 = 0x8045, - Rgb4 = 0x804F, + Rgb4 = 0x804F, - Rgb5 = 0x8050, + Rgb5 = 0x8050, - Rgb8 = 0x8051, + Rgb8 = 0x8051, - Rgb16 = 0x8054, + Rgb16 = 0x8054, - Rgba8 = 0x8058, + Rgba8 = 0x8058, - Rgb565 = 0x8D62, + Rgb565 = 0x8D62, - Rgb10 = 0x8052, + Rgb10 = 0x8052, - Rgb12 = 0x8053, + Rgb12 = 0x8053, - Rgba2 = 0x8055, + Rgba2 = 0x8055, - Rgba4 = 0x8056, + Rgba4 = 0x8056, - Rgba12 = 0x805A, + Rgba12 = 0x805A, - Rgb5A1 = 0x8057, + Rgb5A1 = 0x8057, - Rgb10A2 = 0x8059, + Rgb10A2 = 0x8059, - Rgb9E5 = 0x8C3D, + Rgb9E5 = 0x8C3D, - Rgba16 = 0x805B, + Rgba16 = 0x805B, - R8 = 0x8229, + R8 = 0x8229, - R8UnsignedInt = 0x8232, + R8UnsignedInt = 0x8232, - Rg8UnsignedInt = 0x8238, + Rg8UnsignedInt = 0x8238, - Rgb8UnsignedInt = 0x8D7D, + Rgb8UnsignedInt = 0x8D7D, - RgbaUnsignedInt = 0x8D7C, + RgbaUnsignedInt = 0x8D7C, - R32UnsignedInt = 0x8236, + R32UnsignedInt = 0x8236, - Rg32UnsignedInt = 0x823C, + Rg32UnsignedInt = 0x823C, - Rgb32UnsignedInt = 0x8D71, + Rgb32UnsignedInt = 0x8D71, - Rgba32UnsignedInt = 0x8D70, + Rgba32UnsignedInt = 0x8D70, - R16 = 0x822A, + R16 = 0x822A, - Rg8 = 0x822B, + Rg8 = 0x822B, - Rg16 = 0x822C, + Rg16 = 0x822C, - R8SNorm = 0x8F94, + R8SNorm = 0x8F94, - Rg8SNorm = 0x8F95, + Rg8SNorm = 0x8F95, - Rgb8SNorm = 0x8F96, + Rgb8SNorm = 0x8F96, - RgbaSNorm = 0x8F97, + RgbaSNorm = 0x8F97, - Etc1Rgb8Oes = 0x8D64, + Etc1Rgb8Oes = 0x8D64, - RedRgtc1 = 0x8DBB, + RedRgtc1 = 0x8DBB, - SignedRedRgtc1 = 0x8DBC, + SignedRedRgtc1 = 0x8DBC, - RedGreenRgtc2 = 0x8DBD, + RedGreenRgtc2 = 0x8DBD, - SignedRedGreenRgtc2 = 0x8DBE, + SignedRedGreenRgtc2 = 0x8DBE, - RgbDxt1 = 0x83F0, + RgbDxt1 = 0x83F0, - RgbaDxt1 = 0x83F1, + RgbaDxt1 = 0x83F1, - RgbaDxt3 = 0x83F2, + RgbaDxt3 = 0x83F2, - RgbaDxt5 = 0x83F3, + RgbaDxt5 = 0x83F3, - Sr8 = 0x8FBD, + Sr8 = 0x8FBD, - Srg8 = 0x8FBE, + Srg8 = 0x8FBE, - Srgb8 = 0x8C41, + Srgb8 = 0x8C41, - Srgb8Alpha8 = 0x8C43, + Srgb8Alpha8 = 0x8C43, - SrgbDxt1 = 0x8C4C, + SrgbDxt1 = 0x8C4C, - SrgbAlphaDxt1 = 0x8C4D, + SrgbAlphaDxt1 = 0x8C4D, - SrgbAlphaDxt3 = 0x8C4E, + SrgbAlphaDxt3 = 0x8C4E, - SrgbAlphaDxt5 = 0x8C4F, + SrgbAlphaDxt5 = 0x8C4F, - CompressedRed11Eac = 0x9270, + CompressedRed11Eac = 0x9270, - CompressedRedGreen11Eac = 0x9272, + CompressedRedGreen11Eac = 0x9272, - CompressedRedSignedRedEac = 0x9271, + CompressedRedSignedRedEac = 0x9271, - CompressedRedGreenSignedEac = 0x9273, + CompressedRedGreenSignedEac = 0x9273, - CompressedRgb8Etc2 = 0x9274, + CompressedRgb8Etc2 = 0x9274, - CompressedSrgb8Etc2 = 0x9275, + CompressedSrgb8Etc2 = 0x9275, - CompressedRgb8PunchthroughAlpa1Etc2 = 0x9276, + CompressedRgb8PunchthroughAlpa1Etc2 = 0x9276, - CompressedSrgb8PunchthroughAlpa1Etc2 = 0x9277, + CompressedSrgb8PunchthroughAlpa1Etc2 = 0x9277, - CompressedRgb8Etc2Eac = 0x9278, + CompressedRgb8Etc2Eac = 0x9278, - CompressedSrgb8Alpha8Etc2Eac = 0x9279, + CompressedSrgb8Alpha8Etc2Eac = 0x9279, - CompressedRgbaAstc4x4Khr = 0x93B0, + CompressedRgbaAstc4x4Khr = 0x93B0, - CompressedRgbaAstc5x4Khr = 0x93B1, + CompressedRgbaAstc5x4Khr = 0x93B1, - CompressedRgbaAstc5x5Khr = 0x93B2, + CompressedRgbaAstc5x5Khr = 0x93B2, - CompressedRgbaAstc6x5Khr = 0x93B3, + CompressedRgbaAstc6x5Khr = 0x93B3, - CompressedRgbaAstc6x6Khr = 0x93B4, + CompressedRgbaAstc6x6Khr = 0x93B4, - CompressedRgbaAstc8x5Khr = 0x93B5, + CompressedRgbaAstc8x5Khr = 0x93B5, - CompressedRgbaAstc8x6Khr = 0x93B6, + CompressedRgbaAstc8x6Khr = 0x93B6, - CompressedRgbaAstc8x8Khr = 0x93B7, + CompressedRgbaAstc8x8Khr = 0x93B7, - CompressedRgbaAstc10x5Khr = 0x93B8, + CompressedRgbaAstc10x5Khr = 0x93B8, - CompressedRgbaAstc10x6Khr = 0x93B9, + CompressedRgbaAstc10x6Khr = 0x93B9, - CompressedRgbaAstc10x8Khr = 0x93BA, + CompressedRgbaAstc10x8Khr = 0x93BA, - CompressedRgbaAstc10x10Khr = 0x93BB, + CompressedRgbaAstc10x10Khr = 0x93BB, - CompressedRgbaAstc12x10Khr = 0x93BC, + CompressedRgbaAstc12x10Khr = 0x93BC, - CompressedRgbaAstc12x12Khr = 0x93BD, - } + CompressedRgbaAstc12x12Khr = 0x93BD, } diff --git a/src/ImageSharp.Textures/Formats/Ktx/Enums/GlPixelFormat.cs b/src/ImageSharp.Textures/Formats/Ktx/Enums/GlPixelFormat.cs index c709c886..1dad8007 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/Enums/GlPixelFormat.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/Enums/GlPixelFormat.cs @@ -1,76 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; + +/// +/// Enum for the different OpenGl pixel formats. +/// +internal enum GlPixelFormat : uint { /// - /// Enum for the different OpenGl pixel formats. + /// Zero indicates, that the texture is compressed. /// - internal enum GlPixelFormat : uint - { - /// - /// Zero indicates, that the texture is compressed. - /// - Compressed = 0, + Compressed = 0, - /// - /// Only the red channel. - /// - Red = 0x1903, + /// + /// Only the red channel. + /// + Red = 0x1903, - /// - /// Only the green channel. - /// - Green = 0x1904, + /// + /// Only the green channel. + /// + Green = 0x1904, - /// - /// Only the blue channel. - /// - Blue = 0x1905, + /// + /// Only the blue channel. + /// + Blue = 0x1905, - /// - /// Only the alpha channel. - /// - Alpha = 0x1906, + /// + /// Only the alpha channel. + /// + Alpha = 0x1906, - /// - /// Only luminance. - /// - Luminance = 0x1909, + /// + /// Only luminance. + /// + Luminance = 0x1909, - /// - /// Luminance and alpha. - /// - LuminanceAlpha = 0x190A, + /// + /// Luminance and alpha. + /// + LuminanceAlpha = 0x190A, - /// - /// Pixels are stored only with the red and green channel present. - /// - Rg = 0x8227, + /// + /// Pixels are stored only with the red and green channel present. + /// + Rg = 0x8227, - /// - /// Pixels are stored only with the red and green channel present. - /// - RgInteger = 0x8228, + /// + /// Pixels are stored only with the red and green channel present. + /// + RgInteger = 0x8228, - /// - /// Pixels are stored as RGB. - /// - Rgb = 0x1907, + /// + /// Pixels are stored as RGB. + /// + Rgb = 0x1907, - /// - /// Pixels are stored as RGBA. - /// - Rgba = 0x1908, + /// + /// Pixels are stored as RGBA. + /// + Rgba = 0x1908, - /// - /// Pixels are stored as BGR. - /// - Bgr = 0x80E0, + /// + /// Pixels are stored as BGR. + /// + Bgr = 0x80E0, - /// - /// Pixels are stored as BGRA. - /// - Bgra = 0x80E1 - } + /// + /// Pixels are stored as BGRA. + /// + Bgra = 0x80E1 } diff --git a/src/ImageSharp.Textures/Formats/Ktx/Enums/GlType.cs b/src/ImageSharp.Textures/Formats/Ktx/Enums/GlType.cs index db933289..4abcacce 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/Enums/GlType.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/Enums/GlType.cs @@ -1,51 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx.Enums +namespace SixLabors.ImageSharp.Textures.Formats.Ktx.Enums; + +internal enum GlType : uint { - internal enum GlType : uint - { - /// - /// Zero indicates, that the texture is compressed. - /// - Compressed = 0, + /// + /// Zero indicates, that the texture is compressed. + /// + Compressed = 0, - Byte = 0x1400, + Byte = 0x1400, - UnsignedByte = 0x1401, + UnsignedByte = 0x1401, - Short = 0x1402, + Short = 0x1402, - UnsignedShort = 0x1403, + UnsignedShort = 0x1403, - Int = 0x1404, + Int = 0x1404, - UnsignedInt = 0x1405, + UnsignedInt = 0x1405, - Int64 = 0x140E, + Int64 = 0x140E, - UnsignedInt64 = 0x140F, + UnsignedInt64 = 0x140F, - HalfFloat = 0x140B, + HalfFloat = 0x140B, - HalfFloatOes = 0x8D61, + HalfFloatOes = 0x8D61, - Float = 0x1406, + Float = 0x1406, - Double = 0x140A, + Double = 0x140A, - UsignedByte332 = 0x8032, + UsignedByte332 = 0x8032, - UnsignedByte233 = 0x8362, + UnsignedByte233 = 0x8362, - UnsignedShort565 = 0x8363, + UnsignedShort565 = 0x8363, - UnsignedShort4444 = 0x8033, + UnsignedShort4444 = 0x8033, - UnsignedShort5551 = 0x8034, + UnsignedShort5551 = 0x8034, - UnsignedInt8888 = 0x8035, + UnsignedInt8888 = 0x8035, - UnsignedInt1010102 = 0x8036, - } + UnsignedInt1010102 = 0x8036, } diff --git a/src/ImageSharp.Textures/Formats/Ktx/Enums/KtxEndianness.cs b/src/ImageSharp.Textures/Formats/Ktx/Enums/KtxEndianness.cs index 4864d1ac..b3211310 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/Enums/KtxEndianness.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/Enums/KtxEndianness.cs @@ -1,18 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; + +internal enum KtxEndianness : uint { - internal enum KtxEndianness : uint - { - /// - /// Texture data is little endian. - /// - LittleEndian = 0x04030201, + /// + /// Texture data is little endian. + /// + LittleEndian = 0x04030201, - /// - /// Texture data is big endian. - /// - BigEndian = 0x01020304, - } + /// + /// Texture data is big endian. + /// + BigEndian = 0x01020304, } diff --git a/src/ImageSharp.Textures/Formats/Ktx/IKtxDecoderOptions.cs b/src/ImageSharp.Textures/Formats/Ktx/IKtxDecoderOptions.cs index e2b1c398..1ef51ad6 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/IKtxDecoderOptions.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/IKtxDecoderOptions.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; + +/// +/// The options for decoding ktx textures. Currently empty, but this may change in the future. +/// +internal interface IKtxDecoderOptions { - /// - /// The options for decoding ktx textures. Currently empty, but this may change in the future. - /// - internal interface IKtxDecoderOptions - { - } } diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxConfigurationModule.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxConfigurationModule.cs index 81d2de60..3963acd1 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxConfigurationModule.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxConfigurationModule.cs @@ -1,18 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; + +/// +/// Registers the image encoders, decoders and mime type detectors for the ktx format. +/// +public class KtxConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the ktx format. - /// - public class KtxConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetDecoder(KtxFormat.Instance, new KtxDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new KtxImageFormatDetector()); - } + configuration.ImageFormatsManager.SetDecoder(KtxFormat.Instance, new KtxDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new KtxImageFormatDetector()); } } diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxConstants.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxConstants.cs index 4b2fd316..9c3415dd 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxConstants.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxConstants.cs @@ -1,45 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +internal static class KtxConstants { - internal static class KtxConstants - { - /// - /// The size of a KTX header in bytes. - /// - public const int KtxHeaderSize = 52; + /// + /// The size of a KTX header in bytes. + /// + public const int KtxHeaderSize = 52; - /// - /// The list of mimetypes that equate to a ktx file. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/ktx" }; + /// + /// The list of mimetypes that equate to a ktx file. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/ktx" }; - /// - /// The list of file extensions that equate to a ktx file. - /// - public static readonly IEnumerable FileExtensions = new[] { "ktx" }; + /// + /// The list of file extensions that equate to a ktx file. + /// + public static readonly IEnumerable FileExtensions = new[] { "ktx" }; - /// - /// Gets the magic bytes identifying a ktx texture. - /// - public static ReadOnlySpan MagicBytes => new byte[] - { - 0xAB, // « - 0x4B, // K - 0x54, // T - 0x58, // X - 0x20, // " " - 0x31, // 1 - 0x31, // 1 - 0xBB, // » - 0x0D, // \r - 0x0A, // \n - 0x1A, - 0x0A, // \n - }; - } + /// + /// Gets the magic bytes identifying a ktx texture. + /// + public static ReadOnlySpan MagicBytes => + [ + 0xAB, // « + 0x4B, // K + 0x54, // T + 0x58, // X + 0x20, // " " + 0x31, // 1 + 0x31, // 1 + 0xBB, // » + 0x0D, // \r + 0x0A, // \n + 0x1A, + 0x0A, // \n + ]; } diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxDecoder.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxDecoder.cs index 4f7aa560..7df4b267 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxDecoder.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxDecoder.cs @@ -1,29 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +/// +/// Image decoder for KTX textures. +/// +public sealed class KtxDecoder : ITextureDecoder, IKtxDecoderOptions, ITextureInfoDetector { - /// - /// Image decoder for KTX textures. - /// - public sealed class KtxDecoder : ITextureDecoder, IKtxDecoderOptions, ITextureInfoDetector + /// + public Texture DecodeTexture(Configuration configuration, Stream stream) { - /// - public Texture DecodeTexture(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(stream, nameof(stream)); - return new KtxDecoderCore(configuration, this).DecodeTexture(stream); - } + return new KtxDecoderCore(configuration, this).DecodeTexture(stream); + } - /// - public ITextureInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); + /// + public ITextureInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); - return new KtxDecoderCore(configuration, this).Identify(stream); - } + return new KtxDecoderCore(configuration, this).Identify(stream); } } diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxDecoderCore.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxDecoderCore.cs index 8a6255bf..a1e7487e 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxDecoderCore.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxDecoderCore.cs @@ -1,110 +1,108 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Textures.Common.Exceptions; using SixLabors.ImageSharp.Textures.TextureFormats; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; + +/// +/// Performs the ktx decoding operation. +/// +internal sealed class KtxDecoderCore { /// - /// Performs the ktx decoding operation. + /// The global configuration. /// - internal sealed class KtxDecoderCore - { - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The file header containing general information about the texture. - /// - private KtxHeader ktxHeader; - - /// - /// The texture decoder options. - /// - private readonly IKtxDecoderOptions options; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The options. - public KtxDecoderCore(Configuration configuration, IKtxDecoderOptions options) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; - } - - /// - /// Decodes the texture from the specified stream. - /// - /// The stream, where the texture should be decoded from. Cannot be null. - /// The decoded image. - public Texture DecodeTexture(Stream stream) - { - this.ReadFileHeader(stream); + private readonly Configuration configuration; - if (this.ktxHeader.Width == 0) - { - throw new UnknownTextureFormatException("Width cannot be 0"); - } + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; - int width = (int)this.ktxHeader.Width; - int height = (int)this.ktxHeader.Height; + /// + /// The file header containing general information about the texture. + /// + private KtxHeader ktxHeader; - // Skip over bytesOfKeyValueData, if any is present. - stream.Position += this.ktxHeader.BytesOfKeyValueData; + /// + /// The texture decoder options. + /// + private readonly IKtxDecoderOptions options; - var ktxProcessor = new KtxProcessor(this.ktxHeader); + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public KtxDecoderCore(Configuration configuration, IKtxDecoderOptions options) + { + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; + } - if (this.ktxHeader.NumberOfFaces == 6) - { - CubemapTexture cubeMapTexture = ktxProcessor.DecodeCubeMap(stream, width, height); - return cubeMapTexture; - } + /// + /// Decodes the texture from the specified stream. + /// + /// The stream, where the texture should be decoded from. Cannot be null. + /// The decoded image. + public Texture DecodeTexture(Stream stream) + { + this.ReadFileHeader(stream); - var texture = new FlatTexture(); - MipMap[] mipMaps = ktxProcessor.DecodeMipMaps(stream, width, height, this.ktxHeader.NumberOfMipmapLevels); - texture.MipMaps.AddRange(mipMaps); - return texture; + if (this.ktxHeader.Width == 0) + { + throw new UnknownTextureFormatException("Width cannot be 0"); } - /// - /// Reads the raw texture information from the specified stream. - /// - /// The containing texture data. - public ITextureInfo Identify(Stream currentStream) - { - this.ReadFileHeader(currentStream); + int width = (int)this.ktxHeader.Width; + int height = (int)this.ktxHeader.Height; - var textureInfo = new TextureInfo(new TextureTypeInfo((int)this.ktxHeader.PixelDepth), (int)this.ktxHeader.Width, (int)this.ktxHeader.Height); + // Skip over bytesOfKeyValueData, if any is present. + stream.Position += this.ktxHeader.BytesOfKeyValueData; - return textureInfo; - } + KtxProcessor ktxProcessor = new KtxProcessor(this.ktxHeader); - /// - /// Reads the dds file header from the stream. - /// - /// The containing texture data. - private void ReadFileHeader(Stream stream) + if (this.ktxHeader.NumberOfFaces == 6) { - // Discard the magic bytes, we already know at this point its a ktx file. - stream.Position += KtxConstants.MagicBytes.Length; + CubemapTexture cubeMapTexture = ktxProcessor.DecodeCubeMap(stream, width, height); + return cubeMapTexture; + } - byte[] ktxHeaderBuffer = new byte[KtxConstants.KtxHeaderSize]; - stream.Read(ktxHeaderBuffer, 0, KtxConstants.KtxHeaderSize); + FlatTexture texture = new FlatTexture(); + MipMap[] mipMaps = ktxProcessor.DecodeMipMaps(stream, width, height, this.ktxHeader.NumberOfMipmapLevels); + texture.MipMaps.AddRange(mipMaps); + return texture; + } - this.ktxHeader = KtxHeader.Parse(ktxHeaderBuffer); - } + /// + /// Reads the raw texture information from the specified stream. + /// + /// The containing texture data. + public ITextureInfo Identify(Stream currentStream) + { + this.ReadFileHeader(currentStream); + + TextureInfo textureInfo = new TextureInfo(new TextureTypeInfo((int)this.ktxHeader.PixelDepth), (int)this.ktxHeader.Width, (int)this.ktxHeader.Height); + + return textureInfo; + } + + /// + /// Reads the dds file header from the stream. + /// + /// The containing texture data. + private void ReadFileHeader(Stream stream) + { + // Discard the magic bytes, we already know at this point its a ktx file. + stream.Position += KtxConstants.MagicBytes.Length; + + byte[] ktxHeaderBuffer = new byte[KtxConstants.KtxHeaderSize]; + stream.Read(ktxHeaderBuffer, 0, KtxConstants.KtxHeaderSize); + + this.ktxHeader = KtxHeader.Parse(ktxHeaderBuffer); } } diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxFormat.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxFormat.cs index a46ba1af..0679564a 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxFormat.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxFormat.cs @@ -1,37 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +/// +/// Registers the texture decoders and mime type detectors for the ktx format. +/// +public sealed class KtxFormat : ITextureFormat { /// - /// Registers the texture decoders and mime type detectors for the ktx format. + /// Prevents a default instance of the class from being created. /// - public sealed class KtxFormat : ITextureFormat + private KtxFormat() { - /// - /// Prevents a default instance of the class from being created. - /// - private KtxFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static KtxFormat Instance { get; } = new KtxFormat(); + /// + /// Gets the current instance. + /// + public static KtxFormat Instance { get; } = new KtxFormat(); - /// - public string Name => "KTX"; + /// + public string Name => "KTX"; - /// - public string DefaultMimeType => "image/ktx"; + /// + public string DefaultMimeType => "image/ktx"; - /// - public IEnumerable MimeTypes => KtxConstants.MimeTypes; + /// + public IEnumerable MimeTypes => KtxConstants.MimeTypes; - /// - public IEnumerable FileExtensions => KtxConstants.FileExtensions; - } + /// + public IEnumerable FileExtensions => KtxConstants.FileExtensions; } diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxHeader.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxHeader.cs index 67ccc7c1..51ea641e 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxHeader.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxHeader.cs @@ -1,183 +1,181 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Textures.Common.Exceptions; using SixLabors.ImageSharp.Textures.Formats.Ktx.Enums; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; + +/// +/// Describes a KTX file header. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal struct KtxHeader { + public KtxHeader( + KtxEndianness endianness, + GlType glType, + uint glTypeSize, + GlPixelFormat glFormat, + GlInternalPixelFormat glInternalFormat, + GlBaseInternalPixelFormat glBaseInternalFormat, + uint width, + uint height, + uint pixelDepth, + uint numberOfArrayElements, + uint numberOfFaces, + uint numberOfMipmapLevels, + uint bytesOfKeyValueData) + { + this.Endianness = endianness; + this.GlTypeParameter = glType; + this.GlTypeSize = glTypeSize; + this.GlFormat = glFormat; + this.GlInternalFormat = glInternalFormat; + this.GlBaseInternalFormat = glBaseInternalFormat; + this.Width = width; + this.Height = height; + this.PixelDepth = pixelDepth; + this.NumberOfArrayElements = numberOfArrayElements; + this.NumberOfFaces = numberOfFaces; + this.NumberOfMipmapLevels = numberOfMipmapLevels; + this.BytesOfKeyValueData = bytesOfKeyValueData; + } + + /// + /// Gets the endianness. + /// endianness contains the number 0x04030201 written as a 32 bit integer. If the file is little endian then this is represented as the bytes 0x01 0x02 0x03 0x04. + /// If the file is big endian then this is represented as the bytes 0x04 0x03 0x02 0x01. + /// + public KtxEndianness Endianness { get; } + + /// + /// Gets the glType of the texture. + /// For compressed textures, glType must equal 0. For uncompressed textures, glType specifies the type parameter passed to glTex{,Sub}Image*D, + /// usually one of the values from table 8.2 of the OpenGL 4.4 specification + /// + public GlType GlTypeParameter { get; } + + /// + /// Gets the glTypeSize. + /// glTypeSize specifies the data type size that should be used when endianness conversion is required for the texture data stored in the file. + /// If glType is not 0, this should be the size in bytes corresponding to glType. For texture data which does not depend on platform endianness, + /// including compressed texture data, glTypeSize must equal 1. + /// + public uint GlTypeSize { get; } + + /// + /// Gets the glFormat. + /// For compressed textures, glFormat must equal 0. For uncompressed textures, glFormat specifies the format parameter passed to glTex{,Sub}Image*D, + /// usually one of the values from table 8.3 of the OpenGL 4.4 specification. (RGB, RGBA, BGRA, etc.) + /// + public GlPixelFormat GlFormat { get; } + + /// + /// Gets the internal format. + /// For compressed textures, glInternalFormat must equal the compressed internal format, usually one of the values from table 8.14 of the OpenGL 4.4 specification. + /// For uncompressed textures, glInternalFormat specifies the internalformat parameter passed to glTexStorage*D or glTexImage*D, + /// usually one of the sized internal formats from tables 8.12 and 8.13 of the OpenGL 4.4 specification. + /// + public GlInternalPixelFormat GlInternalFormat { get; } + + /// + /// Gets the base internal format. + /// For both compressed and uncompressed textures, glBaseInternalFormat specifies the base internal format of the texture, + /// usually one of the values from table 8.11 of the OpenGL 4.4 specification (RGB, RGBA, ALPHA, etc.). + /// For uncompressed textures, this value will be the same as glFormat and is used as the internalformat parameter when loading into a context that does not support sized formats, + /// such as an unextended OpenGL ES 2.0 context. + /// + public GlBaseInternalPixelFormat GlBaseInternalFormat { get; } + /// - /// Describes a KTX file header. + /// Gets the width in pixels of the texture at level 0. /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct KtxHeader + public uint Width { get; } + + /// + /// Gets the height in pixels of the texture at level 0. + /// For 1D textures pixelHeight must be 0. + /// + public uint Height { get; } + + /// + /// Gets the pixel depth. + /// For 1D textures pixelDepth must be 0. For 2D and cube textures pixelDepth must be 0. + /// + public uint PixelDepth { get; } + + /// + /// Gets the number of array elements. + /// If the texture is not an array texture, numberOfArrayElements must equal 0. + /// + public uint NumberOfArrayElements { get; } + + /// + /// Gets the number of faces. + /// numberOfFaces specifies the number of cubemap faces. For cubemaps and cubemap arrays this should be 6. For non cubemaps this should be 1. + /// Cube map faces are stored in the order: +X, -X, +Y, -Y, +Z, -Z. + /// + public uint NumberOfFaces { get; } + + /// + /// Gets the number of mipmap levels. + /// numberOfMipmapLevels must equal 1 for non-mipmapped textures. + /// + public uint NumberOfMipmapLevels { get; } + + /// + /// Gets the BytesOfKeyValueData. + /// An arbitrary number of key/value pairs may follow the header. This can be used to encode any arbitrary data. + /// + public uint BytesOfKeyValueData { get; } + + public static KtxHeader Parse(ReadOnlySpan data) { - public KtxHeader( - KtxEndianness endianness, - GlType glType, - uint glTypeSize, - GlPixelFormat glFormat, - GlInternalPixelFormat glInternalFormat, - GlBaseInternalPixelFormat glBaseInternalFormat, - uint width, - uint height, - uint pixelDepth, - uint numberOfArrayElements, - uint numberOfFaces, - uint numberOfMipmapLevels, - uint bytesOfKeyValueData) + if (data.Length < KtxConstants.KtxHeaderSize) { - this.Endianness = endianness; - this.GlTypeParameter = glType; - this.GlTypeSize = glTypeSize; - this.GlFormat = glFormat; - this.GlInternalFormat = glInternalFormat; - this.GlBaseInternalFormat = glBaseInternalFormat; - this.Width = width; - this.Height = height; - this.PixelDepth = pixelDepth; - this.NumberOfArrayElements = numberOfArrayElements; - this.NumberOfFaces = numberOfFaces; - this.NumberOfMipmapLevels = numberOfMipmapLevels; - this.BytesOfKeyValueData = bytesOfKeyValueData; + throw new ArgumentException($"Ktx header must be {KtxConstants.KtxHeaderSize} bytes. Was {data.Length} bytes.", nameof(data)); } - /// - /// Gets the endianness. - /// endianness contains the number 0x04030201 written as a 32 bit integer. If the file is little endian then this is represented as the bytes 0x01 0x02 0x03 0x04. - /// If the file is big endian then this is represented as the bytes 0x04 0x03 0x02 0x01. - /// - public KtxEndianness Endianness { get; } - - /// - /// Gets the glType of the texture. - /// For compressed textures, glType must equal 0. For uncompressed textures, glType specifies the type parameter passed to glTex{,Sub}Image*D, - /// usually one of the values from table 8.2 of the OpenGL 4.4 specification - /// - public GlType GlTypeParameter { get; } - - /// - /// Gets the glTypeSize. - /// glTypeSize specifies the data type size that should be used when endianness conversion is required for the texture data stored in the file. - /// If glType is not 0, this should be the size in bytes corresponding to glType. For texture data which does not depend on platform endianness, - /// including compressed texture data, glTypeSize must equal 1. - /// - public uint GlTypeSize { get; } - - /// - /// Gets the glFormat. - /// For compressed textures, glFormat must equal 0. For uncompressed textures, glFormat specifies the format parameter passed to glTex{,Sub}Image*D, - /// usually one of the values from table 8.3 of the OpenGL 4.4 specification. (RGB, RGBA, BGRA, etc.) - /// - public GlPixelFormat GlFormat { get; } - - /// - /// Gets the internal format. - /// For compressed textures, glInternalFormat must equal the compressed internal format, usually one of the values from table 8.14 of the OpenGL 4.4 specification. - /// For uncompressed textures, glInternalFormat specifies the internalformat parameter passed to glTexStorage*D or glTexImage*D, - /// usually one of the sized internal formats from tables 8.12 and 8.13 of the OpenGL 4.4 specification. - /// - public GlInternalPixelFormat GlInternalFormat { get; } - - /// - /// Gets the base internal format. - /// For both compressed and uncompressed textures, glBaseInternalFormat specifies the base internal format of the texture, - /// usually one of the values from table 8.11 of the OpenGL 4.4 specification (RGB, RGBA, ALPHA, etc.). - /// For uncompressed textures, this value will be the same as glFormat and is used as the internalformat parameter when loading into a context that does not support sized formats, - /// such as an unextended OpenGL ES 2.0 context. - /// - public GlBaseInternalPixelFormat GlBaseInternalFormat { get; } - - /// - /// Gets the width in pixels of the texture at level 0. - /// - public uint Width { get; } - - /// - /// Gets the height in pixels of the texture at level 0. - /// For 1D textures pixelHeight must be 0. - /// - public uint Height { get; } - - /// - /// Gets the pixel depth. - /// For 1D textures pixelDepth must be 0. For 2D and cube textures pixelDepth must be 0. - /// - public uint PixelDepth { get; } - - /// - /// Gets the number of array elements. - /// If the texture is not an array texture, numberOfArrayElements must equal 0. - /// - public uint NumberOfArrayElements { get; } - - /// - /// Gets the number of faces. - /// numberOfFaces specifies the number of cubemap faces. For cubemaps and cubemap arrays this should be 6. For non cubemaps this should be 1. - /// Cube map faces are stored in the order: +X, -X, +Y, -Y, +Z, -Z. - /// - public uint NumberOfFaces { get; } - - /// - /// Gets the number of mipmap levels. - /// numberOfMipmapLevels must equal 1 for non-mipmapped textures. - /// - public uint NumberOfMipmapLevels { get; } - - /// - /// Gets the BytesOfKeyValueData. - /// An arbitrary number of key/value pairs may follow the header. This can be used to encode any arbitrary data. - /// - public uint BytesOfKeyValueData { get; } - - public static KtxHeader Parse(ReadOnlySpan data) + KtxEndianness endianness = (KtxEndianness)BinaryPrimitives.ReadUInt32LittleEndian(data); + if (endianness is not KtxEndianness.BigEndian and not KtxEndianness.LittleEndian) { - if (data.Length < KtxConstants.KtxHeaderSize) - { - throw new ArgumentException($"Ktx header must be {KtxConstants.KtxHeaderSize} bytes. Was {data.Length} bytes.", nameof(data)); - } - - var endianness = (KtxEndianness)BinaryPrimitives.ReadUInt32LittleEndian(data); - if (endianness != KtxEndianness.BigEndian && endianness != KtxEndianness.LittleEndian) - { - throw new TextureFormatException("ktx file header has an invalid value for endianness"); - } - - if (endianness == KtxEndianness.LittleEndian) - { - return new KtxHeader( - (KtxEndianness)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(0, 4)), - (GlType)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(4, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(8, 4)), - (GlPixelFormat)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(12, 4)), - (GlInternalPixelFormat)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(16, 4)), - (GlBaseInternalPixelFormat)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(20, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(24, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(28, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(32, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(36, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(40, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(44, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(48, 4))); - } + throw new TextureFormatException("ktx file header has an invalid value for endianness"); + } + if (endianness == KtxEndianness.LittleEndian) + { return new KtxHeader( - (KtxEndianness)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(0, 4)), - (GlType)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)), - BinaryPrimitives.ReadUInt32BigEndian(data.Slice(8, 4)), - (GlPixelFormat)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(12, 4)), - (GlInternalPixelFormat)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(16, 4)), - (GlBaseInternalPixelFormat)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(20, 4)), - BinaryPrimitives.ReadUInt32BigEndian(data.Slice(24, 4)), - BinaryPrimitives.ReadUInt32BigEndian(data.Slice(28, 4)), - BinaryPrimitives.ReadUInt32BigEndian(data.Slice(32, 4)), - BinaryPrimitives.ReadUInt32BigEndian(data.Slice(36, 4)), - BinaryPrimitives.ReadUInt32BigEndian(data.Slice(40, 4)), - BinaryPrimitives.ReadUInt32BigEndian(data.Slice(44, 4)), - BinaryPrimitives.ReadUInt32BigEndian(data.Slice(48, 4))); + (KtxEndianness)BinaryPrimitives.ReadUInt32LittleEndian(data[..4]), + (GlType)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(4, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(8, 4)), + (GlPixelFormat)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(12, 4)), + (GlInternalPixelFormat)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(16, 4)), + (GlBaseInternalPixelFormat)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(20, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(24, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(28, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(32, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(36, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(40, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(44, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(48, 4))); } + + return new KtxHeader( + (KtxEndianness)BinaryPrimitives.ReadUInt32BigEndian(data[..4]), + (GlType)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)), + BinaryPrimitives.ReadUInt32BigEndian(data.Slice(8, 4)), + (GlPixelFormat)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(12, 4)), + (GlInternalPixelFormat)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(16, 4)), + (GlBaseInternalPixelFormat)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(20, 4)), + BinaryPrimitives.ReadUInt32BigEndian(data.Slice(24, 4)), + BinaryPrimitives.ReadUInt32BigEndian(data.Slice(28, 4)), + BinaryPrimitives.ReadUInt32BigEndian(data.Slice(32, 4)), + BinaryPrimitives.ReadUInt32BigEndian(data.Slice(36, 4)), + BinaryPrimitives.ReadUInt32BigEndian(data.Slice(40, 4)), + BinaryPrimitives.ReadUInt32BigEndian(data.Slice(44, 4)), + BinaryPrimitives.ReadUInt32BigEndian(data.Slice(48, 4))); } } diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxImageFormatDetector.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxImageFormatDetector.cs index 4d4f9c7f..e83eaac6 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxImageFormatDetector.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxImageFormatDetector.cs @@ -1,30 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +/// +/// Detects ktx file headers. +/// +public sealed class KtxImageFormatDetector : ITextureFormatDetector { - /// - /// Detects ktx file headers. - /// - public sealed class KtxImageFormatDetector : ITextureFormatDetector - { - /// - public int HeaderSize => 12; + /// + public int HeaderSize => 12; - /// - public ITextureFormat? DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? KtxFormat.Instance : null; + /// + public ITextureFormat? DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? KtxFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) { - if (header.Length >= this.HeaderSize) - { - ReadOnlySpan magicBytes = header.Slice(0, 12); - return magicBytes.SequenceEqual(KtxConstants.MagicBytes); - } - - return false; + ReadOnlySpan magicBytes = header[..12]; + return magicBytes.SequenceEqual(KtxConstants.MagicBytes); } + + return false; } } diff --git a/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs b/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs index 8dbafc62..908747b1 100644 --- a/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs +++ b/src/ImageSharp.Textures/Formats/Ktx/KtxProcessor.cs @@ -1,356 +1,353 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.IO; using SixLabors.ImageSharp.Textures.Common.Exceptions; using SixLabors.ImageSharp.Textures.TextureFormats; using SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Formats.Ktx; + +/// +/// Decodes ktx textures. +/// +internal class KtxProcessor { /// - /// Decodes ktx textures. + /// A scratch buffer to reduce allocations. /// - internal class KtxProcessor - { - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] buffer = new byte[4]; - - /// - /// Initializes a new instance of the class. - /// - /// The KTX header. - public KtxProcessor(KtxHeader ktxHeader) => this.KtxHeader = ktxHeader; - - /// - /// Gets the KTX header. - /// - public KtxHeader KtxHeader { get; } - - /// - /// Decodes the mipmaps of a KTX textures. - /// - /// The stream to read the texture data from. - /// The width of the texture at level 0. - /// The height of the texture at level 0. - /// The mipmap count. - /// The decoded mipmaps. - public MipMap[] DecodeMipMaps(Stream stream, int width, int height, uint count) - { - if (this.KtxHeader.GlTypeSize is 0 or 1) - { - switch (this.KtxHeader.GlFormat) - { - case GlPixelFormat.Red: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.Rg: - case GlPixelFormat.RgInteger: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.Rgb: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.Rgba: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.Bgr: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.Bgra: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.LuminanceAlpha: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.Luminance: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.Alpha: - return this.AllocateMipMaps(stream, width, height, count); - case GlPixelFormat.Compressed: - switch (this.KtxHeader.GlInternalFormat) - { - case GlInternalPixelFormat.RgbaDxt1: - case GlInternalPixelFormat.RgbDxt1: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.RgbaDxt3: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.RgbaDxt5: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.RedRgtc1: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.SignedRedRgtc1: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.RedGreenRgtc2: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.SignedRedGreenRgtc2: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.Etc1Rgb8Oes: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgb8Etc2: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc4x4Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc5x4Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc5x5Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc6x5Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc6x6Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc8x5Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc8x6Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc8x8Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc10x5Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc10x6Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc10x8Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc10x10Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc12x10Khr: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.CompressedRgbaAstc12x12Khr: - return this.AllocateMipMaps(stream, width, height, count); - } + private readonly byte[] buffer = new byte[4]; - break; - } - } - - if (this.KtxHeader.GlTypeSize is 2 or 4) - { - // TODO: endianess is not respected here. Use stream reader which respects endianess. - switch (this.KtxHeader.GlInternalFormat) - { - case GlInternalPixelFormat.Rgb5A1: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.Rgb10A2: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.Rgb16: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.Rgba16: - return this.AllocateMipMaps(stream, width, height, count); - case GlInternalPixelFormat.Rgba32UnsignedInt: - return this.AllocateMipMaps(stream, width, height, count); - } - } + /// + /// Initializes a new instance of the class. + /// + /// The KTX header. + public KtxProcessor(KtxHeader ktxHeader) => this.KtxHeader = ktxHeader; - throw new NotSupportedException("The pixel format is not supported"); - } + /// + /// Gets the KTX header. + /// + public KtxHeader KtxHeader { get; } - /// - /// Decodes the a KTX cube map texture. - /// - /// The stream to read the texture data from. - /// The width of a texture face. - /// The height of a texture face. - /// A decoded cubemap texture. - /// The pixel format is not supported - public CubemapTexture DecodeCubeMap(Stream stream, int width, int height) + /// + /// Decodes the mipmaps of a KTX textures. + /// + /// The stream to read the texture data from. + /// The width of the texture at level 0. + /// The height of the texture at level 0. + /// The mipmap count. + /// The decoded mipmaps. + public MipMap[] DecodeMipMaps(Stream stream, int width, int height, uint count) + { + if (this.KtxHeader.GlTypeSize is 0 or 1) { switch (this.KtxHeader.GlFormat) { case GlPixelFormat.Red: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.Rg: case GlPixelFormat.RgInteger: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.Rgb: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.Rgba: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.Bgr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.Bgra: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.LuminanceAlpha: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.Luminance: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.Alpha: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlPixelFormat.Compressed: switch (this.KtxHeader.GlInternalFormat) { case GlInternalPixelFormat.RgbaDxt1: case GlInternalPixelFormat.RgbDxt1: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.RgbaDxt3: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.RgbaDxt5: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.RedRgtc1: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.SignedRedRgtc1: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.RedGreenRgtc2: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.SignedRedGreenRgtc2: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.Etc1Rgb8Oes: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgb8Etc2: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc4x4Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc5x4Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc5x5Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc6x5Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc6x6Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc8x5Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc8x6Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc8x8Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc10x5Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc10x6Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc10x8Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc10x10Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc12x10Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); case GlInternalPixelFormat.CompressedRgbaAstc12x12Khr: - return this.AllocateCubeMap(stream, width, height); + return this.AllocateMipMaps(stream, width, height, count); } break; } + } - if (this.KtxHeader.GlTypeSize is 2 or 4) + if (this.KtxHeader.GlTypeSize is 2 or 4) + { + // TODO: endianess is not respected here. Use stream reader which respects endianess. + switch (this.KtxHeader.GlInternalFormat) { - // TODO: endianess is not respected here. Use stream reader which respects endianess. + case GlInternalPixelFormat.Rgb5A1: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.Rgb10A2: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.Rgb16: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.Rgba16: + return this.AllocateMipMaps(stream, width, height, count); + case GlInternalPixelFormat.Rgba32UnsignedInt: + return this.AllocateMipMaps(stream, width, height, count); + } + } + + throw new NotSupportedException("The pixel format is not supported"); + } + + /// + /// Decodes the a KTX cube map texture. + /// + /// The stream to read the texture data from. + /// The width of a texture face. + /// The height of a texture face. + /// A decoded cubemap texture. + /// The pixel format is not supported + public CubemapTexture DecodeCubeMap(Stream stream, int width, int height) + { + switch (this.KtxHeader.GlFormat) + { + case GlPixelFormat.Red: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.Rg: + case GlPixelFormat.RgInteger: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.Rgb: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.Rgba: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.Bgr: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.Bgra: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.LuminanceAlpha: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.Luminance: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.Alpha: + return this.AllocateCubeMap(stream, width, height); + case GlPixelFormat.Compressed: switch (this.KtxHeader.GlInternalFormat) { - case GlInternalPixelFormat.Rgb5A1: - return this.AllocateCubeMap(stream, width, height); - case GlInternalPixelFormat.Rgb10A2: - return this.AllocateCubeMap(stream, width, height); - case GlInternalPixelFormat.Rgb16: - return this.AllocateCubeMap(stream, width, height); - case GlInternalPixelFormat.Rgba16: - return this.AllocateCubeMap(stream, width, height); - case GlInternalPixelFormat.Rgba32UnsignedInt: - return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.RgbaDxt1: + case GlInternalPixelFormat.RgbDxt1: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.RgbaDxt3: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.RgbaDxt5: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.RedRgtc1: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.SignedRedRgtc1: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.RedGreenRgtc2: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.SignedRedGreenRgtc2: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.Etc1Rgb8Oes: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgb8Etc2: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc4x4Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc5x4Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc5x5Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc6x5Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc6x6Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc8x5Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc8x6Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc8x8Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc10x5Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc10x6Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc10x8Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc10x10Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc12x10Khr: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.CompressedRgbaAstc12x12Khr: + return this.AllocateCubeMap(stream, width, height); } - } - throw new NotSupportedException("The pixel format is not supported"); + break; } - /// - /// Allocates and decodes the a KTX cube map texture. - /// - /// The stream to read the texture data from. - /// The width of a texture face. - /// The height of a texture face. - /// A decoded cubemap texture. - /// The pixel format is not supported - private CubemapTexture AllocateCubeMap(Stream stream, int width, int height) - where TBlock : struct, IBlock + if (this.KtxHeader.GlTypeSize is 2 or 4) { - var numberOfMipMaps = this.KtxHeader.NumberOfMipmapLevels != 0 ? this.KtxHeader.NumberOfMipmapLevels : 1; - - var cubeMapTexture = new CubemapTexture(); - var blockFormat = default(TBlock); - for (int i = 0; i < numberOfMipMaps; i++) + // TODO: endianess is not respected here. Use stream reader which respects endianess. + switch (this.KtxHeader.GlInternalFormat) { - var dataForEachFace = this.ReadTextureDataSize(stream); - cubeMapTexture.PositiveX.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.NegativeX.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.PositiveY.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.NegativeY.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.PositiveZ.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.NegativeZ.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - - width >>= 1; - height >>= 1; + case GlInternalPixelFormat.Rgb5A1: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.Rgb10A2: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.Rgb16: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.Rgba16: + return this.AllocateCubeMap(stream, width, height); + case GlInternalPixelFormat.Rgba32UnsignedInt: + return this.AllocateCubeMap(stream, width, height); } - - return cubeMapTexture; } - private static MipMap ReadFaceTexture(Stream stream, int width, int height, TBlock blockFormat, uint dataForEachFace) - where TBlock : struct, IBlock - { - byte[] faceData = new byte[dataForEachFace]; - ReadTextureData(stream, faceData); - return new MipMap(blockFormat, faceData, width, height); - } + throw new NotSupportedException("The pixel format is not supported"); + } + + /// + /// Allocates and decodes the a KTX cube map texture. + /// + /// The stream to read the texture data from. + /// The width of a texture face. + /// The height of a texture face. + /// A decoded cubemap texture. + /// The pixel format is not supported + private CubemapTexture AllocateCubeMap(Stream stream, int width, int height) + where TBlock : struct, IBlock + { + uint numberOfMipMaps = this.KtxHeader.NumberOfMipmapLevels != 0 ? this.KtxHeader.NumberOfMipmapLevels : 1; - /// - /// Allocates and decodes all mipmap levels of a ktx texture. - /// - /// The stream to read the texture data from. - /// The width of the texture at level 0. - /// The height of the texture at level 0. - /// The mipmap count. - /// The decoded mipmaps. - private MipMap[] AllocateMipMaps(Stream stream, int width, int height, uint count) - where TBlock : struct, IBlock + CubemapTexture cubeMapTexture = new CubemapTexture(); + TBlock blockFormat = default(TBlock); + for (int i = 0; i < numberOfMipMaps; i++) { - MipMap[] mipMaps = this.ReadMipMaps(stream, width, height, count); + uint dataForEachFace = this.ReadTextureDataSize(stream); + cubeMapTexture.PositiveX.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.NegativeX.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.PositiveY.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.NegativeY.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.PositiveZ.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.NegativeZ.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - return mipMaps; + width >>= 1; + height >>= 1; } - private MipMap[] ReadMipMaps(Stream stream, int width, int height, uint count) - where TBlock : struct, IBlock - { - // If numberOfMipmapLevels equals 0, it indicates that a full mipmap pyramid should be generated from level 0 at load time. - // TODO: generate mipmap pyramid. For now only the first image is loaded. - if (count == 0) - { - count = 1; - } + return cubeMapTexture; + } - var blockFormat = default(TBlock); - var mipMaps = new MipMap[count]; - for (int i = 0; i < count; i++) - { - var pixelDataSize = this.ReadTextureDataSize(stream); - byte[] mipMapData = new byte[pixelDataSize]; - ReadTextureData(stream, mipMapData); + private static MipMap ReadFaceTexture(Stream stream, int width, int height, TBlock blockFormat, uint dataForEachFace) + where TBlock : struct, IBlock + { + byte[] faceData = new byte[dataForEachFace]; + ReadTextureData(stream, faceData); + return new MipMap(blockFormat, faceData, width, height); + } - mipMaps[i] = new MipMap(blockFormat, mipMapData, width, height); + /// + /// Allocates and decodes all mipmap levels of a ktx texture. + /// + /// The stream to read the texture data from. + /// The width of the texture at level 0. + /// The height of the texture at level 0. + /// The mipmap count. + /// The decoded mipmaps. + private MipMap[] AllocateMipMaps(Stream stream, int width, int height, uint count) + where TBlock : struct, IBlock + { + MipMap[] mipMaps = this.ReadMipMaps(stream, width, height, count); - width >>= 1; - height >>= 1; - } + return mipMaps; + } - return mipMaps; + private MipMap[] ReadMipMaps(Stream stream, int width, int height, uint count) + where TBlock : struct, IBlock + { + // If numberOfMipmapLevels equals 0, it indicates that a full mipmap pyramid should be generated from level 0 at load time. + // TODO: generate mipmap pyramid. For now only the first image is loaded. + if (count == 0) + { + count = 1; } - private static void ReadTextureData(Stream stream, byte[] mipMapData) + TBlock blockFormat = default(TBlock); + MipMap[] mipMaps = new MipMap[count]; + for (int i = 0; i < count; i++) { - int bytesRead = stream.Read(mipMapData, 0, mipMapData.Length); - if (bytesRead != mipMapData.Length) - { - throw new TextureFormatException("could not read enough texture data from the stream"); - } + uint pixelDataSize = this.ReadTextureDataSize(stream); + byte[] mipMapData = new byte[pixelDataSize]; + ReadTextureData(stream, mipMapData); + + mipMaps[i] = new MipMap(blockFormat, mipMapData, width, height); + + width >>= 1; + height >>= 1; } - private uint ReadTextureDataSize(Stream stream) - { - int bytesRead = stream.Read(this.buffer, 0, 4); - if (bytesRead != 4) - { - throw new TextureFormatException("could not read texture data length from the stream"); - } + return mipMaps; + } - var pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + private static void ReadTextureData(Stream stream, byte[] mipMapData) + { + int bytesRead = stream.Read(mipMapData, 0, mipMapData.Length); + if (bytesRead != mipMapData.Length) + { + throw new TextureFormatException("could not read enough texture data from the stream"); + } + } - return pixelDataSize; + private uint ReadTextureDataSize(Stream stream) + { + int bytesRead = stream.Read(this.buffer, 0, 4); + if (bytesRead != 4) + { + throw new TextureFormatException("could not read texture data length from the stream"); } + + uint pixelDataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + + return pixelDataSize; } } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Enums/VkFormat.cs b/src/ImageSharp.Textures/Formats/Ktx2/Enums/VkFormat.cs index a3e89738..044b7287 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Enums/VkFormat.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Enums/VkFormat.cs @@ -2,1089 +2,1088 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2.Enums +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2.Enums; + +/// +/// Vulkan pixel formats. +/// +internal enum VkFormat { /// - /// Vulkan pixel formats. - /// - internal enum VkFormat - { - /// - /// The format is not specified. - /// - VK_FORMAT_UNDEFINED = 0, - - /// - /// Specifies a two-component, 8-bit packed unsigned normalized format that has a 4-bit R component in bits 4..7, and a 4-bit G component in bits 0..3. - /// - VK_FORMAT_R4G4_UNORM_PACK8 = 1, - - /// - /// specifies a four-component, 16-bit packed unsigned normalized format that has a 4-bit R component in bits 12..15, a 4-bit G component in bits 8..11, - /// a 4-bit B component in bits 4..7, and a 4-bit A component in bits 0..3. - /// - VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2, - - /// - /// Specifies a four-component, 16-bit packed unsigned normalized format that has a 4-bit B component in bits 12..15, a 4-bit G component in bits 8..11, - /// a 4-bit R component in bits 4..7, and a 4-bit A component in bits 0..3. - /// - VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3, - - /// - /// specifies a three-component, 16-bit packed unsigned normalized format that has a 5-bit R component in bits 11..15, a 6-bit G component in bits 5..10, - /// and a 5-bit B component in bits 0..4. - /// - VK_FORMAT_R5G6B5_UNORM_PACK16 = 4, - - /// - /// specifies a three-component, 16-bit packed unsigned normalized format that has a 5-bit B component in bits 11..15, a 6-bit G component in bits 5..10, - /// and a 5-bit R component in bits 0..4. - /// - VK_FORMAT_B5G6R5_UNORM_PACK16 = 5, - - /// - /// Specifies a four-component, 16-bit packed unsigned normalized format that has a 5-bit R component in bits 11..15, a 5-bit G component in bits 6..10, - /// a 5-bit B component in bits 1..5, and a 1-bit A component in bit 0. - /// - VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6, - - /// - /// specifies a four-component, 16-bit packed unsigned normalized format that has a 5-bit B component in bits 11..15, a 5-bit G component in bits 6..10, - /// a 5-bit R component in bits 1..5, and a 1-bit A component in bit 0. - /// - VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7, - - /// - /// Specifies a four-component, 16-bit packed unsigned normalized format that has a 1-bit A component in bit 15, a 5-bit R component in bits 10..14, - /// a 5-bit G component in bits 5..9, and a 5-bit B component in bits 0..4. - /// - VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8, - - /// - /// Specifies a one-component, 8-bit unsigned normalized format that has a single 8-bit R component. - /// - VK_FORMAT_R8_UNORM = 9, - - /// - /// Specifies a one-component, 8-bit signed normalized format that has a single 8-bit R component. - /// - VK_FORMAT_R8_SNORM = 10, - - /// - /// Specifies a one-component, 8-bit unsigned scaled integer format that has a single 8-bit R component. - /// - VK_FORMAT_R8_USCALED = 11, - - /// - /// Specifies a one-component, 8-bit signed scaled integer format that has a single 8-bit R component. - /// - VK_FORMAT_R8_SSCALED = 12, - - /// - /// Specifies a one-component, 8-bit unsigned integer format that has a single 8-bit R component. - /// - VK_FORMAT_R8_UINT = 13, - - /// - /// Specifies a one-component, 8-bit signed integer format that has a single 8-bit R component. - /// - VK_FORMAT_R8_SINT = 14, - - /// - /// Specifies a one-component, 8-bit unsigned normalized format that has a single 8-bit R component stored with sRGB nonlinear encoding. - /// - VK_FORMAT_R8_SRGB = 15, - - /// - /// Specifies a two-component, 16-bit unsigned normalized format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. - /// - VK_FORMAT_R8G8_UNORM = 16, - - /// - /// Specifies a two-component, 16-bit signed normalized format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. - /// - VK_FORMAT_R8G8_SNORM = 17, - - /// - /// Specifies a two-component, 16-bit unsigned scaled integer format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. - /// - VK_FORMAT_R8G8_USCALED = 18, - - /// - /// Specifies a two-component, 16-bit signed scaled integer format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. - /// - VK_FORMAT_R8G8_SSCALED = 19, - - /// - /// Specifies a two-component, 16-bit unsigned integer format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. - /// - VK_FORMAT_R8G8_UINT = 20, - - /// - /// Specifies a two-component, 16-bit signed integer format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. - /// - VK_FORMAT_R8G8_SINT = 21, - - /// - /// Specifies a two-component, 16-bit unsigned normalized format that has an 8-bit R component stored with sRGB nonlinear encoding in byte 0, - /// and an 8-bit G component stored with sRGB nonlinear encoding in byte 1. - /// - VK_FORMAT_R8G8_SRGB = 22, - - /// - /// Specifies a three-component, 24-bit unsigned normalized format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. - /// - VK_FORMAT_R8G8B8_UNORM = 23, - - /// - /// Specifies a three-component, 24-bit signed normalized format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. - /// - VK_FORMAT_R8G8B8_SNORM = 24, - - /// - /// Specifies a three-component, 24-bit unsigned scaled format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. - /// - VK_FORMAT_R8G8B8_USCALED = 25, - - /// - /// Specifies a three-component, 24-bit signed scaled format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. - /// - VK_FORMAT_R8G8B8_SSCALED = 26, - - /// - /// Specifies a three-component, 24-bit unsigned integer format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. - /// - VK_FORMAT_R8G8B8_UINT = 27, - - /// - /// Specifies a three-component, 24-bit signed integer format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. - /// - VK_FORMAT_R8G8B8_SINT = 28, - - /// - /// Specifies a three-component, 24-bit unsigned normalized format that has an 8-bit R component stored with sRGB nonlinear encoding in byte 0, - /// an 8-bit G component stored with sRGB nonlinear encoding in byte 1, and an 8-bit B component stored with sRGB nonlinear encoding in byte 2. - /// - VK_FORMAT_R8G8B8_SRGB = 29, - - /// - /// Specifies a three-component, 24-bit unsigned normalized format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. - /// - VK_FORMAT_B8G8R8_UNORM = 30, - - /// - /// Specifies a three-component, 24-bit signed normalized format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. - /// - VK_FORMAT_B8G8R8_SNORM = 31, - - /// - /// Specifies a three-component, 24-bit unsigned scaled format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. - /// - VK_FORMAT_B8G8R8_USCALED = 32, - - /// - /// Specifies a three-component, 24-bit signed scaled format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. - /// - VK_FORMAT_B8G8R8_SSCALED = 33, - - /// - /// Specifies a three-component, 24-bit unsigned integer format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. - /// - VK_FORMAT_B8G8R8_UINT = 34, - - /// - /// Specifies a three-component, 24-bit signed integer format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. - /// - VK_FORMAT_B8G8R8_SINT = 35, - - /// - /// Specifies a three-component, 24-bit unsigned normalized format that has an 8-bit B component stored with sRGB nonlinear encoding in byte 0, - /// an 8-bit G component stored with sRGB nonlinear encoding in byte 1, and an 8-bit R component stored with sRGB nonlinear encoding in byte 2. - /// - VK_FORMAT_B8G8R8_SRGB = 36, - - /// - /// Specifies a four-component, 32-bit unsigned normalized format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, - /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_R8G8B8A8_UNORM = 37, - - /// - /// Specifies a four-component, 32-bit signed normalized format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, - /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_R8G8B8A8_SNORM = 38, - - /// - /// Specifies a four-component, 32-bit unsigned scaled format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, - /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_R8G8B8A8_USCALED = 39, - - /// - /// Specifies a four-component, 32-bit signed scaled format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, - /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_R8G8B8A8_SSCALED = 40, - - /// - /// Specifies a four-component, 32-bit unsigned integer format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, - /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_R8G8B8A8_UINT = 41, - - /// - /// Specifies a four-component, 32-bit signed integer format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, - /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_R8G8B8A8_SINT = 42, - - /// - /// Specifies a four-component, 32-bit unsigned normalized format that has an 8-bit R component stored with sRGB nonlinear encoding in byte 0, - /// an 8-bit G component stored with sRGB nonlinear encoding in byte 1, an 8-bit B component stored with sRGB nonlinear encoding in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_R8G8B8A8_SRGB = 43, - - /// - /// Specifies a four-component, 32-bit unsigned normalized format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_B8G8R8A8_UNORM = 44, - - /// - /// Specifies a four-component, 32-bit signed normalized format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_B8G8R8A8_SNORM = 45, - - /// - /// Specifies a four-component, 32-bit unsigned scaled format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_B8G8R8A8_USCALED = 46, - - /// - /// Specifies a four-component, 32-bit signed scaled format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_B8G8R8A8_SSCALED = 47, - - /// - /// Specifies a four-component, 32-bit unsigned integer format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_B8G8R8A8_UINT = 48, - - /// - /// Specifies a four-component, 32-bit signed integer format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_B8G8R8A8_SINT = 49, - - /// - /// Specifies a four-component, 32-bit unsigned normalized format that has an 8-bit B component stored with sRGB nonlinear encoding in byte 0, - /// an 8-bit G component stored with sRGB nonlinear encoding in byte 1, an 8-bit R component stored with sRGB nonlinear encoding in byte 2, and an 8-bit A component in byte 3. - /// - VK_FORMAT_B8G8R8A8_SRGB = 50, - - /// - /// Specifies a four-component, 32-bit packed unsigned normalized format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, - /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. - /// - VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51, - - /// - /// specifies a four-component, 32-bit packed signed normalized format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, - /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. - /// - VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52, - - /// - /// Specifies a four-component, 32-bit packed unsigned scaled integer format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, - /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. - /// - VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53, - - /// - /// Specifies a four-component, 32-bit packed signed scaled integer format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, - /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. - /// - VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54, - - /// - /// Specifies a four-component, 32-bit packed unsigned integer format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, - /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. - /// - VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55, - - /// - /// Specifies a four-component, 32-bit packed signed integer format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, - /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. - /// - VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56, - - /// - /// Specifies a four-component, 32-bit packed unsigned normalized format that has an 8-bit A component in bits 24..31, - /// an 8-bit B component stored with sRGB nonlinear encoding in bits 16..23, an 8-bit G component stored with sRGB nonlinear encoding in bits 8..15, and an 8-bit R component stored with sRGB nonlinear encoding in bits 0..7. - /// - VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57, - - /// - /// Specifies a four-component, 32-bit packed unsigned normalized format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. - /// - VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58, - - /// - /// Specifies a four-component, 32-bit packed signed normalized format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. - /// - VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59, - - /// - /// Specifies a four-component, 32-bit packed unsigned scaled integer format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. - /// - VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60, - - /// - /// Specifies a four-component, 32-bit packed signed scaled integer format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. - /// - VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61, - - /// - /// Specifies a four-component, 32-bit packed unsigned integer format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. - /// - VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62, - - /// - /// Specifies a four-component, 32-bit packed signed integer format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. - /// - VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63, - - /// - /// Specifies a four-component, 32-bit packed unsigned normalized format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. - /// - VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64, - - /// - /// Specifies a four-component, 32-bit packed signed normalized format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. - /// - VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65, - - /// - /// Specifies a four-component, 32-bit packed unsigned scaled integer format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. - /// - VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66, - - /// - /// Specifies a four-component, 32-bit packed signed scaled integer format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. - /// - VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67, - - /// - /// Specifies a four-component, 32-bit packed unsigned integer format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. - /// - VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68, - - /// - /// Specifies a four-component, 32-bit packed signed integer format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, - /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. - /// - VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69, - - /// - /// Specifies a one-component, 16-bit unsigned normalized format that has a single 16-bit R component. - /// - VK_FORMAT_R16_UNORM = 70, - - /// - /// Specifies a one-component, 16-bit signed normalized format that has a single 16-bit R component. - /// - VK_FORMAT_R16_SNORM = 71, - - /// - /// Specifies a one-component, 16-bit unsigned scaled integer format that has a single 16-bit R component. - /// - VK_FORMAT_R16_USCALED = 72, - - /// - /// Specifies a one-component, 16-bit signed scaled integer format that has a single 16-bit R component. - /// - VK_FORMAT_R16_SSCALED = 73, - - /// - /// Specifies a one-component, 16-bit unsigned integer format that has a single 16-bit R component. - /// - VK_FORMAT_R16_UINT = 74, - - /// - /// Specifies a one-component, 16-bit signed integer format that has a single 16-bit R component. - /// - VK_FORMAT_R16_SINT = 75, - - /// - /// Specifies a one-component, 16-bit signed floating-point format that has a single 16-bit R component. - /// - VK_FORMAT_R16_SFLOAT = 76, - - /// - /// Specifies a two-component, 32-bit unsigned normalized format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. - /// - VK_FORMAT_R16G16_UNORM = 77, - - /// - /// Specifies a two-component, 32-bit signed normalized format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. - /// - VK_FORMAT_R16G16_SNORM = 78, - - /// - /// Specifies a two-component, 32-bit unsigned scaled integer format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. - /// - VK_FORMAT_R16G16_USCALED = 79, - - /// - /// Specifies a two-component, 32-bit signed scaled integer format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. - /// - VK_FORMAT_R16G16_SSCALED = 80, - - /// - /// Specifies a two-component, 32-bit unsigned integer format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. - /// - VK_FORMAT_R16G16_UINT = 81, - - /// - /// Specifies a two-component, 32-bit signed integer format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. - /// - VK_FORMAT_R16G16_SINT = 82, - - /// - /// Specifies a two-component, 32-bit signed floating-point format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. - /// - VK_FORMAT_R16G16_SFLOAT = 83, - - /// - /// Specifies a three-component, 48-bit unsigned normalized format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// and a 16-bit B component in bytes 4..5. - /// - VK_FORMAT_R16G16B16_UNORM = 84, - - /// - /// Specifies a three-component, 48-bit signed normalized format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// and a 16-bit B component in bytes 4..5. - /// - VK_FORMAT_R16G16B16_SNORM = 85, - - /// - /// Specifies a three-component, 48-bit unsigned scaled integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// and a 16-bit B component in bytes 4..5. - /// - VK_FORMAT_R16G16B16_USCALED = 86, - - /// - /// Specifies a three-component, 48-bit signed scaled integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// and a 16-bit B component in bytes 4..5. - /// - VK_FORMAT_R16G16B16_SSCALED = 87, - - /// - /// Specifies a three-component, 48-bit unsigned integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// and a 16-bit B component in bytes 4..5. - /// - VK_FORMAT_R16G16B16_UINT = 88, - - /// - /// Specifies a three-component, 48-bit signed integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// and a 16-bit B component in bytes 4..5. - /// - VK_FORMAT_R16G16B16_SINT = 89, - - /// - /// Specifies a three-component, 48-bit signed floating-point format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// and a 16-bit B component in bytes 4..5. - /// - VK_FORMAT_R16G16B16_SFLOAT = 90, - - /// - /// Specifies a four-component, 64-bit unsigned normalized format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. - /// - VK_FORMAT_R16G16B16A16_UNORM = 91, - - /// - /// Specifies a four-component, 64-bit signed normalized format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. - /// - VK_FORMAT_R16G16B16A16_SNORM = 92, - - /// - /// Specifies a four-component, 64-bit unsigned scaled integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. - /// - VK_FORMAT_R16G16B16A16_USCALED = 93, - - /// - /// Specifies a four-component, 64-bit signed scaled integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. - /// - VK_FORMAT_R16G16B16A16_SSCALED = 94, - - /// - /// Specifies a four-component, 64-bit unsigned integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. - /// - VK_FORMAT_R16G16B16A16_UINT = 95, - - /// - /// Specifies a four-component, 64-bit signed integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. - /// - VK_FORMAT_R16G16B16A16_SINT = 96, - - /// - /// Specifies a four-component, 64-bit signed floating-point format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, - /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. - /// - VK_FORMAT_R16G16B16A16_SFLOAT = 97, - - /// - /// Specifies a one-component, 32-bit unsigned integer format that has a single 32-bit R component. - /// - VK_FORMAT_R32_UINT = 98, - - /// - /// Specifies a one-component, 32-bit signed integer format that has a single 32-bit R component. - /// - VK_FORMAT_R32_SINT = 99, - - /// - /// Specifies a one-component, 32-bit signed floating-point format that has a single 32-bit R component. - /// - VK_FORMAT_R32_SFLOAT = 100, - - /// - /// Specifies a two-component, 64-bit unsigned integer format that has a 32-bit R component in bytes 0..3, and a 32-bit G component in bytes 4..7. - /// - VK_FORMAT_R32G32_UINT = 101, - - /// - /// Specifies a two-component, 64-bit signed integer format that has a 32-bit R component in bytes 0..3, and a 32-bit G component in bytes 4..7. - /// - VK_FORMAT_R32G32_SINT = 102, - - /// - /// Specifies a two-component, 64-bit signed floating-point format that has a 32-bit R component in bytes 0..3, and a 32-bit G component in bytes 4..7. - /// - VK_FORMAT_R32G32_SFLOAT = 103, - - /// - /// Specifies a three-component, 96-bit unsigned integer format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, and a 32-bit B component in bytes 8..11. - /// - VK_FORMAT_R32G32B32_UINT = 104, - - /// - /// Specifies a three-component, 96-bit signed integer format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, and a 32-bit B component in bytes 8..11. - /// - VK_FORMAT_R32G32B32_SINT = 105, - - /// - /// Specifies a three-component, 96-bit signed floating-point format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, and a 32-bit B component in bytes 8..11. - /// - VK_FORMAT_R32G32B32_SFLOAT = 106, - - /// - /// Specifies a four-component, 128-bit unsigned integer format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, a 32-bit B component in bytes 8..11, and a 32-bit A component in bytes 12..15. - /// - VK_FORMAT_R32G32B32A32_UINT = 107, - - /// - /// Specifies a four-component, 128-bit signed integer format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, a 32-bit B component in bytes 8..11, and a 32-bit A component in bytes 12..15. - /// - VK_FORMAT_R32G32B32A32_SINT = 108, - - /// - /// Specifies a four-component, 128-bit signed floating-point format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, a 32-bit B component in bytes 8..11, and a 32-bit A component in bytes 12..15. - /// - VK_FORMAT_R32G32B32A32_SFLOAT = 109, - - /// - /// Specifies a one-component, 64-bit unsigned integer format that has a single 64-bit R component. - /// - VK_FORMAT_R64_UINT = 110, - - /// - /// Specifies a one-component, 64-bit signed integer format that has a single 64-bit R component. - /// - VK_FORMAT_R64_SINT = 111, - - /// - /// Specifies a one-component, 64-bit signed floating-point format that has a single 64-bit R component. - /// - VK_FORMAT_R64_SFLOAT = 112, - - /// - /// Specifies a two-component, 128-bit unsigned integer format that has a 64-bit R component in bytes 0..7, and a 64-bit G component in bytes 8..15. - /// - VK_FORMAT_R64G64_UINT = 113, - - /// - /// Specifies a two-component, 128-bit signed integer format that has a 64-bit R component in bytes 0..7, and a 64-bit G component in bytes 8..15. - /// - VK_FORMAT_R64G64_SINT = 114, - - /// - /// Specifies a two-component, 128-bit signed floating-point format that has a 64-bit R component in bytes 0..7, and a 64-bit G component in bytes 8..15. - /// - VK_FORMAT_R64G64_SFLOAT = 115, - - /// - /// Specifies a three-component, 192-bit unsigned integer format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, and a 64-bit B component in bytes 16..23. - /// - VK_FORMAT_R64G64B64_UINT = 116, - - /// - /// Specifies a three-component, 192-bit signed integer format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, and a 64-bit B component in bytes 16..23. - /// - VK_FORMAT_R64G64B64_SINT = 117, - - /// - /// specifies a three-component, 192-bit signed floating-point format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, and a 64-bit B component in bytes 16..23. - /// - VK_FORMAT_R64G64B64_SFLOAT = 118, - - /// - /// Specifies a four-component, 256-bit unsigned integer format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, a 64-bit B component in bytes 16..23, and a 64-bit A component in bytes 24..31. - /// - VK_FORMAT_R64G64B64A64_UINT = 119, - - /// - /// Specifies a four-component, 256-bit signed integer format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, a 64-bit B component in bytes 16..23, and a 64-bit A component in bytes 24..31. - /// - VK_FORMAT_R64G64B64A64_SINT = 120, - - /// - /// Specifies a four-component, 256-bit signed floating-point format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, a 64-bit B component in bytes 16..23, and a 64-bit A component in bytes 24..31. - /// - VK_FORMAT_R64G64B64A64_SFLOAT = 121, - - /// - /// Specifies a three-component, 32-bit packed unsigned floating-point format that has a 10-bit B component in bits 22..31, an 11-bit G component in bits 11..21, an 11-bit R component in bits 0..10. - /// - VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122, - - /// - /// Specifies a three-component, 32-bit packed unsigned floating-point format that has a 5-bit shared exponent in bits 27..31, a 9-bit B component mantissa in bits 18..26, a 9-bit G component mantissa in bits 9..17, and a 9-bit R component mantissa in bits 0..8. - /// - VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123, - - /// - /// Specifies a one-component, 16-bit unsigned normalized format that has a single 16-bit depth component. - /// - VK_FORMAT_D16_UNORM = 124, - - /// - /// Specifies a two-component, 32-bit format that has 24 unsigned normalized bits in the depth component and, optionally:, 8 bits that are unused. - /// - VK_FORMAT_X8_D24_UNORM_PACK32 = 125, - - /// - /// Specifies a one-component, 32-bit signed floating-point format that has 32-bits in the depth component. - /// - VK_FORMAT_D32_SFLOAT = 126, - - /// - /// Specifies a one-component, 8-bit unsigned integer format that has 8-bits in the stencil component. - /// - VK_FORMAT_S8_UINT = 127, - - /// - /// Specifies a two-component, 24-bit format that has 16 unsigned normalized bits in the depth component and 8 unsigned integer bits in the stencil component. - /// - VK_FORMAT_D16_UNORM_S8_UINT = 128, - - /// - /// Specifies a two-component, 32-bit packed format that has 8 unsigned integer bits in the stencil component, and 24 unsigned normalized bits in the depth component. - /// - VK_FORMAT_D24_UNORM_S8_UINT = 129, - - /// - /// Specifies a two-component format that has 32 signed float bits in the depth component and 8 unsigned integer bits in the stencil component. There are optionally: 24-bits that are unused. - /// - VK_FORMAT_D32_SFLOAT_S8_UINT = 130, - - /// - /// Specifies a three-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data. This format has no alpha and is considered opaque. - /// - VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131, - - /// - /// Specifies a three-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data with sRGB nonlinear encoding. This format has no alpha and is considered opaque. - /// - VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132, - - /// - /// Specifies a four-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data, and provides 1 bit of alpha. - /// - VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133, - - /// - /// Specifies a four-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data with sRGB nonlinear encoding, and provides 1 bit of alpha. - /// - VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134, - - /// - /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values. - /// - VK_FORMAT_BC2_UNORM_BLOCK = 135, - - /// - /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values with sRGB nonlinear encoding. - /// - VK_FORMAT_BC2_SRGB_BLOCK = 136, - - /// - /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values. - /// - VK_FORMAT_BC3_UNORM_BLOCK = 137, - - /// - /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values with sRGB nonlinear encoding. - /// - VK_FORMAT_BC3_SRGB_BLOCK = 138, - - /// - /// Specifies a one-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized red texel data. - /// - VK_FORMAT_BC4_UNORM_BLOCK = 139, - - /// - /// Specifies a one-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of signed normalized red texel data. - /// - VK_FORMAT_BC4_SNORM_BLOCK = 140, - - /// - /// Specifies a two-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RG texel data with the first 64 bits encoding red values followed by 64 bits encoding green values. - /// - VK_FORMAT_BC5_UNORM_BLOCK = 141, - - /// - /// Specifies a two-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of signed normalized RG texel data with the first 64 bits encoding red values followed by 64 bits encoding green values. - /// - VK_FORMAT_BC5_SNORM_BLOCK = 142, - - /// - /// Specifies a three-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned floating-point RGB texel data. - /// - VK_FORMAT_BC6H_UFLOAT_BLOCK = 143, - - /// - /// Specifies a three-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of signed floating-point RGB texel data. - /// - VK_FORMAT_BC6H_SFLOAT_BLOCK = 144, - - /// - /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data - /// - VK_FORMAT_BC7_UNORM_BLOCK = 145, - - /// - /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_BC7_SRGB_BLOCK = 146, - - /// - /// Specifies a three-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data. This format has no alpha and is considered opaque. - /// - VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147, - - /// - /// Specifies a three-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data with sRGB nonlinear encoding. This format has no alpha and is considered opaque. - /// - VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148, - - /// - /// Specifies a four-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data, and provides 1 bit of alpha. - /// - VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149, - - /// - /// Specifies a four-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data with sRGB nonlinear encoding, and provides 1 bit of alpha. - /// - VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150, - - /// - /// Specifies a four-component, ETC2 compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values. - /// - VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151, - - /// - /// Specifies a four-component, ETC2 compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values with sRGB nonlinear encoding applied. - /// - VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152, - - /// - /// Specifies a one-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized red texel data. - /// - VK_FORMAT_EAC_R11_UNORM_BLOCK = 153, - - /// - /// Specifies a one-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of signed normalized red texel data. - /// - VK_FORMAT_EAC_R11_SNORM_BLOCK = 154, - - /// - /// Specifies a two-component, ETC2 compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RG texel data with the first 64 bits encoding red values followed by 64 bits encoding green values. - /// - VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155, - - /// - /// Specifies a two-component, ETC2 compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of signed normalized RG texel data with the first 64 bits encoding red values followed by 64 bits encoding green values. - /// - VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 5×4 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 5×4 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 5×5 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 5×5 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 6×5 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 6×5 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 6×6 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 6×6 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×5 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×5 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×6 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×6 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×8 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×8 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×5 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×5 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×6 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×6 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×8 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177, - - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×8 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178, + /// The format is not specified. + /// + VK_FORMAT_UNDEFINED = 0, + + /// + /// Specifies a two-component, 8-bit packed unsigned normalized format that has a 4-bit R component in bits 4..7, and a 4-bit G component in bits 0..3. + /// + VK_FORMAT_R4G4_UNORM_PACK8 = 1, + + /// + /// specifies a four-component, 16-bit packed unsigned normalized format that has a 4-bit R component in bits 12..15, a 4-bit G component in bits 8..11, + /// a 4-bit B component in bits 4..7, and a 4-bit A component in bits 0..3. + /// + VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2, + + /// + /// Specifies a four-component, 16-bit packed unsigned normalized format that has a 4-bit B component in bits 12..15, a 4-bit G component in bits 8..11, + /// a 4-bit R component in bits 4..7, and a 4-bit A component in bits 0..3. + /// + VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3, + + /// + /// specifies a three-component, 16-bit packed unsigned normalized format that has a 5-bit R component in bits 11..15, a 6-bit G component in bits 5..10, + /// and a 5-bit B component in bits 0..4. + /// + VK_FORMAT_R5G6B5_UNORM_PACK16 = 4, + + /// + /// specifies a three-component, 16-bit packed unsigned normalized format that has a 5-bit B component in bits 11..15, a 6-bit G component in bits 5..10, + /// and a 5-bit R component in bits 0..4. + /// + VK_FORMAT_B5G6R5_UNORM_PACK16 = 5, + + /// + /// Specifies a four-component, 16-bit packed unsigned normalized format that has a 5-bit R component in bits 11..15, a 5-bit G component in bits 6..10, + /// a 5-bit B component in bits 1..5, and a 1-bit A component in bit 0. + /// + VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6, + + /// + /// specifies a four-component, 16-bit packed unsigned normalized format that has a 5-bit B component in bits 11..15, a 5-bit G component in bits 6..10, + /// a 5-bit R component in bits 1..5, and a 1-bit A component in bit 0. + /// + VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7, + + /// + /// Specifies a four-component, 16-bit packed unsigned normalized format that has a 1-bit A component in bit 15, a 5-bit R component in bits 10..14, + /// a 5-bit G component in bits 5..9, and a 5-bit B component in bits 0..4. + /// + VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8, + + /// + /// Specifies a one-component, 8-bit unsigned normalized format that has a single 8-bit R component. + /// + VK_FORMAT_R8_UNORM = 9, + + /// + /// Specifies a one-component, 8-bit signed normalized format that has a single 8-bit R component. + /// + VK_FORMAT_R8_SNORM = 10, + + /// + /// Specifies a one-component, 8-bit unsigned scaled integer format that has a single 8-bit R component. + /// + VK_FORMAT_R8_USCALED = 11, + + /// + /// Specifies a one-component, 8-bit signed scaled integer format that has a single 8-bit R component. + /// + VK_FORMAT_R8_SSCALED = 12, + + /// + /// Specifies a one-component, 8-bit unsigned integer format that has a single 8-bit R component. + /// + VK_FORMAT_R8_UINT = 13, + + /// + /// Specifies a one-component, 8-bit signed integer format that has a single 8-bit R component. + /// + VK_FORMAT_R8_SINT = 14, + + /// + /// Specifies a one-component, 8-bit unsigned normalized format that has a single 8-bit R component stored with sRGB nonlinear encoding. + /// + VK_FORMAT_R8_SRGB = 15, + + /// + /// Specifies a two-component, 16-bit unsigned normalized format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. + /// + VK_FORMAT_R8G8_UNORM = 16, + + /// + /// Specifies a two-component, 16-bit signed normalized format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. + /// + VK_FORMAT_R8G8_SNORM = 17, + + /// + /// Specifies a two-component, 16-bit unsigned scaled integer format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. + /// + VK_FORMAT_R8G8_USCALED = 18, + + /// + /// Specifies a two-component, 16-bit signed scaled integer format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. + /// + VK_FORMAT_R8G8_SSCALED = 19, + + /// + /// Specifies a two-component, 16-bit unsigned integer format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. + /// + VK_FORMAT_R8G8_UINT = 20, + + /// + /// Specifies a two-component, 16-bit signed integer format that has an 8-bit R component in byte 0, and an 8-bit G component in byte 1. + /// + VK_FORMAT_R8G8_SINT = 21, + + /// + /// Specifies a two-component, 16-bit unsigned normalized format that has an 8-bit R component stored with sRGB nonlinear encoding in byte 0, + /// and an 8-bit G component stored with sRGB nonlinear encoding in byte 1. + /// + VK_FORMAT_R8G8_SRGB = 22, + + /// + /// Specifies a three-component, 24-bit unsigned normalized format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. + /// + VK_FORMAT_R8G8B8_UNORM = 23, + + /// + /// Specifies a three-component, 24-bit signed normalized format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. + /// + VK_FORMAT_R8G8B8_SNORM = 24, + + /// + /// Specifies a three-component, 24-bit unsigned scaled format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. + /// + VK_FORMAT_R8G8B8_USCALED = 25, + + /// + /// Specifies a three-component, 24-bit signed scaled format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. + /// + VK_FORMAT_R8G8B8_SSCALED = 26, + + /// + /// Specifies a three-component, 24-bit unsigned integer format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. + /// + VK_FORMAT_R8G8B8_UINT = 27, + + /// + /// Specifies a three-component, 24-bit signed integer format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, and an 8-bit B component in byte 2. + /// + VK_FORMAT_R8G8B8_SINT = 28, + + /// + /// Specifies a three-component, 24-bit unsigned normalized format that has an 8-bit R component stored with sRGB nonlinear encoding in byte 0, + /// an 8-bit G component stored with sRGB nonlinear encoding in byte 1, and an 8-bit B component stored with sRGB nonlinear encoding in byte 2. + /// + VK_FORMAT_R8G8B8_SRGB = 29, + + /// + /// Specifies a three-component, 24-bit unsigned normalized format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. + /// + VK_FORMAT_B8G8R8_UNORM = 30, + + /// + /// Specifies a three-component, 24-bit signed normalized format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. + /// + VK_FORMAT_B8G8R8_SNORM = 31, + + /// + /// Specifies a three-component, 24-bit unsigned scaled format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. + /// + VK_FORMAT_B8G8R8_USCALED = 32, + + /// + /// Specifies a three-component, 24-bit signed scaled format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. + /// + VK_FORMAT_B8G8R8_SSCALED = 33, + + /// + /// Specifies a three-component, 24-bit unsigned integer format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. + /// + VK_FORMAT_B8G8R8_UINT = 34, + + /// + /// Specifies a three-component, 24-bit signed integer format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, and an 8-bit R component in byte 2. + /// + VK_FORMAT_B8G8R8_SINT = 35, + + /// + /// Specifies a three-component, 24-bit unsigned normalized format that has an 8-bit B component stored with sRGB nonlinear encoding in byte 0, + /// an 8-bit G component stored with sRGB nonlinear encoding in byte 1, and an 8-bit R component stored with sRGB nonlinear encoding in byte 2. + /// + VK_FORMAT_B8G8R8_SRGB = 36, + + /// + /// Specifies a four-component, 32-bit unsigned normalized format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, + /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_R8G8B8A8_UNORM = 37, + + /// + /// Specifies a four-component, 32-bit signed normalized format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, + /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_R8G8B8A8_SNORM = 38, + + /// + /// Specifies a four-component, 32-bit unsigned scaled format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, + /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_R8G8B8A8_USCALED = 39, + + /// + /// Specifies a four-component, 32-bit signed scaled format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, + /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_R8G8B8A8_SSCALED = 40, + + /// + /// Specifies a four-component, 32-bit unsigned integer format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, + /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_R8G8B8A8_UINT = 41, + + /// + /// Specifies a four-component, 32-bit signed integer format that has an 8-bit R component in byte 0, an 8-bit G component in byte 1, + /// an 8-bit B component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_R8G8B8A8_SINT = 42, + + /// + /// Specifies a four-component, 32-bit unsigned normalized format that has an 8-bit R component stored with sRGB nonlinear encoding in byte 0, + /// an 8-bit G component stored with sRGB nonlinear encoding in byte 1, an 8-bit B component stored with sRGB nonlinear encoding in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_R8G8B8A8_SRGB = 43, + + /// + /// Specifies a four-component, 32-bit unsigned normalized format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_B8G8R8A8_UNORM = 44, + + /// + /// Specifies a four-component, 32-bit signed normalized format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_B8G8R8A8_SNORM = 45, + + /// + /// Specifies a four-component, 32-bit unsigned scaled format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_B8G8R8A8_USCALED = 46, + + /// + /// Specifies a four-component, 32-bit signed scaled format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_B8G8R8A8_SSCALED = 47, + + /// + /// Specifies a four-component, 32-bit unsigned integer format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_B8G8R8A8_UINT = 48, + + /// + /// Specifies a four-component, 32-bit signed integer format that has an 8-bit B component in byte 0, an 8-bit G component in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_B8G8R8A8_SINT = 49, + + /// + /// Specifies a four-component, 32-bit unsigned normalized format that has an 8-bit B component stored with sRGB nonlinear encoding in byte 0, + /// an 8-bit G component stored with sRGB nonlinear encoding in byte 1, an 8-bit R component stored with sRGB nonlinear encoding in byte 2, and an 8-bit A component in byte 3. + /// + VK_FORMAT_B8G8R8A8_SRGB = 50, + + /// + /// Specifies a four-component, 32-bit packed unsigned normalized format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, + /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. + /// + VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51, + + /// + /// specifies a four-component, 32-bit packed signed normalized format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, + /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. + /// + VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52, + + /// + /// Specifies a four-component, 32-bit packed unsigned scaled integer format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, + /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. + /// + VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53, + + /// + /// Specifies a four-component, 32-bit packed signed scaled integer format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, + /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. + /// + VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54, + + /// + /// Specifies a four-component, 32-bit packed unsigned integer format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, + /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. + /// + VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55, + + /// + /// Specifies a four-component, 32-bit packed signed integer format that has an 8-bit A component in bits 24..31, an 8-bit B component in bits 16..23, + /// an 8-bit G component in bits 8..15, and an 8-bit R component in bits 0..7. + /// + VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56, + + /// + /// Specifies a four-component, 32-bit packed unsigned normalized format that has an 8-bit A component in bits 24..31, + /// an 8-bit B component stored with sRGB nonlinear encoding in bits 16..23, an 8-bit G component stored with sRGB nonlinear encoding in bits 8..15, and an 8-bit R component stored with sRGB nonlinear encoding in bits 0..7. + /// + VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57, + + /// + /// Specifies a four-component, 32-bit packed unsigned normalized format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. + /// + VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58, + + /// + /// Specifies a four-component, 32-bit packed signed normalized format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. + /// + VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59, + + /// + /// Specifies a four-component, 32-bit packed unsigned scaled integer format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. + /// + VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60, + + /// + /// Specifies a four-component, 32-bit packed signed scaled integer format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. + /// + VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61, + + /// + /// Specifies a four-component, 32-bit packed unsigned integer format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. + /// + VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62, + + /// + /// Specifies a four-component, 32-bit packed signed integer format that has a 2-bit A component in bits 30..31, a 10-bit R component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit B component in bits 0..9. + /// + VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63, + + /// + /// Specifies a four-component, 32-bit packed unsigned normalized format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. + /// + VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64, + + /// + /// Specifies a four-component, 32-bit packed signed normalized format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. + /// + VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65, + + /// + /// Specifies a four-component, 32-bit packed unsigned scaled integer format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. + /// + VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66, + + /// + /// Specifies a four-component, 32-bit packed signed scaled integer format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. + /// + VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67, + + /// + /// Specifies a four-component, 32-bit packed unsigned integer format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. + /// + VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68, + + /// + /// Specifies a four-component, 32-bit packed signed integer format that has a 2-bit A component in bits 30..31, a 10-bit B component in bits 20..29, + /// a 10-bit G component in bits 10..19, and a 10-bit R component in bits 0..9. + /// + VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69, + + /// + /// Specifies a one-component, 16-bit unsigned normalized format that has a single 16-bit R component. + /// + VK_FORMAT_R16_UNORM = 70, + + /// + /// Specifies a one-component, 16-bit signed normalized format that has a single 16-bit R component. + /// + VK_FORMAT_R16_SNORM = 71, + + /// + /// Specifies a one-component, 16-bit unsigned scaled integer format that has a single 16-bit R component. + /// + VK_FORMAT_R16_USCALED = 72, + + /// + /// Specifies a one-component, 16-bit signed scaled integer format that has a single 16-bit R component. + /// + VK_FORMAT_R16_SSCALED = 73, + + /// + /// Specifies a one-component, 16-bit unsigned integer format that has a single 16-bit R component. + /// + VK_FORMAT_R16_UINT = 74, + + /// + /// Specifies a one-component, 16-bit signed integer format that has a single 16-bit R component. + /// + VK_FORMAT_R16_SINT = 75, + + /// + /// Specifies a one-component, 16-bit signed floating-point format that has a single 16-bit R component. + /// + VK_FORMAT_R16_SFLOAT = 76, + + /// + /// Specifies a two-component, 32-bit unsigned normalized format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. + /// + VK_FORMAT_R16G16_UNORM = 77, + + /// + /// Specifies a two-component, 32-bit signed normalized format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. + /// + VK_FORMAT_R16G16_SNORM = 78, + + /// + /// Specifies a two-component, 32-bit unsigned scaled integer format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. + /// + VK_FORMAT_R16G16_USCALED = 79, + + /// + /// Specifies a two-component, 32-bit signed scaled integer format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. + /// + VK_FORMAT_R16G16_SSCALED = 80, + + /// + /// Specifies a two-component, 32-bit unsigned integer format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. + /// + VK_FORMAT_R16G16_UINT = 81, - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×10 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179, + /// + /// Specifies a two-component, 32-bit signed integer format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. + /// + VK_FORMAT_R16G16_SINT = 82, - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×10 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180, + /// + /// Specifies a two-component, 32-bit signed floating-point format that has a 16-bit R component in bytes 0..1, and a 16-bit G component in bytes 2..3. + /// + VK_FORMAT_R16G16_SFLOAT = 83, - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 12×10 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181, + /// + /// Specifies a three-component, 48-bit unsigned normalized format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// and a 16-bit B component in bytes 4..5. + /// + VK_FORMAT_R16G16B16_UNORM = 84, - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 12×10 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182, + /// + /// Specifies a three-component, 48-bit signed normalized format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// and a 16-bit B component in bytes 4..5. + /// + VK_FORMAT_R16G16B16_SNORM = 85, - /// - /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 12×12 rectangle of unsigned normalized RGBA texel data. - /// - VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183, + /// + /// Specifies a three-component, 48-bit unsigned scaled integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// and a 16-bit B component in bytes 4..5. + /// + VK_FORMAT_R16G16B16_USCALED = 86, - /// - /// specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 12×12 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. - /// - VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184, + /// + /// Specifies a three-component, 48-bit signed scaled integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// and a 16-bit B component in bytes 4..5. + /// + VK_FORMAT_R16G16B16_SSCALED = 87, + + /// + /// Specifies a three-component, 48-bit unsigned integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// and a 16-bit B component in bytes 4..5. + /// + VK_FORMAT_R16G16B16_UINT = 88, + + /// + /// Specifies a three-component, 48-bit signed integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// and a 16-bit B component in bytes 4..5. + /// + VK_FORMAT_R16G16B16_SINT = 89, + + /// + /// Specifies a three-component, 48-bit signed floating-point format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// and a 16-bit B component in bytes 4..5. + /// + VK_FORMAT_R16G16B16_SFLOAT = 90, + + /// + /// Specifies a four-component, 64-bit unsigned normalized format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. + /// + VK_FORMAT_R16G16B16A16_UNORM = 91, + + /// + /// Specifies a four-component, 64-bit signed normalized format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. + /// + VK_FORMAT_R16G16B16A16_SNORM = 92, + + /// + /// Specifies a four-component, 64-bit unsigned scaled integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. + /// + VK_FORMAT_R16G16B16A16_USCALED = 93, + + /// + /// Specifies a four-component, 64-bit signed scaled integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. + /// + VK_FORMAT_R16G16B16A16_SSCALED = 94, + + /// + /// Specifies a four-component, 64-bit unsigned integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. + /// + VK_FORMAT_R16G16B16A16_UINT = 95, + + /// + /// Specifies a four-component, 64-bit signed integer format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. + /// + VK_FORMAT_R16G16B16A16_SINT = 96, + + /// + /// Specifies a four-component, 64-bit signed floating-point format that has a 16-bit R component in bytes 0..1, a 16-bit G component in bytes 2..3, + /// a 16-bit B component in bytes 4..5, and a 16-bit A component in bytes 6..7. + /// + VK_FORMAT_R16G16B16A16_SFLOAT = 97, + + /// + /// Specifies a one-component, 32-bit unsigned integer format that has a single 32-bit R component. + /// + VK_FORMAT_R32_UINT = 98, + + /// + /// Specifies a one-component, 32-bit signed integer format that has a single 32-bit R component. + /// + VK_FORMAT_R32_SINT = 99, + + /// + /// Specifies a one-component, 32-bit signed floating-point format that has a single 32-bit R component. + /// + VK_FORMAT_R32_SFLOAT = 100, + + /// + /// Specifies a two-component, 64-bit unsigned integer format that has a 32-bit R component in bytes 0..3, and a 32-bit G component in bytes 4..7. + /// + VK_FORMAT_R32G32_UINT = 101, + + /// + /// Specifies a two-component, 64-bit signed integer format that has a 32-bit R component in bytes 0..3, and a 32-bit G component in bytes 4..7. + /// + VK_FORMAT_R32G32_SINT = 102, + + /// + /// Specifies a two-component, 64-bit signed floating-point format that has a 32-bit R component in bytes 0..3, and a 32-bit G component in bytes 4..7. + /// + VK_FORMAT_R32G32_SFLOAT = 103, + + /// + /// Specifies a three-component, 96-bit unsigned integer format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, and a 32-bit B component in bytes 8..11. + /// + VK_FORMAT_R32G32B32_UINT = 104, + + /// + /// Specifies a three-component, 96-bit signed integer format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, and a 32-bit B component in bytes 8..11. + /// + VK_FORMAT_R32G32B32_SINT = 105, + + /// + /// Specifies a three-component, 96-bit signed floating-point format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, and a 32-bit B component in bytes 8..11. + /// + VK_FORMAT_R32G32B32_SFLOAT = 106, + + /// + /// Specifies a four-component, 128-bit unsigned integer format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, a 32-bit B component in bytes 8..11, and a 32-bit A component in bytes 12..15. + /// + VK_FORMAT_R32G32B32A32_UINT = 107, + + /// + /// Specifies a four-component, 128-bit signed integer format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, a 32-bit B component in bytes 8..11, and a 32-bit A component in bytes 12..15. + /// + VK_FORMAT_R32G32B32A32_SINT = 108, + + /// + /// Specifies a four-component, 128-bit signed floating-point format that has a 32-bit R component in bytes 0..3, a 32-bit G component in bytes 4..7, a 32-bit B component in bytes 8..11, and a 32-bit A component in bytes 12..15. + /// + VK_FORMAT_R32G32B32A32_SFLOAT = 109, + + /// + /// Specifies a one-component, 64-bit unsigned integer format that has a single 64-bit R component. + /// + VK_FORMAT_R64_UINT = 110, + + /// + /// Specifies a one-component, 64-bit signed integer format that has a single 64-bit R component. + /// + VK_FORMAT_R64_SINT = 111, + + /// + /// Specifies a one-component, 64-bit signed floating-point format that has a single 64-bit R component. + /// + VK_FORMAT_R64_SFLOAT = 112, + + /// + /// Specifies a two-component, 128-bit unsigned integer format that has a 64-bit R component in bytes 0..7, and a 64-bit G component in bytes 8..15. + /// + VK_FORMAT_R64G64_UINT = 113, + + /// + /// Specifies a two-component, 128-bit signed integer format that has a 64-bit R component in bytes 0..7, and a 64-bit G component in bytes 8..15. + /// + VK_FORMAT_R64G64_SINT = 114, + + /// + /// Specifies a two-component, 128-bit signed floating-point format that has a 64-bit R component in bytes 0..7, and a 64-bit G component in bytes 8..15. + /// + VK_FORMAT_R64G64_SFLOAT = 115, + + /// + /// Specifies a three-component, 192-bit unsigned integer format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, and a 64-bit B component in bytes 16..23. + /// + VK_FORMAT_R64G64B64_UINT = 116, + + /// + /// Specifies a three-component, 192-bit signed integer format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, and a 64-bit B component in bytes 16..23. + /// + VK_FORMAT_R64G64B64_SINT = 117, + + /// + /// specifies a three-component, 192-bit signed floating-point format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, and a 64-bit B component in bytes 16..23. + /// + VK_FORMAT_R64G64B64_SFLOAT = 118, + + /// + /// Specifies a four-component, 256-bit unsigned integer format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, a 64-bit B component in bytes 16..23, and a 64-bit A component in bytes 24..31. + /// + VK_FORMAT_R64G64B64A64_UINT = 119, + + /// + /// Specifies a four-component, 256-bit signed integer format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, a 64-bit B component in bytes 16..23, and a 64-bit A component in bytes 24..31. + /// + VK_FORMAT_R64G64B64A64_SINT = 120, + + /// + /// Specifies a four-component, 256-bit signed floating-point format that has a 64-bit R component in bytes 0..7, a 64-bit G component in bytes 8..15, a 64-bit B component in bytes 16..23, and a 64-bit A component in bytes 24..31. + /// + VK_FORMAT_R64G64B64A64_SFLOAT = 121, + + /// + /// Specifies a three-component, 32-bit packed unsigned floating-point format that has a 10-bit B component in bits 22..31, an 11-bit G component in bits 11..21, an 11-bit R component in bits 0..10. + /// + VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122, + + /// + /// Specifies a three-component, 32-bit packed unsigned floating-point format that has a 5-bit shared exponent in bits 27..31, a 9-bit B component mantissa in bits 18..26, a 9-bit G component mantissa in bits 9..17, and a 9-bit R component mantissa in bits 0..8. + /// + VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123, + + /// + /// Specifies a one-component, 16-bit unsigned normalized format that has a single 16-bit depth component. + /// + VK_FORMAT_D16_UNORM = 124, + + /// + /// Specifies a two-component, 32-bit format that has 24 unsigned normalized bits in the depth component and, optionally:, 8 bits that are unused. + /// + VK_FORMAT_X8_D24_UNORM_PACK32 = 125, + + /// + /// Specifies a one-component, 32-bit signed floating-point format that has 32-bits in the depth component. + /// + VK_FORMAT_D32_SFLOAT = 126, + + /// + /// Specifies a one-component, 8-bit unsigned integer format that has 8-bits in the stencil component. + /// + VK_FORMAT_S8_UINT = 127, + + /// + /// Specifies a two-component, 24-bit format that has 16 unsigned normalized bits in the depth component and 8 unsigned integer bits in the stencil component. + /// + VK_FORMAT_D16_UNORM_S8_UINT = 128, + + /// + /// Specifies a two-component, 32-bit packed format that has 8 unsigned integer bits in the stencil component, and 24 unsigned normalized bits in the depth component. + /// + VK_FORMAT_D24_UNORM_S8_UINT = 129, + + /// + /// Specifies a two-component format that has 32 signed float bits in the depth component and 8 unsigned integer bits in the stencil component. There are optionally: 24-bits that are unused. + /// + VK_FORMAT_D32_SFLOAT_S8_UINT = 130, + + /// + /// Specifies a three-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data. This format has no alpha and is considered opaque. + /// + VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131, + + /// + /// Specifies a three-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data with sRGB nonlinear encoding. This format has no alpha and is considered opaque. + /// + VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132, + + /// + /// Specifies a four-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data, and provides 1 bit of alpha. + /// + VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133, + + /// + /// Specifies a four-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data with sRGB nonlinear encoding, and provides 1 bit of alpha. + /// + VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134, + + /// + /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values. + /// + VK_FORMAT_BC2_UNORM_BLOCK = 135, + + /// + /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values with sRGB nonlinear encoding. + /// + VK_FORMAT_BC2_SRGB_BLOCK = 136, + + /// + /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values. + /// + VK_FORMAT_BC3_UNORM_BLOCK = 137, + + /// + /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values with sRGB nonlinear encoding. + /// + VK_FORMAT_BC3_SRGB_BLOCK = 138, + + /// + /// Specifies a one-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized red texel data. + /// + VK_FORMAT_BC4_UNORM_BLOCK = 139, + + /// + /// Specifies a one-component, block-compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of signed normalized red texel data. + /// + VK_FORMAT_BC4_SNORM_BLOCK = 140, + + /// + /// Specifies a two-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RG texel data with the first 64 bits encoding red values followed by 64 bits encoding green values. + /// + VK_FORMAT_BC5_UNORM_BLOCK = 141, + + /// + /// Specifies a two-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of signed normalized RG texel data with the first 64 bits encoding red values followed by 64 bits encoding green values. + /// + VK_FORMAT_BC5_SNORM_BLOCK = 142, + + /// + /// Specifies a three-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned floating-point RGB texel data. + /// + VK_FORMAT_BC6H_UFLOAT_BLOCK = 143, + + /// + /// Specifies a three-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of signed floating-point RGB texel data. + /// + VK_FORMAT_BC6H_SFLOAT_BLOCK = 144, + + /// + /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data + /// + VK_FORMAT_BC7_UNORM_BLOCK = 145, + + /// + /// Specifies a four-component, block-compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_BC7_SRGB_BLOCK = 146, + + /// + /// Specifies a three-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data. This format has no alpha and is considered opaque. + /// + VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147, + + /// + /// Specifies a three-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data with sRGB nonlinear encoding. This format has no alpha and is considered opaque. + /// + VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148, + + /// + /// Specifies a four-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data, and provides 1 bit of alpha. + /// + VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149, + + /// + /// Specifies a four-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGB texel data with sRGB nonlinear encoding, and provides 1 bit of alpha. + /// + VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150, + + /// + /// Specifies a four-component, ETC2 compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values. + /// + VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151, + + /// + /// Specifies a four-component, ETC2 compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with the first 64 bits encoding alpha values followed by 64 bits encoding RGB values with sRGB nonlinear encoding applied. + /// + VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152, + + /// + /// Specifies a one-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized red texel data. + /// + VK_FORMAT_EAC_R11_UNORM_BLOCK = 153, + + /// + /// Specifies a one-component, ETC2 compressed format where each 64-bit compressed texel block encodes a 4×4 rectangle of signed normalized red texel data. + /// + VK_FORMAT_EAC_R11_SNORM_BLOCK = 154, + + /// + /// Specifies a two-component, ETC2 compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RG texel data with the first 64 bits encoding red values followed by 64 bits encoding green values. + /// + VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155, + + /// + /// Specifies a two-component, ETC2 compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of signed normalized RG texel data with the first 64 bits encoding red values followed by 64 bits encoding green values. + /// + VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 4×4 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 5×4 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 5×4 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 5×5 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 5×5 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 6×5 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 6×5 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 6×6 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 6×6 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×5 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×5 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×6 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×6 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×8 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes an 8×8 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×5 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×5 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×6 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×6 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×8 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×8 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×10 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 10×10 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 12×10 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 12×10 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182, + + /// + /// Specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 12×12 rectangle of unsigned normalized RGBA texel data. + /// + VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183, + + /// + /// specifies a four-component, ASTC compressed format where each 128-bit compressed texel block encodes a 12×12 rectangle of unsigned normalized RGBA texel data with sRGB nonlinear encoding applied to the RGB components. + /// + VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G8B8G8R8_422_UNORM = 1000156000, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G8B8G8R8_422_UNORM = 1000156000, - // Provided by VK_VERSION_1_1 - VK_FORMAT_B8G8R8G8_422_UNORM = 1000156001, + // Provided by VK_VERSION_1_1 + VK_FORMAT_B8G8R8G8_422_UNORM = 1000156001, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM = 1000156002, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM = 1000156002, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G8_B8R8_2PLANE_420_UNORM = 1000156003, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM = 1000156003, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM = 1000156004, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM = 1000156004, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G8_B8R8_2PLANE_422_UNORM = 1000156005, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G8_B8R8_2PLANE_422_UNORM = 1000156005, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM = 1000156006, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM = 1000156006, - // Provided by VK_VERSION_1_1 - VK_FORMAT_R10X6_UNORM_PACK16 = 1000156007, + // Provided by VK_VERSION_1_1 + VK_FORMAT_R10X6_UNORM_PACK16 = 1000156007, - // Provided by VK_VERSION_1_1 - VK_FORMAT_R10X6G10X6_UNORM_2PACK16 = 1000156008, + // Provided by VK_VERSION_1_1 + VK_FORMAT_R10X6G10X6_UNORM_2PACK16 = 1000156008, - // Provided by VK_VERSION_1_1 - VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16 = 1000156009, + // Provided by VK_VERSION_1_1 + VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16 = 1000156009, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16 = 1000156010, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16 = 1000156010, - // Provided by VK_VERSION_1_1 - VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16 = 1000156011, + // Provided by VK_VERSION_1_1 + VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16 = 1000156011, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16 = 1000156012, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16 = 1000156012, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16 = 1000156013, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16 = 1000156013, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16 = 1000156014, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16 = 1000156014, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16 = 1000156015, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16 = 1000156015, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16 = 1000156016, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16 = 1000156016, - // Provided by VK_VERSION_1_1 - VK_FORMAT_R12X4_UNORM_PACK16 = 1000156017, + // Provided by VK_VERSION_1_1 + VK_FORMAT_R12X4_UNORM_PACK16 = 1000156017, - // Provided by VK_VERSION_1_1 - VK_FORMAT_R12X4G12X4_UNORM_2PACK16 = 1000156018, + // Provided by VK_VERSION_1_1 + VK_FORMAT_R12X4G12X4_UNORM_2PACK16 = 1000156018, - // Provided by VK_VERSION_1_1 - VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16 = 1000156019, + // Provided by VK_VERSION_1_1 + VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16 = 1000156019, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16 = 1000156020, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16 = 1000156020, - // Provided by VK_VERSION_1_1 - VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16 = 1000156021, + // Provided by VK_VERSION_1_1 + VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16 = 1000156021, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16 = 1000156022, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16 = 1000156022, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16 = 1000156023, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16 = 1000156023, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16 = 1000156024, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16 = 1000156024, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16 = 1000156025, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16 = 1000156025, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16 = 1000156026, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16 = 1000156026, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G16B16G16R16_422_UNORM = 1000156027, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G16B16G16R16_422_UNORM = 1000156027, - // Provided by VK_VERSION_1_1 - VK_FORMAT_B16G16R16G16_422_UNORM = 1000156028, + // Provided by VK_VERSION_1_1 + VK_FORMAT_B16G16R16G16_422_UNORM = 1000156028, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM = 1000156029, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM = 1000156029, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G16_B16R16_2PLANE_420_UNORM = 1000156030, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G16_B16R16_2PLANE_420_UNORM = 1000156030, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM = 1000156031, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM = 1000156031, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G16_B16R16_2PLANE_422_UNORM = 1000156032, + // Provided by VK_VERSION_1_1 + VK_FORMAT_G16_B16R16_2PLANE_422_UNORM = 1000156032, - // Provided by VK_VERSION_1_1 - VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM = 1000156033, - } + // Provided by VK_VERSION_1_1 + VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM = 1000156033, } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/IKtx2DecoderOptions.cs b/src/ImageSharp.Textures/Formats/Ktx2/IKtx2DecoderOptions.cs index b534ff26..60e5d27e 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/IKtx2DecoderOptions.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/IKtx2DecoderOptions.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; + +/// +/// The options for decoding ktx version 2 textures. Currently empty, but this may change in the future. +/// +internal interface IKtx2DecoderOptions { - /// - /// The options for decoding ktx version 2 textures. Currently empty, but this may change in the future. - /// - internal interface IKtx2DecoderOptions - { - } } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2ConfigurationModule.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2ConfigurationModule.cs index 93e05c59..135b6e04 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2ConfigurationModule.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2ConfigurationModule.cs @@ -1,18 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; + +/// +/// Registers the image encoders, decoders and mime type detectors for the ktx format. +/// +public class Ktx2ConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the ktx format. - /// - public class Ktx2ConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetDecoder(Ktx2Format.Instance, new Ktx2Decoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new Ktx2ImageFormatDetector()); - } + configuration.ImageFormatsManager.SetDecoder(Ktx2Format.Instance, new Ktx2Decoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new Ktx2ImageFormatDetector()); } } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Constants.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Constants.cs index 0305799b..a0df683a 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Constants.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Constants.cs @@ -1,48 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +/// +/// Constants for ktx version 2 textures. +/// +internal static class Ktx2Constants { /// - /// Constants for ktx version 2 textures. + /// The size of a KTX header in bytes. /// - internal static class Ktx2Constants - { - /// - /// The size of a KTX header in bytes. - /// - public const int KtxHeaderSize = 68; + public const int KtxHeaderSize = 68; - /// - /// The list of mimetypes that equate to a ktx2 file. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/ktx2" }; + /// + /// The list of mimetypes that equate to a ktx2 file. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/ktx2" }; - /// - /// The list of file extensions that equate to a ktx2 file. - /// - public static readonly IEnumerable FileExtensions = new[] { "ktx2" }; + /// + /// The list of file extensions that equate to a ktx2 file. + /// + public static readonly IEnumerable FileExtensions = new[] { "ktx2" }; - /// - /// Gets the magic bytes identifying a ktx2 texture. - /// - public static ReadOnlySpan MagicBytes => new byte[] - { - 0xAB, // « - 0x4B, // K - 0x54, // T - 0x58, // X - 0x20, // " " - 0x32, // 2 - 0x30, // 0 - 0xBB, // » - 0x0D, // \r - 0x0A, // \n - 0x1A, - 0x0A, // \n - }; - } + /// + /// Gets the magic bytes identifying a ktx2 texture. + /// + public static ReadOnlySpan MagicBytes => + [ + 0xAB, // « + 0x4B, // K + 0x54, // T + 0x58, // X + 0x20, // " " + 0x32, // 2 + 0x30, // 0 + 0xBB, // » + 0x0D, // \r + 0x0A, // \n + 0x1A, + 0x0A, // \n + ]; } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Decoder.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Decoder.cs index 0442a622..f0c9969d 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Decoder.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Decoder.cs @@ -1,29 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +/// +/// Image decoder for KTX2 textures. +/// +public sealed class Ktx2Decoder : ITextureDecoder, IKtx2DecoderOptions, ITextureInfoDetector { - /// - /// Image decoder for KTX2 textures. - /// - public sealed class Ktx2Decoder : ITextureDecoder, IKtx2DecoderOptions, ITextureInfoDetector + /// + public Texture DecodeTexture(Configuration configuration, Stream stream) { - /// - public Texture DecodeTexture(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(stream, nameof(stream)); - return new Ktx2DecoderCore(configuration, this).DecodeTexture(stream); - } + return new Ktx2DecoderCore(configuration, this).DecodeTexture(stream); + } - /// - public ITextureInfo Identify(Configuration configuration, Stream stream) - { - Guard.NotNull(stream, nameof(stream)); + /// + public ITextureInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); - return new Ktx2DecoderCore(configuration, this).Identify(stream); - } + return new Ktx2DecoderCore(configuration, this).Identify(stream); } } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs index b5367090..74fbb48c 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs @@ -1,153 +1,150 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Textures.Common.Exceptions; using SixLabors.ImageSharp.Textures.TextureFormats; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; + +/// +/// Performs the ktx decoding operation. +/// +internal sealed class Ktx2DecoderCore { /// - /// Performs the ktx decoding operation. + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[24]; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The file header containing general information about the texture. + /// + private Ktx2Header ktxHeader; + + /// + /// The texture decoder options. + /// + private readonly IKtx2DecoderOptions options; + + /// + /// Initializes a new instance of the class. /// - internal sealed class Ktx2DecoderCore + /// The configuration. + /// The options. + public Ktx2DecoderCore(Configuration configuration, IKtx2DecoderOptions options) { - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] buffer = new byte[24]; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The file header containing general information about the texture. - /// - private Ktx2Header ktxHeader; - - /// - /// The texture decoder options. - /// - private readonly IKtx2DecoderOptions options; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The options. - public Ktx2DecoderCore(Configuration configuration, IKtx2DecoderOptions options) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; - } + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.options = options; + } - /// - /// Decodes the texture from the specified stream. - /// - /// The stream, where the texture should be decoded from. Cannot be null. - /// The decoded image. - public Texture DecodeTexture(Stream stream) - { - this.ReadFileHeader(stream); + /// + /// Decodes the texture from the specified stream. + /// + /// The stream, where the texture should be decoded from. Cannot be null. + /// The decoded image. + public Texture DecodeTexture(Stream stream) + { + this.ReadFileHeader(stream); - if (this.ktxHeader.PixelWidth == 0) - { - throw new UnknownTextureFormatException("Width cannot be 0"); - } + if (this.ktxHeader.PixelWidth == 0) + { + throw new UnknownTextureFormatException("Width cannot be 0"); + } - int width = (int)this.ktxHeader.PixelWidth; - int height = (int)this.ktxHeader.PixelHeight; + int width = (int)this.ktxHeader.PixelWidth; + int height = (int)this.ktxHeader.PixelHeight; - // Level indices start immediately after the header - var levelIndices = new LevelIndex[this.ktxHeader.LevelCount]; - for (int i = 0; i < levelIndices.Length; i++) - { - stream.Read(this.buffer, 0, 24); - LevelIndex levelIndex = MemoryMarshal.Cast(this.buffer)[0]; - levelIndices[i] = levelIndex; - } + // Level indices start immediately after the header + LevelIndex[] levelIndices = new LevelIndex[this.ktxHeader.LevelCount]; + for (int i = 0; i < levelIndices.Length; i++) + { + stream.Read(this.buffer, 0, 24); + LevelIndex levelIndex = MemoryMarshal.Cast(this.buffer)[0]; + levelIndices[i] = levelIndex; + } - if (this.ktxHeader.SupercompressionScheme != 0) - { - throw new NotSupportedException("SupercompressionSchemes are not yet supported"); - } + if (this.ktxHeader.SupercompressionScheme != 0) + { + throw new NotSupportedException("SupercompressionSchemes are not yet supported"); + } - var ktxProcessor = new Ktx2Processor(this.ktxHeader); + Ktx2Processor ktxProcessor = new Ktx2Processor(this.ktxHeader); - Texture texture; - if (this.ktxHeader.FaceCount == 6) - { - texture = ktxProcessor.DecodeCubeMap(stream, width, height, levelIndices); - } - else - { - var flatTexture = new FlatTexture(); - MipMap[] mipMaps = ktxProcessor.DecodeMipMaps(stream, width, height, levelIndices); - flatTexture.MipMaps.AddRange(mipMaps); - texture = flatTexture; - } + Texture texture; + if (this.ktxHeader.FaceCount == 6) + { + texture = ktxProcessor.DecodeCubeMap(stream, width, height, levelIndices); + } + else + { + FlatTexture flatTexture = new FlatTexture(); + MipMap[] mipMaps = ktxProcessor.DecodeMipMaps(stream, width, height, levelIndices); + flatTexture.MipMaps.AddRange(mipMaps); + texture = flatTexture; + } - // Seek to the end of the file to ensure the entire stream is consumed. - // KTX2 files use byte offsets for mipmap data, so the stream position may not - // be at the end after reading. We need to find the furthest point read. - if (levelIndices.Length > 0) + // Seek to the end of the file to ensure the entire stream is consumed. + // KTX2 files use byte offsets for mipmap data, so the stream position may not + // be at the end after reading. We need to find the furthest point read. + if (levelIndices.Length > 0) + { + long maxEndPosition = 0; + for (int i = 0; i < levelIndices.Length; i++) { - long maxEndPosition = 0; - for (int i = 0; i < levelIndices.Length; i++) - { - long endPosition = (long)(levelIndices[i].ByteOffset + levelIndices[i].UncompressedByteLength); - if (endPosition > maxEndPosition) - { - maxEndPosition = endPosition; - } - } - - if (stream.Position < maxEndPosition && stream.CanSeek) + long endPosition = (long)(levelIndices[i].ByteOffset + levelIndices[i].UncompressedByteLength); + if (endPosition > maxEndPosition) { - stream.Position = maxEndPosition; + maxEndPosition = endPosition; } } - return texture; + if (stream.Position < maxEndPosition && stream.CanSeek) + { + stream.Position = maxEndPosition; + } } - /// - /// Reads the raw texture information from the specified stream. - /// - /// The containing texture data. - public ITextureInfo Identify(Stream currentStream) - { - this.ReadFileHeader(currentStream); + return texture; + } + + /// + /// Reads the raw texture information from the specified stream. + /// + /// The containing texture data. + public ITextureInfo Identify(Stream currentStream) + { + this.ReadFileHeader(currentStream); - var textureInfo = new TextureInfo(new TextureTypeInfo((int)this.ktxHeader.PixelDepth), (int)this.ktxHeader.PixelWidth, (int)this.ktxHeader.PixelHeight); + TextureInfo textureInfo = new TextureInfo(new TextureTypeInfo((int)this.ktxHeader.PixelDepth), (int)this.ktxHeader.PixelWidth, (int)this.ktxHeader.PixelHeight); - return textureInfo; - } + return textureInfo; + } - /// - /// Reads the dds file header from the stream. - /// - /// The containing texture data. - private void ReadFileHeader(Stream stream) - { - // Discard the magic bytes, we already know at this point its a ktx2 file. - stream.Position += Ktx2Constants.MagicBytes.Length; + /// + /// Reads the dds file header from the stream. + /// + /// The containing texture data. + private void ReadFileHeader(Stream stream) + { + // Discard the magic bytes, we already know at this point its a ktx2 file. + stream.Position += Ktx2Constants.MagicBytes.Length; - byte[] ktxHeaderBuffer = new byte[Ktx2Constants.KtxHeaderSize]; - stream.Read(ktxHeaderBuffer, 0, Ktx2Constants.KtxHeaderSize); + byte[] ktxHeaderBuffer = new byte[Ktx2Constants.KtxHeaderSize]; + stream.Read(ktxHeaderBuffer, 0, Ktx2Constants.KtxHeaderSize); - this.ktxHeader = Ktx2Header.Parse(ktxHeaderBuffer); - } + this.ktxHeader = Ktx2Header.Parse(ktxHeaderBuffer); } } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Format.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Format.cs index 84a6ddf8..9b9338d8 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Format.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Format.cs @@ -1,37 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +/// +/// Registers the texture decoders and mime type detectors for the ktx2 format. +/// +public sealed class Ktx2Format : ITextureFormat { /// - /// Registers the texture decoders and mime type detectors for the ktx2 format. + /// Prevents a default instance of the class from being created. /// - public sealed class Ktx2Format : ITextureFormat + private Ktx2Format() { - /// - /// Prevents a default instance of the class from being created. - /// - private Ktx2Format() - { - } + } - /// - /// Gets the current instance. - /// - public static Ktx2Format Instance { get; } = new Ktx2Format(); + /// + /// Gets the current instance. + /// + public static Ktx2Format Instance { get; } = new Ktx2Format(); - /// - public string Name => "KTX2"; + /// + public string Name => "KTX2"; - /// - public string DefaultMimeType => "image/ktx2"; + /// + public string DefaultMimeType => "image/ktx2"; - /// - public IEnumerable MimeTypes => Ktx2Constants.MimeTypes; + /// + public IEnumerable MimeTypes => Ktx2Constants.MimeTypes; - /// - public IEnumerable FileExtensions => Ktx2Constants.FileExtensions; - } + /// + public IEnumerable FileExtensions => Ktx2Constants.FileExtensions; } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Header.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Header.cs index 341757fa..60aa1161 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Header.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Header.cs @@ -1,171 +1,169 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Textures.Formats.Ktx2.Enums; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; + +/// +/// Describes a KTX2 file header. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal struct Ktx2Header { + public Ktx2Header( + VkFormat vkFormat, + uint typeSize, + uint pixelWidth, + uint pixelHeight, + uint pixelDepth, + uint layerCount, + uint faceCount, + uint levelCount, + uint supercompressionScheme, + uint dfdByteOffset, + uint dfdByteLength, + uint kvdByteOffset, + uint kvdByteLength, + ulong sgdByteOffset, + ulong sgdByteLength) + { + this.VkFormat = vkFormat; + this.TypeSize = typeSize; + this.PixelWidth = pixelWidth; + this.PixelHeight = pixelHeight; + this.PixelDepth = pixelDepth; + this.LayerCount = layerCount; + this.FaceCount = faceCount; + this.LevelCount = levelCount; + this.SupercompressionScheme = supercompressionScheme; + this.DfdByteOffset = dfdByteOffset; + this.DfdByteLength = dfdByteLength; + this.KvdByteOffset = kvdByteOffset; + this.KvdByteLength = kvdByteLength; + this.SgdByteOffset = sgdByteOffset; + this.SgdByteLength = sgdByteLength; + } + + /// + /// Gets the vkFormat. + /// vkFormat specifies the image format using Vulkan VkFormat enum values. It can be any value defined in core Vulkan 1.2. + /// + public VkFormat VkFormat { get; } + + /// + /// Gets the type size. + /// typeSize specifies the size of the data type in bytes used to upload the data to a graphics API. + /// When typeSize is greater than 1, software on big-endian systems must endian convert all image data since it is little-endian. + /// + public uint TypeSize { get; } + + /// + /// Gets the width of the texture image for level 0, in pixels. + /// pixelWidth cannot be 0. + /// + public uint PixelWidth { get; } + + /// + /// Gets the height of the texture image for level 0, in pixels. + /// For 1D textures, pixelHeight and pixelDepth must be 0. + /// + public uint PixelHeight { get; } + + /// + /// Gets the pixel depth. + /// For 1D textures pixelDepth must be 0. + /// For 2D and cubemap textures, pixelDepth must be 0. + /// pixelDepth must be 0 for depth or stencil formats. + /// + public uint PixelDepth { get; } + + /// + /// Gets the layer count. + /// layerCount specifies the number of array elements. If the texture is not an array texture, layerCount must equal 0. + /// + public uint LayerCount { get; } + + /// + /// Gets the face count. + /// faceCount specifies the number of cubemap faces. For cubemaps and cubemap arrays this must be 6. For non cubemaps this must be 1. + /// Cubemap faces are stored in the order: +X, -X, +Y, -Y, +Z, -Z. + /// + public uint FaceCount { get; } + /// - /// Describes a KTX2 file header. + /// Gets the level count. + /// levelCount specifies the number of levels in the Mip Level Array and, by extension, the number of indices in the Level Index array. + /// levelCount=0 is allowed, except for block-compressed formats, and means that a file contains only the base level and consumers, + /// particularly loaders, should generate other levels if needed. /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct Ktx2Header + public uint LevelCount { get; } + + /// + /// Gets the supercompression scheme. + /// supercompressionScheme indicates if a supercompression scheme has been applied to the data in levelImages. + /// It must be one of the values from Table 2, “Supercompression Schemes”. A value of 0 indicates no supercompression. + /// + public uint SupercompressionScheme { get; } + + /// + /// Gets the DFD byte offset. + /// The offset from the start of the file of the dfdTotalSize field of the Data Format Descriptor. + /// + public uint DfdByteOffset { get; } + + /// + /// Gets the total number of bytes in the Data Format Descriptor including the dfdTotalSize field. dfdByteLength must equal dfdTotalSize. + /// + public uint DfdByteLength { get; } + + /// + /// Gets the key value pair offsets. + /// An arbitrary number of key/value pairs may follow the Index. These can be used to encode any arbitrary data. + /// The kvdByteOffset field gives the offset of this data, i.e. that of first key/value pair, from the start of the file. The value must be 0 when kvdByteLength = 0. + /// + public uint KvdByteOffset { get; } + + /// + /// Gets the total number of bytes of key/value data including all keyAndValueByteLength fields, all keyAndValue fields and all valuePadding fields. + /// + public uint KvdByteLength { get; } + + /// + /// Gets the offset from the start of the file of supercompressionGlobalData. The value must be 0 when sgdByteLength = 0. + /// + public ulong SgdByteOffset { get; } + + /// + /// Gets the number of bytes of supercompressionGlobalData. + /// For supercompression schemes for which no reference is provided in the Global Data Format column of Table 2, “Supercompression Schemes”. the value must be 0. + /// + public ulong SgdByteLength { get; } + + public static Ktx2Header Parse(ReadOnlySpan data) { - public Ktx2Header( - VkFormat vkFormat, - uint typeSize, - uint pixelWidth, - uint pixelHeight, - uint pixelDepth, - uint layerCount, - uint faceCount, - uint levelCount, - uint supercompressionScheme, - uint dfdByteOffset, - uint dfdByteLength, - uint kvdByteOffset, - uint kvdByteLength, - ulong sgdByteOffset, - ulong sgdByteLength) + if (data.Length < Ktx2Constants.KtxHeaderSize) { - this.VkFormat = vkFormat; - this.TypeSize = typeSize; - this.PixelWidth = pixelWidth; - this.PixelHeight = pixelHeight; - this.PixelDepth = pixelDepth; - this.LayerCount = layerCount; - this.FaceCount = faceCount; - this.LevelCount = levelCount; - this.SupercompressionScheme = supercompressionScheme; - this.DfdByteOffset = dfdByteOffset; - this.DfdByteLength = dfdByteLength; - this.KvdByteOffset = kvdByteOffset; - this.KvdByteLength = kvdByteLength; - this.SgdByteOffset = sgdByteOffset; - this.SgdByteLength = sgdByteLength; + throw new ArgumentException($"Ktx2 header must be {Ktx2Constants.KtxHeaderSize} bytes. Was {data.Length} bytes.", nameof(data)); } - /// - /// Gets the vkFormat. - /// vkFormat specifies the image format using Vulkan VkFormat enum values. It can be any value defined in core Vulkan 1.2. - /// - public VkFormat VkFormat { get; } - - /// - /// Gets the type size. - /// typeSize specifies the size of the data type in bytes used to upload the data to a graphics API. - /// When typeSize is greater than 1, software on big-endian systems must endian convert all image data since it is little-endian. - /// - public uint TypeSize { get; } - - /// - /// Gets the width of the texture image for level 0, in pixels. - /// pixelWidth cannot be 0. - /// - public uint PixelWidth { get; } - - /// - /// Gets the height of the texture image for level 0, in pixels. - /// For 1D textures, pixelHeight and pixelDepth must be 0. - /// - public uint PixelHeight { get; } - - /// - /// Gets the pixel depth. - /// For 1D textures pixelDepth must be 0. - /// For 2D and cubemap textures, pixelDepth must be 0. - /// pixelDepth must be 0 for depth or stencil formats. - /// - public uint PixelDepth { get; } - - /// - /// Gets the layer count. - /// layerCount specifies the number of array elements. If the texture is not an array texture, layerCount must equal 0. - /// - public uint LayerCount { get; } - - /// - /// Gets the face count. - /// faceCount specifies the number of cubemap faces. For cubemaps and cubemap arrays this must be 6. For non cubemaps this must be 1. - /// Cubemap faces are stored in the order: +X, -X, +Y, -Y, +Z, -Z. - /// - public uint FaceCount { get; } - - /// - /// Gets the level count. - /// levelCount specifies the number of levels in the Mip Level Array and, by extension, the number of indices in the Level Index array. - /// levelCount=0 is allowed, except for block-compressed formats, and means that a file contains only the base level and consumers, - /// particularly loaders, should generate other levels if needed. - /// - public uint LevelCount { get; } - - /// - /// Gets the supercompression scheme. - /// supercompressionScheme indicates if a supercompression scheme has been applied to the data in levelImages. - /// It must be one of the values from Table 2, “Supercompression Schemes”. A value of 0 indicates no supercompression. - /// - public uint SupercompressionScheme { get; } - - /// - /// Gets the DFD byte offset. - /// The offset from the start of the file of the dfdTotalSize field of the Data Format Descriptor. - /// - public uint DfdByteOffset { get; } - - /// - /// Gets the total number of bytes in the Data Format Descriptor including the dfdTotalSize field. dfdByteLength must equal dfdTotalSize. - /// - public uint DfdByteLength { get; } - - /// - /// Gets the key value pair offsets. - /// An arbitrary number of key/value pairs may follow the Index. These can be used to encode any arbitrary data. - /// The kvdByteOffset field gives the offset of this data, i.e. that of first key/value pair, from the start of the file. The value must be 0 when kvdByteLength = 0. - /// - public uint KvdByteOffset { get; } - - /// - /// Gets the total number of bytes of key/value data including all keyAndValueByteLength fields, all keyAndValue fields and all valuePadding fields. - /// - public uint KvdByteLength { get; } - - /// - /// Gets the offset from the start of the file of supercompressionGlobalData. The value must be 0 when sgdByteLength = 0. - /// - public ulong SgdByteOffset { get; } - - /// - /// Gets the number of bytes of supercompressionGlobalData. - /// For supercompression schemes for which no reference is provided in the Global Data Format column of Table 2, “Supercompression Schemes”. the value must be 0. - /// - public ulong SgdByteLength { get; } - - public static Ktx2Header Parse(ReadOnlySpan data) - { - if (data.Length < Ktx2Constants.KtxHeaderSize) - { - throw new ArgumentException($"Ktx2 header must be {Ktx2Constants.KtxHeaderSize} bytes. Was {data.Length} bytes.", nameof(data)); - } - - return new Ktx2Header( - (VkFormat)BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(0, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(4, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(8, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(12, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(16, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(20, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(24, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(28, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(32, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(36, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(40, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(44, 4)), - BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(48, 4)), - BinaryPrimitives.ReadUInt64LittleEndian(data.Slice(52, 8)), - BinaryPrimitives.ReadUInt64LittleEndian(data.Slice(60, 8))); - } + return new Ktx2Header( + (VkFormat)BinaryPrimitives.ReadUInt32LittleEndian(data[..4]), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(4, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(8, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(12, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(16, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(20, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(24, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(28, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(32, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(36, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(40, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(44, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(48, 4)), + BinaryPrimitives.ReadUInt64LittleEndian(data.Slice(52, 8)), + BinaryPrimitives.ReadUInt64LittleEndian(data.Slice(60, 8))); } } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2ImageFormatDetector.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2ImageFormatDetector.cs index c1ab0878..33ebfdf8 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2ImageFormatDetector.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2ImageFormatDetector.cs @@ -1,30 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +/// +/// Detects ktx version 2 texture file headers. +/// +public sealed class Ktx2ImageFormatDetector : ITextureFormatDetector { - /// - /// Detects ktx version 2 texture file headers. - /// - public sealed class Ktx2ImageFormatDetector : ITextureFormatDetector - { - /// - public int HeaderSize => 12; + /// + public int HeaderSize => 12; - /// - public ITextureFormat? DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? Ktx2Format.Instance : null; + /// + public ITextureFormat? DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? Ktx2Format.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) { - if (header.Length >= this.HeaderSize) - { - ReadOnlySpan magicBytes = header.Slice(0, 12); - return magicBytes.SequenceEqual(Ktx2Constants.MagicBytes); - } - - return false; + ReadOnlySpan magicBytes = header[..12]; + return magicBytes.SequenceEqual(Ktx2Constants.MagicBytes); } + + return false; } } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs index ce516cda..a02160e4 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2Processor.cs @@ -1,476 +1,473 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Textures.Common.Exceptions; using SixLabors.ImageSharp.Textures.Formats.Ktx2.Enums; using SixLabors.ImageSharp.Textures.TextureFormats; using SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; + +/// +/// Decodes ktx textures. +/// +internal class Ktx2Processor { /// - /// Decodes ktx textures. + /// Initializes a new instance of the class. /// - internal class Ktx2Processor - { - /// - /// Initializes a new instance of the class. - /// - /// The KTX header. - public Ktx2Processor(Ktx2Header ktxHeader) => this.KtxHeader = ktxHeader; - - /// - /// Gets the KTX header. - /// - public Ktx2Header KtxHeader { get; } + /// The KTX header. + public Ktx2Processor(Ktx2Header ktxHeader) => this.KtxHeader = ktxHeader; - /// - /// Decodes the mipmaps of a KTX2 textures. - /// - /// The stream to read the texture data from. - /// The width of the texture at level 0. - /// The height of the texture at level 0. - /// The start offsets and byte length of each texture. - /// The decoded mipmaps. - public MipMap[] DecodeMipMaps(Stream stream, int width, int height, LevelIndex[] levelIndices) - { - DebugGuard.MustBeGreaterThan(width, 0, nameof(width)); - DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); - DebugGuard.MustBeGreaterThan(levelIndices.Length, 0, nameof(levelIndices.Length)); + /// + /// Gets the KTX header. + /// + public Ktx2Header KtxHeader { get; } - var allMipMapBytes = ReadAllMipMapBytes(stream, levelIndices); - using var memoryStream = new MemoryStream(allMipMapBytes); + /// + /// Decodes the mipmaps of a KTX2 textures. + /// + /// The stream to read the texture data from. + /// The width of the texture at level 0. + /// The height of the texture at level 0. + /// The start offsets and byte length of each texture. + /// The decoded mipmaps. + public MipMap[] DecodeMipMaps(Stream stream, int width, int height, LevelIndex[] levelIndices) + { + DebugGuard.MustBeGreaterThan(width, 0, nameof(width)); + DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); + DebugGuard.MustBeGreaterThan(levelIndices.Length, 0, nameof(levelIndices.Length)); - switch (this.KtxHeader.VkFormat) - { - case VkFormat.VK_FORMAT_R8_UNORM: - case VkFormat.VK_FORMAT_R8_SNORM: - case VkFormat.VK_FORMAT_R8_UINT: - case VkFormat.VK_FORMAT_R8_SINT: - case VkFormat.VK_FORMAT_R8_SRGB: - // Single channel textures will be decoded to luminance image. - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16_UNORM: - case VkFormat.VK_FORMAT_R16_SNORM: - case VkFormat.VK_FORMAT_R16_UINT: - case VkFormat.VK_FORMAT_R16_SINT: - // Single channel textures will be decoded to luminance image. - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16_SFLOAT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R8G8_UNORM: - case VkFormat.VK_FORMAT_R8G8_SNORM: - case VkFormat.VK_FORMAT_R8G8_UINT: - case VkFormat.VK_FORMAT_R8G8_SINT: - case VkFormat.VK_FORMAT_R8G8_SRGB: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16G16_UNORM: - case VkFormat.VK_FORMAT_R16G16_SNORM: - case VkFormat.VK_FORMAT_R16G16_UINT: - case VkFormat.VK_FORMAT_R16G16_SINT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32_UINT: - case VkFormat.VK_FORMAT_R32G32_SINT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32_SFLOAT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16G16B16_UNORM: - case VkFormat.VK_FORMAT_R16G16B16_SNORM: - case VkFormat.VK_FORMAT_R16G16B16_UINT: - case VkFormat.VK_FORMAT_R16G16B16_SINT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16G16B16A16_UNORM: - case VkFormat.VK_FORMAT_R16G16B16A16_SNORM: - case VkFormat.VK_FORMAT_R16G16B16A16_UINT: - case VkFormat.VK_FORMAT_R16G16B16A16_SINT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16G16B16A16_SFLOAT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32B32_UINT: - case VkFormat.VK_FORMAT_R32G32B32_SINT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32B32_SFLOAT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32B32A32_UINT: - case VkFormat.VK_FORMAT_R32G32B32A32_SINT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32B32A32_SFLOAT: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B8G8R8_UNORM: - case VkFormat.VK_FORMAT_B8G8R8_SNORM: - case VkFormat.VK_FORMAT_B8G8R8_UINT: - case VkFormat.VK_FORMAT_B8G8R8_SINT: - case VkFormat.VK_FORMAT_B8G8R8_SRGB: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R8G8B8_UNORM: - case VkFormat.VK_FORMAT_R8G8B8_SNORM: - case VkFormat.VK_FORMAT_R8G8B8_UINT: - case VkFormat.VK_FORMAT_R8G8B8_SINT: - case VkFormat.VK_FORMAT_R8G8B8_SRGB: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R5G6B5_UNORM_PACK16: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R4G4B4A4_UNORM_PACK16: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B8G8R8A8_UNORM: - case VkFormat.VK_FORMAT_B8G8R8A8_SNORM: - case VkFormat.VK_FORMAT_B8G8R8A8_UINT: - case VkFormat.VK_FORMAT_B8G8R8A8_SINT: - case VkFormat.VK_FORMAT_B8G8R8A8_SRGB: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B5G5R5A1_UNORM_PACK16: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B5G6R5_UNORM_PACK16: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B4G4R4A4_UNORM_PACK16: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R8G8B8A8_UNORM: - case VkFormat.VK_FORMAT_R8G8B8A8_SNORM: - case VkFormat.VK_FORMAT_R8G8B8A8_UINT: - case VkFormat.VK_FORMAT_R8G8B8A8_SINT: - case VkFormat.VK_FORMAT_R8G8B8A8_SRGB: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R5G5B5A1_UNORM_PACK16: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC1_RGB_UNORM_BLOCK: - case VkFormat.VK_FORMAT_BC1_RGBA_UNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC2_UNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC3_UNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC4_UNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC4_SNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC5_UNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC5_SNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC6H_UFLOAT_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC6H_SFLOAT_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC7_UNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_4x4_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_5x4_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_5x4_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_5x5_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_5x5_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_6x5_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_6x5_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_6x6_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_6x6_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_8x5_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_8x5_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_8x6_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_8x6_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_8x8_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_10x5_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_10x5_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_10x6_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_10x6_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_10x8_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_10x8_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_10x10_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_10x10_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_12x10_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_12x10_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_12x12_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_12x12_SRGB_BLOCK: - return AllocateMipMaps(memoryStream, width, height, levelIndices); - } + byte[] allMipMapBytes = ReadAllMipMapBytes(stream, levelIndices); + using MemoryStream memoryStream = new MemoryStream(allMipMapBytes); - throw new NotSupportedException("The pixel format is not supported"); + switch (this.KtxHeader.VkFormat) + { + case VkFormat.VK_FORMAT_R8_UNORM: + case VkFormat.VK_FORMAT_R8_SNORM: + case VkFormat.VK_FORMAT_R8_UINT: + case VkFormat.VK_FORMAT_R8_SINT: + case VkFormat.VK_FORMAT_R8_SRGB: + // Single channel textures will be decoded to luminance image. + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16_UNORM: + case VkFormat.VK_FORMAT_R16_SNORM: + case VkFormat.VK_FORMAT_R16_UINT: + case VkFormat.VK_FORMAT_R16_SINT: + // Single channel textures will be decoded to luminance image. + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16_SFLOAT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R8G8_UNORM: + case VkFormat.VK_FORMAT_R8G8_SNORM: + case VkFormat.VK_FORMAT_R8G8_UINT: + case VkFormat.VK_FORMAT_R8G8_SINT: + case VkFormat.VK_FORMAT_R8G8_SRGB: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16G16_UNORM: + case VkFormat.VK_FORMAT_R16G16_SNORM: + case VkFormat.VK_FORMAT_R16G16_UINT: + case VkFormat.VK_FORMAT_R16G16_SINT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32_UINT: + case VkFormat.VK_FORMAT_R32G32_SINT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32_SFLOAT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16G16B16_UNORM: + case VkFormat.VK_FORMAT_R16G16B16_SNORM: + case VkFormat.VK_FORMAT_R16G16B16_UINT: + case VkFormat.VK_FORMAT_R16G16B16_SINT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16G16B16A16_UNORM: + case VkFormat.VK_FORMAT_R16G16B16A16_SNORM: + case VkFormat.VK_FORMAT_R16G16B16A16_UINT: + case VkFormat.VK_FORMAT_R16G16B16A16_SINT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16G16B16A16_SFLOAT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32B32_UINT: + case VkFormat.VK_FORMAT_R32G32B32_SINT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32B32_SFLOAT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32B32A32_UINT: + case VkFormat.VK_FORMAT_R32G32B32A32_SINT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32B32A32_SFLOAT: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B8G8R8_UNORM: + case VkFormat.VK_FORMAT_B8G8R8_SNORM: + case VkFormat.VK_FORMAT_B8G8R8_UINT: + case VkFormat.VK_FORMAT_B8G8R8_SINT: + case VkFormat.VK_FORMAT_B8G8R8_SRGB: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R8G8B8_UNORM: + case VkFormat.VK_FORMAT_R8G8B8_SNORM: + case VkFormat.VK_FORMAT_R8G8B8_UINT: + case VkFormat.VK_FORMAT_R8G8B8_SINT: + case VkFormat.VK_FORMAT_R8G8B8_SRGB: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R5G6B5_UNORM_PACK16: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R4G4B4A4_UNORM_PACK16: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B8G8R8A8_UNORM: + case VkFormat.VK_FORMAT_B8G8R8A8_SNORM: + case VkFormat.VK_FORMAT_B8G8R8A8_UINT: + case VkFormat.VK_FORMAT_B8G8R8A8_SINT: + case VkFormat.VK_FORMAT_B8G8R8A8_SRGB: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B5G5R5A1_UNORM_PACK16: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B5G6R5_UNORM_PACK16: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B4G4R4A4_UNORM_PACK16: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R8G8B8A8_UNORM: + case VkFormat.VK_FORMAT_R8G8B8A8_SNORM: + case VkFormat.VK_FORMAT_R8G8B8A8_UINT: + case VkFormat.VK_FORMAT_R8G8B8A8_SINT: + case VkFormat.VK_FORMAT_R8G8B8A8_SRGB: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R5G5B5A1_UNORM_PACK16: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC1_RGB_UNORM_BLOCK: + case VkFormat.VK_FORMAT_BC1_RGBA_UNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC2_UNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC3_UNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC4_UNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC4_SNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC5_UNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC5_SNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC6H_UFLOAT_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC6H_SFLOAT_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC7_UNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_5x4_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_5x5_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_6x5_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_6x6_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x5_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x6_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x5_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x6_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x8_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x10_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_12x10_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_12x12_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_12x12_SRGB_BLOCK: + return AllocateMipMaps(memoryStream, width, height, levelIndices); } - /// - /// Allocates and decodes the a KTX2 cube map texture. - /// - /// The stream to read the texture data from. - /// The width of a texture face. - /// The height of a texture face. - /// The start offsets and byte length of each texture. - /// A decoded cubemap texture. - /// The pixel format is not supported - public CubemapTexture DecodeCubeMap(Stream stream, int width, int height, LevelIndex[] levelIndices) - { - DebugGuard.MustBeGreaterThan(width, 0, nameof(width)); - DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); + throw new NotSupportedException("The pixel format is not supported"); + } - switch (this.KtxHeader.VkFormat) - { - case VkFormat.VK_FORMAT_R8_UNORM: - case VkFormat.VK_FORMAT_R8_SNORM: - case VkFormat.VK_FORMAT_R8_UINT: - case VkFormat.VK_FORMAT_R8_SINT: - case VkFormat.VK_FORMAT_R8_SRGB: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16_UNORM: - case VkFormat.VK_FORMAT_R16_SNORM: - case VkFormat.VK_FORMAT_R16_UINT: - case VkFormat.VK_FORMAT_R16_SINT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R8G8_UNORM: - case VkFormat.VK_FORMAT_R8G8_SNORM: - case VkFormat.VK_FORMAT_R8G8_UINT: - case VkFormat.VK_FORMAT_R8G8_SINT: - case VkFormat.VK_FORMAT_R8G8_SRGB: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16G16_UNORM: - case VkFormat.VK_FORMAT_R16G16_SNORM: - case VkFormat.VK_FORMAT_R16G16_UINT: - case VkFormat.VK_FORMAT_R16G16_SINT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16G16B16_UNORM: - case VkFormat.VK_FORMAT_R16G16B16_SNORM: - case VkFormat.VK_FORMAT_R16G16B16_UINT: - case VkFormat.VK_FORMAT_R16G16B16_SINT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16G16B16A16_UNORM: - case VkFormat.VK_FORMAT_R16G16B16A16_SNORM: - case VkFormat.VK_FORMAT_R16G16B16A16_UINT: - case VkFormat.VK_FORMAT_R16G16B16A16_SINT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16_SFLOAT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R16G16B16A16_SFLOAT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32_UINT: - case VkFormat.VK_FORMAT_R32G32_SINT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32_SFLOAT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32B32_UINT: - case VkFormat.VK_FORMAT_R32G32B32_SINT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32B32_SFLOAT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32B32A32_UINT: - case VkFormat.VK_FORMAT_R32G32B32A32_SINT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R32G32B32A32_SFLOAT: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B8G8R8_UNORM: - case VkFormat.VK_FORMAT_B8G8R8_SNORM: - case VkFormat.VK_FORMAT_B8G8R8_UINT: - case VkFormat.VK_FORMAT_B8G8R8_SINT: - case VkFormat.VK_FORMAT_B8G8R8_SRGB: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R8G8B8_UNORM: - case VkFormat.VK_FORMAT_R8G8B8_SNORM: - case VkFormat.VK_FORMAT_R8G8B8_UINT: - case VkFormat.VK_FORMAT_R8G8B8_SINT: - case VkFormat.VK_FORMAT_R8G8B8_SRGB: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R5G6B5_UNORM_PACK16: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R4G4B4A4_UNORM_PACK16: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B8G8R8A8_UNORM: - case VkFormat.VK_FORMAT_B8G8R8A8_SNORM: - case VkFormat.VK_FORMAT_B8G8R8A8_UINT: - case VkFormat.VK_FORMAT_B8G8R8A8_SINT: - case VkFormat.VK_FORMAT_B8G8R8A8_SRGB: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B5G5R5A1_UNORM_PACK16: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B5G6R5_UNORM_PACK16: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_B4G4R4A4_UNORM_PACK16: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R8G8B8A8_UNORM: - case VkFormat.VK_FORMAT_R8G8B8A8_SNORM: - case VkFormat.VK_FORMAT_R8G8B8A8_UINT: - case VkFormat.VK_FORMAT_R8G8B8A8_SINT: - case VkFormat.VK_FORMAT_R8G8B8A8_SRGB: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_R5G5B5A1_UNORM_PACK16: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC1_RGB_UNORM_BLOCK: - case VkFormat.VK_FORMAT_BC1_RGBA_UNORM_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC2_UNORM_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC4_UNORM_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC4_SNORM_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC5_UNORM_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC5_SNORM_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC6H_UFLOAT_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC6H_SFLOAT_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_BC7_UNORM_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_4x4_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_5x4_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_5x4_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_5x5_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_5x5_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_6x5_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_6x5_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_6x6_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_6x6_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_8x5_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_8x5_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_8x6_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_8x6_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_8x8_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_10x5_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_10x5_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_10x6_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_10x6_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_10x8_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_10x8_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_10x10_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_10x10_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_12x10_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_12x10_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - case VkFormat.VK_FORMAT_ASTC_12x12_UNORM_BLOCK: - case VkFormat.VK_FORMAT_ASTC_12x12_SRGB_BLOCK: - return AllocateCubeMap(stream, width, height, levelIndices); - } + /// + /// Allocates and decodes the a KTX2 cube map texture. + /// + /// The stream to read the texture data from. + /// The width of a texture face. + /// The height of a texture face. + /// The start offsets and byte length of each texture. + /// A decoded cubemap texture. + /// The pixel format is not supported + public CubemapTexture DecodeCubeMap(Stream stream, int width, int height, LevelIndex[] levelIndices) + { + DebugGuard.MustBeGreaterThan(width, 0, nameof(width)); + DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); - throw new NotSupportedException("The pixel format is not supported"); + switch (this.KtxHeader.VkFormat) + { + case VkFormat.VK_FORMAT_R8_UNORM: + case VkFormat.VK_FORMAT_R8_SNORM: + case VkFormat.VK_FORMAT_R8_UINT: + case VkFormat.VK_FORMAT_R8_SINT: + case VkFormat.VK_FORMAT_R8_SRGB: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16_UNORM: + case VkFormat.VK_FORMAT_R16_SNORM: + case VkFormat.VK_FORMAT_R16_UINT: + case VkFormat.VK_FORMAT_R16_SINT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R8G8_UNORM: + case VkFormat.VK_FORMAT_R8G8_SNORM: + case VkFormat.VK_FORMAT_R8G8_UINT: + case VkFormat.VK_FORMAT_R8G8_SINT: + case VkFormat.VK_FORMAT_R8G8_SRGB: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16G16_UNORM: + case VkFormat.VK_FORMAT_R16G16_SNORM: + case VkFormat.VK_FORMAT_R16G16_UINT: + case VkFormat.VK_FORMAT_R16G16_SINT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16G16B16_UNORM: + case VkFormat.VK_FORMAT_R16G16B16_SNORM: + case VkFormat.VK_FORMAT_R16G16B16_UINT: + case VkFormat.VK_FORMAT_R16G16B16_SINT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16G16B16A16_UNORM: + case VkFormat.VK_FORMAT_R16G16B16A16_SNORM: + case VkFormat.VK_FORMAT_R16G16B16A16_UINT: + case VkFormat.VK_FORMAT_R16G16B16A16_SINT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16_SFLOAT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R16G16B16A16_SFLOAT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32_UINT: + case VkFormat.VK_FORMAT_R32G32_SINT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32_SFLOAT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32B32_UINT: + case VkFormat.VK_FORMAT_R32G32B32_SINT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32B32_SFLOAT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32B32A32_UINT: + case VkFormat.VK_FORMAT_R32G32B32A32_SINT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R32G32B32A32_SFLOAT: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B8G8R8_UNORM: + case VkFormat.VK_FORMAT_B8G8R8_SNORM: + case VkFormat.VK_FORMAT_B8G8R8_UINT: + case VkFormat.VK_FORMAT_B8G8R8_SINT: + case VkFormat.VK_FORMAT_B8G8R8_SRGB: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R8G8B8_UNORM: + case VkFormat.VK_FORMAT_R8G8B8_SNORM: + case VkFormat.VK_FORMAT_R8G8B8_UINT: + case VkFormat.VK_FORMAT_R8G8B8_SINT: + case VkFormat.VK_FORMAT_R8G8B8_SRGB: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R5G6B5_UNORM_PACK16: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R4G4B4A4_UNORM_PACK16: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B8G8R8A8_UNORM: + case VkFormat.VK_FORMAT_B8G8R8A8_SNORM: + case VkFormat.VK_FORMAT_B8G8R8A8_UINT: + case VkFormat.VK_FORMAT_B8G8R8A8_SINT: + case VkFormat.VK_FORMAT_B8G8R8A8_SRGB: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B5G5R5A1_UNORM_PACK16: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B5G6R5_UNORM_PACK16: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_B4G4R4A4_UNORM_PACK16: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R8G8B8A8_UNORM: + case VkFormat.VK_FORMAT_R8G8B8A8_SNORM: + case VkFormat.VK_FORMAT_R8G8B8A8_UINT: + case VkFormat.VK_FORMAT_R8G8B8A8_SINT: + case VkFormat.VK_FORMAT_R8G8B8A8_SRGB: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_R5G5B5A1_UNORM_PACK16: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC1_RGB_UNORM_BLOCK: + case VkFormat.VK_FORMAT_BC1_RGBA_UNORM_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC2_UNORM_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC4_UNORM_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC4_SNORM_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC5_UNORM_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC5_SNORM_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC6H_UFLOAT_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC6H_SFLOAT_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_BC7_UNORM_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_5x4_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_5x5_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_6x5_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_6x6_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x5_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x6_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x5_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x6_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x8_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_10x10_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_12x10_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); + case VkFormat.VK_FORMAT_ASTC_12x12_UNORM_BLOCK: + case VkFormat.VK_FORMAT_ASTC_12x12_SRGB_BLOCK: + return AllocateCubeMap(stream, width, height, levelIndices); } - /// - /// Allocates and decodes all mipmap levels of a ktx texture. - /// - /// The stream to read the texture data from. - /// The width of the texture at level 0. - /// The height of the texture at level 0. - /// The start offsets and byte length of each texture. - /// The decoded mipmaps. - private static MipMap[] AllocateMipMaps(Stream stream, int width, int height, LevelIndex[] levelIndices) - where TBlock : struct, IBlock - { - var blockFormat = default(TBlock); - var mipMaps = new MipMap[levelIndices.Length]; - for (int i = 0; i < levelIndices.Length; i++) - { - var pixelDataSize = levelIndices[i].UncompressedByteLength; - byte[] mipMapData = new byte[pixelDataSize]; - ReadTextureData(stream, mipMapData); - mipMaps[i] = new MipMap(blockFormat, mipMapData, width, height); + throw new NotSupportedException("The pixel format is not supported"); + } - width >>= 1; - height >>= 1; - } + /// + /// Allocates and decodes all mipmap levels of a ktx texture. + /// + /// The stream to read the texture data from. + /// The width of the texture at level 0. + /// The height of the texture at level 0. + /// The start offsets and byte length of each texture. + /// The decoded mipmaps. + private static MipMap[] AllocateMipMaps(Stream stream, int width, int height, LevelIndex[] levelIndices) + where TBlock : struct, IBlock + { + TBlock blockFormat = default(TBlock); + MipMap[] mipMaps = new MipMap[levelIndices.Length]; + for (int i = 0; i < levelIndices.Length; i++) + { + ulong pixelDataSize = levelIndices[i].UncompressedByteLength; + byte[] mipMapData = new byte[pixelDataSize]; + ReadTextureData(stream, mipMapData); + mipMaps[i] = new MipMap(blockFormat, mipMapData, width, height); - return mipMaps; + width >>= 1; + height >>= 1; } - private static CubemapTexture AllocateCubeMap(Stream stream, int width, int height, LevelIndex[] levelIndices) - where TBlock : struct, IBlock - { - var numberOfMipMaps = levelIndices.Length; + return mipMaps; + } - var cubeMapTexture = new CubemapTexture(); - var blockFormat = default(TBlock); - for (int i = 0; i < numberOfMipMaps; i++) - { - stream.Position = (long)levelIndices[i].ByteOffset; - var uncompressedDataLength = levelIndices[i].UncompressedByteLength; - var dataForEachFace = (uint)uncompressedDataLength / 6; + private static CubemapTexture AllocateCubeMap(Stream stream, int width, int height, LevelIndex[] levelIndices) + where TBlock : struct, IBlock + { + int numberOfMipMaps = levelIndices.Length; - cubeMapTexture.PositiveX.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.NegativeX.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.PositiveY.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.NegativeY.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.PositiveZ.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - cubeMapTexture.NegativeZ.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + CubemapTexture cubeMapTexture = new CubemapTexture(); + TBlock blockFormat = default(TBlock); + for (int i = 0; i < numberOfMipMaps; i++) + { + stream.Position = (long)levelIndices[i].ByteOffset; + ulong uncompressedDataLength = levelIndices[i].UncompressedByteLength; + uint dataForEachFace = (uint)uncompressedDataLength / 6; - width >>= 1; - height >>= 1; - } + cubeMapTexture.PositiveX.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.NegativeX.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.PositiveY.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.NegativeY.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.PositiveZ.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); + cubeMapTexture.NegativeZ.MipMaps.Add(ReadFaceTexture(stream, width, height, blockFormat, dataForEachFace)); - return cubeMapTexture; + width >>= 1; + height >>= 1; } - /// - /// Read all mip maps and store them in a byte array so the level 0 mipmap will be at the beginning - /// followed by all other mip map levels. - /// - /// The stream to read the mipmap data from. - /// The level indices. - /// A byte array containing all the mipmaps. - private static byte[] ReadAllMipMapBytes(Stream stream, LevelIndex[] levelIndices) - { - ulong totalBytes = 0; - for (int i = 0; i < levelIndices.Length; i++) - { - totalBytes += levelIndices[i].UncompressedByteLength; - } - - byte[] allMipMapBytes = new byte[totalBytes]; - var idx = 0; - for (int i = 0; i < levelIndices.Length; i++) - { - stream.Position = (long)levelIndices[i].ByteOffset; - stream.Read(allMipMapBytes, idx, (int)levelIndices[i].UncompressedByteLength); - idx += (int)levelIndices[i].UncompressedByteLength; - } + return cubeMapTexture; + } - return allMipMapBytes; + /// + /// Read all mip maps and store them in a byte array so the level 0 mipmap will be at the beginning + /// followed by all other mip map levels. + /// + /// The stream to read the mipmap data from. + /// The level indices. + /// A byte array containing all the mipmaps. + private static byte[] ReadAllMipMapBytes(Stream stream, LevelIndex[] levelIndices) + { + ulong totalBytes = 0; + for (int i = 0; i < levelIndices.Length; i++) + { + totalBytes += levelIndices[i].UncompressedByteLength; } - private static MipMap ReadFaceTexture(Stream stream, int width, int height, TBlock blockFormat, uint dataForEachFace) - where TBlock : struct, IBlock + byte[] allMipMapBytes = new byte[totalBytes]; + int idx = 0; + for (int i = 0; i < levelIndices.Length; i++) { - byte[] faceData = new byte[dataForEachFace]; - ReadTextureData(stream, faceData); - return new MipMap(blockFormat, faceData, width, height); + stream.Position = (long)levelIndices[i].ByteOffset; + stream.Read(allMipMapBytes, idx, (int)levelIndices[i].UncompressedByteLength); + idx += (int)levelIndices[i].UncompressedByteLength; } - private static void ReadTextureData(Stream stream, byte[] mipMapData) + return allMipMapBytes; + } + + private static MipMap ReadFaceTexture(Stream stream, int width, int height, TBlock blockFormat, uint dataForEachFace) + where TBlock : struct, IBlock + { + byte[] faceData = new byte[dataForEachFace]; + ReadTextureData(stream, faceData); + return new MipMap(blockFormat, faceData, width, height); + } + + private static void ReadTextureData(Stream stream, byte[] mipMapData) + { + int bytesRead = stream.Read(mipMapData, 0, mipMapData.Length); + if (bytesRead != mipMapData.Length) { - int bytesRead = stream.Read(mipMapData, 0, mipMapData.Length); - if (bytesRead != mipMapData.Length) - { - throw new TextureFormatException("could not read enough texture data from the stream"); - } + throw new TextureFormatException("could not read enough texture data from the stream"); } } } diff --git a/src/ImageSharp.Textures/Formats/Ktx2/LevelIndex.cs b/src/ImageSharp.Textures/Formats/Ktx2/LevelIndex.cs index 80d38f6f..02a79a0c 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/LevelIndex.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/LevelIndex.cs @@ -3,15 +3,14 @@ using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Textures.Formats.Ktx2 +namespace SixLabors.ImageSharp.Textures.Formats.Ktx2; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal struct LevelIndex { - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct LevelIndex - { - public readonly ulong ByteOffset; + public readonly ulong ByteOffset; - public readonly ulong ByteLength; + public readonly ulong ByteLength; - public readonly ulong UncompressedByteLength; - } + public readonly ulong UncompressedByteLength; } diff --git a/src/ImageSharp.Textures/Formats/TextureFormatManager.cs b/src/ImageSharp.Textures/Formats/TextureFormatManager.cs index 11d36fb7..4dd5e248 100644 --- a/src/ImageSharp.Textures/Formats/TextureFormatManager.cs +++ b/src/ImageSharp.Textures/Formats/TextureFormatManager.cs @@ -99,7 +99,7 @@ public void AddImageFormat(ITextureFormat format) if (extension[0] == '.') { - extension = extension.Substring(1); + extension = extension[1..]; } return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); @@ -141,7 +141,7 @@ public void SetDecoder(ITextureFormat imageFormat, ITextureDecoder decoder) /// /// Removes all the registered image format detectors. /// - public void ClearImageFormatDetectors() => this.imageFormatDetectors = new ConcurrentBag(); + public void ClearImageFormatDetectors() => this.imageFormatDetectors = []; /// /// Adds a new detector for detecting mime types. diff --git a/src/ImageSharp.Textures/Formats/TextureTypeInfo.cs b/src/ImageSharp.Textures/Formats/TextureTypeInfo.cs index ab632015..6a5182bb 100644 --- a/src/ImageSharp.Textures/Formats/TextureTypeInfo.cs +++ b/src/ImageSharp.Textures/Formats/TextureTypeInfo.cs @@ -1,25 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Formats +namespace SixLabors.ImageSharp.Textures.Formats; + +/// +/// Contains information about the pixels that make up an images visual data. +/// +public class TextureTypeInfo { /// - /// Contains information about the pixels that make up an images visual data. + /// Initializes a new instance of the class. /// - public class TextureTypeInfo - { - /// - /// Initializes a new instance of the class. - /// - /// Color depth, in number of bits per pixel. - internal TextureTypeInfo(int bitsPerPixel) - { - this.BitsPerPixel = bitsPerPixel; - } + /// Color depth, in number of bits per pixel. + internal TextureTypeInfo(int bitsPerPixel) => this.BitsPerPixel = bitsPerPixel; - /// - /// Gets color depth, in number of bits per pixel. - /// - public int BitsPerPixel { get; } - } + /// + /// Gets color depth, in number of bits per pixel. + /// + public int BitsPerPixel { get; } } diff --git a/src/ImageSharp.Textures/IConfigurationModule.cs b/src/ImageSharp.Textures/IConfigurationModule.cs index 13362391..10622f72 100644 --- a/src/ImageSharp.Textures/IConfigurationModule.cs +++ b/src/ImageSharp.Textures/IConfigurationModule.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures +namespace SixLabors.ImageSharp.Textures; + +/// +/// Represents an interface that can register image encoders, decoders and image format detectors. +/// +public interface IConfigurationModule { /// - /// Represents an interface that can register image encoders, decoders and image format detectors. + /// Called when loaded into a configuration object so the module can register items into the configuration. /// - public interface IConfigurationModule - { - /// - /// Called when loaded into a configuration object so the module can register items into the configuration. - /// - /// The configuration that will retain the encoders, decodes and mime type detectors. - void Configure(Configuration configuration); - } + /// The configuration that will retain the encoders, decodes and mime type detectors. + void Configure(Configuration configuration); } diff --git a/src/ImageSharp.Textures/IO/IFileSystem.cs b/src/ImageSharp.Textures/IO/IFileSystem.cs index a36fd7eb..79656b16 100644 --- a/src/ImageSharp.Textures/IO/IFileSystem.cs +++ b/src/ImageSharp.Textures/IO/IFileSystem.cs @@ -1,27 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.IO -{ - using System.IO; +namespace SixLabors.ImageSharp.Textures.IO; + +using System.IO; +/// +/// A simple interface representing the filesystem. +/// +internal interface IFileSystem +{ /// - /// A simple interface representing the filesystem. + /// Returns a readable stream as defined by the path. /// - internal interface IFileSystem - { - /// - /// Returns a readable stream as defined by the path. - /// - /// Path to the file to open. - /// A stream representing the file to open. - Stream OpenRead(string path); + /// Path to the file to open. + /// A stream representing the file to open. + Stream OpenRead(string path); - /// - /// Creates or opens a file and returns it as a writable stream as defined by the path. - /// - /// Path to the file to open. - /// A stream representing the file to open. - Stream Create(string path); - } + /// + /// Creates or opens a file and returns it as a writable stream as defined by the path. + /// + /// Path to the file to open. + /// A stream representing the file to open. + Stream Create(string path); } diff --git a/src/ImageSharp.Textures/IO/LocalFileSystem.cs b/src/ImageSharp.Textures/IO/LocalFileSystem.cs index 1ff7fb9d..522486dc 100644 --- a/src/ImageSharp.Textures/IO/LocalFileSystem.cs +++ b/src/ImageSharp.Textures/IO/LocalFileSystem.cs @@ -1,19 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.IO -{ - using System.IO; +namespace SixLabors.ImageSharp.Textures.IO; - /// - /// A wrapper around the local File apis. - /// - internal sealed class LocalFileSystem : IFileSystem - { - /// - public Stream OpenRead(string path) => File.OpenRead(path); +/// +/// A wrapper around the local File apis. +/// +internal sealed class LocalFileSystem : IFileSystem +{ + /// + public Stream OpenRead(string path) => File.OpenRead(path); - /// - public Stream Create(string path) => File.Create(path); - } + /// + public Stream Create(string path) => File.Create(path); } diff --git a/src/ImageSharp.Textures/ITexture.cs b/src/ImageSharp.Textures/ITexture.cs index 8c978350..d7ae516e 100644 --- a/src/ImageSharp.Textures/ITexture.cs +++ b/src/ImageSharp.Textures/ITexture.cs @@ -1,15 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures; -namespace SixLabors.ImageSharp.Textures +/// +/// Interface for a texture. +/// +/// +public interface ITexture : IDisposable { - /// - /// Interface for a texture. - /// - /// - public interface ITexture : IDisposable - { - } } diff --git a/src/ImageSharp.Textures/ITextureInfo.cs b/src/ImageSharp.Textures/ITextureInfo.cs index 35443f67..76ad1ef4 100644 --- a/src/ImageSharp.Textures/ITextureInfo.cs +++ b/src/ImageSharp.Textures/ITextureInfo.cs @@ -3,27 +3,26 @@ using SixLabors.ImageSharp.Textures.Formats; -namespace SixLabors.ImageSharp.Textures +namespace SixLabors.ImageSharp.Textures; + +/// +/// Encapsulates properties that describe basic image information including dimensions, pixel type information +/// and additional metadata. +/// +public interface ITextureInfo { /// - /// Encapsulates properties that describe basic image information including dimensions, pixel type information - /// and additional metadata. + /// Gets information about the image pixels. /// - public interface ITextureInfo - { - /// - /// Gets information about the image pixels. - /// - TextureTypeInfo PixelType { get; } + TextureTypeInfo PixelType { get; } - /// - /// Gets the width. - /// - int Width { get; } + /// + /// Gets the width. + /// + int Width { get; } - /// - /// Gets the height. - /// - int Height { get; } - } + /// + /// Gets the height. + /// + int Height { get; } } diff --git a/src/ImageSharp.Textures/MipMap.cs b/src/ImageSharp.Textures/MipMap.cs index c0e5a518..6ca31f8e 100644 --- a/src/ImageSharp.Textures/MipMap.cs +++ b/src/ImageSharp.Textures/MipMap.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures +namespace SixLabors.ImageSharp.Textures; + +/// +/// Represents a MipMap. +/// +public abstract class MipMap { /// - /// Represents a MipMap. + /// Gets the image at a given mipmap level. /// - public abstract class MipMap - { - /// - /// Gets the image at a given mipmap level. - /// - /// The image. - public abstract Image GetImage(); - } + /// The image. + public abstract Image GetImage(); } diff --git a/src/ImageSharp.Textures/MipMap{TBlock}.cs b/src/ImageSharp.Textures/MipMap{TBlock}.cs index 9d51b590..eb48a78c 100644 --- a/src/ImageSharp.Textures/MipMap{TBlock}.cs +++ b/src/ImageSharp.Textures/MipMap{TBlock}.cs @@ -3,52 +3,51 @@ using SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures +namespace SixLabors.ImageSharp.Textures; + +/// +/// Represents a mipmap for a specific texture format. +/// +/// The type of the texture block. +/// +public sealed class MipMap : MipMap + where TBlock : struct, IBlock { /// - /// Represents a mipmap for a specific texture format. + /// Initializes a new instance of the class. /// - /// The type of the texture block. - /// - public sealed class MipMap : MipMap - where TBlock : struct, IBlock + /// The block format. + /// The block data. + /// The width of the texture. + /// The height of the texture. + public MipMap(TBlock blockFormat, byte[] blockData, int width, int height) { - /// - /// Initializes a new instance of the class. - /// - /// The block format. - /// The block data. - /// The width of the texture. - /// The height of the texture. - public MipMap(TBlock blockFormat, byte[] blockData, int width, int height) - { - this.BlockFormat = blockFormat; - this.BlockData = blockData; - this.Width = width; - this.Height = height; - } - - /// - /// Gets the block format. - /// - public TBlock BlockFormat { get; } - - /// - /// Gets or sets the byte data for the mipmap. - /// - public byte[] BlockData { get; set; } - - /// - /// Gets the width of the mipmap. - /// - public int Width { get; } - - /// - /// Gets the height of the mipmap. - /// - public int Height { get; } - - /// - public override Image GetImage() => this.BlockFormat.GetImage(this.BlockData, this.Width, this.Height); + this.BlockFormat = blockFormat; + this.BlockData = blockData; + this.Width = width; + this.Height = height; } + + /// + /// Gets the block format. + /// + public TBlock BlockFormat { get; } + + /// + /// Gets or sets the byte data for the mipmap. + /// + public byte[] BlockData { get; set; } + + /// + /// Gets the width of the mipmap. + /// + public int Width { get; } + + /// + /// Gets the height of the mipmap. + /// + public int Height { get; } + + /// + public override Image GetImage() => this.BlockFormat.GetImage(this.BlockData, this.Width, this.Height); } diff --git a/src/ImageSharp.Textures/PixelFormats/Ayuv.cs b/src/ImageSharp.Textures/PixelFormats/Ayuv.cs index f31800b9..9b52a341 100644 --- a/src/ImageSharp.Textures/PixelFormats/Ayuv.cs +++ b/src/ImageSharp.Textures/PixelFormats/Ayuv.cs @@ -1,152 +1,149 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel format for YUV 4:4:4 data. +/// +public struct Ayuv : IPixel, IPackedVector { + /// + public uint PackedValue { get; set; } + /// - /// Pixel format for YUV 4:4:4 data. + /// Gets or sets the packed representation of the Ayuv struct. /// - public struct Ayuv : IPixel, IPackedVector + public uint Yuv { - /// - public uint PackedValue { get; set; } - - /// - /// Gets or sets the packed representation of the Ayuv struct. - /// - public uint Yuv - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Ayuv left, Ayuv right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Ayuv left, Ayuv right) => !left.Equals(right); + set => Unsafe.As(ref this) = value; + } - /// - public override readonly string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Ayuv({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Ayuv left, Ayuv right) => left.Equals(right); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public PixelOperations CreatePixelOperations() => new(); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Ayuv left, Ayuv right) => !left.Equals(right); + + /// + public override readonly string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Ayuv({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override readonly int GetHashCode() => this.Yuv.GetHashCode(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly PixelOperations CreatePixelOperations() => new(); - /// - public void FromArgb32(Argb32 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.Yuv.GetHashCode(); - /// - public void FromBgr24(Bgr24 source) => throw new NotImplementedException(); + /// + public void FromArgb32(Argb32 source) => throw new NotImplementedException(); - /// - public void FromBgra32(Bgra32 source) => throw new NotImplementedException(); + /// + public void FromBgr24(Bgr24 source) => throw new NotImplementedException(); - /// - public void FromAbgr32(Abgr32 source) => throw new NotImplementedException(); + /// + public void FromBgra32(Bgra32 source) => throw new NotImplementedException(); - /// - public void FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); + /// + public void FromAbgr32(Abgr32 source) => throw new NotImplementedException(); - /// - public void FromL16(L16 source) => throw new NotImplementedException(); + /// + public void FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); - /// - public void FromL8(L8 source) => throw new NotImplementedException(); + /// + public void FromL16(L16 source) => throw new NotImplementedException(); - /// - public void FromLa16(La16 source) => throw new NotImplementedException(); + /// + public void FromL8(L8 source) => throw new NotImplementedException(); - /// - public void FromLa32(La32 source) => throw new NotImplementedException(); + /// + public void FromLa16(La16 source) => throw new NotImplementedException(); - /// - public void FromRgb24(Rgb24 source) => throw new NotImplementedException(); + /// + public void FromLa32(La32 source) => throw new NotImplementedException(); - /// - public void FromRgb48(Rgb48 source) => throw new NotImplementedException(); + /// + public void FromRgb24(Rgb24 source) => throw new NotImplementedException(); - /// - public void FromRgba32(Rgba32 source) => throw new NotImplementedException(); + /// + public void FromRgb48(Rgb48 source) => throw new NotImplementedException(); - /// - public void FromRgba64(Rgba64 source) => throw new NotImplementedException(); + /// + public void FromRgba32(Rgba32 source) => throw new NotImplementedException(); - /// - public void FromScaledVector4(Vector4 vector) => throw new NotImplementedException(); + /// + public void FromRgba64(Rgba64 source) => throw new NotImplementedException(); - /// - public void FromVector4(Vector4 vector) => throw new NotImplementedException(); + /// + public void FromScaledVector4(Vector4 vector) => throw new NotImplementedException(); - /// - public void ToRgba32(ref Rgba32 dest) => throw new NotImplementedException(); + /// + public void FromVector4(Vector4 vector) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + /// + public void ToRgba32(ref Rgba32 dest) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - int v = (int)(this.PackedValue & 0xFF); - int u = (int)((this.PackedValue >> 8) & 0xFF); - int y = (int)((this.PackedValue >> 16) & 0xFF); - int a = (int)((this.PackedValue >> 24) & 0xFF); - - // http://msdn.microsoft.com/en-us/library/windows/desktop/dd206750.aspx - - // Y' = Y - 16 - // Cb' = Cb - 128 - // Cr' = Cr - 128 - y -= 16; - u -= 128; - v -= 128; - - // R = 1.1644Y' + 1.5960Cr' - // G = 1.1644Y' - 0.3917Cb' - 0.8128Cr' - // B = 1.1644Y' + 2.0172Cb' - return ColorSpaceConversion.YuvToRgba8Bit(y, u, v, a); - } - - /// - public override readonly bool Equals(object? obj) => obj is Ayuv ayuv && this.Equals(ayuv); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(Ayuv other) => this.Yuv.Equals(other.Yuv); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() + { + int v = (int)(this.PackedValue & 0xFF); + int u = (int)((this.PackedValue >> 8) & 0xFF); + int y = (int)((this.PackedValue >> 16) & 0xFF); + int a = (int)((this.PackedValue >> 24) & 0xFF); + + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd206750.aspx + + // Y' = Y - 16 + // Cb' = Cb - 128 + // Cr' = Cr - 128 + y -= 16; + u -= 128; + v -= 128; + + // R = 1.1644Y' + 1.5960Cr' + // G = 1.1644Y' - 0.3917Cb' - 0.8128Cr' + // B = 1.1644Y' + 2.0172Cb' + return ColorSpaceConversion.YuvToRgba8Bit(y, u, v, a); } + + /// + public override readonly bool Equals(object? obj) => obj is Ayuv ayuv && this.Equals(ayuv); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Ayuv other) => this.Yuv.Equals(other.Yuv); } diff --git a/src/ImageSharp.Textures/PixelFormats/ColorSpaceConversion.cs b/src/ImageSharp.Textures/PixelFormats/ColorSpaceConversion.cs index 36f78ee4..462562e3 100644 --- a/src/ImageSharp.Textures/PixelFormats/ColorSpaceConversion.cs +++ b/src/ImageSharp.Textures/PixelFormats/ColorSpaceConversion.cs @@ -1,72 +1,70 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +internal static class ColorSpaceConversion { - internal static class ColorSpaceConversion + private const float Max8Bit = 256F; + + private const float Max10Bit = 1023F; + + private const float Max16Bit = 65535F; + + private static readonly Vector4 Multiplier16Bit = new(Max16Bit, Max16Bit, Max16Bit, Max16Bit); + + private static readonly Vector4 Multiplier10Bit = new(Max10Bit, Max10Bit, Max10Bit, 3F); + + private static readonly Vector4 Multiplier8Bit = new(Max8Bit, Max8Bit, Max8Bit, Max8Bit); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 YuvToRgba16Bit(uint y, uint u, uint v, uint a = ushort.MaxValue) + { + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx + // R = 1.1689Y' + 1.6023Cr' + // G = 1.1689Y' - 0.3933Cb' - 0.8160Cr' + // B = 1.1689Y'+ 2.0251Cb' + uint r = ((76607 * y) + (105006 * v) + 32768) >> 16; + uint g = ((76607 * y) - (25772 * u) - (53477 * v) + 32768) >> 16; + uint b = ((76607 * y) + (132718 * u) + 32768) >> 16; + + return new Vector4(Clamp(r, 0, ushort.MaxValue), Clamp(g, 0, ushort.MaxValue), Clamp(b, 0, ushort.MaxValue), Clamp(a, 0, ushort.MaxValue)) / Multiplier16Bit; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 YuvToRgba10Bit(uint y, uint u, uint v, uint a = 1023) { - private const float Max8Bit = 256F; - - private const float Max10Bit = 1023F; - - private const float Max16Bit = 65535F; - - private static readonly Vector4 Multiplier16Bit = new Vector4(Max16Bit, Max16Bit, Max16Bit, Max16Bit); - - private static readonly Vector4 Multiplier10Bit = new Vector4(Max10Bit, Max10Bit, Max10Bit, 3F); - - private static readonly Vector4 Multiplier8Bit = new Vector4(Max8Bit, Max8Bit, Max8Bit, Max8Bit); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 YuvToRgba16Bit(uint y, uint u, uint v, uint a = ushort.MaxValue) - { - // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx - // R = 1.1689Y' + 1.6023Cr' - // G = 1.1689Y' - 0.3933Cb' - 0.8160Cr' - // B = 1.1689Y'+ 2.0251Cb' - uint r = ((76607 * y) + (105006 * v) + 32768) >> 16; - uint g = ((76607 * y) - (25772 * u) - (53477 * v) + 32768) >> 16; - uint b = ((76607 * y) + (132718 * u) + 32768) >> 16; - - return new Vector4(Clamp(r, 0, ushort.MaxValue), Clamp(g, 0, ushort.MaxValue), Clamp(b, 0, ushort.MaxValue), Clamp(a, 0, ushort.MaxValue)) / Multiplier16Bit; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 YuvToRgba10Bit(uint y, uint u, uint v, uint a = 1023) - { - // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx - // R = 1.1678Y' + 1.6007Cr' - // G = 1.1678Y' - 0.3929Cb' - 0.8152Cr' - // B = 1.1678Y' + 2.0232Cb' - uint r = ((76533 * y) + (104905 * v) + 32768) >> 16; - uint g = ((76533 * y) - (25747 * u) - (53425 * v) + 32768) >> 16; - uint b = ((76533 * y) + (132590 * u) + 32768) >> 16; - - return new Vector4(Clamp(r, 0, 1023), Clamp(g, 0, 1023), Clamp(b, 0, 1023), Clamp(a, 0, 1023)) / Multiplier10Bit; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 YuvToRgba8Bit(int y, int u, int v, int a = 256) - { - // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx - // R = 1.1644Y' + 1.5960Cr' - // G = 1.1644Y' - 0.3917Cb' - 0.8128Cr' - // B = 1.1644Y' + 2.0172Cb' - int r = ((298 * y) + (409 * v) + 128) >> 8; - int g = ((298 * y) - (100 * u) - (208 * v) + 128) >> 8; - int b = ((298 * y) + (516 * u) + 128) >> 8; - - return new Vector4(Clamp(r, 0, 256), Clamp(g, 0, 256), Clamp(b, 0, 256), Clamp(a, 0, 256)) / Multiplier8Bit; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Clamp(uint val, uint min, uint max) => Math.Min(Math.Max(val, min), max); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Clamp(int val, int min, int max) => Math.Min(Math.Max(val, min), max); + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx + // R = 1.1678Y' + 1.6007Cr' + // G = 1.1678Y' - 0.3929Cb' - 0.8152Cr' + // B = 1.1678Y' + 2.0232Cb' + uint r = ((76533 * y) + (104905 * v) + 32768) >> 16; + uint g = ((76533 * y) - (25747 * u) - (53425 * v) + 32768) >> 16; + uint b = ((76533 * y) + (132590 * u) + 32768) >> 16; + + return new Vector4(Clamp(r, 0, 1023), Clamp(g, 0, 1023), Clamp(b, 0, 1023), Clamp(a, 0, 1023)) / Multiplier10Bit; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 YuvToRgba8Bit(int y, int u, int v, int a = 256) + { + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx + // R = 1.1644Y' + 1.5960Cr' + // G = 1.1644Y' - 0.3917Cb' - 0.8128Cr' + // B = 1.1644Y' + 2.0172Cb' + int r = ((298 * y) + (409 * v) + 128) >> 8; + int g = ((298 * y) - (100 * u) - (208 * v) + 128) >> 8; + int b = ((298 * y) + (516 * u) + 128) >> 8; + + return new Vector4(Clamp(r, 0, 256), Clamp(g, 0, 256), Clamp(b, 0, 256), Clamp(a, 0, 256)) / Multiplier8Bit; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Clamp(uint val, uint min, uint max) => Math.Min(Math.Max(val, min), max); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Clamp(int val, int min, int max) => Math.Min(Math.Max(val, min), max); } diff --git a/src/ImageSharp.Textures/PixelFormats/Fp32.cs b/src/ImageSharp.Textures/PixelFormats/Fp32.cs index 828dd0f4..1f025556 100644 --- a/src/ImageSharp.Textures/PixelFormats/Fp32.cs +++ b/src/ImageSharp.Textures/PixelFormats/Fp32.cs @@ -1,159 +1,157 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel where each pixel is represented by a 32 bit float value. +/// +public struct Fp32 : IPixel, IPackedVector { /// - /// Pixel where each pixel is represented by a 32 bit float value. + /// Initializes a new instance of the struct. /// - public struct Fp32 : IPixel, IPackedVector + /// The x-component. + public Fp32(float x) + : this(new Vector(x)) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - public Fp32(float x) - : this(new Vector(x)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Fp32(Vector vector) => this.PackedValue = Pack(vector); - - /// - public float PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Fp32 left, Fp32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Fp32 left, Fp32 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) => this.PackedValue = (ushort)Pack(new Vector(vector.X)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new(this.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public void ToRgba32(ref Rgba32 dest) => throw new NotImplementedException(); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector ToVector() => new Vector(this.PackedValue); - - /// - public override readonly bool Equals(object? obj) => obj is Fp32 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(Fp32 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector(); - return FormattableString.Invariant($"Fp32({vector:#0.##}"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Fp32(Vector vector) => this.PackedValue = Pack(vector); + + /// + public float PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Fp32 left, Fp32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Fp32 left, Fp32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) => this.PackedValue = (ushort)Pack(new Vector(vector.X)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new(this.PackedValue); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float Pack(Vector vector) => vector[0]; + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public void ToRgba32(ref Rgba32 dest) => throw new NotImplementedException(); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector ToVector() => new(this.PackedValue); + + /// + public override readonly bool Equals(object? obj) => obj is Fp32 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Fp32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector vector = this.ToVector(); + return FormattableString.Invariant($"Fp32({vector:#0.##}"); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Pack(Vector vector) => vector[0]; } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Bgr32.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Bgr32.cs index bac1a29c..d88d9841 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Bgr32.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Bgr32.cs @@ -1,175 +1,173 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x, y and z components use 8 bits. +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +public partial struct Bgr32 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x, y and z components use 8 bits. - /// - /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + public Bgr32(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Bgr32(Vector3 vector) => this.PackedValue = Pack(ref vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Bgr32 : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Bgr32 left, Bgr32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Bgr32 left, Bgr32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector3 vector3 = new Vector3(vector.X, vector.Y, vector.Z); + this.PackedValue = Pack(ref vector3); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + ((this.PackedValue >> 16) & 255) / 255F, + ((this.PackedValue >> 8) & 255) / 255F, + (this.PackedValue & 255) / 255F, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Bgr32 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Bgr32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Bgr32({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(ref Vector3 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - public Bgr32(float x, float y, float z) - : this(new Vector3(x, y, z)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Bgr32(Vector3 vector) => this.PackedValue = Pack(ref vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Bgr32 left, Bgr32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Bgr32 left, Bgr32 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector3 = new Vector3(vector.X, vector.Y, vector.Z); - this.PackedValue = Pack(ref vector3); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - ((this.PackedValue >> 16) & 255) / 255F, - ((this.PackedValue >> 8) & 255) / 255F, - (this.PackedValue & 255) / 255F, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Bgr32 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Bgr32 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Bgr32({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(ref Vector3 vector) - { - vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); - return (uint)( - (((uint)Math.Round(vector.X * 255F) & 255) << 16) - | (((uint)Math.Round(vector.Y * 255F) & 255) << 8) - | ((uint)Math.Round(vector.Z * 255F) & 255)); - } + vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + return + (((uint)Math.Round(vector.X * 255F) & 255) << 16) + | (((uint)Math.Round(vector.Y * 255F) & 255) << 8) + | ((uint)Math.Round(vector.Z * 255F) & 255); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Bgr555.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Bgr555.cs index 3eb78bc8..9dc52e75 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Bgr555.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Bgr555.cs @@ -1,175 +1,173 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x, y and z components use 5 bits. +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +public partial struct Bgr555 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x, y and z components use 5 bits. - /// - /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + public Bgr555(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Bgr555(Vector3 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Bgr555 : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Bgr555 left, Bgr555 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Bgr555 left, Bgr555 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector3 vector3 = new Vector3(vector.X, vector.Y, vector.Z); + this.PackedValue = Pack(ref vector3); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + ((this.PackedValue >> 10) & 31) / 31F, + ((this.PackedValue >> 5) & 31) / 31F, + (this.PackedValue & 31) / 31F, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Bgr555 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Bgr555 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Bgr555({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort Pack(ref Vector3 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - public Bgr555(float x, float y, float z) - : this(new Vector3(x, y, z)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Bgr555(Vector3 vector) => this.PackedValue = Pack(ref vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Bgr555 left, Bgr555 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Bgr555 left, Bgr555 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector3 = new Vector3(vector.X, vector.Y, vector.Z); - this.PackedValue = Pack(ref vector3); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - ((this.PackedValue >> 10) & 31) / 31F, - ((this.PackedValue >> 5) & 31) / 31F, - (this.PackedValue & 31) / 31F, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Bgr555 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Bgr555 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Bgr555({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(ref Vector3 vector) - { - vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); - return (ushort)( - (((uint)Math.Round(vector.X * 31F) & 31) << 10) - | (((uint)Math.Round(vector.Y * 31F) & 31) << 5) - | ((uint)Math.Round(vector.Z * 31F) & 31)); - } + vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + return (ushort)( + (((uint)Math.Round(vector.X * 31F) & 31) << 10) + | (((uint)Math.Round(vector.Y * 31F) & 31) << 5) + | ((uint)Math.Round(vector.Z * 31F) & 31)); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/D32_FLOAT_S8X24_UINT.cs b/src/ImageSharp.Textures/PixelFormats/Generated/D32_FLOAT_S8X24_UINT.cs index 5c8c2fb6..50e8ce7e 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/D32_FLOAT_S8X24_UINT.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/D32_FLOAT_S8X24_UINT.cs @@ -1,179 +1,174 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Common.Helpers; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x and y components uses 32 and 8 bits respectively. +/// +/// Ranges from [0, 0] to [1, 1] in vector form. +/// +/// +#pragma warning disable CA1707 // Identifiers should not contain underscores +public partial struct D32_FLOAT_S8X24_UINT : IPixel, IPackedVector +#pragma warning restore CA1707 // Identifiers should not contain underscores { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x and y components uses 32 and 8 bits respectively. - /// - /// Ranges from [0, 0] to [1, 1] in vector form. - /// + /// Initializes a new instance of the struct. /// -#pragma warning disable CA1707 // Identifiers should not contain underscores - public partial struct D32_FLOAT_S8X24_UINT : IPixel, IPackedVector -#pragma warning restore CA1707 // Identifiers should not contain underscores + /// The x-component + /// The y-component + public D32_FLOAT_S8X24_UINT(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public D32_FLOAT_S8X24_UINT(Vector2 vector) => this.PackedValue = Pack(ref vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(D32_FLOAT_S8X24_UINT left, D32_FLOAT_S8X24_UINT right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(D32_FLOAT_S8X24_UINT left, D32_FLOAT_S8X24_UINT right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector2 vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(ref vector2); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + FloatHelper.UnpackFloat32ToFloat((uint)(this.PackedValue & 4294967295)), + ((this.PackedValue >> 32) & 255) / 255F, + 0.0f, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is D32_FLOAT_S8X24_UINT other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(D32_FLOAT_S8X24_UINT other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"D32_FLOAT_S8X24_UINT({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Pack(ref Vector2 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - public D32_FLOAT_S8X24_UINT(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public D32_FLOAT_S8X24_UINT(Vector2 vector) => this.PackedValue = Pack(ref vector); - - /// - public ulong PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(D32_FLOAT_S8X24_UINT left, D32_FLOAT_S8X24_UINT right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(D32_FLOAT_S8X24_UINT left, D32_FLOAT_S8X24_UINT right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = Pack(ref vector2); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() - { - return new Vector4( - FloatHelper.UnpackFloat32ToFloat((uint)(this.PackedValue & 4294967295)), - ((this.PackedValue >> 32) & 255) / 255F, - 0.0f, - 1.0f); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is D32_FLOAT_S8X24_UINT other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(D32_FLOAT_S8X24_UINT other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"D32_FLOAT_S8X24_UINT({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Pack(ref Vector2 vector) - { - vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One); - return (ulong)( - (uint)FloatHelper.PackFloatToFloat32(vector.X) - | (((uint)Math.Round(vector.Y * 255F) & 255) << 32)); - } + vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One); + return + FloatHelper.PackFloatToFloat32(vector.X) + | (((uint)Math.Round(vector.Y * 255F) & 255) << 32); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/R11G11B10Float.cs b/src/ImageSharp.Textures/PixelFormats/Generated/R11G11B10Float.cs index 0be3301f..c7c6c1a8 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/R11G11B10Float.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/R11G11B10Float.cs @@ -1,182 +1,174 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Common.Helpers; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x, y and z components uses 11, 11 and 10 bits respectively. +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +public partial struct R11G11B10Float : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x, y and z components uses 11, 11 and 10 bits respectively. - /// - /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + public R11G11B10Float(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public R11G11B10Float(Vector3 vector) => this.PackedValue = Pack(ref vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct R11G11B10Float : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(R11G11B10Float left, R11G11B10Float right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(R11G11B10Float left, R11G11B10Float right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector3 vector3 = new Vector3(vector.X, vector.Y, vector.Z); + this.PackedValue = Pack(ref vector3); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + FloatHelper.UnpackFloat11ToFloat((ushort)(this.PackedValue & 2047)), + FloatHelper.UnpackFloat11ToFloat((ushort)((this.PackedValue >> 11) & 2047)), + FloatHelper.UnpackFloat10ToFloat((ushort)((this.PackedValue >> 22) & 1023)), + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is R11G11B10Float other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(R11G11B10Float other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"R11G11B10Float({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(ref Vector3 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - public R11G11B10Float(float x, float y, float z) - : this(new Vector3(x, y, z)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public R11G11B10Float(Vector3 vector) => this.PackedValue = Pack(ref vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(R11G11B10Float left, R11G11B10Float right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(R11G11B10Float left, R11G11B10Float right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector3 = new Vector3(vector.X, vector.Y, vector.Z); - this.PackedValue = Pack(ref vector3); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() - { - return new Vector4( - FloatHelper.UnpackFloat11ToFloat((ushort)(this.PackedValue & 2047)), - FloatHelper.UnpackFloat11ToFloat((ushort)((this.PackedValue >> 11) & 2047)), - FloatHelper.UnpackFloat10ToFloat((ushort)((this.PackedValue >> 22) & 1023)), - 1.0f); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) - { - dest.FromScaledVector4(this.ToScaledVector4()); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is R11G11B10Float other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(R11G11B10Float other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"R11G11B10Float({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(ref Vector3 vector) - { - vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); - return (uint)( - (uint)FloatHelper.PackFloatToFloat11(vector.X) - | ((uint)FloatHelper.PackFloatToFloat11(vector.Y) << 11) - | ((uint)FloatHelper.PackFloatToFloat10(vector.Z) << 22)); - } + vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + return + FloatHelper.PackFloatToFloat11(vector.X) + | (FloatHelper.PackFloatToFloat11(vector.Y) << 11) + | (FloatHelper.PackFloatToFloat10(vector.Z) << 22); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rg32Float.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rg32Float.cs index 36100cf1..d493f509 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rg32Float.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rg32Float.cs @@ -1,174 +1,172 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Common.Helpers; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x and y components use 16 bits. +/// +/// Ranges from [0, 0] to [1, 1] in vector form. +/// +/// +public partial struct Rg32Float : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x and y components use 16 bits. - /// - /// Ranges from [0, 0] to [1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + public Rg32Float(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Rg32Float(Vector2 vector) => this.PackedValue = Pack(ref vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Rg32Float : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rg32Float left, Rg32Float right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rg32Float left, Rg32Float right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector2 vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(ref vector2); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + FloatHelper.UnpackFloat16ToFloat((ushort)(this.PackedValue & 65535)), + FloatHelper.UnpackFloat16ToFloat((ushort)((this.PackedValue >> 16) & 65535)), + 0.0f, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rg32Float other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rg32Float other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Rg32Float({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(ref Vector2 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - public Rg32Float(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Rg32Float(Vector2 vector) => this.PackedValue = Pack(ref vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rg32Float left, Rg32Float right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rg32Float left, Rg32Float right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = Pack(ref vector2); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - FloatHelper.UnpackFloat16ToFloat((ushort)(this.PackedValue & 65535)), - FloatHelper.UnpackFloat16ToFloat((ushort)((this.PackedValue >> 16) & 65535)), - 0.0f, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rg32Float other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rg32Float other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Rg32Float({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(ref Vector2 vector) - { - vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One); - return (uint)( - (uint)FloatHelper.PackFloatToFloat16(vector.X) - | ((uint)FloatHelper.PackFloatToFloat16(vector.Y) << 16)); - } + vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One); + return + FloatHelper.PackFloatToFloat16(vector.X) + | (FloatHelper.PackFloatToFloat16(vector.Y) << 16); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rg64.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rg64.cs index fd444284..e85aa415 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rg64.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rg64.cs @@ -1,165 +1,163 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel type containing two 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green +/// +/// Ranges from [0, 0] to [1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct Rg64 : IPixel { /// - /// Pixel type containing two 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green - /// - /// Ranges from [0, 0] to [1, 1] in vector form. - /// + /// Gets or sets the red component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct Rg64 : IPixel + [FieldOffset(0)] + public uint R; + + /// + /// Gets or sets the green component. + /// + [FieldOffset(4)] + public uint G; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rg64(uint r, uint g) { - /// - /// Gets or sets the red component. - /// - [FieldOffset(0)] - public uint R; - - /// - /// Gets or sets the green component. - /// - [FieldOffset(4)] - public uint G; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rg64(uint r, uint g) - { - this.R = r; - this.G = g; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rg64 left, Rg64 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rg64 left, Rg64 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - this.R = (uint)(vector.X * 4294967295); - this.G = (uint)(vector.Y * 4294967295); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - this.R / 4294967295F, - this.G / 4294967295F, - 0.0f, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rg64 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rg64 other) => this.R.Equals(other.R) && this.G.Equals(other.G); - - /// - public override string ToString() => FormattableString.Invariant($"Rg64({this.R}, {this.G})"); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G); + this.R = r; + this.G = g; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rg64 left, Rg64 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rg64 left, Rg64 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + this.R = (uint)(vector.X * 4294967295); + this.G = (uint)(vector.Y * 4294967295); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + this.R / 4294967295F, + this.G / 4294967295F, + 0.0f, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rg64 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rg64 other) => this.R.Equals(other.R) && this.G.Equals(other.G); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rg64({this.R}, {this.G})"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G); } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rg64Float.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rg64Float.cs index 7eb39b56..8dcfc88a 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rg64Float.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rg64Float.cs @@ -1,165 +1,163 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel type containing two 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green +/// +/// Ranges from [0, 0] to [1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct Rg64Float : IPixel { /// - /// Pixel type containing two 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green - /// - /// Ranges from [0, 0] to [1, 1] in vector form. - /// + /// Gets or sets the red component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct Rg64Float : IPixel + [FieldOffset(0)] + public float R; + + /// + /// Gets or sets the green component. + /// + [FieldOffset(4)] + public float G; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rg64Float(float r, float g) { - /// - /// Gets or sets the red component. - /// - [FieldOffset(0)] - public float R; - - /// - /// Gets or sets the green component. - /// - [FieldOffset(4)] - public float G; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rg64Float(float r, float g) - { - this.R = r; - this.G = g; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rg64Float left, Rg64Float right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rg64Float left, Rg64Float right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - this.R = vector.X; - this.G = vector.Y; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - this.R, - this.G, - 0.0f, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rg64Float other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rg64Float other) => this.R.Equals(other.R) && this.G.Equals(other.G); - - /// - public override string ToString() => FormattableString.Invariant($"Rg64Float({this.R}, {this.G})"); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G); + this.R = r; + this.G = g; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rg64Float left, Rg64Float right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rg64Float left, Rg64Float right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + this.R = vector.X; + this.G = vector.Y; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + this.R, + this.G, + 0.0f, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rg64Float other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rg64Float other) => this.R.Equals(other.R) && this.G.Equals(other.G); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rg64Float({this.R}, {this.G})"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G); } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgb32.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgb32.cs index 4791df50..25aa16e6 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgb32.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgb32.cs @@ -1,175 +1,173 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x, y and z components use 8 bits. +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +public partial struct Rgb32 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x, y and z components use 8 bits. - /// - /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + public Rgb32(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Rgb32(Vector3 vector) => this.PackedValue = Pack(ref vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Rgb32 : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb32 left, Rgb32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb32 left, Rgb32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector3 vector3 = new Vector3(vector.X, vector.Y, vector.Z); + this.PackedValue = Pack(ref vector3); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + (this.PackedValue & 255) / 255F, + ((this.PackedValue >> 8) & 255) / 255F, + ((this.PackedValue >> 16) & 255) / 255F, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgb32 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgb32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Rgb32({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(ref Vector3 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - public Rgb32(float x, float y, float z) - : this(new Vector3(x, y, z)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Rgb32(Vector3 vector) => this.PackedValue = Pack(ref vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb32 left, Rgb32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgb32 left, Rgb32 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector3 = new Vector3(vector.X, vector.Y, vector.Z); - this.PackedValue = Pack(ref vector3); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - (this.PackedValue & 255) / 255F, - ((this.PackedValue >> 8) & 255) / 255F, - ((this.PackedValue >> 16) & 255) / 255F, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgb32 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgb32 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Rgb32({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(ref Vector3 vector) - { - vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); - return (uint)( - ((uint)Math.Round(vector.X * 255F) & 255) - | (((uint)Math.Round(vector.Y * 255F) & 255) << 8) - | (((uint)Math.Round(vector.Z * 255F) & 255) << 16)); - } + vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + return + ((uint)Math.Round(vector.X * 255F) & 255) + | (((uint)Math.Round(vector.Y * 255F) & 255) << 8) + | (((uint)Math.Round(vector.Z * 255F) & 255) << 16); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgb565.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgb565.cs index 1eff83a7..22572e97 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgb565.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgb565.cs @@ -1,178 +1,173 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x, y and z components uses 5, 6 and 5 bits respectively. +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +public partial struct Rgb565 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x, y and z components uses 5, 6 and 5 bits respectively. - /// - /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + public Rgb565(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Rgb565(Vector3 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Rgb565 : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb565 left, Rgb565 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb565 left, Rgb565 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector3 vector3 = new Vector3(vector.X, vector.Y, vector.Z); + this.PackedValue = Pack(ref vector3); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + (this.PackedValue & 31) / 31F, + ((this.PackedValue >> 5) & 63) / 63F, + ((this.PackedValue >> 11) & 31) / 31F, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgb565 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgb565 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Rgb565({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort Pack(ref Vector3 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - public Rgb565(float x, float y, float z) - : this(new Vector3(x, y, z)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Rgb565(Vector3 vector) => this.PackedValue = Pack(ref vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb565 left, Rgb565 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgb565 left, Rgb565 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector3 = new Vector3(vector.X, vector.Y, vector.Z); - this.PackedValue = Pack(ref vector3); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - (this.PackedValue & 31) / 31F, - ((this.PackedValue >> 5) & 63) / 63F, - ((this.PackedValue >> 11) & 31) / 31F, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) - { - dest.FromScaledVector4(this.ToScaledVector4()); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgb565 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgb565 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Rgb565({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(ref Vector3 vector) - { - vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); - return (ushort)( - ((uint)Math.Round(vector.X * 31F) & 31) - | (((uint)Math.Round(vector.Y * 63F) & 63) << 5) - | (((uint)Math.Round(vector.Z * 31F) & 31) << 11)); - } + vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + return (ushort)( + ((uint)Math.Round(vector.X * 31F) & 31) + | (((uint)Math.Round(vector.Y * 63F) & 63) << 5) + | (((uint)Math.Round(vector.Z * 31F) & 31) << 11)); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgb96.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgb96.cs index 13fac465..297e02ea 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgb96.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgb96.cs @@ -1,177 +1,172 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green, blue +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct Rgb96 : IPixel { /// - /// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green, blue - /// - /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. - /// + /// Gets or sets the red component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct Rgb96 : IPixel + [FieldOffset(0)] + public uint R; + + /// + /// Gets or sets the green component. + /// + [FieldOffset(4)] + public uint G; + + /// + /// Gets or sets the blue component. + /// + [FieldOffset(8)] + public uint B; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgb96(uint r, uint g, uint b) { - /// - /// Gets or sets the red component. - /// - [FieldOffset(0)] - public uint R; - - /// - /// Gets or sets the green component. - /// - [FieldOffset(4)] - public uint G; - - /// - /// Gets or sets the blue component. - /// - [FieldOffset(8)] - public uint B; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb96(uint r, uint g, uint b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - this.R = (uint)(vector.X * 4294967295); - this.G = (uint)(vector.Y * 4294967295); - this.B = (uint)(vector.Z * 4294967295); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - this.R / 4294967295F, - this.G / 4294967295F, - this.B / 4294967295F, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgb96 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - - /// - public override string ToString() - { - return FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + this.R = r; + this.G = g; + this.B = b; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + this.R = (uint)(vector.X * 4294967295); + this.G = (uint)(vector.Y * 4294967295); + this.B = (uint)(vector.Z * 4294967295); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + this.R / 4294967295F, + this.G / 4294967295F, + this.B / 4294967295F, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgb96 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgb96Float.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgb96Float.cs index ab1cd9d5..467a3035 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgb96Float.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgb96Float.cs @@ -1,174 +1,172 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green, blue +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct Rgb96Float : IPixel { /// - /// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green, blue - /// - /// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. - /// + /// Gets or sets the red component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct Rgb96Float : IPixel + [FieldOffset(0)] + public float R; + + /// + /// Gets or sets the green component. + /// + [FieldOffset(4)] + public float G; + + /// + /// Gets or sets the blue component. + /// + [FieldOffset(8)] + public float B; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgb96Float(float r, float g, float b) { - /// - /// Gets or sets the red component. - /// - [FieldOffset(0)] - public float R; - - /// - /// Gets or sets the green component. - /// - [FieldOffset(4)] - public float G; - - /// - /// Gets or sets the blue component. - /// - [FieldOffset(8)] - public float B; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb96Float(float r, float g, float b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb96Float left, Rgb96Float right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgb96Float left, Rgb96Float right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - this.R = vector.X; - this.G = vector.Y; - this.B = vector.Z; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - this.R, - this.G, - this.B, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgb96Float other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgb96Float other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - - /// - public override string ToString() => FormattableString.Invariant($"Rgb96Float({this.R}, {this.G}, {this.B})"); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + this.R = r; + this.G = g; + this.B = b; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb96Float left, Rgb96Float right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb96Float left, Rgb96Float right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + this.R, + this.G, + this.B, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgb96Float other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgb96Float other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rgb96Float({this.R}, {this.G}, {this.B})"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba128.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba128.cs index c4728c3f..bbc719d4 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba128.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba128.cs @@ -1,183 +1,181 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green, blue, alpha +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct Rgba128 : IPixel { /// - /// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green, blue, alpha - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the red component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct Rgba128 : IPixel + [FieldOffset(0)] + public uint R; + + /// + /// Gets or sets the green component. + /// + [FieldOffset(4)] + public uint G; + + /// + /// Gets or sets the blue component. + /// + [FieldOffset(8)] + public uint B; + + /// + /// Gets or sets the alpha component. + /// + [FieldOffset(12)] + public uint A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba128(uint r, uint g, uint b, uint a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) { - /// - /// Gets or sets the red component. - /// - [FieldOffset(0)] - public uint R; - - /// - /// Gets or sets the green component. - /// - [FieldOffset(4)] - public uint G; - - /// - /// Gets or sets the blue component. - /// - [FieldOffset(8)] - public uint B; - - /// - /// Gets or sets the alpha component. - /// - [FieldOffset(12)] - public uint A; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba128(uint r, uint g, uint b, uint a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - this.R = (uint)(vector.X * 4294967295); - this.G = (uint)(vector.Y * 4294967295); - this.B = (uint)(vector.Z * 4294967295); - this.A = (uint)(vector.W * 4294967295); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - this.R / 4294967295F, - this.G / 4294967295F, - this.B / 4294967295F, - this.A / 4294967295F); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgba128 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); - - /// - public override string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + this.R = (uint)(vector.X * 4294967295); + this.G = (uint)(vector.Y * 4294967295); + this.B = (uint)(vector.Z * 4294967295); + this.A = (uint)(vector.W * 4294967295); } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + this.R / 4294967295F, + this.G / 4294967295F, + this.B / 4294967295F, + this.A / 4294967295F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgba128 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba128Float.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba128Float.cs index f21a8641..7557d7b9 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba128Float.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba128Float.cs @@ -1,183 +1,181 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green, blue, alpha +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct Rgba128Float : IPixel { /// - /// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295. - /// The color components are stored in red, green, blue, alpha - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the red component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct Rgba128Float : IPixel + [FieldOffset(0)] + public float R; + + /// + /// Gets or sets the green component. + /// + [FieldOffset(4)] + public float G; + + /// + /// Gets or sets the blue component. + /// + [FieldOffset(8)] + public float B; + + /// + /// Gets or sets the alpha component. + /// + [FieldOffset(12)] + public float A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba128Float(float r, float g, float b, float a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba128Float left, Rgba128Float right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba128Float left, Rgba128Float right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) { - /// - /// Gets or sets the red component. - /// - [FieldOffset(0)] - public float R; - - /// - /// Gets or sets the green component. - /// - [FieldOffset(4)] - public float G; - - /// - /// Gets or sets the blue component. - /// - [FieldOffset(8)] - public float B; - - /// - /// Gets or sets the alpha component. - /// - [FieldOffset(12)] - public float A; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba128Float(float r, float g, float b, float a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba128Float left, Rgba128Float right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba128Float left, Rgba128Float right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - this.R = vector.X; - this.G = vector.Y; - this.B = vector.Z; - this.A = vector.W; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - this.R, - this.G, - this.B, - this.A); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgba128Float other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba128Float other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); - - /// - public override string ToString() => FormattableString.Invariant($"Rgba128Float({this.R}, {this.G}, {this.B}, {this.A})"); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.A = vector.W; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + this.R, + this.G, + this.B, + this.A); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgba128Float other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgba128Float other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rgba128Float({this.R}, {this.G}, {this.B}, {this.A})"); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba4444.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba4444.cs index 64b7d7df..f844636e 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba4444.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba4444.cs @@ -1,177 +1,175 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x, y, z and w components use 4 bits. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct Rgba4444 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x, y, z and w components use 4 bits. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Rgba4444(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Rgba4444(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Rgba4444 : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba4444 left, Rgba4444 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba4444 left, Rgba4444 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector4 vector4 = new Vector4(vector.X, vector.Y, vector.Z, vector.W); + this.PackedValue = Pack(ref vector4); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + (this.PackedValue & 15) / 15F, + ((this.PackedValue >> 4) & 15) / 15F, + ((this.PackedValue >> 8) & 15) / 15F, + ((this.PackedValue >> 12) & 15) / 15F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgba4444 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgba4444 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Rgba4444({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort Pack(ref Vector4 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Rgba4444(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Rgba4444(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba4444 left, Rgba4444 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba4444 left, Rgba4444 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector4 = new Vector4(vector.X, vector.Y, vector.Z, vector.W); - this.PackedValue = Pack(ref vector4); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - (this.PackedValue & 15) / 15F, - ((this.PackedValue >> 4) & 15) / 15F, - ((this.PackedValue >> 8) & 15) / 15F, - ((this.PackedValue >> 12) & 15) / 15F); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgba4444 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba4444 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Rgba4444({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(ref Vector4 vector) - { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); - return (ushort)( - ((uint)Math.Round(vector.X * 15F) & 15) - | (((uint)Math.Round(vector.Y * 15F) & 15) << 4) - | (((uint)Math.Round(vector.Z * 15F) & 15) << 8) - | (((uint)Math.Round(vector.W * 15F) & 15) << 12)); - } + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + return (ushort)( + ((uint)Math.Round(vector.X * 15F) & 15) + | (((uint)Math.Round(vector.Y * 15F) & 15) << 4) + | (((uint)Math.Round(vector.Z * 15F) & 15) << 8) + | (((uint)Math.Round(vector.W * 15F) & 15) << 12)); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba5551.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba5551.cs index 7c99ac48..99f6ada6 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba5551.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba5551.cs @@ -1,177 +1,175 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x, y, z and w components uses 5, 5, 5 and 1 bits respectively. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct Rgba5551 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x, y, z and w components uses 5, 5, 5 and 1 bits respectively. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Rgba5551(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Rgba5551(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Rgba5551 : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba5551 left, Rgba5551 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba5551 left, Rgba5551 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector4 vector4 = new Vector4(vector.X, vector.Y, vector.Z, vector.W); + this.PackedValue = Pack(ref vector4); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + (this.PackedValue & 31) / 31F, + ((this.PackedValue >> 5) & 31) / 31F, + ((this.PackedValue >> 10) & 31) / 31F, + ((this.PackedValue >> 15) & 1) / 1F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgba5551 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgba5551 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Rgba5551({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort Pack(ref Vector4 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Rgba5551(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Rgba5551(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba5551 left, Rgba5551 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba5551 left, Rgba5551 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector4 = new Vector4(vector.X, vector.Y, vector.Z, vector.W); - this.PackedValue = Pack(ref vector4); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - (this.PackedValue & 31) / 31F, - ((this.PackedValue >> 5) & 31) / 31F, - ((this.PackedValue >> 10) & 31) / 31F, - ((this.PackedValue >> 15) & 1) / 1F); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgba5551 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba5551 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Rgba5551({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(ref Vector4 vector) - { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); - return (ushort)( - ((uint)Math.Round(vector.X * 31F) & 31) - | (((uint)Math.Round(vector.Y * 31F) & 31) << 5) - | (((uint)Math.Round(vector.Z * 31F) & 31) << 10) - | (((uint)Math.Round(vector.W * 1F) & 1) << 15)); - } + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + return (ushort)( + ((uint)Math.Round(vector.X * 31F) & 31) + | (((uint)Math.Round(vector.Y * 31F) & 31) << 5) + | (((uint)Math.Round(vector.Z * 31F) & 31) << 10) + | (((uint)Math.Round(vector.W * 1F) & 1) << 15)); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba64Float.cs b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba64Float.cs index 8cd8db4f..c989e7b8 100644 --- a/src/ImageSharp.Textures/PixelFormats/Generated/Rgba64Float.cs +++ b/src/ImageSharp.Textures/PixelFormats/Generated/Rgba64Float.cs @@ -1,181 +1,176 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Common.Helpers; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x, y, z and w components use 16 bits. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct Rgba64Float : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x, y, z and w components use 16 bits. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Rgba64Float(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Rgba64Float(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Rgba64Float : IPixel, IPackedVector + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba64Float left, Rgba64Float right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba64Float left, Rgba64Float right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector4 vector4 = new Vector4(vector.X, vector.Y, vector.Z, vector.W); + this.PackedValue = Pack(ref vector4); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + FloatHelper.UnpackFloat16ToFloat((ushort)(this.PackedValue & 65535)), + FloatHelper.UnpackFloat16ToFloat((ushort)((this.PackedValue >> 16) & 65535)), + FloatHelper.UnpackFloat16ToFloat((ushort)((this.PackedValue >> 32) & 65535)), + FloatHelper.UnpackFloat16ToFloat((ushort)((this.PackedValue >> 48) & 65535))); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is Rgba64Float other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgba64Float other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Rgba64Float({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Pack(ref Vector4 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Rgba64Float(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Rgba64Float(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public ulong PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba64Float left, Rgba64Float right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba64Float left, Rgba64Float right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector4 = new Vector4(vector.X, vector.Y, vector.Z, vector.W); - this.PackedValue = Pack(ref vector4); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - FloatHelper.UnpackFloat16ToFloat((ushort)(this.PackedValue & 65535)), - FloatHelper.UnpackFloat16ToFloat((ushort)((this.PackedValue >> 16) & 65535)), - FloatHelper.UnpackFloat16ToFloat((ushort)((this.PackedValue >> 32) & 65535)), - FloatHelper.UnpackFloat16ToFloat((ushort)((this.PackedValue >> 48) & 65535))); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) - { - dest.FromScaledVector4(this.ToScaledVector4()); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is Rgba64Float other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba64Float other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Rgba64Float({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Pack(ref Vector4 vector) - { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); - return (ulong)( - (uint)FloatHelper.PackFloatToFloat16(vector.X) - | ((uint)FloatHelper.PackFloatToFloat16(vector.Y) << 16) - | ((uint)FloatHelper.PackFloatToFloat16(vector.Z) << 32) - | ((uint)FloatHelper.PackFloatToFloat16(vector.W) << 48)); - } + vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + return + FloatHelper.PackFloatToFloat16(vector.X) + | (FloatHelper.PackFloatToFloat16(vector.Y) << 16) + | (FloatHelper.PackFloatToFloat16(vector.Z) << 32) + | (FloatHelper.PackFloatToFloat16(vector.W) << 48); } } diff --git a/src/ImageSharp.Textures/PixelFormats/L32.cs b/src/ImageSharp.Textures/PixelFormats/L32.cs index 97caeefe..c930f082 100644 --- a/src/ImageSharp.Textures/PixelFormats/L32.cs +++ b/src/ImageSharp.Textures/PixelFormats/L32.cs @@ -5,260 +5,257 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing a single 32-bit normalized luminance value. +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. +/// +/// +public struct L32 : IPixel, IPackedVector { + private const float Max = uint.MaxValue; + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component + public L32(uint luminance) => this.PackedValue = luminance; + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(L32 left, L32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(L32 left, L32 right) => !left.Equals(right); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() + { + float scaled = this.PackedValue / Max; + return new Vector4(scaled, scaled, scaled, 1F); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.PackedValue = Get16BitBT709Luminance( + UpscaleFrom8BitTo16Bit(source.R), + UpscaleFrom8BitTo16Bit(source.G), + UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.PackedValue = Get16BitBT709Luminance( + UpscaleFrom8BitTo16Bit(source.R), + UpscaleFrom8BitTo16Bit(source.G), + UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.PackedValue = Get16BitBT709Luminance( + UpscaleFrom8BitTo16Bit(source.R), + UpscaleFrom8BitTo16Bit(source.G), + UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => throw new System.NotImplementedException(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.PackedValue = UpscaleFrom8BitTo16Bit(source.PackedValue); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this = new L32(source.PackedValue); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.PackedValue = UpscaleFrom8BitTo16Bit(source.L); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.PackedValue = source.L; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.PackedValue = Get16BitBT709Luminance( + UpscaleFrom8BitTo16Bit(source.R), + UpscaleFrom8BitTo16Bit(source.G), + UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.PackedValue = Get16BitBT709Luminance( + UpscaleFrom8BitTo16Bit(source.R), + UpscaleFrom8BitTo16Bit(source.G), + UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void ToRgba32(ref Rgba32 dest) + { + byte rgb = DownScaleFrom32BitTo8Bit(this.PackedValue); + dest.R = rgb; + dest.G = rgb; + dest.B = rgb; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.PackedValue = Get16BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.PackedValue = Get16BitBT709Luminance(source.R, source.G, source.B); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + public override readonly bool Equals(object? obj) => obj is L16 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(L32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => $"L32({this.PackedValue})"; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector = Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.PackedValue = Get16BitBT709Luminance( + vector.X, + vector.Y, + vector.Z); + } + + /// + /// Gets the luminance from the rgb components using the formula as + /// specified by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) + => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); + + /// + /// Gets the luminance from the rgb components using the formula as specified + /// by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort Get16BitBT709Luminance(float r, float g, float b) + => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); + /// - /// Packed pixel type containing a single 32-bit normalized luminance value. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// + /// Scales a value from an 8 bit to + /// an 16 bit equivalent. /// - public struct L32 : IPixel, IPackedVector + /// The 8 bit component value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort UpscaleFrom8BitTo16Bit(byte component) + => (ushort)(component * 257); + + /// + /// Returns the value clamped to the inclusive range of min and max. + /// 5x Faster than + /// on platforms < NET 5. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Clamp(Vector4 value, Vector4 min, Vector4 max) + => Vector4.Min(Vector4.Max(value, min), max); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte DownScaleFrom32BitTo8Bit(uint component) { - private const float Max = uint.MaxValue; - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component - public L32(uint luminance) => this.PackedValue = luminance; - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(L32 left, L32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(L32 left, L32 right) => !left.Equals(right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - float scaled = this.PackedValue / Max; - return new Vector4(scaled, scaled, scaled, 1F); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.PackedValue = Get16BitBT709Luminance( - UpscaleFrom8BitTo16Bit(source.R), - UpscaleFrom8BitTo16Bit(source.G), - UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.PackedValue = Get16BitBT709Luminance( - UpscaleFrom8BitTo16Bit(source.R), - UpscaleFrom8BitTo16Bit(source.G), - UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.PackedValue = Get16BitBT709Luminance( - UpscaleFrom8BitTo16Bit(source.R), - UpscaleFrom8BitTo16Bit(source.G), - UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => throw new System.NotImplementedException(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.PackedValue = UpscaleFrom8BitTo16Bit(source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this = new L32(source.PackedValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.PackedValue = UpscaleFrom8BitTo16Bit(source.L); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.PackedValue = source.L; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.PackedValue = Get16BitBT709Luminance( - UpscaleFrom8BitTo16Bit(source.R), - UpscaleFrom8BitTo16Bit(source.G), - UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.PackedValue = Get16BitBT709Luminance( - UpscaleFrom8BitTo16Bit(source.R), - UpscaleFrom8BitTo16Bit(source.G), - UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) - { - byte rgb = DownScaleFrom32BitTo8Bit(this.PackedValue); - dest.R = rgb; - dest.G = rgb; - dest.B = rgb; - dest.A = byte.MaxValue; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.PackedValue = Get16BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.PackedValue = Get16BitBT709Luminance(source.R, source.G, source.B); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - public override readonly bool Equals(object? obj) => obj is L16 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(L32 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => $"L32({this.PackedValue})"; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ConvertFromRgbaScaledVector4(Vector4 vector) - { - vector = Clamp(vector, Vector4.Zero, Vector4.One) * Max; - this.PackedValue = Get16BitBT709Luminance( - vector.X, - vector.Y, - vector.Z); - } - - /// - /// Gets the luminance from the rgb components using the formula as - /// specified by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) - => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); - - /// - /// Gets the luminance from the rgb components using the formula as specified - /// by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Get16BitBT709Luminance(float r, float g, float b) - => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); - - /// - /// Scales a value from an 8 bit to - /// an 16 bit equivalent. - /// - /// The 8 bit component value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort UpscaleFrom8BitTo16Bit(byte component) - => (ushort)(component * 257); - - /// - /// Returns the value clamped to the inclusive range of min and max. - /// 5x Faster than - /// on platforms < NET 5. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 Clamp(Vector4 value, Vector4 min, Vector4 max) - => Vector4.Min(Vector4.Max(value, min), max); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte DownScaleFrom32BitTo8Bit(uint component) - { - ushort componentAsShort = (ushort)(component >> 16); - return DownScaleFrom16BitTo8Bit(componentAsShort); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte DownScaleFrom16BitTo8Bit(ushort component) - { - // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is: - // - // (V * 255) / 65535 - // - // This reduces to round(V / 257), or floor((V + 128.5)/257) - // - // Represent V as the two byte value vhi.vlo. Make a guess that the - // result is the top byte of V, vhi, then the correction to this value - // is: - // - // error = floor(((V-vhi.vhi) + 128.5) / 257) - // = floor(((vlo-vhi) + 128.5) / 257) - // - // This can be approximated using integer arithmetic (and a signed - // shift): - // - // error = (vlo-vhi+128) >> 8; - // - // The approximate differs from the exact answer only when (vlo-vhi) is - // 128; it then gives a correction of +1 when the exact correction is - // 0. This gives 128 errors. The exact answer (correct for all 16-bit - // input values) is: - // - // error = (vlo-vhi+128)*65535 >> 24; - // - // An alternative arithmetic calculation which also gives no errors is: - // - // (V * 255 + 32895) >> 16 - return (byte)(((component * 255) + 32895) >> 16); - } + ushort componentAsShort = (ushort)(component >> 16); + return DownScaleFrom16BitTo8Bit(componentAsShort); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte DownScaleFrom16BitTo8Bit(ushort component) => + // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is: + // + // (V * 255) / 65535 + // + // This reduces to round(V / 257), or floor((V + 128.5)/257) + // + // Represent V as the two byte value vhi.vlo. Make a guess that the + // result is the top byte of V, vhi, then the correction to this value + // is: + // + // error = floor(((V-vhi.vhi) + 128.5) / 257) + // = floor(((vlo-vhi) + 128.5) / 257) + // + // This can be approximated using integer arithmetic (and a signed + // shift): + // + // error = (vlo-vhi+128) >> 8; + // + // The approximate differs from the exact answer only when (vlo-vhi) is + // 128; it then gives a correction of +1 when the exact correction is + // 0. This gives 128 errors. The exact answer (correct for all 16-bit + // input values) is: + // + // error = (vlo-vhi+128)*65535 >> 24; + // + // An alternative arithmetic calculation which also gives no errors is: + // + // (V * 255 + 32895) >> 16 + (byte)(((component * 255) + 32895) >> 16); } diff --git a/src/ImageSharp.Textures/PixelFormats/R16Float.cs b/src/ImageSharp.Textures/PixelFormats/R16Float.cs index d951de29..719fbe20 100644 --- a/src/ImageSharp.Textures/PixelFormats/R16Float.cs +++ b/src/ImageSharp.Textures/PixelFormats/R16Float.cs @@ -1,154 +1,152 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Common.Helpers; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values only for the red channel ranging from 0 to 1. +/// The x component uses 16 bits. +/// +/// Ranges from [0] to [1] in vector form. +/// +/// +public struct R16Float : IPixel, IPackedVector { + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(R16Float left, R16Float right) => left.Equals(right); + /// - /// Packed pixel type containing unsigned normalized values only for the red channel ranging from 0 to 1. - /// The x component uses 16 bits. - /// - /// Ranges from [0] to [1] in vector form. - /// + /// Compares two objects for equality. /// - public struct R16Float : IPixel, IPackedVector + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(R16Float left, R16Float right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector2 vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(ref vector2); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + FloatHelper.UnpackFloat16ToFloat(this.PackedValue), + 0.0f, + 0.0f, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object? obj) => obj is R16Float other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(R16Float other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"R16f({vector.X:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort Pack(ref Vector2 vector) { - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - /// The on the right side of the operand. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(R16Float left, R16Float right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(R16Float left, R16Float right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = Pack(ref vector2); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new( - FloatHelper.UnpackFloat16ToFloat(this.PackedValue), - 0.0f, - 0.0f, - 1.0f); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object? obj) => obj is R16Float other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(R16Float other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"R16f({vector.X:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort Pack(ref Vector2 vector) - { - vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One); - return (ushort)FloatHelper.PackFloatToFloat16(vector.X); - } + vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One); + return (ushort)FloatHelper.PackFloatToFloat16(vector.X); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Rg16.cs b/src/ImageSharp.Textures/PixelFormats/Rg16.cs index 70a4ef6f..ff347d22 100644 --- a/src/ImageSharp.Textures/PixelFormats/Rg16.cs +++ b/src/ImageSharp.Textures/PixelFormats/Rg16.cs @@ -1,174 +1,172 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing two 8-bit unsigned normalized values ranging from 0 to 1. +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. +/// +/// +public struct Rg16 : IPixel, IPackedVector { + private static readonly Vector2 Max = new(byte.MaxValue); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + public Rg16(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Rg16(Vector2 vector) => this.PackedValue = (ushort)Pack(vector); + + /// + public ushort PackedValue { get; set; } + /// - /// Packed pixel type containing two 8-bit unsigned normalized values ranging from 0 to 1. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. - /// + /// Compares two objects for equality. /// - public struct Rg16 : IPixel, IPackedVector + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rg16 left, Rg16 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rg16 left, Rg16 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector2 vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = (ushort)Pack(vector2); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() => new(this.ToVector2(), 0F, 1F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFF, (this.PackedValue >> 8) & 0xFF) / Max; + + /// + public override bool Equals(object? obj) => obj is Rg16 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rg16 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector2 vector = this.ToVector2(); + return FormattableString.Invariant($"Rg16({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(Vector2 vector) { - private static readonly Vector2 Max = new(byte.MaxValue); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - public Rg16(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Rg16(Vector2 vector) => this.PackedValue = (ushort)Pack(vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rg16 left, Rg16 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rg16 left, Rg16 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = (ushort)Pack(vector2); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new(this.ToVector2(), 0F, 1F); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFF, (this.PackedValue >> 8) & 0xFF) / Max; - - /// - public override bool Equals(object? obj) => obj is Rg16 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rg16 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector2(); - return FormattableString.Invariant($"Rg16({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(Vector2 vector) - { - vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One) * Max; - return (uint)(((int)Math.Round(vector.X) & 0xFF) | (((int)Math.Round(vector.Y) & 0xFF) << 8)); - } + vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One) * Max; + return (uint)(((int)Math.Round(vector.X) & 0xFF) | (((int)Math.Round(vector.Y) & 0xFF) << 8)); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Rg32.cs b/src/ImageSharp.Textures/PixelFormats/Rg32.cs index d87dc6b2..372e7a82 100644 --- a/src/ImageSharp.Textures/PixelFormats/Rg32.cs +++ b/src/ImageSharp.Textures/PixelFormats/Rg32.cs @@ -1,174 +1,172 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. +/// +/// +public struct Rg32 : IPixel, IPackedVector { + private static readonly Vector2 Max = new(byte.MaxValue); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + public Rg32(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Rg32(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + /// - /// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. - /// + /// Compares two objects for equality. /// - public struct Rg32 : IPixel, IPackedVector + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rg32 left, Rg32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 vector) + { + Vector2 vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = (ushort)Pack(vector2); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() => new(this.ToVector2(), 0F, 1F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFF, (this.PackedValue >> 8) & 0xFF) / Max; + + /// + public override bool Equals(object? obj) => obj is Rg32 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override string ToString() + { + Vector2 vector = this.ToVector2(); + return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(Vector2 vector) { - private static readonly Vector2 Max = new(byte.MaxValue); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - public Rg32(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Rg32(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rg32 left, Rg32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = (ushort)Pack(vector2); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => new(this.ToVector2(), 0F, 1F); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFF, (this.PackedValue >> 8) & 0xFF) / Max; - - /// - public override bool Equals(object? obj) => obj is Rg32 other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override string ToString() - { - var vector = this.ToVector2(); - return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(Vector2 vector) - { - vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One) * Max; - return (uint)(((int)Math.Round(vector.X) & 0xFF) | (((int)Math.Round(vector.Y) & 0xFF) << 8)); - } + vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One) * Max; + return (uint)(((int)Math.Round(vector.X) & 0xFF) | (((int)Math.Round(vector.Y) & 0xFF) << 8)); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Y410.cs b/src/ImageSharp.Textures/PixelFormats/Y410.cs index 701a1456..20dc0591 100644 --- a/src/ImageSharp.Textures/PixelFormats/Y410.cs +++ b/src/ImageSharp.Textures/PixelFormats/Y410.cs @@ -1,150 +1,148 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel format based on 16-bit per channel packed YUV 4:4:4 data. +/// +public struct Y410 : IPixel, IPackedVector { + /// + public uint PackedValue { get; set; } + /// - /// Pixel format based on 16-bit per channel packed YUV 4:4:4 data. + /// Gets or sets the packed representation of the Y410 struct. /// - public struct Y410 : IPixel, IPackedVector + public uint Yuv { - /// - public uint PackedValue { get; set; } - - /// - /// Gets or sets the packed representation of the Y410 struct. - /// - public uint Yuv - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Y410 left, Y410 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Y410 left, Y410 right) => !left.Equals(right); + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - /// - public override readonly bool Equals(object? obj) => obj is Y410 other && this.Equals(other); - - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(Y410 other) => this.Yuv.Equals(other.Yuv); + set => Unsafe.As(ref this) = value; + } - /// - public override readonly string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Y416({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Y410 left, Y410 right) => left.Equals(right); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public PixelOperations CreatePixelOperations() => new(); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Y410 left, Y410 right) => !left.Equals(right); + + /// + public override readonly bool Equals(object? obj) => obj is Y410 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Y410 other) => this.Yuv.Equals(other.Yuv); + + /// + public override readonly string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Y416({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override readonly int GetHashCode() => this.Yuv.GetHashCode(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly PixelOperations CreatePixelOperations() => new(); - /// - public void FromArgb32(Argb32 source) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.Yuv.GetHashCode(); - /// - public void FromBgr24(Bgr24 source) => throw new NotImplementedException(); + /// + public void FromArgb32(Argb32 source) => throw new NotImplementedException(); - /// - public void FromBgra32(Bgra32 source) => throw new NotImplementedException(); + /// + public void FromBgr24(Bgr24 source) => throw new NotImplementedException(); - /// - public void FromAbgr32(Abgr32 source) => throw new NotImplementedException(); + /// + public void FromBgra32(Bgra32 source) => throw new NotImplementedException(); - /// - public void FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); + /// + public void FromAbgr32(Abgr32 source) => throw new NotImplementedException(); - /// - public void FromL16(L16 source) => throw new NotImplementedException(); + /// + public void FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); - /// - public void FromL8(L8 source) => throw new NotImplementedException(); + /// + public void FromL16(L16 source) => throw new NotImplementedException(); - /// - public void FromLa16(La16 source) => throw new NotImplementedException(); + /// + public void FromL8(L8 source) => throw new NotImplementedException(); - /// - public void FromLa32(La32 source) => throw new NotImplementedException(); + /// + public void FromLa16(La16 source) => throw new NotImplementedException(); - /// - public void FromRgb24(Rgb24 source) => throw new NotImplementedException(); + /// + public void FromLa32(La32 source) => throw new NotImplementedException(); - /// - public void FromRgb48(Rgb48 source) => throw new NotImplementedException(); + /// + public void FromRgb24(Rgb24 source) => throw new NotImplementedException(); - /// - public void FromRgba32(Rgba32 source) => throw new NotImplementedException(); + /// + public void FromRgb48(Rgb48 source) => throw new NotImplementedException(); - /// - public void FromRgba64(Rgba64 source) => throw new NotImplementedException(); + /// + public void FromRgba32(Rgba32 source) => throw new NotImplementedException(); - /// - public void FromScaledVector4(Vector4 vector) => throw new NotImplementedException(); + /// + public void FromRgba64(Rgba64 source) => throw new NotImplementedException(); - /// - public void FromVector4(Vector4 vector) => throw new NotImplementedException(); + /// + public void FromScaledVector4(Vector4 vector) => throw new NotImplementedException(); - /// - public void ToRgba32(ref Rgba32 dest) => throw new NotImplementedException(); + /// + public void FromVector4(Vector4 vector) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + /// + public void ToRgba32(ref Rgba32 dest) => throw new NotImplementedException(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - uint u = (this.PackedValue >> 0) & 0x03FF; - uint y = (this.PackedValue >> 10) & 0x03FF; - uint v = (this.PackedValue >> 20) & 0x03FF; - uint a = (this.PackedValue >> 30) & 0x03; - - // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx - // Y' = Y - 64 - // Cb' = Cb - 512 - // Cr' = Cr - 512 - y -= 64; - u -= 512; - v -= 512; - - // R = 1.1678Y' + 1.6007Cr' - // G = 1.1678Y' - 0.3929Cb' - 0.8152Cr' - // B = 1.1678Y' + 2.0232Cb' - return ColorSpaceConversion.YuvToRgba10Bit(y, u, v, a); - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() + { + uint u = (this.PackedValue >> 0) & 0x03FF; + uint y = (this.PackedValue >> 10) & 0x03FF; + uint v = (this.PackedValue >> 20) & 0x03FF; + uint a = (this.PackedValue >> 30) & 0x03; + + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx + // Y' = Y - 64 + // Cb' = Cb - 512 + // Cr' = Cr - 512 + y -= 64; + u -= 512; + v -= 512; + + // R = 1.1678Y' + 1.6007Cr' + // G = 1.1678Y' - 0.3929Cb' - 0.8152Cr' + // B = 1.1678Y' + 2.0232Cb' + return ColorSpaceConversion.YuvToRgba10Bit(y, u, v, a); } } diff --git a/src/ImageSharp.Textures/PixelFormats/Y416.cs b/src/ImageSharp.Textures/PixelFormats/Y416.cs index 33887f04..ecc42eb4 100644 --- a/src/ImageSharp.Textures/PixelFormats/Y416.cs +++ b/src/ImageSharp.Textures/PixelFormats/Y416.cs @@ -1,148 +1,146 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.PixelFormats +namespace SixLabors.ImageSharp.Textures.PixelFormats; + +/// +/// Pixel format for 16-bit per channel packed YUV 4:4:4 data. +/// +public struct Y416 : IPixel, IPackedVector { + /// + public ulong PackedValue { get; set; } + /// - /// Pixel format for 16-bit per channel packed YUV 4:4:4 data. + /// Gets or sets the packed representation of the Y416 struct. /// - public struct Y416 : IPixel, IPackedVector + public ulong Yuv { - /// - public ulong PackedValue { get; set; } - - /// - /// Gets or sets the packed representation of the Y416 struct. - /// - public ulong Yuv - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Unsafe.As(ref this) = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Y416 left, Y416 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Y416 left, Y416 right) => !left.Equals(right); + set => Unsafe.As(ref this) = value; + } - /// - public override readonly bool Equals(object? obj) => obj is Y416 other && this.Equals(other); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Y416 left, Y416 right) => left.Equals(right); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(Y416 other) => this.Yuv.Equals(other.Yuv); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Y416 left, Y416 right) => !left.Equals(right); + + /// + public override readonly bool Equals(object? obj) => obj is Y416 other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Y416 other) => this.Yuv.Equals(other.Yuv); + + /// + public override readonly string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"Y416({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } - /// - public override readonly string ToString() - { - var vector = this.ToVector4(); - return FormattableString.Invariant($"Y416({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly PixelOperations CreatePixelOperations() => new(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public PixelOperations CreatePixelOperations() => new(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => this.Yuv.GetHashCode(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override readonly int GetHashCode() => this.Yuv.GetHashCode(); + /// + public void FromArgb32(Argb32 source) => throw new NotImplementedException(); - /// - public void FromArgb32(Argb32 source) => throw new NotImplementedException(); + /// + public void FromBgr24(Bgr24 source) => throw new NotImplementedException(); - /// - public void FromBgr24(Bgr24 source) => throw new NotImplementedException(); + /// + public void FromBgra32(Bgra32 source) => throw new NotImplementedException(); - /// - public void FromBgra32(Bgra32 source) => throw new NotImplementedException(); + /// + public void FromAbgr32(Abgr32 source) => throw new NotImplementedException(); - /// - public void FromAbgr32(Abgr32 source) => throw new NotImplementedException(); + /// + public void FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); - /// - public void FromBgra5551(Bgra5551 source) => throw new NotImplementedException(); + /// + public void FromL16(L16 source) => throw new NotImplementedException(); - /// - public void FromL16(L16 source) => throw new NotImplementedException(); + /// + public void FromL8(L8 source) => throw new NotImplementedException(); - /// - public void FromL8(L8 source) => throw new NotImplementedException(); + /// + public void FromLa16(La16 source) => throw new NotImplementedException(); - /// - public void FromLa16(La16 source) => throw new NotImplementedException(); + /// + public void FromLa32(La32 source) => throw new NotImplementedException(); - /// - public void FromLa32(La32 source) => throw new NotImplementedException(); + /// + public void FromRgb24(Rgb24 source) => throw new NotImplementedException(); - /// - public void FromRgb24(Rgb24 source) => throw new NotImplementedException(); + /// + public void FromRgb48(Rgb48 source) => throw new NotImplementedException(); - /// - public void FromRgb48(Rgb48 source) => throw new NotImplementedException(); + /// + public void FromRgba32(Rgba32 source) => throw new NotImplementedException(); - /// - public void FromRgba32(Rgba32 source) => throw new NotImplementedException(); + /// + public void FromRgba64(Rgba64 source) => throw new NotImplementedException(); - /// - public void FromRgba64(Rgba64 source) => throw new NotImplementedException(); + /// + public void FromScaledVector4(Vector4 vector) => throw new NotImplementedException(); - /// - public void FromScaledVector4(Vector4 vector) => throw new NotImplementedException(); + /// + public void FromVector4(Vector4 vector) => throw new NotImplementedException(); - /// - public void FromVector4(Vector4 vector) => throw new NotImplementedException(); + /// + public void ToRgba32(ref Rgba32 dest) => throw new NotImplementedException(); - /// - public void ToRgba32(ref Rgba32 dest) => throw new NotImplementedException(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() + { + uint u = (uint)(this.PackedValue & 0xFFFF); + uint y = (uint)((this.PackedValue >> 16) & 0xFFFF); + uint v = (uint)((this.PackedValue >> 32) & 0xFFFF); + uint a = (uint)((this.PackedValue >> 48) & 0xFFFF); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() - { - uint u = (uint)(this.PackedValue & 0xFFFF); - uint y = (uint)((this.PackedValue >> 16) & 0xFFFF); - uint v = (uint)((this.PackedValue >> 32) & 0xFFFF); - uint a = (uint)((this.PackedValue >> 48) & 0xFFFF); - - // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx - - // Y' = Y - 4096 - // Cb' = Cb - 32768 - // Cr' = Cr - 32768 - y -= 4096; - u -= 32768; - v -= 32768; - - return ColorSpaceConversion.YuvToRgba16Bit(y, u, v, a); - } + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb970578.aspx + + // Y' = Y - 4096 + // Cb' = Cb - 32768 + // Cr' = Cr - 32768 + y -= 4096; + u -= 32768; + v -= 32768; + + return ColorSpaceConversion.YuvToRgba16Bit(y, u, v, a); } } diff --git a/src/ImageSharp.Textures/Texture.FromStream.cs b/src/ImageSharp.Textures/Texture.FromStream.cs index fcd7dfd1..36d2a454 100644 --- a/src/ImageSharp.Textures/Texture.FromStream.cs +++ b/src/ImageSharp.Textures/Texture.FromStream.cs @@ -181,7 +181,7 @@ private static T WithSeekableStream(Configuration config, Stream stream, Func } // We want to be able to load images from things like HttpContext.Request.Body - using var memoryStream = new MemoryStream(); + using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); memoryStream.Position = 0; diff --git a/src/ImageSharp.Textures/Texture.cs b/src/ImageSharp.Textures/Texture.cs index c4f97e47..f6ddce89 100644 --- a/src/ImageSharp.Textures/Texture.cs +++ b/src/ImageSharp.Textures/Texture.cs @@ -1,31 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures; -namespace SixLabors.ImageSharp.Textures +/// +/// Represents a texture. +/// +public abstract partial class Texture : IDisposable { - /// - /// Represents a texture. - /// - public abstract partial class Texture : IDisposable + /// + public void Dispose() { - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } + this.Dispose(true); + GC.SuppressFinalize(this); + } - /// - /// Throws if the image is disposed. - /// - internal abstract void EnsureNotDisposed(); + /// + /// Throws if the image is disposed. + /// + internal abstract void EnsureNotDisposed(); - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose of managed and unmanaged objects. - protected abstract void Dispose(bool disposing); - } + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected abstract void Dispose(bool disposing); } diff --git a/src/ImageSharp.Textures/TextureFormats/CubemapTexture.cs b/src/ImageSharp.Textures/TextureFormats/CubemapTexture.cs index fc51a3d1..5a1d1864 100644 --- a/src/ImageSharp.Textures/TextureFormats/CubemapTexture.cs +++ b/src/ImageSharp.Textures/TextureFormats/CubemapTexture.cs @@ -1,84 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats +/// +/// Represents a cube map texture. +/// +/// +public class CubemapTexture : Texture { + private bool isDisposed; + /// - /// Represents a cube map texture. + /// Initializes a new instance of the class. /// - /// - public class CubemapTexture : Texture + public CubemapTexture() { - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - public CubemapTexture() - { - this.PositiveX = new FlatTexture(); - this.NegativeX = new FlatTexture(); - this.PositiveY = new FlatTexture(); - this.NegativeY = new FlatTexture(); - this.PositiveZ = new FlatTexture(); - this.NegativeZ = new FlatTexture(); - } + this.PositiveX = new FlatTexture(); + this.NegativeX = new FlatTexture(); + this.PositiveY = new FlatTexture(); + this.NegativeY = new FlatTexture(); + this.PositiveZ = new FlatTexture(); + this.NegativeZ = new FlatTexture(); + } - /// - /// Gets the positive x texture. - /// - public FlatTexture PositiveX { get; } + /// + /// Gets the positive x texture. + /// + public FlatTexture PositiveX { get; } - /// - /// Gets the negative x texture. - /// - public FlatTexture NegativeX { get; } + /// + /// Gets the negative x texture. + /// + public FlatTexture NegativeX { get; } - /// - /// Gets the positive y texture. - /// - public FlatTexture PositiveY { get; } + /// + /// Gets the positive y texture. + /// + public FlatTexture PositiveY { get; } - /// - /// Gets the negative y texture. - /// - public FlatTexture NegativeY { get; } + /// + /// Gets the negative y texture. + /// + public FlatTexture NegativeY { get; } - /// - /// Gets the positive z texture. - /// - public FlatTexture PositiveZ { get; } + /// + /// Gets the positive z texture. + /// + public FlatTexture PositiveZ { get; } - /// - /// Gets the negative z texture. - /// - public FlatTexture NegativeZ { get; } + /// + /// Gets the negative z texture. + /// + public FlatTexture NegativeZ { get; } - /// - protected override void Dispose(bool disposing) + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - this.PositiveX.Dispose(); - this.NegativeX.Dispose(); - this.PositiveY.Dispose(); - this.NegativeY.Dispose(); - this.PositiveZ.Dispose(); - this.NegativeZ.Dispose(); - } + return; + } - this.isDisposed = true; + if (disposing) + { + this.PositiveX.Dispose(); + this.NegativeX.Dispose(); + this.PositiveY.Dispose(); + this.NegativeY.Dispose(); + this.PositiveZ.Dispose(); + this.NegativeZ.Dispose(); } - /// - internal override void EnsureNotDisposed() - => ObjectDisposedException.ThrowIf(this.isDisposed, "Trying to execute an operation on a disposed image."); + this.isDisposed = true; } + + /// + internal override void EnsureNotDisposed() + => ObjectDisposedException.ThrowIf(this.isDisposed, "Trying to execute an operation on a disposed image."); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/A8.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/A8.cs index 98be4a8d..3269fe4b 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/A8.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/A8.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixel data which only contains the alpha value. - /// - internal struct A8 : IBlock - { - /// - public int BitsPerPixel => 8; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 1; +/// +/// Texture for pixel data which only contains the alpha value. +/// +internal struct A8 : IBlock +{ + /// + public readonly int BitsPerPixel => 8; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 1; - /// - public byte CompressedBytesPerBlock => 1; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 1; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Ayuv.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Ayuv.cs index 7277cba0..ceff2887 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Ayuv.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Ayuv.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for YUV 4:4:4 pixel data. - /// - internal struct Ayuv : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture for YUV 4:4:4 pixel data. +/// +internal struct Ayuv : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc4.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc4.cs index 9fc80456..3ff52e04 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc4.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc4.cs @@ -3,106 +3,105 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with BC4 with one color channel (8 bits). +/// +internal struct Bc4 : IBlock { - /// - /// Texture compressed with BC4 with one color channel (8 bits). - /// - internal struct Bc4 : IBlock - { - /// - public int BitsPerPixel => 8; + /// + public readonly int BitsPerPixel => 8; - /// - public byte PixelDepthBytes => 1; + /// + public readonly byte PixelDepthBytes => 1; - /// - public byte DivSize => 4; + /// + public readonly byte DivSize => 4; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + Bc4 self = this; + + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => { - Bc4 self = this; + byte red0 = blockData[streamIndex++]; + byte red1 = blockData[streamIndex++]; + ulong rIndex = blockData[streamIndex++]; + rIndex |= (ulong)blockData[streamIndex++] << 8; + rIndex |= (ulong)blockData[streamIndex++] << 16; + rIndex |= (ulong)blockData[streamIndex++] << 24; + rIndex |= (ulong)blockData[streamIndex++] << 32; + rIndex |= (ulong)blockData[streamIndex++] << 40; - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + for (int i = 0; i < 16; ++i) { - byte red0 = blockData[streamIndex++]; - byte red1 = blockData[streamIndex++]; - ulong rIndex = blockData[streamIndex++]; - rIndex |= (ulong)blockData[streamIndex++] << 8; - rIndex |= (ulong)blockData[streamIndex++] << 16; - rIndex |= (ulong)blockData[streamIndex++] << 24; - rIndex |= (ulong)blockData[streamIndex++] << 32; - rIndex |= (ulong)blockData[streamIndex++] << 40; - - for (int i = 0; i < 16; ++i) - { - byte index = (byte)((uint)(rIndex >> (3 * i)) & 0x07); + byte index = (byte)((uint)(rIndex >> (3 * i)) & 0x07); - data[dataIndex++] = InterpolateColor(index, red0, red1); + data[dataIndex++] = InterpolateColor(index, red0, red1); - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + // Is mult 4? + if (((i + 1) & 0x3) == 0) + { + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } + } - return streamIndex; - }); - } + return streamIndex; + }); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static byte InterpolateColor(byte index, byte red0, byte red1) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static byte InterpolateColor(byte index, byte red0, byte red1) + { + byte red; + if (index == 0) { - byte red; - if (index == 0) - { - red = red0; - } - else if (index == 1) + red = red0; + } + else if (index == 1) + { + red = red1; + } + else + { + if (red0 > red1) { - red = red1; + index -= 1; + red = (byte)((((red0 * (7 - index)) + (red1 * index)) / 7.0f) + 0.5f); } else { - if (red0 > red1) + if (index == 6) { - index -= 1; - red = (byte)((((red0 * (7 - index)) + (red1 * index)) / 7.0f) + 0.5f); + red = 0; + } + else if (index == 7) + { + red = 255; } else { - if (index == 6) - { - red = 0; - } - else if (index == 7) - { - red = 255; - } - else - { - index -= 1; - red = (byte)((((red0 * (5 - index)) + (red1 * index)) / 5.0f) + 0.5f); - } + index -= 1; + red = (byte)((((red0 * (5 - index)) + (red1 * index)) / 5.0f) + 0.5f); } } - - return red; } + + return red; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc4s.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc4s.cs index e70f1575..cba718b9 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc4s.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc4s.cs @@ -3,111 +3,110 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with BC4S with one color channel (8 bits). +/// +internal struct Bc4s : IBlock { - /// - /// Texture compressed with BC4S with one color channel (8 bits). - /// - internal struct Bc4s : IBlock - { - private const float Multiplier = 255.0f / 254.0f; + private const float Multiplier = 255.0f / 254.0f; - /// - public int BitsPerPixel => 8; + /// + public readonly int BitsPerPixel => 8; - /// - public byte PixelDepthBytes => 1; + /// + public readonly byte PixelDepthBytes => 1; - /// - public byte DivSize => 4; + /// + public readonly byte DivSize => 4; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - Bc4s self = this; + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + Bc4s self = this; - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + { + sbyte red0 = (sbyte)blockData[streamIndex++]; + sbyte red1 = (sbyte)blockData[streamIndex++]; + red0 = red0 == -128 ? (sbyte)-127 : red0; + red1 = red1 == -128 ? (sbyte)-127 : red1; + + ulong rIndex = blockData[streamIndex++]; + rIndex |= (ulong)blockData[streamIndex++] << 8; + rIndex |= (ulong)blockData[streamIndex++] << 16; + rIndex |= (ulong)blockData[streamIndex++] << 24; + rIndex |= (ulong)blockData[streamIndex++] << 32; + rIndex |= (ulong)blockData[streamIndex++] << 40; + + for (int i = 0; i < 16; ++i) { - sbyte red0 = (sbyte)blockData[streamIndex++]; - sbyte red1 = (sbyte)blockData[streamIndex++]; - red0 = red0 == -128 ? (sbyte)-127 : red0; - red1 = red1 == -128 ? (sbyte)-127 : red1; - - ulong rIndex = blockData[streamIndex++]; - rIndex |= (ulong)blockData[streamIndex++] << 8; - rIndex |= (ulong)blockData[streamIndex++] << 16; - rIndex |= (ulong)blockData[streamIndex++] << 24; - rIndex |= (ulong)blockData[streamIndex++] << 32; - rIndex |= (ulong)blockData[streamIndex++] << 40; - - for (int i = 0; i < 16; ++i) - { - uint index = (byte)((uint)(rIndex >> (3 * i)) & 0x07); + uint index = (byte)((uint)(rIndex >> (3 * i)) & 0x07); - data[dataIndex++] = InterpolateColor((byte)index, red0, red1); + data[dataIndex++] = InterpolateColor((byte)index, red0, red1); - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + // Is mult 4? + if (((i + 1) & 0x3) == 0) + { + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } + } - return streamIndex; - }); - } + return streamIndex; + }); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static byte InterpolateColor(byte index, sbyte red0, sbyte red1) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static byte InterpolateColor(byte index, sbyte red0, sbyte red1) + { + float red; + if (index == 0) { - float red; - if (index == 0) - { - red = red0; - } - else if (index == 1) + red = red0; + } + else if (index == 1) + { + red = red1; + } + else + { + if (red0 > red1) { - red = red1; + index -= 1; + red = ((red0 * (7 - index)) + (red1 * index)) / 7.0f; } else { - if (red0 > red1) + if (index == 6) { - index -= 1; - red = ((red0 * (7 - index)) + (red1 * index)) / 7.0f; + red = -127.0f; + } + else if (index == 7) + { + red = 127.0f; } else { - if (index == 6) - { - red = -127.0f; - } - else if (index == 7) - { - red = 127.0f; - } - else - { - index -= 1; - red = ((red0 * (5 - index)) + (red1 * index)) / 5.0f; - } + index -= 1; + red = ((red0 * (5 - index)) + (red1 * index)) / 5.0f; } } - - return (byte)(((red + 127) * Multiplier) + 0.5f); } + + return (byte)(((red + 127) * Multiplier) + 0.5f); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc5.cs index d1f535fc..990d4ab7 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc5.cs @@ -1,112 +1,110 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with BC5 with two color channels, red and green. +/// +internal struct Bc5 : IBlock { - /// - /// Texture compressed with BC5 with two color channels, red and green. - /// - internal struct Bc5 : IBlock - { - /// - public int BitsPerPixel => 24; + /// + public readonly int BitsPerPixel => 24; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 4; + /// + public readonly byte DivSize => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public readonly byte CompressedBytesPerBlock => 16; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); - // Should RG format be used instead RGB24? - return Image.LoadPixelData(decompressedData, width, height); - } + // Should RG format be used instead RGB24? + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - Bc5 self = this; + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + Bc5 self = this; - byte[] firstGradient = new byte[8]; - byte[] secondGradient = new byte[8]; + byte[] firstGradient = new byte[8]; + byte[] secondGradient = new byte[8]; - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + { + streamIndex = ExtractGradient(firstGradient, blockData, streamIndex); + ulong firstCodes = blockData[streamIndex++]; + firstCodes |= (ulong)blockData[streamIndex++] << 8; + firstCodes |= (ulong)blockData[streamIndex++] << 16; + firstCodes |= (ulong)blockData[streamIndex++] << 24; + firstCodes |= (ulong)blockData[streamIndex++] << 32; + firstCodes |= (ulong)blockData[streamIndex++] << 40; + + streamIndex = ExtractGradient(secondGradient, blockData, streamIndex); + ulong secondCodes = blockData[streamIndex++]; + secondCodes |= (ulong)blockData[streamIndex++] << 8; + secondCodes |= (ulong)blockData[streamIndex++] << 16; + secondCodes |= (ulong)blockData[streamIndex++] << 24; + secondCodes |= (ulong)blockData[streamIndex++] << 32; + secondCodes |= (ulong)blockData[streamIndex++] << 40; + + for (int alphaShift = 0; alphaShift < 48; alphaShift += 12) { - streamIndex = ExtractGradient(firstGradient, blockData, streamIndex); - ulong firstCodes = blockData[streamIndex++]; - firstCodes |= (ulong)blockData[streamIndex++] << 8; - firstCodes |= (ulong)blockData[streamIndex++] << 16; - firstCodes |= (ulong)blockData[streamIndex++] << 24; - firstCodes |= (ulong)blockData[streamIndex++] << 32; - firstCodes |= (ulong)blockData[streamIndex++] << 40; - - streamIndex = ExtractGradient(secondGradient, blockData, streamIndex); - ulong secondCodes = blockData[streamIndex++]; - secondCodes |= (ulong)blockData[streamIndex++] << 8; - secondCodes |= (ulong)blockData[streamIndex++] << 16; - secondCodes |= (ulong)blockData[streamIndex++] << 24; - secondCodes |= (ulong)blockData[streamIndex++] << 32; - secondCodes |= (ulong)blockData[streamIndex++] << 40; - - for (int alphaShift = 0; alphaShift < 48; alphaShift += 12) + for (int j = 0; j < 4; j++) { - for (int j = 0; j < 4; j++) - { - // 3 bits determine alpha index to use. - byte firstIndex = (byte)((firstCodes >> (alphaShift + (3 * j))) & 0x07); - byte secondIndex = (byte)((secondCodes >> (alphaShift + (3 * j))) & 0x07); - data[dataIndex++] = firstGradient[firstIndex]; - data[dataIndex++] = secondGradient[secondIndex]; - data[dataIndex++] = 0; // Skip blue. - } - - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); + // 3 bits determine alpha index to use. + byte firstIndex = (byte)((firstCodes >> (alphaShift + (3 * j))) & 0x07); + byte secondIndex = (byte)((secondCodes >> (alphaShift + (3 * j))) & 0x07); + data[dataIndex++] = firstGradient[firstIndex]; + data[dataIndex++] = secondGradient[secondIndex]; + data[dataIndex++] = 0; // Skip blue. } - return streamIndex; - }); - } + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int ExtractGradient(Span gradient, Span stream, int bIndex) - { - byte endpoint0; - byte endpoint1; - gradient[0] = endpoint0 = stream[bIndex++]; - gradient[1] = endpoint1 = stream[bIndex++]; + return streamIndex; + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int ExtractGradient(Span gradient, Span stream, int bIndex) + { + byte endpoint0; + byte endpoint1; + gradient[0] = endpoint0 = stream[bIndex++]; + gradient[1] = endpoint1 = stream[bIndex++]; - if (endpoint0 > endpoint1) + if (endpoint0 > endpoint1) + { + for (int i = 1; i < 7; i++) { - for (int i = 1; i < 7; i++) - { - gradient[1 + i] = (byte)((((7 - i) * endpoint0) + (i * endpoint1)) / 7); - } + gradient[1 + i] = (byte)((((7 - i) * endpoint0) + (i * endpoint1)) / 7); } - else + } + else + { + for (int i = 1; i < 5; ++i) { - for (int i = 1; i < 5; ++i) - { - gradient[1 + i] = (byte)((((5 - i) * endpoint0) + (i * endpoint1)) / 5); - } - - gradient[6] = 0; - gradient[7] = 255; + gradient[1 + i] = (byte)((((5 - i) * endpoint0) + (i * endpoint1)) / 5); } - return bIndex; + gradient[6] = 0; + gradient[7] = 255; } + + return bIndex; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc5s.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc5s.cs index abd1b526..92f8472c 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc5s.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc5s.cs @@ -1,84 +1,83 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with BC5S with two color channels, red and green. +/// +internal struct Bc5s : IBlock { - /// - /// Texture compressed with BC5S with two color channels, red and green. - /// - internal struct Bc5s : IBlock - { - /// - public int BitsPerPixel => 24; + /// + public readonly int BitsPerPixel => 24; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 4; + /// + public readonly byte DivSize => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public readonly byte CompressedBytesPerBlock => 16; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + + // Should RG format be used instead RGB24? + return Image.LoadPixelData(decompressedData, width, height); + } - // Should RG format be used instead RGB24? - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + Bc5s self = this; - /// - public byte[] Decompress(byte[] blockData, int width, int height) + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => { - Bc5s self = this; + sbyte red0 = (sbyte)blockData[streamIndex++]; + sbyte red1 = (sbyte)blockData[streamIndex++]; + red0 = red0 == -128 ? (sbyte)-127 : red0; + red1 = red1 == -128 ? (sbyte)-127 : red1; + ulong rIndex = blockData[streamIndex++]; + rIndex |= (ulong)blockData[streamIndex++] << 8; + rIndex |= (ulong)blockData[streamIndex++] << 16; + rIndex |= (ulong)blockData[streamIndex++] << 24; + rIndex |= (ulong)blockData[streamIndex++] << 32; + rIndex |= (ulong)blockData[streamIndex++] << 40; + + sbyte green0 = (sbyte)blockData[streamIndex++]; + sbyte green1 = (sbyte)blockData[streamIndex++]; + green0 = green0 == -128 ? (sbyte)-127 : green0; + green1 = green1 == -128 ? (sbyte)-127 : green1; + ulong gIndex = blockData[streamIndex++]; + gIndex |= (ulong)blockData[streamIndex++] << 8; + gIndex |= (ulong)blockData[streamIndex++] << 16; + gIndex |= (ulong)blockData[streamIndex++] << 24; + gIndex |= (ulong)blockData[streamIndex++] << 32; + gIndex |= (ulong)blockData[streamIndex++] << 40; - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + for (int i = 0; i < 16; ++i) { - sbyte red0 = (sbyte)blockData[streamIndex++]; - sbyte red1 = (sbyte)blockData[streamIndex++]; - red0 = red0 == -128 ? (sbyte)-127 : red0; - red1 = red1 == -128 ? (sbyte)-127 : red1; - ulong rIndex = blockData[streamIndex++]; - rIndex |= (ulong)blockData[streamIndex++] << 8; - rIndex |= (ulong)blockData[streamIndex++] << 16; - rIndex |= (ulong)blockData[streamIndex++] << 24; - rIndex |= (ulong)blockData[streamIndex++] << 32; - rIndex |= (ulong)blockData[streamIndex++] << 40; + byte rSel = (byte)((uint)(rIndex >> (3 * i)) & 0x07); + byte gSel = (byte)((uint)(gIndex >> (3 * i)) & 0x07); - sbyte green0 = (sbyte)blockData[streamIndex++]; - sbyte green1 = (sbyte)blockData[streamIndex++]; - green0 = green0 == -128 ? (sbyte)-127 : green0; - green1 = green1 == -128 ? (sbyte)-127 : green1; - ulong gIndex = blockData[streamIndex++]; - gIndex |= (ulong)blockData[streamIndex++] << 8; - gIndex |= (ulong)blockData[streamIndex++] << 16; - gIndex |= (ulong)blockData[streamIndex++] << 24; - gIndex |= (ulong)blockData[streamIndex++] << 32; - gIndex |= (ulong)blockData[streamIndex++] << 40; + data[dataIndex++] = Bc4s.InterpolateColor(rSel, red0, red1); + data[dataIndex++] = Bc4s.InterpolateColor(gSel, green0, green1); + data[dataIndex++] = 0; // Skip blue. - for (int i = 0; i < 16; ++i) + // Is mult 4? + if (((i + 1) & 0x3) == 0) { - byte rSel = (byte)((uint)(rIndex >> (3 * i)) & 0x07); - byte gSel = (byte)((uint)(gIndex >> (3 * i)) & 0x07); - - data[dataIndex++] = Bc4s.InterpolateColor(rSel, red0, red1); - data[dataIndex++] = Bc4s.InterpolateColor(gSel, green0, green1); - data[dataIndex++] = 0; // Skip blue. - - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } + } - return streamIndex; - }); - } + return streamIndex; + }); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6h.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6h.cs index 6636c9f0..68e17853 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6h.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6h.cs @@ -1,583 +1,567 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Textures.Common.Helpers; using SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with BC6H, three color channels (16 bits:16 bits:16 bits) in "half" floating point. +/// +internal struct Bc6h : IBlock { - /// - /// Texture compressed with BC6H, three color channels (16 bits:16 bits:16 bits) in "half" floating point. - /// - internal struct Bc6h : IBlock + // Code based on commit 138efff1b9c53fd9a5dd34b8c865e8f5ae798030 2019/10/24 in DirectXTex C++ library + private static readonly Bc6HModeDescriptor[][] ModeDescriptors = + [ + [ + // Mode 1 (0x00) - 10 5 5 5 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 2 (0x01) - 7 6 6 6 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GZ, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.BY, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), + new Bc6HModeDescriptor(Bc6hEField.RY, 5), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.RZ, 5), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 3 (0x02) - 11 5 4 4 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 10), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 10), + new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 10), + new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 4 (0x06) - 11 4 5 4 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 10), + new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 10), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 10), + new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 0), + new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 5 (0x0a) - 11 4 4 5 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 10), + new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 10), + new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 10), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 1), + new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 6 (0x0e) - 9 5 5 5 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 7 (0x12) - 8 6 5 5 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), + new Bc6HModeDescriptor(Bc6hEField.RY, 5), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.RZ, 5), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 8 (0x16) - 8 5 6 5 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GY, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.GZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 9 (0x1a) - 8 5 5 6 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.BY, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), + new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 10 (0x1e) - 6 6 6 6 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 5), new Bc6HModeDescriptor(Bc6hEField.BY, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), + new Bc6HModeDescriptor(Bc6hEField.RY, 5), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.RZ, 5), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), + new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 11 (0x03) - 10 10 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.RX, 6), new Bc6HModeDescriptor(Bc6hEField.RX, 7), new Bc6HModeDescriptor(Bc6hEField.RX, 8), new Bc6HModeDescriptor(Bc6hEField.RX, 9), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GX, 6), new Bc6HModeDescriptor(Bc6hEField.GX, 7), new Bc6HModeDescriptor(Bc6hEField.GX, 8), new Bc6HModeDescriptor(Bc6hEField.GX, 9), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BX, 6), new Bc6HModeDescriptor(Bc6hEField.BX, 7), new Bc6HModeDescriptor(Bc6hEField.BX, 8), new Bc6HModeDescriptor(Bc6hEField.BX, 9), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), + new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), + new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0) + ], + + [ + // Mode 12 (0x07) - 11 9 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.RX, 6), new Bc6HModeDescriptor(Bc6hEField.RX, 7), new Bc6HModeDescriptor(Bc6hEField.RX, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 10), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GX, 6), new Bc6HModeDescriptor(Bc6hEField.GX, 7), new Bc6HModeDescriptor(Bc6hEField.GX, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 10), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BX, 6), new Bc6HModeDescriptor(Bc6hEField.BX, 7), new Bc6HModeDescriptor(Bc6hEField.BX, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 10), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), + new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), + new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0) + ], + + [ + // Mode 13 (0x0b) - 12 8 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), + new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.RX, 6), new Bc6HModeDescriptor(Bc6hEField.RX, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 11), new Bc6HModeDescriptor(Bc6hEField.RW, 10), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), + new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GX, 6), new Bc6HModeDescriptor(Bc6hEField.GX, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 11), new Bc6HModeDescriptor(Bc6hEField.GW, 10), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), + new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BX, 6), new Bc6HModeDescriptor(Bc6hEField.BX, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 11), new Bc6HModeDescriptor(Bc6hEField.BW, 10), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), + new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), + new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0) + ], + + [ + // Mode 14 (0x0f) - 16 4 + new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), + new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), + new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), + new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 15), + new Bc6HModeDescriptor(Bc6hEField.RW, 14), new Bc6HModeDescriptor(Bc6hEField.RW, 13), new Bc6HModeDescriptor(Bc6hEField.RW, 12), new Bc6HModeDescriptor(Bc6hEField.RW, 11), new Bc6HModeDescriptor(Bc6hEField.RW, 10), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 15), + new Bc6HModeDescriptor(Bc6hEField.GW, 14), new Bc6HModeDescriptor(Bc6hEField.GW, 13), new Bc6HModeDescriptor(Bc6hEField.GW, 12), new Bc6HModeDescriptor(Bc6hEField.GW, 11), new Bc6HModeDescriptor(Bc6hEField.GW, 10), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 15), + new Bc6HModeDescriptor(Bc6hEField.BW, 14), new Bc6HModeDescriptor(Bc6hEField.BW, 13), new Bc6HModeDescriptor(Bc6hEField.BW, 12), new Bc6HModeDescriptor(Bc6hEField.BW, 11), new Bc6HModeDescriptor(Bc6hEField.BW, 10), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), + new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), + new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0) + ], + ]; + + private static readonly Bc6hModeInfo[] ModeInfo = + [ + new Bc6hModeInfo(0x00, 1, true, 3, [[new LdrColorA(10, 10, 10, 0), new LdrColorA(5, 5, 5, 0)], [new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0)]]), // Mode 1 + new Bc6hModeInfo(0x01, 1, true, 3, [[new LdrColorA(7, 7, 7, 0), new LdrColorA(6, 6, 6, 0)], [new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0)]]), // Mode 2 + new Bc6hModeInfo(0x02, 1, true, 3, [[new LdrColorA(11, 11, 11, 0), new LdrColorA(5, 4, 4, 0)], [new LdrColorA(5, 4, 4, 0), new LdrColorA(5, 4, 4, 0)]]), // Mode 3 + new Bc6hModeInfo(0x06, 1, true, 3, [[new LdrColorA(11, 11, 11, 0), new LdrColorA(4, 5, 4, 0)], [new LdrColorA(4, 5, 4, 0), new LdrColorA(4, 5, 4, 0)]]), // Mode 4 + new Bc6hModeInfo(0x0a, 1, true, 3, [[new LdrColorA(11, 11, 11, 0), new LdrColorA(4, 4, 5, 0)], [new LdrColorA(4, 4, 5, 0), new LdrColorA(4, 4, 5, 0)]]), // Mode 5 + new Bc6hModeInfo(0x0e, 1, true, 3, [[new LdrColorA(9, 9, 9, 0), new LdrColorA(5, 5, 5, 0)], [new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0)]]), // Mode 6 + new Bc6hModeInfo(0x12, 1, true, 3, [[new LdrColorA(8, 8, 8, 0), new LdrColorA(6, 5, 5, 0)], [new LdrColorA(6, 5, 5, 0), new LdrColorA(6, 5, 5, 0)]]), // Mode 7 + new Bc6hModeInfo(0x16, 1, true, 3, [[new LdrColorA(8, 8, 8, 0), new LdrColorA(5, 6, 5, 0)], [new LdrColorA(5, 6, 5, 0), new LdrColorA(5, 6, 5, 0)]]), // Mode 8 + new Bc6hModeInfo(0x1a, 1, true, 3, [[new LdrColorA(8, 8, 8, 0), new LdrColorA(5, 5, 6, 0)], [new LdrColorA(5, 5, 6, 0), new LdrColorA(5, 5, 6, 0)]]), // Mode 9 + new Bc6hModeInfo(0x1e, 1, false, 3, [[new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0)], [new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0)]]), // Mode 10 + new Bc6hModeInfo(0x03, 0, false, 4, [[new LdrColorA(10, 10, 10, 0), new LdrColorA(10, 10, 10, 0)], [new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0)]]), // Mode 11 + new Bc6hModeInfo(0x07, 0, true, 4, [[new LdrColorA(11, 11, 11, 0), new LdrColorA(9, 9, 9, 0)], [new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0)]]), // Mode 12 + new Bc6hModeInfo(0x0b, 0, true, 4, [[new LdrColorA(12, 12, 12, 0), new LdrColorA(8, 8, 8, 0)], [new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0)]]), // Mode 13 + new Bc6hModeInfo(0x0f, 0, true, 4, [[new LdrColorA(16, 16, 16, 0), new LdrColorA(4, 4, 4, 0)], [new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0)]]), // Mode 14 + ]; + + private static readonly int[] ModeToInfo = + [ + 0, // Mode 1 - 0x00 + 1, // Mode 2 - 0x01 + 2, // Mode 3 - 0x02 + 10, // Mode 11 - 0x03 + -1, // Invalid - 0x04 + -1, // Invalid - 0x05 + 3, // Mode 4 - 0x06 + 11, // Mode 12 - 0x07 + -1, // Invalid - 0x08 + -1, // Invalid - 0x09 + 4, // Mode 5 - 0x0a + 12, // Mode 13 - 0x0b + -1, // Invalid - 0x0c + -1, // Invalid - 0x0d + 5, // Mode 6 - 0x0e + 13, // Mode 14 - 0x0f + -1, // Invalid - 0x10 + -1, // Invalid - 0x11 + 6, // Mode 7 - 0x12 + -1, // Reserved - 0x13 + -1, // Invalid - 0x14 + -1, // Invalid - 0x15 + 7, // Mode 8 - 0x16 + -1, // Reserved - 0x17 + -1, // Invalid - 0x18 + -1, // Invalid - 0x19 + 8, // Mode 9 - 0x1a + -1, // Reserved - 0x1b + -1, // Invalid - 0x1c + -1, // Invalid - 0x1d + 9, // Mode 10 - 0x1e + -1, // Resreved - 0x1f + ]; + + /// + public readonly int BitsPerPixel => 32; + + /// + public readonly byte PixelDepthBytes => 4; + + /// + public readonly byte DivSize => 4; + + /// + public readonly byte CompressedBytesPerBlock => 16; + + /// + public readonly bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) { - // Code based on commit 138efff1b9c53fd9a5dd34b8c865e8f5ae798030 2019/10/24 in DirectXTex C++ library - private static readonly Bc6HModeDescriptor[][] ModeDescriptors = new[] + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + Bc6h self = this; + byte[] currentBlock = new byte[this.CompressedBytesPerBlock]; + + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => { - new[] - { - // Mode 1 (0x00) - 10 5 5 5 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 2 (0x01) - 7 6 6 6 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GZ, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.BY, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), - new Bc6HModeDescriptor(Bc6hEField.RY, 5), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.RZ, 5), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 3 (0x02) - 11 5 4 4 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 10), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 10), - new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 10), - new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 4 (0x06) - 11 4 5 4 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 10), - new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 10), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 10), - new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 0), - new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 5 (0x0a) - 11 4 4 5 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 10), - new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 10), - new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 10), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 1), - new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 6 (0x0e) - 9 5 5 5 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 7 (0x12) - 8 6 5 5 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), - new Bc6HModeDescriptor(Bc6hEField.RY, 5), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.RZ, 5), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] + // I would prefer to use Span, but not sure if I should reference System.Memory in this project + // copy data instead + Buffer.BlockCopy(blockData, streamIndex, currentBlock, 0, currentBlock.Length); + streamIndex += currentBlock.Length; + + uint uStartBit = 0; + byte uMode = GetBits(currentBlock, ref uStartBit, 2u); + if (uMode is not 0x00 and not 0x01) { - // Mode 8 (0x16) - 8 5 6 5 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GY, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.GZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 9 (0x1a) - 8 5 5 6 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.BY, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), - new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 10 (0x1e) - 6 6 6 6 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 4), new Bc6HModeDescriptor(Bc6hEField.BZ, 0), new Bc6HModeDescriptor(Bc6hEField.BZ, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 4), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 5), new Bc6HModeDescriptor(Bc6hEField.BY, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 4), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 3), new Bc6HModeDescriptor(Bc6hEField.BZ, 5), new Bc6HModeDescriptor(Bc6hEField.BZ, 4), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.GY, 0), new Bc6HModeDescriptor(Bc6hEField.GY, 1), new Bc6HModeDescriptor(Bc6hEField.GY, 2), new Bc6HModeDescriptor(Bc6hEField.GY, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GZ, 0), new Bc6HModeDescriptor(Bc6hEField.GZ, 1), new Bc6HModeDescriptor(Bc6hEField.GZ, 2), new Bc6HModeDescriptor(Bc6hEField.GZ, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BY, 0), new Bc6HModeDescriptor(Bc6hEField.BY, 1), new Bc6HModeDescriptor(Bc6hEField.BY, 2), new Bc6HModeDescriptor(Bc6hEField.BY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 0), new Bc6HModeDescriptor(Bc6hEField.RY, 1), new Bc6HModeDescriptor(Bc6hEField.RY, 2), new Bc6HModeDescriptor(Bc6hEField.RY, 3), new Bc6HModeDescriptor(Bc6hEField.RY, 4), - new Bc6HModeDescriptor(Bc6hEField.RY, 5), new Bc6HModeDescriptor(Bc6hEField.RZ, 0), new Bc6HModeDescriptor(Bc6hEField.RZ, 1), new Bc6HModeDescriptor(Bc6hEField.RZ, 2), new Bc6HModeDescriptor(Bc6hEField.RZ, 3), new Bc6HModeDescriptor(Bc6hEField.RZ, 4), new Bc6HModeDescriptor(Bc6hEField.RZ, 5), new Bc6HModeDescriptor(Bc6hEField.D, 0), new Bc6HModeDescriptor(Bc6hEField.D, 1), new Bc6HModeDescriptor(Bc6hEField.D, 2), - new Bc6HModeDescriptor(Bc6hEField.D, 3), new Bc6HModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 11 (0x03) - 10 10 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.RX, 6), new Bc6HModeDescriptor(Bc6hEField.RX, 7), new Bc6HModeDescriptor(Bc6hEField.RX, 8), new Bc6HModeDescriptor(Bc6hEField.RX, 9), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GX, 6), new Bc6HModeDescriptor(Bc6hEField.GX, 7), new Bc6HModeDescriptor(Bc6hEField.GX, 8), new Bc6HModeDescriptor(Bc6hEField.GX, 9), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BX, 6), new Bc6HModeDescriptor(Bc6hEField.BX, 7), new Bc6HModeDescriptor(Bc6hEField.BX, 8), new Bc6HModeDescriptor(Bc6hEField.BX, 9), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), - new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), - new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0) - }, - - new[] - { - // Mode 12 (0x07) - 11 9 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.RX, 6), new Bc6HModeDescriptor(Bc6hEField.RX, 7), new Bc6HModeDescriptor(Bc6hEField.RX, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 10), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GX, 6), new Bc6HModeDescriptor(Bc6hEField.GX, 7), new Bc6HModeDescriptor(Bc6hEField.GX, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 10), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BX, 6), new Bc6HModeDescriptor(Bc6hEField.BX, 7), new Bc6HModeDescriptor(Bc6hEField.BX, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 10), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), - new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), - new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0) - }, - - new[] - { - // Mode 13 (0x0b) - 12 8 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RX, 4), - new Bc6HModeDescriptor(Bc6hEField.RX, 5), new Bc6HModeDescriptor(Bc6hEField.RX, 6), new Bc6HModeDescriptor(Bc6hEField.RX, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 11), new Bc6HModeDescriptor(Bc6hEField.RW, 10), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GX, 4), - new Bc6HModeDescriptor(Bc6hEField.GX, 5), new Bc6HModeDescriptor(Bc6hEField.GX, 6), new Bc6HModeDescriptor(Bc6hEField.GX, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 11), new Bc6HModeDescriptor(Bc6hEField.GW, 10), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BX, 4), - new Bc6HModeDescriptor(Bc6hEField.BX, 5), new Bc6HModeDescriptor(Bc6hEField.BX, 6), new Bc6HModeDescriptor(Bc6hEField.BX, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 11), new Bc6HModeDescriptor(Bc6hEField.BW, 10), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), - new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), - new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0) - }, - - new[] - { - // Mode 14 (0x0f) - 16 4 - new Bc6HModeDescriptor(Bc6hEField.M, 0), new Bc6HModeDescriptor(Bc6hEField.M, 1), new Bc6HModeDescriptor(Bc6hEField.M, 2), new Bc6HModeDescriptor(Bc6hEField.M, 3), new Bc6HModeDescriptor(Bc6hEField.M, 4), new Bc6HModeDescriptor(Bc6hEField.RW, 0), new Bc6HModeDescriptor(Bc6hEField.RW, 1), new Bc6HModeDescriptor(Bc6hEField.RW, 2), new Bc6HModeDescriptor(Bc6hEField.RW, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 4), - new Bc6HModeDescriptor(Bc6hEField.RW, 5), new Bc6HModeDescriptor(Bc6hEField.RW, 6), new Bc6HModeDescriptor(Bc6hEField.RW, 7), new Bc6HModeDescriptor(Bc6hEField.RW, 8), new Bc6HModeDescriptor(Bc6hEField.RW, 9), new Bc6HModeDescriptor(Bc6hEField.GW, 0), new Bc6HModeDescriptor(Bc6hEField.GW, 1), new Bc6HModeDescriptor(Bc6hEField.GW, 2), new Bc6HModeDescriptor(Bc6hEField.GW, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 4), - new Bc6HModeDescriptor(Bc6hEField.GW, 5), new Bc6HModeDescriptor(Bc6hEField.GW, 6), new Bc6HModeDescriptor(Bc6hEField.GW, 7), new Bc6HModeDescriptor(Bc6hEField.GW, 8), new Bc6HModeDescriptor(Bc6hEField.GW, 9), new Bc6HModeDescriptor(Bc6hEField.BW, 0), new Bc6HModeDescriptor(Bc6hEField.BW, 1), new Bc6HModeDescriptor(Bc6hEField.BW, 2), new Bc6HModeDescriptor(Bc6hEField.BW, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 4), - new Bc6HModeDescriptor(Bc6hEField.BW, 5), new Bc6HModeDescriptor(Bc6hEField.BW, 6), new Bc6HModeDescriptor(Bc6hEField.BW, 7), new Bc6HModeDescriptor(Bc6hEField.BW, 8), new Bc6HModeDescriptor(Bc6hEField.BW, 9), new Bc6HModeDescriptor(Bc6hEField.RX, 0), new Bc6HModeDescriptor(Bc6hEField.RX, 1), new Bc6HModeDescriptor(Bc6hEField.RX, 2), new Bc6HModeDescriptor(Bc6hEField.RX, 3), new Bc6HModeDescriptor(Bc6hEField.RW, 15), - new Bc6HModeDescriptor(Bc6hEField.RW, 14), new Bc6HModeDescriptor(Bc6hEField.RW, 13), new Bc6HModeDescriptor(Bc6hEField.RW, 12), new Bc6HModeDescriptor(Bc6hEField.RW, 11), new Bc6HModeDescriptor(Bc6hEField.RW, 10), new Bc6HModeDescriptor(Bc6hEField.GX, 0), new Bc6HModeDescriptor(Bc6hEField.GX, 1), new Bc6HModeDescriptor(Bc6hEField.GX, 2), new Bc6HModeDescriptor(Bc6hEField.GX, 3), new Bc6HModeDescriptor(Bc6hEField.GW, 15), - new Bc6HModeDescriptor(Bc6hEField.GW, 14), new Bc6HModeDescriptor(Bc6hEField.GW, 13), new Bc6HModeDescriptor(Bc6hEField.GW, 12), new Bc6HModeDescriptor(Bc6hEField.GW, 11), new Bc6HModeDescriptor(Bc6hEField.GW, 10), new Bc6HModeDescriptor(Bc6hEField.BX, 0), new Bc6HModeDescriptor(Bc6hEField.BX, 1), new Bc6HModeDescriptor(Bc6hEField.BX, 2), new Bc6HModeDescriptor(Bc6hEField.BX, 3), new Bc6HModeDescriptor(Bc6hEField.BW, 15), - new Bc6HModeDescriptor(Bc6hEField.BW, 14), new Bc6HModeDescriptor(Bc6hEField.BW, 13), new Bc6HModeDescriptor(Bc6hEField.BW, 12), new Bc6HModeDescriptor(Bc6hEField.BW, 11), new Bc6HModeDescriptor(Bc6hEField.BW, 10), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), - new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0), - new Bc6HModeDescriptor(Bc6hEField.NA, 0), new Bc6HModeDescriptor(Bc6hEField.NA, 0) - }, - }; - - private static readonly Bc6hModeInfo[] ModeInfo = - { - new Bc6hModeInfo(0x00, 1, true, 3, new[] { new[] { new LdrColorA(10, 10, 10, 0), new LdrColorA(5, 5, 5, 0) }, new[] { new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0) } }), // Mode 1 - new Bc6hModeInfo(0x01, 1, true, 3, new[] { new[] { new LdrColorA(7, 7, 7, 0), new LdrColorA(6, 6, 6, 0) }, new[] { new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0) } }), // Mode 2 - new Bc6hModeInfo(0x02, 1, true, 3, new[] { new[] { new LdrColorA(11, 11, 11, 0), new LdrColorA(5, 4, 4, 0) }, new[] { new LdrColorA(5, 4, 4, 0), new LdrColorA(5, 4, 4, 0) } }), // Mode 3 - new Bc6hModeInfo(0x06, 1, true, 3, new[] { new[] { new LdrColorA(11, 11, 11, 0), new LdrColorA(4, 5, 4, 0) }, new[] { new LdrColorA(4, 5, 4, 0), new LdrColorA(4, 5, 4, 0) } }), // Mode 4 - new Bc6hModeInfo(0x0a, 1, true, 3, new[] { new[] { new LdrColorA(11, 11, 11, 0), new LdrColorA(4, 4, 5, 0) }, new[] { new LdrColorA(4, 4, 5, 0), new LdrColorA(4, 4, 5, 0) } }), // Mode 5 - new Bc6hModeInfo(0x0e, 1, true, 3, new[] { new[] { new LdrColorA(9, 9, 9, 0), new LdrColorA(5, 5, 5, 0) }, new[] { new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0) } }), // Mode 6 - new Bc6hModeInfo(0x12, 1, true, 3, new[] { new[] { new LdrColorA(8, 8, 8, 0), new LdrColorA(6, 5, 5, 0) }, new[] { new LdrColorA(6, 5, 5, 0), new LdrColorA(6, 5, 5, 0) } }), // Mode 7 - new Bc6hModeInfo(0x16, 1, true, 3, new[] { new[] { new LdrColorA(8, 8, 8, 0), new LdrColorA(5, 6, 5, 0) }, new[] { new LdrColorA(5, 6, 5, 0), new LdrColorA(5, 6, 5, 0) } }), // Mode 8 - new Bc6hModeInfo(0x1a, 1, true, 3, new[] { new[] { new LdrColorA(8, 8, 8, 0), new LdrColorA(5, 5, 6, 0) }, new[] { new LdrColorA(5, 5, 6, 0), new LdrColorA(5, 5, 6, 0) } }), // Mode 9 - new Bc6hModeInfo(0x1e, 1, false, 3, new[] { new[] { new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0) }, new[] { new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0) } }), // Mode 10 - new Bc6hModeInfo(0x03, 0, false, 4, new[] { new[] { new LdrColorA(10, 10, 10, 0), new LdrColorA(10, 10, 10, 0) }, new[] { new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0) } }), // Mode 11 - new Bc6hModeInfo(0x07, 0, true, 4, new[] { new[] { new LdrColorA(11, 11, 11, 0), new LdrColorA(9, 9, 9, 0) }, new[] { new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0) } }), // Mode 12 - new Bc6hModeInfo(0x0b, 0, true, 4, new[] { new[] { new LdrColorA(12, 12, 12, 0), new LdrColorA(8, 8, 8, 0) }, new[] { new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0) } }), // Mode 13 - new Bc6hModeInfo(0x0f, 0, true, 4, new[] { new[] { new LdrColorA(16, 16, 16, 0), new LdrColorA(4, 4, 4, 0) }, new[] { new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0) } }), // Mode 14 - }; - - private static readonly int[] ModeToInfo = - { - 0, // Mode 1 - 0x00 - 1, // Mode 2 - 0x01 - 2, // Mode 3 - 0x02 - 10, // Mode 11 - 0x03 - -1, // Invalid - 0x04 - -1, // Invalid - 0x05 - 3, // Mode 4 - 0x06 - 11, // Mode 12 - 0x07 - -1, // Invalid - 0x08 - -1, // Invalid - 0x09 - 4, // Mode 5 - 0x0a - 12, // Mode 13 - 0x0b - -1, // Invalid - 0x0c - -1, // Invalid - 0x0d - 5, // Mode 6 - 0x0e - 13, // Mode 14 - 0x0f - -1, // Invalid - 0x10 - -1, // Invalid - 0x11 - 6, // Mode 7 - 0x12 - -1, // Reserved - 0x13 - -1, // Invalid - 0x14 - -1, // Invalid - 0x15 - 7, // Mode 8 - 0x16 - -1, // Reserved - 0x17 - -1, // Invalid - 0x18 - -1, // Invalid - 0x19 - 8, // Mode 9 - 0x1a - -1, // Reserved - 0x1b - -1, // Invalid - 0x1c - -1, // Invalid - 0x1d - 9, // Mode 10 - 0x1e - -1, // Resreved - 0x1f - }; - - /// - public int BitsPerPixel => 32; - - /// - public byte PixelDepthBytes => 4; - - /// - public byte DivSize => 4; - - /// - public byte CompressedBytesPerBlock => 16; - - /// - public bool Compressed => true; - - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + uMode = (byte)((GetBits(currentBlock, ref uStartBit, 3) << 2) | uMode); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - Bc6h self = this; - byte[] currentBlock = new byte[this.CompressedBytesPerBlock]; + Debug.Assert(uMode < 32, "uMode should be less then 32"); - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + if (ModeToInfo[uMode] >= 0) { - // I would prefer to use Span, but not sure if I should reference System.Memory in this project - // copy data instead - Buffer.BlockCopy(blockData, streamIndex, currentBlock, 0, currentBlock.Length); - streamIndex += currentBlock.Length; - - uint uStartBit = 0; - byte uMode = GetBits(currentBlock, ref uStartBit, 2u); - if (uMode != 0x00 && uMode != 0x01) - { - uMode = (byte)((GetBits(currentBlock, ref uStartBit, 3) << 2) | uMode); - } + Debug.Assert(ModeToInfo[uMode] < ModeInfo.Length, "ModeToInfo[uMode] should be less then ModeInfo.Length"); + Bc6HModeDescriptor[] desc = ModeDescriptors[ModeToInfo[uMode]]; - Debug.Assert(uMode < 32, "uMode should be less then 32"); + Debug.Assert(ModeToInfo[uMode] < ModeDescriptors.Length, "ModeToInfo[uMode] should be less then ModeDescriptors.Length"); + ref Bc6hModeInfo info = ref ModeInfo[ModeToInfo[uMode]]; - if (ModeToInfo[uMode] >= 0) + IntEndPntPair[] aEndPts = new IntEndPntPair[Constants.BC6H_MAX_REGIONS]; + for (int i = 0; i < aEndPts.Length; ++i) { - Debug.Assert(ModeToInfo[uMode] < ModeInfo.Length, "ModeToInfo[uMode] should be less then ModeInfo.Length"); - Bc6HModeDescriptor[] desc = ModeDescriptors[ModeToInfo[uMode]]; - - Debug.Assert(ModeToInfo[uMode] < ModeDescriptors.Length, "ModeToInfo[uMode] should be less then ModeDescriptors.Length"); - ref Bc6hModeInfo info = ref ModeInfo[ModeToInfo[uMode]]; - - var aEndPts = new IntEndPntPair[Constants.BC6H_MAX_REGIONS]; - for (int i = 0; i < aEndPts.Length; ++i) - { - aEndPts[i] = new IntEndPntPair(new IntColor(), new IntColor()); - } + aEndPts[i] = new IntEndPntPair(new IntColor(), new IntColor()); + } - uint uShape = 0; + uint uShape = 0; - // Read header - uint uHeaderBits = info.Partitions > 0 ? 82u : 65u; - while (uStartBit < uHeaderBits) + // Read header + uint uHeaderBits = info.Partitions > 0 ? 82u : 65u; + while (uStartBit < uHeaderBits) + { + uint uCurBit = uStartBit; + if (GetBit(currentBlock, ref uStartBit) != 0) { - uint uCurBit = uStartBit; - if (GetBit(currentBlock, ref uStartBit) != 0) + switch (desc[uCurBit].MBc6HEField) { - switch (desc[uCurBit].MBc6HEField) + case Bc6hEField.D: + uShape |= 1u << desc[uCurBit].Bit; + break; + case Bc6hEField.RW: + aEndPts[0].A.R |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.RX: + aEndPts[0].B.R |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.RY: + aEndPts[1].A.R |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.RZ: + aEndPts[1].B.R |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.GW: + aEndPts[0].A.G |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.GX: + aEndPts[0].B.G |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.GY: + aEndPts[1].A.G |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.GZ: + aEndPts[1].B.G |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.BW: + aEndPts[0].A.B |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.BX: + aEndPts[0].B.B |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.BY: + aEndPts[1].A.B |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.BZ: + aEndPts[1].B.B |= 1 << desc[uCurBit].Bit; + break; + default: { - case Bc6hEField.D: - uShape |= 1u << desc[uCurBit].Bit; - break; - case Bc6hEField.RW: - aEndPts[0].A.R |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.RX: - aEndPts[0].B.R |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.RY: - aEndPts[1].A.R |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.RZ: - aEndPts[1].B.R |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.GW: - aEndPts[0].A.G |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.GX: - aEndPts[0].B.G |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.GY: - aEndPts[1].A.G |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.GZ: - aEndPts[1].B.G |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.BW: - aEndPts[0].A.B |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.BX: - aEndPts[0].B.B |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.BY: - aEndPts[1].A.B |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.BZ: - aEndPts[1].B.B |= 1 << desc[uCurBit].Bit; - break; - default: - { - Debug.WriteLine("BC6H: Invalid header bits encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } + Debug.WriteLine("BC6H: Invalid header bits encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; } } } + } - Debug.Assert(uShape < 64, "uShape should be less then 64"); + Debug.Assert(uShape < 64, "uShape should be less then 64"); - if (info.Transformed) + if (info.Transformed) + { + Debug.Assert(info.Partitions < Constants.BC6H_MAX_REGIONS, $"info.Partitions should be less then {Constants.BC6H_MAX_REGIONS}"); + for (int p = 0; p <= info.Partitions; ++p) { - Debug.Assert(info.Partitions < Constants.BC6H_MAX_REGIONS, $"info.Partitions should be less then {Constants.BC6H_MAX_REGIONS}"); - for (int p = 0; p <= info.Partitions; ++p) + if (p != 0) { - if (p != 0) - { - aEndPts[p].A.SignExtend(info.RgbaPrec[p][0]); - } - - aEndPts[p].B.SignExtend(info.RgbaPrec[p][1]); + aEndPts[p].A.SignExtend(info.RgbaPrec[p][0]); } - } - // Inverse transform the end points - if (info.Transformed) - { - Helpers.TransformInverseUnsigned(aEndPts, info.RgbaPrec[0][0]); + aEndPts[p].B.SignExtend(info.RgbaPrec[p][1]); } + } - // Read indices - for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) + // Inverse transform the end points + if (info.Transformed) + { + Helpers.TransformInverseUnsigned(aEndPts, info.RgbaPrec[0][0]); + } + + // Read indices + for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) + { + uint uNumBits = Helpers.IsFixUpOffset(info.Partitions, (byte)uShape, i) ? info.IndexPrec - 1u : info.IndexPrec; + if (uStartBit + uNumBits > 128) { - uint uNumBits = Helpers.IsFixUpOffset(info.Partitions, (byte)uShape, i) ? info.IndexPrec - 1u : info.IndexPrec; - if (uStartBit + uNumBits > 128) - { - Debug.WriteLine("BC6H: Invalid block encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } + Debug.WriteLine("BC6H: Invalid block encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; + } - uint uIndex = GetBits(currentBlock, ref uStartBit, uNumBits); + uint uIndex = GetBits(currentBlock, ref uStartBit, uNumBits); - if (uIndex >= ((info.Partitions > 0) ? 8 : 16)) - { - Debug.WriteLine("BC6H: Invalid index encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } + if (uIndex >= ((info.Partitions > 0) ? 8 : 16)) + { + Debug.WriteLine("BC6H: Invalid index encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; + } - uint uRegion = Constants.PartitionTable[info.Partitions][uShape][i]; - Debug.Assert(uRegion < Constants.BC6H_MAX_REGIONS, $"uRegion should be smaller than {Constants.BC6H_MAX_REGIONS}"); - - // Unquantize endpoints and interpolate - int r1 = Unquantize(aEndPts[uRegion].A.R, info.RgbaPrec[0][0].R); - int g1 = Unquantize(aEndPts[uRegion].A.G, info.RgbaPrec[0][0].G); - int b1 = Unquantize(aEndPts[uRegion].A.B, info.RgbaPrec[0][0].B); - int r2 = Unquantize(aEndPts[uRegion].B.R, info.RgbaPrec[0][0].R); - int g2 = Unquantize(aEndPts[uRegion].B.G, info.RgbaPrec[0][0].G); - int b2 = Unquantize(aEndPts[uRegion].B.B, info.RgbaPrec[0][0].B); - int[] aWeights = info.Partitions > 0 ? Constants.Weights3 : Constants.Weights4; - var fc = new IntColor - { - R = FinishUnquantize(((r1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (r2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT), - G = FinishUnquantize(((g1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (g2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT), - B = FinishUnquantize(((b1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (b2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT) - }; - - ushort[] rgb = new ushort[3]; - fc.ToF16Unsigned(rgb); - - // Clamp 0..1, and convert to byte (we're losing high dynamic range) - data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[0]))) * 255.0f) + 0.5f); // red - data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[1]))) * 255.0f) + 0.5f); // green - data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[2]))) * 255.0f) + 0.5f); // blue - data[dataIndex++] = 255; - - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + uint uRegion = Constants.PartitionTable[info.Partitions][uShape][i]; + Debug.Assert(uRegion < Constants.BC6H_MAX_REGIONS, $"uRegion should be smaller than {Constants.BC6H_MAX_REGIONS}"); + + // Unquantize endpoints and interpolate + int r1 = Unquantize(aEndPts[uRegion].A.R, info.RgbaPrec[0][0].R); + int g1 = Unquantize(aEndPts[uRegion].A.G, info.RgbaPrec[0][0].G); + int b1 = Unquantize(aEndPts[uRegion].A.B, info.RgbaPrec[0][0].B); + int r2 = Unquantize(aEndPts[uRegion].B.R, info.RgbaPrec[0][0].R); + int g2 = Unquantize(aEndPts[uRegion].B.G, info.RgbaPrec[0][0].G); + int b2 = Unquantize(aEndPts[uRegion].B.B, info.RgbaPrec[0][0].B); + int[] aWeights = info.Partitions > 0 ? Constants.Weights3 : Constants.Weights4; + IntColor fc = new IntColor + { + R = FinishUnquantize(((r1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (r2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT), + G = FinishUnquantize(((g1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (g2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT), + B = FinishUnquantize(((b1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (b2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT) + }; + + ushort[] rgb = new ushort[3]; + fc.ToF16Unsigned(rgb); + + // Clamp 0..1, and convert to byte (we're losing high dynamic range) + data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[0]))) * 255.0f) + 0.5f); // red + data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[1]))) * 255.0f) + 0.5f); // green + data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[2]))) * 255.0f) + 0.5f); // blue + data[dataIndex++] = 255; + + // Is mult 4? + if (((i + 1) & 0x3) == 0) + { + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } } - else + } + else + { + string warnStr = "BC6H: Invalid mode encountered during decoding"; + switch (uMode) { - string warnStr = "BC6H: Invalid mode encountered during decoding"; - switch (uMode) - { - case 0x13: - warnStr = "BC6H: Reserved mode 10011 encountered during decoding"; - break; - case 0x17: - warnStr = "BC6H: Reserved mode 10111 encountered during decoding"; - break; - case 0x1B: - warnStr = "BC6H: Reserved mode 11011 encountered during decoding"; - break; - case 0x1F: - warnStr = "BC6H: Reserved mode 11111 encountered during decoding"; - break; - } + case 0x13: + warnStr = "BC6H: Reserved mode 10011 encountered during decoding"; + break; + case 0x17: + warnStr = "BC6H: Reserved mode 10111 encountered during decoding"; + break; + case 0x1B: + warnStr = "BC6H: Reserved mode 11011 encountered during decoding"; + break; + case 0x1F: + warnStr = "BC6H: Reserved mode 11111 encountered during decoding"; + break; + } - Debug.WriteLine(warnStr); + Debug.WriteLine(warnStr); - // Per the BC6H format spec, we must return opaque black - for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) - { - data[dataIndex++] = 0; - data[dataIndex++] = 0; - data[dataIndex++] = 0; - data[dataIndex++] = 0; + // Per the BC6H format spec, we must return opaque black + for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) + { + data[dataIndex++] = 0; + data[dataIndex++] = 0; + data[dataIndex++] = 0; + data[dataIndex++] = 0; - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + // Is mult 4? + if (((i + 1) & 0x3) == 0) + { + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } } + } - return streamIndex; - }); - } + return streamIndex; + }); + } + + /// + /// Gets a bit for a given position. + /// + /// The current block. + /// The start bit. + /// A bit at a given position. + public static byte GetBit(byte[] currentBlock, ref uint uStartBit) + { + Debug.Assert(uStartBit < 128, "uStartBit should be less then 128"); + uint uIndex = uStartBit >> 3; + byte ret = (byte)((currentBlock[uIndex] >> (int)(uStartBit - (uIndex << 3))) & 0x01); + uStartBit++; + return ret; + } - /// - /// Gets a bit for a given position. - /// - /// The current block. - /// The start bit. - /// A bit at a given position. - public static byte GetBit(byte[] currentBlock, ref uint uStartBit) + /// + /// Gets n bits at a given start position. + /// + /// The current block. + /// The start bit. + /// The number of bits. + /// Bits at a given position. + public static byte GetBits(byte[] currentBlock, ref uint uStartBit, uint uNumBits) + { + if (uNumBits == 0) { - Debug.Assert(uStartBit < 128, "uStartBit should be less then 128"); - uint uIndex = uStartBit >> 3; - byte ret = (byte)((currentBlock[uIndex] >> (int)(uStartBit - (uIndex << 3))) & 0x01); - uStartBit++; - return ret; + return 0; } - /// - /// Gets n bits at a given start position. - /// - /// The current block. - /// The start bit. - /// The number of bits. - /// Bits at a given position. - public static byte GetBits(byte[] currentBlock, ref uint uStartBit, uint uNumBits) + Debug.Assert(uStartBit + uNumBits <= 128 && uNumBits <= 8, "uStartBit + uNumBits <= 128 && uNumBits <= 8"); + byte ret; + uint uIndex = uStartBit >> 3; + uint uBase = uStartBit - (uIndex << 3); + if (uBase + uNumBits > 8) { - if (uNumBits == 0) - { - return 0; - } - - Debug.Assert(uStartBit + uNumBits <= 128 && uNumBits <= 8, "uStartBit + uNumBits <= 128 && uNumBits <= 8"); - byte ret; - uint uIndex = uStartBit >> 3; - uint uBase = uStartBit - (uIndex << 3); - if (uBase + uNumBits > 8) - { - uint uFirstIndexBits = 8 - uBase; - uint uNextIndexBits = uNumBits - uFirstIndexBits; - ret = (byte)((uint)(currentBlock[uIndex] >> (int)uBase) | ((currentBlock[uIndex + 1] & ((1u << (int)uNextIndexBits) - 1)) << (int)uFirstIndexBits)); - } - else - { - ret = (byte)((currentBlock[uIndex] >> (int)uBase) & ((1 << (int)uNumBits) - 1)); - } - - Debug.Assert(ret < (1 << (int)uNumBits), $"GetBits() return value should be less then {1 << (int)uNumBits}"); - uStartBit += uNumBits; - return ret; + uint uFirstIndexBits = 8 - uBase; + uint uNextIndexBits = uNumBits - uFirstIndexBits; + ret = (byte)((uint)(currentBlock[uIndex] >> (int)uBase) | ((currentBlock[uIndex + 1] & ((1u << (int)uNextIndexBits) - 1)) << (int)uFirstIndexBits)); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Unquantize(int comp, byte uBitsPerComp) + else { - int unq; + ret = (byte)((currentBlock[uIndex] >> (int)uBase) & ((1 << (int)uNumBits) - 1)); + } - if (uBitsPerComp >= 15) - { - unq = comp; - } - else if (comp == 0) - { - unq = 0; - } - else if (comp == ((1 << uBitsPerComp) - 1)) - { - unq = 0xFFFF; - } - else - { - unq = ((comp << 16) + 0x8000) >> uBitsPerComp; - } + Debug.Assert(ret < (1 << (int)uNumBits), $"GetBits() return value should be less then {1 << (int)uNumBits}"); + uStartBit += uNumBits; + return ret; + } - return unq; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Unquantize(int comp, byte uBitsPerComp) + { + int unq; + + if (uBitsPerComp >= 15) + { + unq = comp; + } + else if (comp == 0) + { + unq = 0; + } + else if (comp == ((1 << uBitsPerComp) - 1)) + { + unq = 0xFFFF; + } + else + { + unq = ((comp << 16) + 0x8000) >> uBitsPerComp; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FinishUnquantize(int comp) => (comp * 31) >> 6; // scale the magnitude by 31/64 + return unq; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FinishUnquantize(int comp) => (comp * 31) >> 6; // scale the magnitude by 31/64 } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hEField.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hEField.cs index fd26730b..842971b8 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hEField.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hEField.cs @@ -1,24 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +internal enum Bc6hEField : byte { - internal enum Bc6hEField : byte - { - NA, // N/A - M, // Mode - D, // Shape - RW, - RX, - RY, - RZ, - GW, - GX, - GY, - GZ, - BW, - BX, - BY, - BZ, - } + NA, // N/A + M, // Mode + D, // Shape + RW, + RX, + RY, + RZ, + GW, + GX, + GY, + GZ, + BW, + BX, + BY, + BZ, } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hModeDescriptor.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hModeDescriptor.cs index 3bc2f8b0..72c8d392 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hModeDescriptor.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hModeDescriptor.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +internal struct Bc6HModeDescriptor { - internal struct Bc6HModeDescriptor - { - public readonly Bc6hEField MBc6HEField; - public readonly byte Bit; + public readonly Bc6hEField MBc6HEField; + public readonly byte Bit; - public Bc6HModeDescriptor(Bc6hEField bc6Hef, byte uB) - { - this.MBc6HEField = bc6Hef; - this.Bit = uB; - } + public Bc6HModeDescriptor(Bc6hEField bc6Hef, byte uB) + { + this.MBc6HEField = bc6Hef; + this.Bit = uB; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hModeInfo.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hModeInfo.cs index 4a460cee..849d644a 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hModeInfo.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hModeInfo.cs @@ -3,23 +3,22 @@ using SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +internal struct Bc6hModeInfo { - internal struct Bc6hModeInfo - { - public byte Mode; - public byte Partitions; - public bool Transformed; - public byte IndexPrec; - public readonly LdrColorA[][] RgbaPrec; // [Constants.BC6H_MAX_REGIONS][2]; + public byte Mode; + public byte Partitions; + public bool Transformed; + public byte IndexPrec; + public readonly LdrColorA[][] RgbaPrec; // [Constants.BC6H_MAX_REGIONS][2]; - public Bc6hModeInfo(byte m, byte p, bool t, byte i, LdrColorA[][] prec) - { - this.Mode = m; - this.Partitions = p; - this.Transformed = t; - this.IndexPrec = i; - this.RgbaPrec = prec; - } + public Bc6hModeInfo(byte m, byte p, bool t, byte i, LdrColorA[][] prec) + { + this.Mode = m; + this.Partitions = p; + this.Transformed = t; + this.IndexPrec = i; + this.RgbaPrec = prec; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hs.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hs.cs index 0de87e61..55721ed8 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hs.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hs.cs @@ -1,600 +1,584 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Textures.Common.Helpers; using SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with BC6HS, three color channels (16 bits:16 bits:16 bits) in "half" floating point. +/// +internal struct Bc6hs : IBlock { - /// - /// Texture compressed with BC6HS, three color channels (16 bits:16 bits:16 bits) in "half" floating point. - /// - internal struct Bc6hs : IBlock + // Code based on commit 138efff1b9c53fd9a5dd34b8c865e8f5ae798030 2019/10/24 in DirectXTex C++ library + private static readonly Bc6hsModeDescriptor[][] MsADesc = + [ + [ + // Mode 1 (0x00) - 10 5 5 5 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 2 (0x01) - 7 6 6 6 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GZ, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.BY, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), + new Bc6hsModeDescriptor(Bc6hEField.RY, 5), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RZ, 5), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 3 (0x02) - 11 5 4 4 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 10), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 4 (0x06) - 11 4 5 4 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), + new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 10), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 5 (0x0a) - 11 4 4 5 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), + new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 10), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 6 (0x0e) - 9 5 5 5 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 7 (0x12) - 8 6 5 5 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), + new Bc6hsModeDescriptor(Bc6hEField.RY, 5), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RZ, 5), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 8 (0x16) - 8 5 6 5 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GY, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.GZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 9 (0x1a) - 8 5 5 6 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.BY, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), + new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 10 (0x1e) - 6 6 6 6 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 5), new Bc6hsModeDescriptor(Bc6hEField.BY, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), + new Bc6hsModeDescriptor(Bc6hEField.RY, 5), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RZ, 5), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), + new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) + ], + + [ + // Mode 11 (0x03) - 10 10 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.RX, 6), new Bc6hsModeDescriptor(Bc6hEField.RX, 7), new Bc6hsModeDescriptor(Bc6hEField.RX, 8), new Bc6hsModeDescriptor(Bc6hEField.RX, 9), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GX, 6), new Bc6hsModeDescriptor(Bc6hEField.GX, 7), new Bc6hsModeDescriptor(Bc6hEField.GX, 8), new Bc6hsModeDescriptor(Bc6hEField.GX, 9), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BX, 6), new Bc6hsModeDescriptor(Bc6hEField.BX, 7), new Bc6hsModeDescriptor(Bc6hEField.BX, 8), new Bc6hsModeDescriptor(Bc6hEField.BX, 9), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), + new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), + new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0) + ], + + [ + // Mode 12 (0x07) - 11 9 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.RX, 6), new Bc6hsModeDescriptor(Bc6hEField.RX, 7), new Bc6hsModeDescriptor(Bc6hEField.RX, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GX, 6), new Bc6hsModeDescriptor(Bc6hEField.GX, 7), new Bc6hsModeDescriptor(Bc6hEField.GX, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BX, 6), new Bc6hsModeDescriptor(Bc6hEField.BX, 7), new Bc6hsModeDescriptor(Bc6hEField.BX, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), + new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), + new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0) + ], + + [ + // Mode 13 (0x0b) - 12 8 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), + new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.RX, 6), new Bc6hsModeDescriptor(Bc6hEField.RX, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 11), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), + new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GX, 6), new Bc6hsModeDescriptor(Bc6hEField.GX, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 11), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), + new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BX, 6), new Bc6hsModeDescriptor(Bc6hEField.BX, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 11), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), + new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), + new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0) + ], + + [ + // Mode 14 (0x0f) - 16 4 + new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), + new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), + new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), + new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 15), + new Bc6hsModeDescriptor(Bc6hEField.RW, 14), new Bc6hsModeDescriptor(Bc6hEField.RW, 13), new Bc6hsModeDescriptor(Bc6hEField.RW, 12), new Bc6hsModeDescriptor(Bc6hEField.RW, 11), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 15), + new Bc6hsModeDescriptor(Bc6hEField.GW, 14), new Bc6hsModeDescriptor(Bc6hEField.GW, 13), new Bc6hsModeDescriptor(Bc6hEField.GW, 12), new Bc6hsModeDescriptor(Bc6hEField.GW, 11), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 15), + new Bc6hsModeDescriptor(Bc6hEField.BW, 14), new Bc6hsModeDescriptor(Bc6hEField.BW, 13), new Bc6hsModeDescriptor(Bc6hEField.BW, 12), new Bc6hsModeDescriptor(Bc6hEField.BW, 11), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), + new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), + new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0) + ] + ]; + + private static readonly Bc6hsModeInfo[] MsAInfo = + [ + new Bc6hsModeInfo(0x00, 1, true, 3, [[new LdrColorA(10, 10, 10, 0), new LdrColorA(5, 5, 5, 0)], [new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0)]]), // Mode 1 + new Bc6hsModeInfo(0x01, 1, true, 3, [[new LdrColorA(7, 7, 7, 0), new LdrColorA(6, 6, 6, 0)], [new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0)]]), // Mode 2 + new Bc6hsModeInfo(0x02, 1, true, 3, [[new LdrColorA(11, 11, 11, 0), new LdrColorA(5, 4, 4, 0)], [new LdrColorA(5, 4, 4, 0), new LdrColorA(5, 4, 4, 0)]]), // Mode 3 + new Bc6hsModeInfo(0x06, 1, true, 3, [[new LdrColorA(11, 11, 11, 0), new LdrColorA(4, 5, 4, 0)], [new LdrColorA(4, 5, 4, 0), new LdrColorA(4, 5, 4, 0)]]), // Mode 4 + new Bc6hsModeInfo(0x0a, 1, true, 3, [[new LdrColorA(11, 11, 11, 0), new LdrColorA(4, 4, 5, 0)], [new LdrColorA(4, 4, 5, 0), new LdrColorA(4, 4, 5, 0)]]), // Mode 5 + new Bc6hsModeInfo(0x0e, 1, true, 3, [[new LdrColorA(9, 9, 9, 0), new LdrColorA(5, 5, 5, 0)], [new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0)]]), // Mode 6 + new Bc6hsModeInfo(0x12, 1, true, 3, [[new LdrColorA(8, 8, 8, 0), new LdrColorA(6, 5, 5, 0)], [new LdrColorA(6, 5, 5, 0), new LdrColorA(6, 5, 5, 0)]]), // Mode 7 + new Bc6hsModeInfo(0x16, 1, true, 3, [[new LdrColorA(8, 8, 8, 0), new LdrColorA(5, 6, 5, 0)], [new LdrColorA(5, 6, 5, 0), new LdrColorA(5, 6, 5, 0)]]), // Mode 8 + new Bc6hsModeInfo(0x1a, 1, true, 3, [[new LdrColorA(8, 8, 8, 0), new LdrColorA(5, 5, 6, 0)], [new LdrColorA(5, 5, 6, 0), new LdrColorA(5, 5, 6, 0)]]), // Mode 9 + new Bc6hsModeInfo(0x1e, 1, false, 3, [[new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0)], [new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0)]]), // Mode 10 + new Bc6hsModeInfo(0x03, 0, false, 4, [[new LdrColorA(10, 10, 10, 0), new LdrColorA(10, 10, 10, 0)], [new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0)]]), // Mode 11 + new Bc6hsModeInfo(0x07, 0, true, 4, [[new LdrColorA(11, 11, 11, 0), new LdrColorA(9, 9, 9, 0)], [new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0)]]), // Mode 12 + new Bc6hsModeInfo(0x0b, 0, true, 4, [[new LdrColorA(12, 12, 12, 0), new LdrColorA(8, 8, 8, 0)], [new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0)]]), // Mode 13 + new Bc6hsModeInfo(0x0f, 0, true, 4, [[new LdrColorA(16, 16, 16, 0), new LdrColorA(4, 4, 4, 0)], [new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0)]]), // Mode 14 + ]; + + private static readonly int[] MsAModeToInfo = + [ + 0, // Mode 1 - 0x00 + 1, // Mode 2 - 0x01 + 2, // Mode 3 - 0x02 + 10, // Mode 11 - 0x03 + -1, // Invalid - 0x04 + -1, // Invalid - 0x05 + 3, // Mode 4 - 0x06 + 11, // Mode 12 - 0x07 + -1, // Invalid - 0x08 + -1, // Invalid - 0x09 + 4, // Mode 5 - 0x0a + 12, // Mode 13 - 0x0b + -1, // Invalid - 0x0c + -1, // Invalid - 0x0d + 5, // Mode 6 - 0x0e + 13, // Mode 14 - 0x0f + -1, // Invalid - 0x10 + -1, // Invalid - 0x11 + 6, // Mode 7 - 0x12 + -1, // Reserved - 0x13 + -1, // Invalid - 0x14 + -1, // Invalid - 0x15 + 7, // Mode 8 - 0x16 + -1, // Reserved - 0x17 + -1, // Invalid - 0x18 + -1, // Invalid - 0x19 + 8, // Mode 9 - 0x1a + -1, // Reserved - 0x1b + -1, // Invalid - 0x1c + -1, // Invalid - 0x1d + 9, // Mode 10 - 0x1e + -1, // Resreved - 0x1f + ]; + + /// + public readonly int BitsPerPixel => 32; + + /// + public readonly byte PixelDepthBytes => 4; + + /// + public readonly byte DivSize => 4; + + /// + public readonly byte CompressedBytesPerBlock => 16; + + /// + public readonly bool Compressed => true; + + /// + public Image GetImage(byte[] blockData, int width, int height) { - // Code based on commit 138efff1b9c53fd9a5dd34b8c865e8f5ae798030 2019/10/24 in DirectXTex C++ library - private static readonly Bc6hsModeDescriptor[][] MsADesc = new[] + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + Bc6hs self = this; + byte[] currentBlock = new byte[this.CompressedBytesPerBlock]; + + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => { - new[] - { - // Mode 1 (0x00) - 10 5 5 5 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 2 (0x01) - 7 6 6 6 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GZ, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.BY, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), - new Bc6hsModeDescriptor(Bc6hEField.RY, 5), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RZ, 5), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 3 (0x02) - 11 5 4 4 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 10), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 4 (0x06) - 11 4 5 4 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), - new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 10), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 5 (0x0a) - 11 4 4 5 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), - new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 10), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 6 (0x0e) - 9 5 5 5 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 7 (0x12) - 8 6 5 5 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), - new Bc6hsModeDescriptor(Bc6hEField.RY, 5), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RZ, 5), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] + // I would prefer to use Span, but not sure if I should reference System.Memory in this project + // copy data instead + Buffer.BlockCopy(blockData, streamIndex, currentBlock, 0, currentBlock.Length); + streamIndex += currentBlock.Length; + + uint uStartBit = 0; + byte uMode = GetBits(currentBlock, ref uStartBit, 2u); + if (uMode is not 0x00 and not 0x01) { - // Mode 8 (0x16) - 8 5 6 5 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GY, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.GZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 9 (0x1a) - 8 5 5 6 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.BY, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), - new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 10 (0x1e) - 6 6 6 6 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 4), new Bc6hsModeDescriptor(Bc6hEField.BZ, 0), new Bc6hsModeDescriptor(Bc6hEField.BZ, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 4), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 5), new Bc6hsModeDescriptor(Bc6hEField.BY, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 4), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BZ, 5), new Bc6hsModeDescriptor(Bc6hEField.BZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.GY, 0), new Bc6hsModeDescriptor(Bc6hEField.GY, 1), new Bc6hsModeDescriptor(Bc6hEField.GY, 2), new Bc6hsModeDescriptor(Bc6hEField.GY, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GZ, 0), new Bc6hsModeDescriptor(Bc6hEField.GZ, 1), new Bc6hsModeDescriptor(Bc6hEField.GZ, 2), new Bc6hsModeDescriptor(Bc6hEField.GZ, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BY, 0), new Bc6hsModeDescriptor(Bc6hEField.BY, 1), new Bc6hsModeDescriptor(Bc6hEField.BY, 2), new Bc6hsModeDescriptor(Bc6hEField.BY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 0), new Bc6hsModeDescriptor(Bc6hEField.RY, 1), new Bc6hsModeDescriptor(Bc6hEField.RY, 2), new Bc6hsModeDescriptor(Bc6hEField.RY, 3), new Bc6hsModeDescriptor(Bc6hEField.RY, 4), - new Bc6hsModeDescriptor(Bc6hEField.RY, 5), new Bc6hsModeDescriptor(Bc6hEField.RZ, 0), new Bc6hsModeDescriptor(Bc6hEField.RZ, 1), new Bc6hsModeDescriptor(Bc6hEField.RZ, 2), new Bc6hsModeDescriptor(Bc6hEField.RZ, 3), new Bc6hsModeDescriptor(Bc6hEField.RZ, 4), new Bc6hsModeDescriptor(Bc6hEField.RZ, 5), new Bc6hsModeDescriptor(Bc6hEField.D, 0), new Bc6hsModeDescriptor(Bc6hEField.D, 1), new Bc6hsModeDescriptor(Bc6hEField.D, 2), - new Bc6hsModeDescriptor(Bc6hEField.D, 3), new Bc6hsModeDescriptor(Bc6hEField.D, 4) - }, - - new[] - { - // Mode 11 (0x03) - 10 10 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.RX, 6), new Bc6hsModeDescriptor(Bc6hEField.RX, 7), new Bc6hsModeDescriptor(Bc6hEField.RX, 8), new Bc6hsModeDescriptor(Bc6hEField.RX, 9), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GX, 6), new Bc6hsModeDescriptor(Bc6hEField.GX, 7), new Bc6hsModeDescriptor(Bc6hEField.GX, 8), new Bc6hsModeDescriptor(Bc6hEField.GX, 9), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BX, 6), new Bc6hsModeDescriptor(Bc6hEField.BX, 7), new Bc6hsModeDescriptor(Bc6hEField.BX, 8), new Bc6hsModeDescriptor(Bc6hEField.BX, 9), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), - new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), - new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0) - }, - - new[] - { - // Mode 12 (0x07) - 11 9 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.RX, 6), new Bc6hsModeDescriptor(Bc6hEField.RX, 7), new Bc6hsModeDescriptor(Bc6hEField.RX, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GX, 6), new Bc6hsModeDescriptor(Bc6hEField.GX, 7), new Bc6hsModeDescriptor(Bc6hEField.GX, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BX, 6), new Bc6hsModeDescriptor(Bc6hEField.BX, 7), new Bc6hsModeDescriptor(Bc6hEField.BX, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), - new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), - new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0) - }, - - new[] - { - // Mode 13 (0x0b) - 12 8 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RX, 4), - new Bc6hsModeDescriptor(Bc6hEField.RX, 5), new Bc6hsModeDescriptor(Bc6hEField.RX, 6), new Bc6hsModeDescriptor(Bc6hEField.RX, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 11), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GX, 4), - new Bc6hsModeDescriptor(Bc6hEField.GX, 5), new Bc6hsModeDescriptor(Bc6hEField.GX, 6), new Bc6hsModeDescriptor(Bc6hEField.GX, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 11), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BX, 4), - new Bc6hsModeDescriptor(Bc6hEField.BX, 5), new Bc6hsModeDescriptor(Bc6hEField.BX, 6), new Bc6hsModeDescriptor(Bc6hEField.BX, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 11), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), - new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), - new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0) - }, - - new[] - { - // Mode 14 (0x0f) - 16 4 - new Bc6hsModeDescriptor(Bc6hEField.M, 0), new Bc6hsModeDescriptor(Bc6hEField.M, 1), new Bc6hsModeDescriptor(Bc6hEField.M, 2), new Bc6hsModeDescriptor(Bc6hEField.M, 3), new Bc6hsModeDescriptor(Bc6hEField.M, 4), new Bc6hsModeDescriptor(Bc6hEField.RW, 0), new Bc6hsModeDescriptor(Bc6hEField.RW, 1), new Bc6hsModeDescriptor(Bc6hEField.RW, 2), new Bc6hsModeDescriptor(Bc6hEField.RW, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 4), - new Bc6hsModeDescriptor(Bc6hEField.RW, 5), new Bc6hsModeDescriptor(Bc6hEField.RW, 6), new Bc6hsModeDescriptor(Bc6hEField.RW, 7), new Bc6hsModeDescriptor(Bc6hEField.RW, 8), new Bc6hsModeDescriptor(Bc6hEField.RW, 9), new Bc6hsModeDescriptor(Bc6hEField.GW, 0), new Bc6hsModeDescriptor(Bc6hEField.GW, 1), new Bc6hsModeDescriptor(Bc6hEField.GW, 2), new Bc6hsModeDescriptor(Bc6hEField.GW, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 4), - new Bc6hsModeDescriptor(Bc6hEField.GW, 5), new Bc6hsModeDescriptor(Bc6hEField.GW, 6), new Bc6hsModeDescriptor(Bc6hEField.GW, 7), new Bc6hsModeDescriptor(Bc6hEField.GW, 8), new Bc6hsModeDescriptor(Bc6hEField.GW, 9), new Bc6hsModeDescriptor(Bc6hEField.BW, 0), new Bc6hsModeDescriptor(Bc6hEField.BW, 1), new Bc6hsModeDescriptor(Bc6hEField.BW, 2), new Bc6hsModeDescriptor(Bc6hEField.BW, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 4), - new Bc6hsModeDescriptor(Bc6hEField.BW, 5), new Bc6hsModeDescriptor(Bc6hEField.BW, 6), new Bc6hsModeDescriptor(Bc6hEField.BW, 7), new Bc6hsModeDescriptor(Bc6hEField.BW, 8), new Bc6hsModeDescriptor(Bc6hEField.BW, 9), new Bc6hsModeDescriptor(Bc6hEField.RX, 0), new Bc6hsModeDescriptor(Bc6hEField.RX, 1), new Bc6hsModeDescriptor(Bc6hEField.RX, 2), new Bc6hsModeDescriptor(Bc6hEField.RX, 3), new Bc6hsModeDescriptor(Bc6hEField.RW, 15), - new Bc6hsModeDescriptor(Bc6hEField.RW, 14), new Bc6hsModeDescriptor(Bc6hEField.RW, 13), new Bc6hsModeDescriptor(Bc6hEField.RW, 12), new Bc6hsModeDescriptor(Bc6hEField.RW, 11), new Bc6hsModeDescriptor(Bc6hEField.RW, 10), new Bc6hsModeDescriptor(Bc6hEField.GX, 0), new Bc6hsModeDescriptor(Bc6hEField.GX, 1), new Bc6hsModeDescriptor(Bc6hEField.GX, 2), new Bc6hsModeDescriptor(Bc6hEField.GX, 3), new Bc6hsModeDescriptor(Bc6hEField.GW, 15), - new Bc6hsModeDescriptor(Bc6hEField.GW, 14), new Bc6hsModeDescriptor(Bc6hEField.GW, 13), new Bc6hsModeDescriptor(Bc6hEField.GW, 12), new Bc6hsModeDescriptor(Bc6hEField.GW, 11), new Bc6hsModeDescriptor(Bc6hEField.GW, 10), new Bc6hsModeDescriptor(Bc6hEField.BX, 0), new Bc6hsModeDescriptor(Bc6hEField.BX, 1), new Bc6hsModeDescriptor(Bc6hEField.BX, 2), new Bc6hsModeDescriptor(Bc6hEField.BX, 3), new Bc6hsModeDescriptor(Bc6hEField.BW, 15), - new Bc6hsModeDescriptor(Bc6hEField.BW, 14), new Bc6hsModeDescriptor(Bc6hEField.BW, 13), new Bc6hsModeDescriptor(Bc6hEField.BW, 12), new Bc6hsModeDescriptor(Bc6hEField.BW, 11), new Bc6hsModeDescriptor(Bc6hEField.BW, 10), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), - new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0), - new Bc6hsModeDescriptor(Bc6hEField.NA, 0), new Bc6hsModeDescriptor(Bc6hEField.NA, 0) + uMode = (byte)((GetBits(currentBlock, ref uStartBit, 3) << 2) | uMode); } - }; - - private static readonly Bc6hsModeInfo[] MsAInfo = - { - new Bc6hsModeInfo(0x00, 1, true, 3, new[] { new[] { new LdrColorA(10, 10, 10, 0), new LdrColorA(5, 5, 5, 0) }, new[] { new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0) } }), // Mode 1 - new Bc6hsModeInfo(0x01, 1, true, 3, new[] { new[] { new LdrColorA(7, 7, 7, 0), new LdrColorA(6, 6, 6, 0) }, new[] { new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0) } }), // Mode 2 - new Bc6hsModeInfo(0x02, 1, true, 3, new[] { new[] { new LdrColorA(11, 11, 11, 0), new LdrColorA(5, 4, 4, 0) }, new[] { new LdrColorA(5, 4, 4, 0), new LdrColorA(5, 4, 4, 0) } }), // Mode 3 - new Bc6hsModeInfo(0x06, 1, true, 3, new[] { new[] { new LdrColorA(11, 11, 11, 0), new LdrColorA(4, 5, 4, 0) }, new[] { new LdrColorA(4, 5, 4, 0), new LdrColorA(4, 5, 4, 0) } }), // Mode 4 - new Bc6hsModeInfo(0x0a, 1, true, 3, new[] { new[] { new LdrColorA(11, 11, 11, 0), new LdrColorA(4, 4, 5, 0) }, new[] { new LdrColorA(4, 4, 5, 0), new LdrColorA(4, 4, 5, 0) } }), // Mode 5 - new Bc6hsModeInfo(0x0e, 1, true, 3, new[] { new[] { new LdrColorA(9, 9, 9, 0), new LdrColorA(5, 5, 5, 0) }, new[] { new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0) } }), // Mode 6 - new Bc6hsModeInfo(0x12, 1, true, 3, new[] { new[] { new LdrColorA(8, 8, 8, 0), new LdrColorA(6, 5, 5, 0) }, new[] { new LdrColorA(6, 5, 5, 0), new LdrColorA(6, 5, 5, 0) } }), // Mode 7 - new Bc6hsModeInfo(0x16, 1, true, 3, new[] { new[] { new LdrColorA(8, 8, 8, 0), new LdrColorA(5, 6, 5, 0) }, new[] { new LdrColorA(5, 6, 5, 0), new LdrColorA(5, 6, 5, 0) } }), // Mode 8 - new Bc6hsModeInfo(0x1a, 1, true, 3, new[] { new[] { new LdrColorA(8, 8, 8, 0), new LdrColorA(5, 5, 6, 0) }, new[] { new LdrColorA(5, 5, 6, 0), new LdrColorA(5, 5, 6, 0) } }), // Mode 9 - new Bc6hsModeInfo(0x1e, 1, false, 3, new[] { new[] { new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0) }, new[] { new LdrColorA(6, 6, 6, 0), new LdrColorA(6, 6, 6, 0) } }), // Mode 10 - new Bc6hsModeInfo(0x03, 0, false, 4, new[] { new[] { new LdrColorA(10, 10, 10, 0), new LdrColorA(10, 10, 10, 0) }, new[] { new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0) } }), // Mode 11 - new Bc6hsModeInfo(0x07, 0, true, 4, new[] { new[] { new LdrColorA(11, 11, 11, 0), new LdrColorA(9, 9, 9, 0) }, new[] { new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0) } }), // Mode 12 - new Bc6hsModeInfo(0x0b, 0, true, 4, new[] { new[] { new LdrColorA(12, 12, 12, 0), new LdrColorA(8, 8, 8, 0) }, new[] { new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0) } }), // Mode 13 - new Bc6hsModeInfo(0x0f, 0, true, 4, new[] { new[] { new LdrColorA(16, 16, 16, 0), new LdrColorA(4, 4, 4, 0) }, new[] { new LdrColorA(0, 0, 0, 0), new LdrColorA(0, 0, 0, 0) } }), // Mode 14 - }; - - private static readonly int[] MsAModeToInfo = - { - 0, // Mode 1 - 0x00 - 1, // Mode 2 - 0x01 - 2, // Mode 3 - 0x02 - 10, // Mode 11 - 0x03 - -1, // Invalid - 0x04 - -1, // Invalid - 0x05 - 3, // Mode 4 - 0x06 - 11, // Mode 12 - 0x07 - -1, // Invalid - 0x08 - -1, // Invalid - 0x09 - 4, // Mode 5 - 0x0a - 12, // Mode 13 - 0x0b - -1, // Invalid - 0x0c - -1, // Invalid - 0x0d - 5, // Mode 6 - 0x0e - 13, // Mode 14 - 0x0f - -1, // Invalid - 0x10 - -1, // Invalid - 0x11 - 6, // Mode 7 - 0x12 - -1, // Reserved - 0x13 - -1, // Invalid - 0x14 - -1, // Invalid - 0x15 - 7, // Mode 8 - 0x16 - -1, // Reserved - 0x17 - -1, // Invalid - 0x18 - -1, // Invalid - 0x19 - 8, // Mode 9 - 0x1a - -1, // Reserved - 0x1b - -1, // Invalid - 0x1c - -1, // Invalid - 0x1d - 9, // Mode 10 - 0x1e - -1, // Resreved - 0x1f - }; - - /// - public int BitsPerPixel => 32; - - /// - public byte PixelDepthBytes => 4; - - /// - public byte DivSize => 4; - - /// - public byte CompressedBytesPerBlock => 16; - - /// - public bool Compressed => true; - - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - Bc6hs self = this; - byte[] currentBlock = new byte[this.CompressedBytesPerBlock]; + Debug.Assert(uMode < 32, "uMode should be less then 32"); - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + if (MsAModeToInfo[uMode] >= 0) { - // I would prefer to use Span, but not sure if I should reference System.Memory in this project - // copy data instead - Buffer.BlockCopy(blockData, streamIndex, currentBlock, 0, currentBlock.Length); - streamIndex += currentBlock.Length; - - uint uStartBit = 0; - byte uMode = GetBits(currentBlock, ref uStartBit, 2u); - if (uMode != 0x00 && uMode != 0x01) - { - uMode = (byte)((GetBits(currentBlock, ref uStartBit, 3) << 2) | uMode); - } + Debug.Assert(MsAModeToInfo[uMode] < MsAInfo.Length, "MsAModeToInfo[uMode] should be smaller then MsAInfo.Length"); + Bc6hsModeDescriptor[] desc = MsADesc[MsAModeToInfo[uMode]]; - Debug.Assert(uMode < 32, "uMode should be less then 32"); + Debug.Assert(MsAModeToInfo[uMode] < MsADesc.Length, "MsAModeToInfo[uMode] should be smaller then MsADesc.Length"); + ref Bc6hsModeInfo info = ref MsAInfo[MsAModeToInfo[uMode]]; - if (MsAModeToInfo[uMode] >= 0) + IntEndPntPair[] aEndPts = new IntEndPntPair[Constants.BC6H_MAX_REGIONS]; + for (int i = 0; i < aEndPts.Length; ++i) { - Debug.Assert(MsAModeToInfo[uMode] < MsAInfo.Length, "MsAModeToInfo[uMode] should be smaller then MsAInfo.Length"); - Bc6hsModeDescriptor[] desc = MsADesc[MsAModeToInfo[uMode]]; - - Debug.Assert(MsAModeToInfo[uMode] < MsADesc.Length, "MsAModeToInfo[uMode] should be smaller then MsADesc.Length"); - ref Bc6hsModeInfo info = ref MsAInfo[MsAModeToInfo[uMode]]; - - var aEndPts = new IntEndPntPair[Constants.BC6H_MAX_REGIONS]; - for (int i = 0; i < aEndPts.Length; ++i) - { - aEndPts[i] = new IntEndPntPair(new IntColor(), new IntColor()); - } + aEndPts[i] = new IntEndPntPair(new IntColor(), new IntColor()); + } - uint uShape = 0; + uint uShape = 0; - // Read header - uint uHeaderBits = info.UPartitions > 0 ? 82u : 65u; - while (uStartBit < uHeaderBits) + // Read header + uint uHeaderBits = info.UPartitions > 0 ? 82u : 65u; + while (uStartBit < uHeaderBits) + { + uint uCurBit = uStartBit; + if (GetBit(currentBlock, ref uStartBit) != 0) { - uint uCurBit = uStartBit; - if (GetBit(currentBlock, ref uStartBit) != 0) + switch (desc[uCurBit].MBc6HEField) { - switch (desc[uCurBit].MBc6HEField) + case Bc6hEField.D: + uShape |= 1u << desc[uCurBit].Bit; + break; + case Bc6hEField.RW: + aEndPts[0].A.R |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.RX: + aEndPts[0].B.R |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.RY: + aEndPts[1].A.R |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.RZ: + aEndPts[1].B.R |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.GW: + aEndPts[0].A.G |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.GX: + aEndPts[0].B.G |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.GY: + aEndPts[1].A.G |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.GZ: + aEndPts[1].B.G |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.BW: + aEndPts[0].A.B |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.BX: + aEndPts[0].B.B |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.BY: + aEndPts[1].A.B |= 1 << desc[uCurBit].Bit; + break; + case Bc6hEField.BZ: + aEndPts[1].B.B |= 1 << desc[uCurBit].Bit; + break; + default: { - case Bc6hEField.D: - uShape |= 1u << desc[uCurBit].Bit; - break; - case Bc6hEField.RW: - aEndPts[0].A.R |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.RX: - aEndPts[0].B.R |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.RY: - aEndPts[1].A.R |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.RZ: - aEndPts[1].B.R |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.GW: - aEndPts[0].A.G |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.GX: - aEndPts[0].B.G |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.GY: - aEndPts[1].A.G |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.GZ: - aEndPts[1].B.G |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.BW: - aEndPts[0].A.B |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.BX: - aEndPts[0].B.B |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.BY: - aEndPts[1].A.B |= 1 << desc[uCurBit].Bit; - break; - case Bc6hEField.BZ: - aEndPts[1].B.B |= 1 << desc[uCurBit].Bit; - break; - default: - { - Debug.WriteLine("BC6H: Invalid header bits encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } + Debug.WriteLine("BC6H: Invalid header bits encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; } } } + } - Debug.Assert(uShape < 64, "uShape shoul be less then 64"); + Debug.Assert(uShape < 64, "uShape shoul be less then 64"); - aEndPts[0].A.SignExtend(info.RgbaPrec[0][0]); + aEndPts[0].A.SignExtend(info.RgbaPrec[0][0]); - if (info.BTransformed) + if (info.BTransformed) + { + Debug.Assert(info.UPartitions < Constants.BC6H_MAX_REGIONS, $"info.UPartitions should be less then {Constants.BC6H_MAX_REGIONS}"); + for (int p = 0; p <= info.UPartitions; ++p) { - Debug.Assert(info.UPartitions < Constants.BC6H_MAX_REGIONS, $"info.UPartitions should be less then {Constants.BC6H_MAX_REGIONS}"); - for (int p = 0; p <= info.UPartitions; ++p) + if (p != 0) { - if (p != 0) - { - aEndPts[p].A.SignExtend(info.RgbaPrec[p][0]); - } - - aEndPts[p].B.SignExtend(info.RgbaPrec[p][1]); + aEndPts[p].A.SignExtend(info.RgbaPrec[p][0]); } - } - // Inverse transform the end points. - if (info.BTransformed) - { - Helpers.TransformInverseSigned(aEndPts, info.RgbaPrec[0][0]); + aEndPts[p].B.SignExtend(info.RgbaPrec[p][1]); } + } - // Read indices. - for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) + // Inverse transform the end points. + if (info.BTransformed) + { + Helpers.TransformInverseSigned(aEndPts, info.RgbaPrec[0][0]); + } + + // Read indices. + for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) + { + uint uNumBits = Helpers.IsFixUpOffset(info.UPartitions, (byte)uShape, i) ? info.UIndexPrec - 1u : info.UIndexPrec; + if (uStartBit + uNumBits > 128) { - uint uNumBits = Helpers.IsFixUpOffset(info.UPartitions, (byte)uShape, i) ? info.UIndexPrec - 1u : info.UIndexPrec; - if (uStartBit + uNumBits > 128) - { - Debug.WriteLine("BC6H: Invalid block encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } + Debug.WriteLine("BC6H: Invalid block encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; + } - uint uIndex = GetBits(currentBlock, ref uStartBit, uNumBits); + uint uIndex = GetBits(currentBlock, ref uStartBit, uNumBits); - if (uIndex >= ((info.UPartitions > 0) ? 8 : 16)) - { - Debug.WriteLine("BC6H: Invalid index encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } + if (uIndex >= ((info.UPartitions > 0) ? 8 : 16)) + { + Debug.WriteLine("BC6H: Invalid index encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; + } - uint uRegion = Constants.PartitionTable[info.UPartitions][uShape][i]; - Debug.Assert(uRegion < Constants.BC6H_MAX_REGIONS, $"uRegion should be less then {Constants.BC6H_MAX_REGIONS}"); - - // Unquantize endpoints and interpolate - int r1 = Unquantize(aEndPts[uRegion].A.R, info.RgbaPrec[0][0].R); - int g1 = Unquantize(aEndPts[uRegion].A.G, info.RgbaPrec[0][0].G); - int b1 = Unquantize(aEndPts[uRegion].A.B, info.RgbaPrec[0][0].B); - int r2 = Unquantize(aEndPts[uRegion].B.R, info.RgbaPrec[0][0].R); - int g2 = Unquantize(aEndPts[uRegion].B.G, info.RgbaPrec[0][0].G); - int b2 = Unquantize(aEndPts[uRegion].B.B, info.RgbaPrec[0][0].B); - int[] aWeights = info.UPartitions > 0 ? Constants.Weights3 : Constants.Weights4; - var fc = new IntColor - { - R = FinishUnquantize(((r1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (r2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT), - G = FinishUnquantize(((g1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (g2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT), - B = FinishUnquantize(((b1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (b2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT) - }; - - ushort[] rgb = new ushort[3]; - fc.ToF16Signed(rgb); - - // Clamp 0..1, and convert to byte (we're losing high dynamic range) - data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[0]))) * 255.0f) + 0.5f); // red - data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[1]))) * 255.0f) + 0.5f); // green - data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[2]))) * 255.0f) + 0.5f); // blue - data[dataIndex++] = 255; - - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + uint uRegion = Constants.PartitionTable[info.UPartitions][uShape][i]; + Debug.Assert(uRegion < Constants.BC6H_MAX_REGIONS, $"uRegion should be less then {Constants.BC6H_MAX_REGIONS}"); + + // Unquantize endpoints and interpolate + int r1 = Unquantize(aEndPts[uRegion].A.R, info.RgbaPrec[0][0].R); + int g1 = Unquantize(aEndPts[uRegion].A.G, info.RgbaPrec[0][0].G); + int b1 = Unquantize(aEndPts[uRegion].A.B, info.RgbaPrec[0][0].B); + int r2 = Unquantize(aEndPts[uRegion].B.R, info.RgbaPrec[0][0].R); + int g2 = Unquantize(aEndPts[uRegion].B.G, info.RgbaPrec[0][0].G); + int b2 = Unquantize(aEndPts[uRegion].B.B, info.RgbaPrec[0][0].B); + int[] aWeights = info.UPartitions > 0 ? Constants.Weights3 : Constants.Weights4; + IntColor fc = new IntColor + { + R = FinishUnquantize(((r1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (r2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT), + G = FinishUnquantize(((g1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (g2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT), + B = FinishUnquantize(((b1 * (Constants.BC67_WEIGHT_MAX - aWeights[uIndex])) + (b2 * aWeights[uIndex]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT) + }; + + ushort[] rgb = new ushort[3]; + fc.ToF16Signed(rgb); + + // Clamp 0..1, and convert to byte (we're losing high dynamic range) + data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[0]))) * 255.0f) + 0.5f); // red + data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[1]))) * 255.0f) + 0.5f); // green + data[dataIndex++] = (byte)((Math.Max(0.0f, Math.Min(1.0f, FloatHelper.UnpackFloat16ToFloat(rgb[2]))) * 255.0f) + 0.5f); // blue + data[dataIndex++] = 255; + + // Is mult 4? + if (((i + 1) & 0x3) == 0) + { + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } } - else + } + else + { + string warnStr = "BC6H: Invalid mode encountered during decoding"; + switch (uMode) { - string warnStr = "BC6H: Invalid mode encountered during decoding"; - switch (uMode) - { - case 0x13: - warnStr = "BC6H: Reserved mode 10011 encountered during decoding"; - break; - case 0x17: - warnStr = "BC6H: Reserved mode 10111 encountered during decoding"; - break; - case 0x1B: - warnStr = "BC6H: Reserved mode 11011 encountered during decoding"; - break; - case 0x1F: - warnStr = "BC6H: Reserved mode 11111 encountered during decoding"; - break; - } + case 0x13: + warnStr = "BC6H: Reserved mode 10011 encountered during decoding"; + break; + case 0x17: + warnStr = "BC6H: Reserved mode 10111 encountered during decoding"; + break; + case 0x1B: + warnStr = "BC6H: Reserved mode 11011 encountered during decoding"; + break; + case 0x1F: + warnStr = "BC6H: Reserved mode 11111 encountered during decoding"; + break; + } - Debug.WriteLine(warnStr); + Debug.WriteLine(warnStr); - // Per the BC6H format spec, we must return opaque black - for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) - { - data[dataIndex++] = 0; - data[dataIndex++] = 0; - data[dataIndex++] = 0; - data[dataIndex++] = 0; + // Per the BC6H format spec, we must return opaque black + for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) + { + data[dataIndex++] = 0; + data[dataIndex++] = 0; + data[dataIndex++] = 0; + data[dataIndex++] = 0; - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + // Is mult 4? + if (((i + 1) & 0x3) == 0) + { + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } } + } - return streamIndex; - }); - } + return streamIndex; + }); + } + + /// + /// Gets a bit for a given position. + /// + /// The current block. + /// The start bit. + /// A bit at a given position. + public static byte GetBit(byte[] currentBlock, ref uint uStartBit) + { + Guard.MustBeLessThan(uStartBit, 128, nameof(uStartBit)); + + uint uIndex = uStartBit >> 3; + byte ret = (byte)((currentBlock[uIndex] >> (int)(uStartBit - (uIndex << 3))) & 0x01); + uStartBit++; + return ret; + } - /// - /// Gets a bit for a given position. - /// - /// The current block. - /// The start bit. - /// A bit at a given position. - public static byte GetBit(byte[] currentBlock, ref uint uStartBit) + /// + /// Gets n bits at a given start position. + /// + /// The current block. + /// The start bit. + /// The number of bits. + /// Bits at a given position. + public static byte GetBits(byte[] currentBlock, ref uint uStartBit, uint uNumBits) + { + if (uNumBits == 0) { - Guard.MustBeLessThan(uStartBit, 128, nameof(uStartBit)); + return 0; + } - uint uIndex = uStartBit >> 3; - byte ret = (byte)((currentBlock[uIndex] >> (int)(uStartBit - (uIndex << 3))) & 0x01); - uStartBit++; - return ret; + Debug.Assert(uStartBit + uNumBits <= 128 && uNumBits <= 8, "uStartBit + uNumBits <= 128 && uNumBits <= 8"); + byte ret; + uint uIndex = uStartBit >> 3; + uint uBase = uStartBit - (uIndex << 3); + if (uBase + uNumBits > 8) + { + uint uFirstIndexBits = 8 - uBase; + uint uNextIndexBits = uNumBits - uFirstIndexBits; + ret = (byte)((uint)(currentBlock[uIndex] >> (int)uBase) | ((currentBlock[uIndex + 1] & ((1u << (int)uNextIndexBits) - 1)) << (int)uFirstIndexBits)); + } + else + { + ret = (byte)((currentBlock[uIndex] >> (int)uBase) & ((1 << (int)uNumBits) - 1)); } - /// - /// Gets n bits at a given start position. - /// - /// The current block. - /// The start bit. - /// The number of bits. - /// Bits at a given position. - public static byte GetBits(byte[] currentBlock, ref uint uStartBit, uint uNumBits) + Debug.Assert(ret < (1 << (int)uNumBits), $"GetBits return value should be less then {1 << (int)uNumBits}"); + uStartBit += uNumBits; + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Unquantize(int comp, byte uBitsPerComp) + { + int s = 0; + int unq; + if (uBitsPerComp >= 16) { - if (uNumBits == 0) + unq = comp; + } + else + { + if (comp < 0) { - return 0; + s = 1; + comp = -comp; } - Debug.Assert(uStartBit + uNumBits <= 128 && uNumBits <= 8, "uStartBit + uNumBits <= 128 && uNumBits <= 8"); - byte ret; - uint uIndex = uStartBit >> 3; - uint uBase = uStartBit - (uIndex << 3); - if (uBase + uNumBits > 8) - { - uint uFirstIndexBits = 8 - uBase; - uint uNextIndexBits = uNumBits - uFirstIndexBits; - ret = (byte)((uint)(currentBlock[uIndex] >> (int)uBase) | ((currentBlock[uIndex + 1] & ((1u << (int)uNextIndexBits) - 1)) << (int)uFirstIndexBits)); - } - else + if (comp == 0) { - ret = (byte)((currentBlock[uIndex] >> (int)uBase) & ((1 << (int)uNumBits) - 1)); + unq = 0; } - - Debug.Assert(ret < (1 << (int)uNumBits), $"GetBits return value should be less then {1 << (int)uNumBits}"); - uStartBit += uNumBits; - return ret; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Unquantize(int comp, byte uBitsPerComp) - { - int s = 0; - int unq; - if (uBitsPerComp >= 16) + else if (comp >= ((1 << (uBitsPerComp - 1)) - 1)) { - unq = comp; + unq = 0x7FFF; } else { - if (comp < 0) - { - s = 1; - comp = -comp; - } - - if (comp == 0) - { - unq = 0; - } - else if (comp >= ((1 << (uBitsPerComp - 1)) - 1)) - { - unq = 0x7FFF; - } - else - { - unq = ((comp << 15) + 0x4000) >> (uBitsPerComp - 1); - } - - if (s != 0) - { - unq = -unq; - } + unq = ((comp << 15) + 0x4000) >> (uBitsPerComp - 1); } - return unq; + if (s != 0) + { + unq = -unq; + } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FinishUnquantize(int comp) => (comp < 0) ? -(((-comp) * 31) >> 5) : (comp * 31) >> 5; // scale the magnitude by 31/32 + return unq; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FinishUnquantize(int comp) => (comp < 0) ? -(((-comp) * 31) >> 5) : (comp * 31) >> 5; // scale the magnitude by 31/32 } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hsModeDescriptor.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hsModeDescriptor.cs index 97e2fd81..2a82b437 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hsModeDescriptor.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hsModeDescriptor.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +internal struct Bc6hsModeDescriptor { - internal struct Bc6hsModeDescriptor - { - public readonly Bc6hEField MBc6HEField; - public readonly byte Bit; + public readonly Bc6hEField MBc6HEField; + public readonly byte Bit; - public Bc6hsModeDescriptor(Bc6hEField bc6Hef, byte b) - { - this.MBc6HEField = bc6Hef; - this.Bit = b; - } + public Bc6hsModeDescriptor(Bc6hEField bc6Hef, byte b) + { + this.MBc6HEField = bc6Hef; + this.Bit = b; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hsModeInfo.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hsModeInfo.cs index 0c9047ec..4c98bf0f 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hsModeInfo.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc6hsModeInfo.cs @@ -3,23 +3,22 @@ using SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +internal struct Bc6hsModeInfo { - internal struct Bc6hsModeInfo - { - public byte UMode; - public readonly byte UPartitions; - public readonly bool BTransformed; - public readonly byte UIndexPrec; - public readonly LdrColorA[][] RgbaPrec; // [Constants.BC6H_MAX_REGIONS][2]; + public byte UMode; + public readonly byte UPartitions; + public readonly bool BTransformed; + public readonly byte UIndexPrec; + public readonly LdrColorA[][] RgbaPrec; // [Constants.BC6H_MAX_REGIONS][2]; - public Bc6hsModeInfo(byte uM, byte uP, bool bT, byte uI, LdrColorA[][] prec) - { - this.UMode = uM; - this.UPartitions = uP; - this.BTransformed = bT; - this.UIndexPrec = uI; - this.RgbaPrec = prec; - } + public Bc6hsModeInfo(byte uM, byte uP, bool bT, byte uI, LdrColorA[][] prec) + { + this.UMode = uM; + this.UPartitions = uP; + this.BTransformed = bT; + this.UIndexPrec = uI; + this.RgbaPrec = prec; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc7.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc7.cs index a10234ff..69b7c195 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc7.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc7.cs @@ -1,210 +1,225 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with BC7 with three color channels (4 to 7 bits per channel) with 0 to 8 bits of alpha. +/// +internal struct Bc7 : IBlock { - /// - /// Texture compressed with BC7 with three color channels (4 to 7 bits per channel) with 0 to 8 bits of alpha. - /// - internal struct Bc7 : IBlock - { - // Code based on commit 138efff1b9c53fd9a5dd34b8c865e8f5ae798030 2019/10/24 in DirectXTex C++ library - private static readonly Bc7ModeInfo[] ModeInfos = - { - // Mode 0: Color only, 3 Subsets, RGBP 4441 (unique P-bit), 3-bit indecies, 16 partitions - new Bc7ModeInfo(2, 4, 6, 0, 0, 3, 0, new LdrColorA(4, 4, 4, 0), new LdrColorA(5, 5, 5, 0)), + // Code based on commit 138efff1b9c53fd9a5dd34b8c865e8f5ae798030 2019/10/24 in DirectXTex C++ library + private static readonly Bc7ModeInfo[] ModeInfos = + [ + // Mode 0: Color only, 3 Subsets, RGBP 4441 (unique P-bit), 3-bit indecies, 16 partitions + new Bc7ModeInfo(2, 4, 6, 0, 0, 3, 0, new LdrColorA(4, 4, 4, 0), new LdrColorA(5, 5, 5, 0)), - // Mode 1: Color only, 2 Subsets, RGBP 6661 (shared P-bit), 3-bit indecies, 64 partitions - new Bc7ModeInfo(1, 6, 2, 0, 0, 3, 0, new LdrColorA(6, 6, 6, 0), new LdrColorA(7, 7, 7, 0)), + // Mode 1: Color only, 2 Subsets, RGBP 6661 (shared P-bit), 3-bit indecies, 64 partitions + new Bc7ModeInfo(1, 6, 2, 0, 0, 3, 0, new LdrColorA(6, 6, 6, 0), new LdrColorA(7, 7, 7, 0)), - // Mode 2: Color only, 3 Subsets, RGB 555, 2-bit indecies, 64 partitions - new Bc7ModeInfo(2, 6, 0, 0, 0, 2, 0, new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0)), + // Mode 2: Color only, 3 Subsets, RGB 555, 2-bit indecies, 64 partitions + new Bc7ModeInfo(2, 6, 0, 0, 0, 2, 0, new LdrColorA(5, 5, 5, 0), new LdrColorA(5, 5, 5, 0)), - // Mode 3: Color only, 2 Subsets, RGBP 7771 (unique P-bit), 2-bits indecies, 64 partitions - new Bc7ModeInfo(1, 6, 4, 0, 0, 2, 0, new LdrColorA(7, 7, 7, 0), new LdrColorA(8, 8, 8, 0)), + // Mode 3: Color only, 2 Subsets, RGBP 7771 (unique P-bit), 2-bits indecies, 64 partitions + new Bc7ModeInfo(1, 6, 4, 0, 0, 2, 0, new LdrColorA(7, 7, 7, 0), new LdrColorA(8, 8, 8, 0)), - // Mode 4: Color w/ Separate Alpha, 1 Subset, RGB 555, A6, 16x2/16x3-bit indices, 2-bit rotation, 1-bit index selector - new Bc7ModeInfo(0, 0, 0, 2, 1, 2, 3, new LdrColorA(5, 5, 5, 6), new LdrColorA(5, 5, 5, 6)), + // Mode 4: Color w/ Separate Alpha, 1 Subset, RGB 555, A6, 16x2/16x3-bit indices, 2-bit rotation, 1-bit index selector + new Bc7ModeInfo(0, 0, 0, 2, 1, 2, 3, new LdrColorA(5, 5, 5, 6), new LdrColorA(5, 5, 5, 6)), - // Mode 5: Color w/ Separate Alpha, 1 Subset, RGB 777, A8, 16x2/16x2-bit indices, 2-bit rotation - new Bc7ModeInfo(0, 0, 0, 2, 0, 2, 2, new LdrColorA(7, 7, 7, 8), new LdrColorA(7, 7, 7, 8)), + // Mode 5: Color w/ Separate Alpha, 1 Subset, RGB 777, A8, 16x2/16x2-bit indices, 2-bit rotation + new Bc7ModeInfo(0, 0, 0, 2, 0, 2, 2, new LdrColorA(7, 7, 7, 8), new LdrColorA(7, 7, 7, 8)), - // Mode 6: Color+Alpha, 1 Subset, RGBAP 77771 (unique P-bit), 16x4-bit indecies - new Bc7ModeInfo(0, 0, 2, 0, 0, 4, 0, new LdrColorA(7, 7, 7, 7), new LdrColorA(8, 8, 8, 8)), + // Mode 6: Color+Alpha, 1 Subset, RGBAP 77771 (unique P-bit), 16x4-bit indecies + new Bc7ModeInfo(0, 0, 2, 0, 0, 4, 0, new LdrColorA(7, 7, 7, 7), new LdrColorA(8, 8, 8, 8)), - // Mode 7: Color+Alpha, 2 Subsets, RGBAP 55551 (unique P-bit), 2-bit indices, 64 partitions - new Bc7ModeInfo(1, 6, 4, 0, 0, 2, 0, new LdrColorA(5, 5, 5, 5), new LdrColorA(6, 6, 6, 6)) - }; + // Mode 7: Color+Alpha, 2 Subsets, RGBAP 55551 (unique P-bit), 2-bit indices, 64 partitions + new Bc7ModeInfo(1, 6, 4, 0, 0, 2, 0, new LdrColorA(5, 5, 5, 5), new LdrColorA(6, 6, 6, 6)) + ]; - /// - public int BitsPerPixel => 32; + /// + public readonly int BitsPerPixel => 32; - /// - public byte PixelDepthBytes => 4; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte DivSize => 4; + /// + public readonly byte DivSize => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public readonly byte CompressedBytesPerBlock => 16; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } + + /// + public byte[] Decompress(byte[] blockData, int width, int height) + { + byte[] currentBlock = new byte[this.CompressedBytesPerBlock]; + Bc7 self = this; - /// - public byte[] Decompress(byte[] blockData, int width, int height) + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => { - byte[] currentBlock = new byte[this.CompressedBytesPerBlock]; - Bc7 self = this; + // I would prefer to use Span, but not sure if I should reference System.Memory in this project copy data instead. + Buffer.BlockCopy(blockData, streamIndex, currentBlock, 0, currentBlock.Length); + streamIndex += currentBlock.Length; - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + uint uFirst = 0; + while (uFirst < 128 && GetBit(currentBlock, ref uFirst) == 0) { - // I would prefer to use Span, but not sure if I should reference System.Memory in this project copy data instead. - Buffer.BlockCopy(blockData, streamIndex, currentBlock, 0, currentBlock.Length); - streamIndex += currentBlock.Length; + } - uint uFirst = 0; - while (uFirst < 128 && GetBit(currentBlock, ref uFirst) == 0) + byte uMode = (byte)(uFirst - 1); + + if (uMode < 8) + { + byte uPartitions = ModeInfos[uMode].UPartitions; + Debug.Assert(uPartitions < Constants.BC7_MAX_REGIONS, $"uPartitions should be smaller then {Constants.BC7_MAX_REGIONS}"); + + byte uNumEndPts = (byte)((uPartitions + 1u) << 1); + byte uIndexPrec = ModeInfos[uMode].UIndexPrec; + byte uIndexPrec2 = ModeInfos[uMode].UIndexPrec2; + int i; + uint uStartBit = uMode + 1u; + int[] p = new int[6]; + byte uShape = GetBits(currentBlock, ref uStartBit, ModeInfos[uMode].UPartitionBits); + Debug.Assert(uShape < Constants.BC7_MAX_SHAPES, $"uShape should be smaller then {Constants.BC7_MAX_SHAPES}"); + + byte uRotation = GetBits(currentBlock, ref uStartBit, ModeInfos[uMode].URotationBits); + Debug.Assert(uRotation < 4, "uRotation should be less then 4"); + + byte uIndexMode = GetBits(currentBlock, ref uStartBit, ModeInfos[uMode].UIndexModeBits); + Debug.Assert(uIndexMode < 2, "uIndexMode should be less then 2"); + + LdrColorA[] c = new LdrColorA[Constants.BC7_MAX_REGIONS << 1]; + for (i = 0; i < c.Length; ++i) { + c[i] = new LdrColorA(); } - byte uMode = (byte)(uFirst - 1); + LdrColorA rgbaPrec = ModeInfos[uMode].RgbaPrec; + LdrColorA rgbaPrecWithP = ModeInfos[uMode].RgbaPrecWithP; - if (uMode < 8) + Debug.Assert(uNumEndPts <= (Constants.BC7_MAX_REGIONS << 1), $"uNumEndPts should be smaller or equal to {Constants.BC7_MAX_REGIONS << 1}"); + + // Red channel + for (i = 0; i < uNumEndPts; i++) { - byte uPartitions = ModeInfos[uMode].UPartitions; - Debug.Assert(uPartitions < Constants.BC7_MAX_REGIONS, $"uPartitions should be smaller then {Constants.BC7_MAX_REGIONS}"); - - byte uNumEndPts = (byte)((uPartitions + 1u) << 1); - byte uIndexPrec = ModeInfos[uMode].UIndexPrec; - byte uIndexPrec2 = ModeInfos[uMode].UIndexPrec2; - int i; - uint uStartBit = uMode + 1u; - int[] p = new int[6]; - byte uShape = GetBits(currentBlock, ref uStartBit, ModeInfos[uMode].UPartitionBits); - Debug.Assert(uShape < Constants.BC7_MAX_SHAPES, $"uShape should be smaller then {Constants.BC7_MAX_SHAPES}"); - - byte uRotation = GetBits(currentBlock, ref uStartBit, ModeInfos[uMode].URotationBits); - Debug.Assert(uRotation < 4, "uRotation should be less then 4"); - - byte uIndexMode = GetBits(currentBlock, ref uStartBit, ModeInfos[uMode].UIndexModeBits); - Debug.Assert(uIndexMode < 2, "uIndexMode should be less then 2"); - - var c = new LdrColorA[Constants.BC7_MAX_REGIONS << 1]; - for (i = 0; i < c.Length; ++i) + if (uStartBit + rgbaPrec.R > 128) { - c[i] = new LdrColorA(); + Debug.WriteLine("BC7: Invalid block encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; } - LdrColorA rgbaPrec = ModeInfos[uMode].RgbaPrec; - LdrColorA rgbaPrecWithP = ModeInfos[uMode].RgbaPrecWithP; - - Debug.Assert(uNumEndPts <= (Constants.BC7_MAX_REGIONS << 1), $"uNumEndPts should be smaller or equal to {Constants.BC7_MAX_REGIONS << 1}"); + c[i].R = GetBits(currentBlock, ref uStartBit, rgbaPrec.R); + } - // Red channel - for (i = 0; i < uNumEndPts; i++) + // Green channel + for (i = 0; i < uNumEndPts; i++) + { + if (uStartBit + rgbaPrec.G > 128) { - if (uStartBit + rgbaPrec.R > 128) - { - Debug.WriteLine("BC7: Invalid block encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } - - c[i].R = GetBits(currentBlock, ref uStartBit, rgbaPrec.R); + Debug.WriteLine("BC7: Invalid block encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; } - // Green channel - for (i = 0; i < uNumEndPts; i++) - { - if (uStartBit + rgbaPrec.G > 128) - { - Debug.WriteLine("BC7: Invalid block encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } - - c[i].G = GetBits(currentBlock, ref uStartBit, rgbaPrec.G); - } + c[i].G = GetBits(currentBlock, ref uStartBit, rgbaPrec.G); + } - // Blue channel - for (i = 0; i < uNumEndPts; i++) + // Blue channel + for (i = 0; i < uNumEndPts; i++) + { + if (uStartBit + rgbaPrec.B > 128) { - if (uStartBit + rgbaPrec.B > 128) - { - Debug.WriteLine("BC7: Invalid block encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } - - c[i].B = GetBits(currentBlock, ref uStartBit, rgbaPrec.B); + Debug.WriteLine("BC7: Invalid block encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; } - // Alpha channel - for (i = 0; i < uNumEndPts; i++) - { - if (uStartBit + rgbaPrec.A > 128) - { - Debug.WriteLine("BC7: Invalid block encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } + c[i].B = GetBits(currentBlock, ref uStartBit, rgbaPrec.B); + } - c[i].A = (byte)(rgbaPrec.A != 0 ? GetBits(currentBlock, ref uStartBit, rgbaPrec.A) : 255u); + // Alpha channel + for (i = 0; i < uNumEndPts; i++) + { + if (uStartBit + rgbaPrec.A > 128) + { + Debug.WriteLine("BC7: Invalid block encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; } - // P-bits - Debug.Assert(ModeInfos[uMode].UPBits <= 6, "ModeInfos[uMode].UPBits should be less then 7"); - for (i = 0; i < ModeInfos[uMode].UPBits; i++) - { - if (uStartBit > 127) - { - Debug.WriteLine("BC7: Invalid block encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } + c[i].A = (byte)(rgbaPrec.A != 0 ? GetBits(currentBlock, ref uStartBit, rgbaPrec.A) : 255u); + } - p[i] = GetBit(currentBlock, ref uStartBit); + // P-bits + Debug.Assert(ModeInfos[uMode].UPBits <= 6, "ModeInfos[uMode].UPBits should be less then 7"); + for (i = 0; i < ModeInfos[uMode].UPBits; i++) + { + if (uStartBit > 127) + { + Debug.WriteLine("BC7: Invalid block encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; } - if (ModeInfos[uMode].UPBits != 0) + p[i] = GetBit(currentBlock, ref uStartBit); + } + + if (ModeInfos[uMode].UPBits != 0) + { + for (i = 0; i < uNumEndPts; i++) { - for (i = 0; i < uNumEndPts; i++) + int pi = i * ModeInfos[uMode].UPBits / uNumEndPts; + for (byte ch = 0; ch < Constants.BC7_NUM_CHANNELS; ch++) { - int pi = i * ModeInfos[uMode].UPBits / uNumEndPts; - for (byte ch = 0; ch < Constants.BC7_NUM_CHANNELS; ch++) + if (rgbaPrec[ch] != rgbaPrecWithP[ch]) { - if (rgbaPrec[ch] != rgbaPrecWithP[ch]) - { - c[i][ch] = (byte)((c[i][ch] << 1) | p[pi]); - } + c[i][ch] = (byte)((c[i][ch] << 1) | p[pi]); } } } + } - for (i = 0; i < uNumEndPts; i++) + for (i = 0; i < uNumEndPts; i++) + { + c[i] = Unquantize(c[i], rgbaPrecWithP); + } + + byte[] w1 = new byte[Constants.NumPixelsPerBlock], w2 = new byte[Constants.NumPixelsPerBlock]; + + // read color indices + for (i = 0; i < Constants.NumPixelsPerBlock; i++) + { + uint uNumBits = Helpers.IsFixUpOffset(ModeInfos[uMode].UPartitions, uShape, i) ? uIndexPrec - 1u : uIndexPrec; + if (uStartBit + uNumBits > 128) { - c[i] = Unquantize(c[i], rgbaPrecWithP); + Debug.WriteLine("BC7: Invalid block encountered during decoding"); + Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); + return dataIndex; } - byte[] w1 = new byte[Constants.NumPixelsPerBlock], w2 = new byte[Constants.NumPixelsPerBlock]; + w1[i] = GetBits(currentBlock, ref uStartBit, uNumBits); + } - // read color indices + // read alpha indices + if (uIndexPrec2 != 0) + { for (i = 0; i < Constants.NumPixelsPerBlock; i++) { - uint uNumBits = Helpers.IsFixUpOffset(ModeInfos[uMode].UPartitions, uShape, i) ? uIndexPrec - 1u : uIndexPrec; + uint uNumBits = i != 0 ? uIndexPrec2 : uIndexPrec2 - 1u; if (uStartBit + uNumBits > 128) { Debug.WriteLine("BC7: Invalid block encountered during decoding"); @@ -212,167 +227,150 @@ public byte[] Decompress(byte[] blockData, int width, int height) return dataIndex; } - w1[i] = GetBits(currentBlock, ref uStartBit, uNumBits); + w2[i] = GetBits(currentBlock, ref uStartBit, uNumBits); } + } - // read alpha indices - if (uIndexPrec2 != 0) + for (i = 0; i < Constants.NumPixelsPerBlock; ++i) + { + byte uRegion = Constants.PartitionTable[uPartitions][uShape][i]; + LdrColorA outPixel = new LdrColorA(); + if (uIndexPrec2 == 0) { - for (i = 0; i < Constants.NumPixelsPerBlock; i++) - { - uint uNumBits = i != 0 ? uIndexPrec2 : uIndexPrec2 - 1u; - if (uStartBit + uNumBits > 128) - { - Debug.WriteLine("BC7: Invalid block encountered during decoding"); - Helpers.FillWithErrorColors(data, ref dataIndex, Constants.NumPixelsPerBlock, self.DivSize, stride); - return dataIndex; - } - - w2[i] = GetBits(currentBlock, ref uStartBit, uNumBits); - } + LdrColorA.Interpolate(c[uRegion << 1], c[(uRegion << 1) + 1], w1[i], w1[i], uIndexPrec, uIndexPrec, outPixel); } - - for (i = 0; i < Constants.NumPixelsPerBlock; ++i) + else { - byte uRegion = Constants.PartitionTable[uPartitions][uShape][i]; - var outPixel = new LdrColorA(); - if (uIndexPrec2 == 0) + if (uIndexMode == 0) { - LdrColorA.Interpolate(c[uRegion << 1], c[(uRegion << 1) + 1], w1[i], w1[i], uIndexPrec, uIndexPrec, outPixel); + LdrColorA.Interpolate(c[uRegion << 1], c[(uRegion << 1) + 1], w1[i], w2[i], uIndexPrec, uIndexPrec2, outPixel); } else { - if (uIndexMode == 0) - { - LdrColorA.Interpolate(c[uRegion << 1], c[(uRegion << 1) + 1], w1[i], w2[i], uIndexPrec, uIndexPrec2, outPixel); - } - else - { - LdrColorA.Interpolate(c[uRegion << 1], c[(uRegion << 1) + 1], w2[i], w1[i], uIndexPrec2, uIndexPrec, outPixel); - } + LdrColorA.Interpolate(c[uRegion << 1], c[(uRegion << 1) + 1], w2[i], w1[i], uIndexPrec2, uIndexPrec, outPixel); } + } - switch (uRotation) - { - case 1: - outPixel.SwapRedWithAlpha(); - break; - case 2: - outPixel.SwapGreenWithAlpha(); - break; - case 3: - outPixel.SwapBlueWithAlpha(); - break; - } + switch (uRotation) + { + case 1: + outPixel.SwapRedWithAlpha(); + break; + case 2: + outPixel.SwapGreenWithAlpha(); + break; + case 3: + outPixel.SwapBlueWithAlpha(); + break; + } - // Note: whether it's sRGB is not taken into consideration - // we're returning data that could be either/or depending - // on the input BC7 format - data[dataIndex++] = outPixel.R; - data[dataIndex++] = outPixel.G; - data[dataIndex++] = outPixel.B; - data[dataIndex++] = outPixel.A; + // Note: whether it's sRGB is not taken into consideration + // we're returning data that could be either/or depending + // on the input BC7 format + data[dataIndex++] = outPixel.R; + data[dataIndex++] = outPixel.G; + data[dataIndex++] = outPixel.B; + data[dataIndex++] = outPixel.A; - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + // Is mult 4? + if (((i + 1) & 0x3) == 0) + { + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } } - else + } + else + { + Debug.WriteLine("BC7: Reserved mode 8 encountered during decoding"); + + // Per the BC7 format spec, we must return transparent black + for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) { - Debug.WriteLine("BC7: Reserved mode 8 encountered during decoding"); + data[dataIndex++] = 0; + data[dataIndex++] = 0; + data[dataIndex++] = 0; + data[dataIndex++] = 0; - // Per the BC7 format spec, we must return transparent black - for (int i = 0; i < Constants.NumPixelsPerBlock; ++i) + // Is mult 4? + if (((i + 1) & 0x3) == 0) { - data[dataIndex++] = 0; - data[dataIndex++] = 0; - data[dataIndex++] = 0; - data[dataIndex++] = 0; - - // Is mult 4? - if (((i + 1) & 0x3) == 0) - { - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); - } + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); } } + } - return streamIndex; - }); - } + return streamIndex; + }); + } - /// - /// Gets a bit for a given position. - /// - /// The current block. - /// The start bit. - /// A bit at a given position. - public static byte GetBit(byte[] currentBlock, ref uint uStartBit) + /// + /// Gets a bit for a given position. + /// + /// The current block. + /// The start bit. + /// A bit at a given position. + public static byte GetBit(byte[] currentBlock, ref uint uStartBit) + { + Guard.MustBeLessThan(uStartBit, 128, nameof(uStartBit)); + uint uIndex = uStartBit >> 3; + byte ret = (byte)((currentBlock[uIndex] >> (int)(uStartBit - (uIndex << 3))) & 0x01); + uStartBit++; + return ret; + } + + /// + /// Gets n bits at a given start position. + /// + /// The current block. + /// The start bit. + /// The number of bits. + /// Bits at a given position. + public static byte GetBits(byte[] currentBlock, ref uint uStartBit, uint uNumBits) + { + if (uNumBits == 0) { - Guard.MustBeLessThan(uStartBit, 128, nameof(uStartBit)); - uint uIndex = uStartBit >> 3; - byte ret = (byte)((currentBlock[uIndex] >> (int)(uStartBit - (uIndex << 3))) & 0x01); - uStartBit++; - return ret; + return 0; } - /// - /// Gets n bits at a given start position. - /// - /// The current block. - /// The start bit. - /// The number of bits. - /// Bits at a given position. - public static byte GetBits(byte[] currentBlock, ref uint uStartBit, uint uNumBits) + Debug.Assert(uStartBit + uNumBits <= 128 && uNumBits <= 8, "uStartBit + uNumBits <= 128 && uNumBits <= 8"); + byte ret; + uint uIndex = uStartBit >> 3; + uint uBase = uStartBit - (uIndex << 3); + if (uBase + uNumBits > 8) { - if (uNumBits == 0) - { - return 0; - } - - Debug.Assert(uStartBit + uNumBits <= 128 && uNumBits <= 8, "uStartBit + uNumBits <= 128 && uNumBits <= 8"); - byte ret; - uint uIndex = uStartBit >> 3; - uint uBase = uStartBit - (uIndex << 3); - if (uBase + uNumBits > 8) - { - uint uFirstIndexBits = 8 - uBase; - uint uNextIndexBits = uNumBits - uFirstIndexBits; - ret = (byte)((uint)(currentBlock[uIndex] >> (int)uBase) | ((currentBlock[uIndex + 1] & ((1u << (int)uNextIndexBits) - 1)) << (int)uFirstIndexBits)); - } - else - { - ret = (byte)((currentBlock[uIndex] >> (int)uBase) & ((1 << (int)uNumBits) - 1)); - } - - Debug.Assert(ret < (1 << (int)uNumBits), $"GetBits() return value should be smaller then {1 << (int)uNumBits}"); - uStartBit += uNumBits; - return ret; + uint uFirstIndexBits = 8 - uBase; + uint uNextIndexBits = uNumBits - uFirstIndexBits; + ret = (byte)((uint)(currentBlock[uIndex] >> (int)uBase) | ((currentBlock[uIndex + 1] & ((1u << (int)uNextIndexBits) - 1)) << (int)uFirstIndexBits)); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte Unquantize(byte comp, uint uPrec) + else { - Guard.MustBeBetweenOrEqualTo(uPrec, 1, 8, nameof(uPrec)); - comp = (byte)(comp << (int)(8u - uPrec)); - return (byte)(comp | (comp >> (int)uPrec)); + ret = (byte)((currentBlock[uIndex] >> (int)uBase) & ((1 << (int)uNumBits) - 1)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static LdrColorA Unquantize(LdrColorA c, LdrColorA rgbaPrec) + Debug.Assert(ret < (1 << (int)uNumBits), $"GetBits() return value should be smaller then {1 << (int)uNumBits}"); + uStartBit += uNumBits; + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte Unquantize(byte comp, uint uPrec) + { + Guard.MustBeBetweenOrEqualTo(uPrec, 1, 8, nameof(uPrec)); + comp = (byte)(comp << (int)(8u - uPrec)); + return (byte)(comp | (comp >> (int)uPrec)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static LdrColorA Unquantize(LdrColorA c, LdrColorA rgbaPrec) + { + LdrColorA q = new LdrColorA { - var q = new LdrColorA - { - R = Unquantize(c.R, rgbaPrec.R), - G = Unquantize(c.G, rgbaPrec.G), - B = Unquantize(c.B, rgbaPrec.B), - A = rgbaPrec.A > 0 ? Unquantize(c.A, rgbaPrec.A) : (byte)255u - }; + R = Unquantize(c.R, rgbaPrec.R), + G = Unquantize(c.G, rgbaPrec.G), + B = Unquantize(c.B, rgbaPrec.B), + A = rgbaPrec.A > 0 ? Unquantize(c.A, rgbaPrec.A) : (byte)255u + }; - return q; - } + return q; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc7ModeInfo.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc7ModeInfo.cs index e3debea2..b5bde5ac 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bc7ModeInfo.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bc7ModeInfo.cs @@ -3,31 +3,30 @@ using SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +internal struct Bc7ModeInfo { - internal struct Bc7ModeInfo - { - public readonly byte UPartitions; - public readonly byte UPartitionBits; - public readonly byte UPBits; - public readonly byte URotationBits; - public readonly byte UIndexModeBits; - public readonly byte UIndexPrec; - public readonly byte UIndexPrec2; - public readonly LdrColorA RgbaPrec; - public readonly LdrColorA RgbaPrecWithP; + public readonly byte UPartitions; + public readonly byte UPartitionBits; + public readonly byte UPBits; + public readonly byte URotationBits; + public readonly byte UIndexModeBits; + public readonly byte UIndexPrec; + public readonly byte UIndexPrec2; + public readonly LdrColorA RgbaPrec; + public readonly LdrColorA RgbaPrecWithP; - public Bc7ModeInfo(byte uParts, byte uPartBits, byte upBits, byte uRotBits, byte uIndModeBits, byte uIndPrec, byte uIndPrec2, LdrColorA rgbaPrec, LdrColorA rgbaPrecWithP) - { - this.UPartitions = uParts; - this.UPartitionBits = uPartBits; - this.UPBits = upBits; - this.URotationBits = uRotBits; - this.UIndexModeBits = uIndModeBits; - this.UIndexPrec = uIndPrec; - this.UIndexPrec2 = uIndPrec2; - this.RgbaPrec = rgbaPrec; - this.RgbaPrecWithP = rgbaPrecWithP; - } + public Bc7ModeInfo(byte uParts, byte uPartBits, byte upBits, byte uRotBits, byte uIndModeBits, byte uIndPrec, byte uIndPrec2, LdrColorA rgbaPrec, LdrColorA rgbaPrecWithP) + { + this.UPartitions = uParts; + this.UPartitionBits = uPartBits; + this.UPBits = upBits; + this.URotationBits = uRotBits; + this.UIndexModeBits = uIndModeBits; + this.UIndexPrec = uIndPrec; + this.UIndexPrec2 = uIndPrec2; + this.RgbaPrec = rgbaPrec; + this.RgbaPrecWithP = rgbaPrecWithP; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr24.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr24.cs index d349a910..7de43926 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr24.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr24.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixel with the BGR24 format. - /// - internal struct Bgr24 : IBlock - { - /// - public int BitsPerPixel => 24; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 3; +/// +/// Texture for pixel with the BGR24 format. +/// +internal struct Bgr24 : IBlock +{ + /// + public readonly int BitsPerPixel => 24; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte CompressedBytesPerBlock => 3; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 3; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr32.cs index b3374cec..1deca0f2 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr32.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for the pixel format Bgr32. - /// - internal struct Bgr32 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture for the pixel format Bgr32. +/// +internal struct Bgr32 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr555.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr555.cs index 1d0ddbeb..4bcc8b18 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr555.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr555.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixels with 5 bits per channel (without a alpha channel). - /// - internal struct Bgr555 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture for pixels with 5 bits per channel (without a alpha channel). +/// +internal struct Bgr555 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr565.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr565.cs index e8d1ee2c..0954f017 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr565.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgr565.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixels with 5 bits for the red 6 bits for blue and 5 bits for green. - /// - internal struct Bgr565 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture for pixels with 5 bits for the red 6 bits for blue and 5 bits for green. +/// +internal struct Bgr565 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra16.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra16.cs index 0ff94436..4bb23fe7 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra16.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra16.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixels with 4 bits for each channel including the alpha channel. - /// - internal struct Bgra16 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture for pixels with 4 bits for each channel including the alpha channel. +/// +internal struct Bgra16 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra32.cs index a2f5e85f..e08ed97e 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra32.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for the pixel format Bgra32. - /// - internal struct Bgra32 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture for the pixel format Bgra32. +/// +internal struct Bgra32 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra4444.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra4444.cs index 97b67963..ab357fc6 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra4444.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra4444.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixels with 4 bits per channel (including alpha channel). - /// - internal struct Bgra4444 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture for pixels with 4 bits per channel (including alpha channel). +/// +internal struct Bgra4444 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra5551.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra5551.cs index a52919ec..17a31554 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra5551.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Bgra5551.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixel data with 5 bit for each color channel and 1 bit for the alpha channel. - /// - internal struct Bgra5551 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture format for pixel data with 5 bit for each color channel and 1 bit for the alpha channel. +/// +internal struct Bgra5551 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt1.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt1.cs index 635826c6..9476dd60 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt1.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt1.cs @@ -3,98 +3,97 @@ using SixLabors.ImageSharp.Textures.Common.Helpers; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with DXT1. +/// +internal struct Dxt1 : IBlock { - /// - /// Texture compressed with DXT1. - /// - internal struct Dxt1 : IBlock - { - /// - public int BitsPerPixel => 24; + /// + public readonly int BitsPerPixel => 24; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 4; + /// + public readonly byte DivSize => 4; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - Dxt1 self = this; - var colors = new ImageSharp.PixelFormats.Rgb24[4]; + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + Dxt1 self = this; + ImageSharp.PixelFormats.Rgb24[] colors = new ImageSharp.PixelFormats.Rgb24[4]; - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => - { - ushort color0 = blockData[streamIndex++]; - color0 |= (ushort)(blockData[streamIndex++] << 8); + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + { + ushort color0 = blockData[streamIndex++]; + color0 |= (ushort)(blockData[streamIndex++] << 8); - ushort color1 = blockData[streamIndex++]; - color1 |= (ushort)(blockData[streamIndex++] << 8); + ushort color1 = blockData[streamIndex++]; + color1 |= (ushort)(blockData[streamIndex++] << 8); - // Extract R5G6B5 - PixelUtils.ExtractR5G6B5(color0, ref colors[0]); - PixelUtils.ExtractR5G6B5(color1, ref colors[1]); + // Extract R5G6B5 + PixelUtils.ExtractR5G6B5(color0, ref colors[0]); + PixelUtils.ExtractR5G6B5(color1, ref colors[1]); - // Used the two extracted colors to create two new colors that are - // slightly different. - if (color0 > color1) - { - colors[2].R = (byte)(((2 * colors[0].R) + colors[1].R) / 3); - colors[2].G = (byte)(((2 * colors[0].G) + colors[1].G) / 3); - colors[2].B = (byte)(((2 * colors[0].B) + colors[1].B) / 3); + // Used the two extracted colors to create two new colors that are + // slightly different. + if (color0 > color1) + { + colors[2].R = (byte)(((2 * colors[0].R) + colors[1].R) / 3); + colors[2].G = (byte)(((2 * colors[0].G) + colors[1].G) / 3); + colors[2].B = (byte)(((2 * colors[0].B) + colors[1].B) / 3); + + colors[3].R = (byte)((colors[0].R + (2 * colors[1].R)) / 3); + colors[3].G = (byte)((colors[0].G + (2 * colors[1].G)) / 3); + colors[3].B = (byte)((colors[0].B + (2 * colors[1].B)) / 3); + } + else + { + colors[2].R = (byte)((colors[0].R + colors[1].R) / 2); + colors[2].G = (byte)((colors[0].G + colors[1].G) / 2); + colors[2].B = (byte)((colors[0].B + colors[1].B) / 2); - colors[3].R = (byte)((colors[0].R + (2 * colors[1].R)) / 3); - colors[3].G = (byte)((colors[0].G + (2 * colors[1].G)) / 3); - colors[3].B = (byte)((colors[0].B + (2 * colors[1].B)) / 3); - } - else - { - colors[2].R = (byte)((colors[0].R + colors[1].R) / 2); - colors[2].G = (byte)((colors[0].G + colors[1].G) / 2); - colors[2].B = (byte)((colors[0].B + colors[1].B) / 2); + colors[3].R = 0; + colors[3].G = 0; + colors[3].B = 0; + } - colors[3].R = 0; - colors[3].G = 0; - colors[3].B = 0; - } + for (int i = 0; i < 4; i++) + { + // Every 2 bit is a code [0-3] and represent what color the current pixel is. - for (int i = 0; i < 4; i++) + // Read in a byte and thus 4 colors. + byte rowVal = blockData[streamIndex++]; + for (int j = 0; j < 8; j += 2) { - // Every 2 bit is a code [0-3] and represent what color the current pixel is. - - // Read in a byte and thus 4 colors. - byte rowVal = blockData[streamIndex++]; - for (int j = 0; j < 8; j += 2) - { - // Extract code by shifting the row byte so that we can - // AND it with 3 and get a value [0-3] - ImageSharp.PixelFormats.Rgb24 col = colors[(rowVal >> j) & 0x03]; - data[dataIndex++] = col.R; - data[dataIndex++] = col.G; - data[dataIndex++] = col.B; - } - - // Jump down a row and start at the beginning of the row. - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); + // Extract code by shifting the row byte so that we can + // AND it with 3 and get a value [0-3] + ImageSharp.PixelFormats.Rgb24 col = colors[(rowVal >> j) & 0x03]; + data[dataIndex++] = col.R; + data[dataIndex++] = col.G; + data[dataIndex++] = col.B; } - return streamIndex; - }); - } + // Jump down a row and start at the beginning of the row. + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); + } + + return streamIndex; + }); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt3.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt3.cs index c00291df..773d5824 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt3.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt3.cs @@ -3,101 +3,100 @@ using SixLabors.ImageSharp.Textures.Common.Helpers; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with DXT3. +/// +internal struct Dxt3 : IBlock { - /// - /// Texture compressed with DXT3. - /// - internal struct Dxt3 : IBlock - { - /// - public int BitsPerPixel => 32; + /// + public readonly int BitsPerPixel => 32; - /// - public byte PixelDepthBytes => 4; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte DivSize => 4; + /// + public readonly byte DivSize => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public readonly byte CompressedBytesPerBlock => 16; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + Dxt3 self = this; + ImageSharp.PixelFormats.Rgb24[] colors = new ImageSharp.PixelFormats.Rgb24[4]; + + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => { - Dxt3 self = this; - var colors = new ImageSharp.PixelFormats.Rgb24[4]; + /* + * Strategy for decompression: + * -We're going to decode both alpha and color at the same time + * to save on space and time as we don't have to allocate an array + * to store values for later use. + */ - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => - { - /* - * Strategy for decompression: - * -We're going to decode both alpha and color at the same time - * to save on space and time as we don't have to allocate an array - * to store values for later use. - */ + // Remember where the alpha data is stored so we can decode simultaneously. + int alphaPtr = streamIndex; - // Remember where the alpha data is stored so we can decode simultaneously. - int alphaPtr = streamIndex; + // Jump ahead to the color data. + streamIndex += 8; - // Jump ahead to the color data. - streamIndex += 8; + // Colors are stored in a pair of 16 bits. + ushort color0 = blockData[streamIndex++]; + color0 |= (ushort)(blockData[streamIndex++] << 8); - // Colors are stored in a pair of 16 bits. - ushort color0 = blockData[streamIndex++]; - color0 |= (ushort)(blockData[streamIndex++] << 8); + ushort color1 = blockData[streamIndex++]; + color1 |= (ushort)(blockData[streamIndex++] << 8); - ushort color1 = blockData[streamIndex++]; - color1 |= (ushort)(blockData[streamIndex++] << 8); + // Extract R5G6B5. + PixelUtils.ExtractR5G6B5(color0, ref colors[0]); + PixelUtils.ExtractR5G6B5(color1, ref colors[1]); - // Extract R5G6B5. - PixelUtils.ExtractR5G6B5(color0, ref colors[0]); - PixelUtils.ExtractR5G6B5(color1, ref colors[1]); + // Used the two extracted colors to create two new colors + // that are slightly different. + colors[2].R = (byte)(((2 * colors[0].R) + colors[1].R) / 3); + colors[2].G = (byte)(((2 * colors[0].G) + colors[1].G) / 3); + colors[2].B = (byte)(((2 * colors[0].B) + colors[1].B) / 3); - // Used the two extracted colors to create two new colors - // that are slightly different. - colors[2].R = (byte)(((2 * colors[0].R) + colors[1].R) / 3); - colors[2].G = (byte)(((2 * colors[0].G) + colors[1].G) / 3); - colors[2].B = (byte)(((2 * colors[0].B) + colors[1].B) / 3); + colors[3].R = (byte)((colors[0].R + (2 * colors[1].R)) / 3); + colors[3].G = (byte)((colors[0].G + (2 * colors[1].G)) / 3); + colors[3].B = (byte)((colors[0].B + (2 * colors[1].B)) / 3); - colors[3].R = (byte)((colors[0].R + (2 * colors[1].R)) / 3); - colors[3].G = (byte)((colors[0].G + (2 * colors[1].G)) / 3); - colors[3].B = (byte)((colors[0].B + (2 * colors[1].B)) / 3); + for (int i = 0; i < 4; i++) + { + byte rowVal = blockData[streamIndex++]; - for (int i = 0; i < 4; i++) + // Each row of rgb values have 4 alpha values that are encoded in 4 bits. + ushort rowAlpha = blockData[alphaPtr++]; + rowAlpha |= (ushort)(blockData[alphaPtr++] << 8); + + for (int j = 0; j < 8; j += 2) { - byte rowVal = blockData[streamIndex++]; - - // Each row of rgb values have 4 alpha values that are encoded in 4 bits. - ushort rowAlpha = blockData[alphaPtr++]; - rowAlpha |= (ushort)(blockData[alphaPtr++] << 8); - - for (int j = 0; j < 8; j += 2) - { - byte currentAlpha = (byte)((rowAlpha >> (j * 2)) & 0x0f); - currentAlpha |= (byte)(currentAlpha << 4); - ImageSharp.PixelFormats.Rgb24 col = colors[(rowVal >> j) & 0x03]; - data[dataIndex++] = col.R; - data[dataIndex++] = col.G; - data[dataIndex++] = col.B; - data[dataIndex++] = currentAlpha; - } - - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); + byte currentAlpha = (byte)((rowAlpha >> (j * 2)) & 0x0f); + currentAlpha |= (byte)(currentAlpha << 4); + ImageSharp.PixelFormats.Rgb24 col = colors[(rowVal >> j) & 0x03]; + data[dataIndex++] = col.R; + data[dataIndex++] = col.G; + data[dataIndex++] = col.B; + data[dataIndex++] = currentAlpha; } - return streamIndex; - }); - } + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); + } + + return streamIndex; + }); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt5.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt5.cs index f16df743..9d3734a9 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt5.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Dxt5.cs @@ -3,91 +3,90 @@ using SixLabors.ImageSharp.Textures.Common.Helpers; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture compressed with DXT5. +/// +internal struct Dxt5 : IBlock { - /// - /// Texture compressed with DXT5. - /// - internal struct Dxt5 : IBlock - { - /// - public int BitsPerPixel => 32; + /// + public readonly int BitsPerPixel => 32; - /// - public byte PixelDepthBytes => 4; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte DivSize => 4; + /// + public readonly byte DivSize => 4; - /// - public byte CompressedBytesPerBlock => 16; + /// + public readonly byte CompressedBytesPerBlock => 16; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - Dxt5 self = this; - byte[] alpha = new byte[8]; - var colors = new ImageSharp.PixelFormats.Rgb24[4]; + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + Dxt5 self = this; + byte[] alpha = new byte[8]; + ImageSharp.PixelFormats.Rgb24[] colors = new ImageSharp.PixelFormats.Rgb24[4]; - return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => - { - streamIndex = Bc5.ExtractGradient(alpha, blockData, streamIndex); + return Helper.InMemoryDecode(blockData, width, height, (stream, data, streamIndex, dataIndex, stride) => + { + streamIndex = Bc5.ExtractGradient(alpha, blockData, streamIndex); - ulong alphaCodes = blockData[streamIndex++]; - alphaCodes |= (ulong)blockData[streamIndex++] << 8; - alphaCodes |= (ulong)blockData[streamIndex++] << 16; - alphaCodes |= (ulong)blockData[streamIndex++] << 24; - alphaCodes |= (ulong)blockData[streamIndex++] << 32; - alphaCodes |= (ulong)blockData[streamIndex++] << 40; + ulong alphaCodes = blockData[streamIndex++]; + alphaCodes |= (ulong)blockData[streamIndex++] << 8; + alphaCodes |= (ulong)blockData[streamIndex++] << 16; + alphaCodes |= (ulong)blockData[streamIndex++] << 24; + alphaCodes |= (ulong)blockData[streamIndex++] << 32; + alphaCodes |= (ulong)blockData[streamIndex++] << 40; - // Colors are stored in a pair of 16 bits. - ushort color0 = blockData[streamIndex++]; - color0 |= (ushort)(blockData[streamIndex++] << 8); + // Colors are stored in a pair of 16 bits. + ushort color0 = blockData[streamIndex++]; + color0 |= (ushort)(blockData[streamIndex++] << 8); - ushort color1 = blockData[streamIndex++]; - color1 |= (ushort)(blockData[streamIndex++] << 8); + ushort color1 = blockData[streamIndex++]; + color1 |= (ushort)(blockData[streamIndex++] << 8); - // Extract R5G6B5. - PixelUtils.ExtractR5G6B5(color0, ref colors[0]); - PixelUtils.ExtractR5G6B5(color1, ref colors[1]); + // Extract R5G6B5. + PixelUtils.ExtractR5G6B5(color0, ref colors[0]); + PixelUtils.ExtractR5G6B5(color1, ref colors[1]); - colors[2].R = (byte)(((2 * colors[0].R) + colors[1].R) / 3); - colors[2].G = (byte)(((2 * colors[0].G) + colors[1].G) / 3); - colors[2].B = (byte)(((2 * colors[0].B) + colors[1].B) / 3); + colors[2].R = (byte)(((2 * colors[0].R) + colors[1].R) / 3); + colors[2].G = (byte)(((2 * colors[0].G) + colors[1].G) / 3); + colors[2].B = (byte)(((2 * colors[0].B) + colors[1].B) / 3); - colors[3].R = (byte)((colors[0].R + (2 * colors[1].R)) / 3); - colors[3].G = (byte)((colors[0].G + (2 * colors[1].G)) / 3); - colors[3].B = (byte)((colors[0].B + (2 * colors[1].B)) / 3); + colors[3].R = (byte)((colors[0].R + (2 * colors[1].R)) / 3); + colors[3].G = (byte)((colors[0].G + (2 * colors[1].G)) / 3); + colors[3].B = (byte)((colors[0].B + (2 * colors[1].B)) / 3); - for (int alphaShift = 0; alphaShift < 48; alphaShift += 12) + for (int alphaShift = 0; alphaShift < 48; alphaShift += 12) + { + byte rowVal = blockData[streamIndex++]; + for (int j = 0; j < 4; j++) { - byte rowVal = blockData[streamIndex++]; - for (int j = 0; j < 4; j++) - { - // 3 bits determine alpha index to use. - byte alphaIndex = (byte)((alphaCodes >> (alphaShift + (3 * j))) & 0x07); - ImageSharp.PixelFormats.Rgb24 col = colors[(rowVal >> (j * 2)) & 0x03]; - data[dataIndex++] = col.R; - data[dataIndex++] = col.G; - data[dataIndex++] = col.B; - data[dataIndex++] = alpha[alphaIndex]; - } - - dataIndex += self.PixelDepthBytes * (stride - self.DivSize); + // 3 bits determine alpha index to use. + byte alphaIndex = (byte)((alphaCodes >> (alphaShift + (3 * j))) & 0x07); + ImageSharp.PixelFormats.Rgb24 col = colors[(rowVal >> (j * 2)) & 0x03]; + data[dataIndex++] = col.R; + data[dataIndex++] = col.G; + data[dataIndex++] = col.B; + data[dataIndex++] = alpha[alphaIndex]; } - return streamIndex; - }); - } + dataIndex += self.PixelDepthBytes * (stride - self.DivSize); + } + + return streamIndex; + }); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Etc1.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Etc1.cs index 7ce1acb3..4d1b9c62 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Etc1.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Etc1.cs @@ -1,72 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with ETC1. +/// +internal struct Etc1 : IBlock { - /// - /// Texture compressed with ETC1. - /// - internal struct Etc1 : IBlock - { - /// - public int BitsPerPixel => 24; + /// + public readonly int BitsPerPixel => 24; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 1; + /// + public readonly byte DivSize => 1; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int extraX = 4 - (width % 4); - int extraY = 4 - (height % 4); - byte[] decompressedData = new byte[(width + extraX) * (height + extraY) * 3]; - byte[] decodedPixels = new byte[16 * 3]; - Span decodedPixelSpan = decodedPixels.AsSpan(); - int blockDataIdx = 0; + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + int extraX = 4 - (width % 4); + int extraY = 4 - (height % 4); + byte[] decompressedData = new byte[(width + extraX) * (height + extraY) * 3]; + byte[] decodedPixels = new byte[16 * 3]; + Span decodedPixelSpan = decodedPixels.AsSpan(); + int blockDataIdx = 0; - for (int y = 0; y < height; y += 4) + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) { - for (int x = 0; x < width; x += 4) - { - EtcDecoder.DecodeEtc1Block(blockData.AsSpan(blockDataIdx, 8), decodedPixelSpan); + EtcDecoder.DecodeEtc1Block(blockData.AsSpan(blockDataIdx, 8), decodedPixelSpan); - int decodedPixelSpanIdx = 0; - for (int b = 0; b < 4; b++) + int decodedPixelSpanIdx = 0; + for (int b = 0; b < 4; b++) + { + for (int a = 0; a < 4; a++) { - for (int a = 0; a < 4; a++) - { - int imageX = x + b; - int imageY = y + a; - int offset = (imageY * width * 3) + (imageX * 3); - decompressedData[offset] = decodedPixelSpan[decodedPixelSpanIdx++]; - decompressedData[offset + 1] = decodedPixelSpan[decodedPixelSpanIdx++]; - decompressedData[offset + 2] = decodedPixelSpan[decodedPixelSpanIdx++]; - } + int imageX = x + b; + int imageY = y + a; + int offset = (imageY * width * 3) + (imageX * 3); + decompressedData[offset] = decodedPixelSpan[decodedPixelSpanIdx++]; + decompressedData[offset + 1] = decodedPixelSpan[decodedPixelSpanIdx++]; + decompressedData[offset + 2] = decodedPixelSpan[decodedPixelSpanIdx++]; } - - blockDataIdx += 8; } - } - return decompressedData; + blockDataIdx += 8; + } } + + return decompressedData; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Etc2.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Etc2.cs index 0b59fa38..c05ca006 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Etc2.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Etc2.cs @@ -1,72 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture compressed with ETC2. +/// +internal struct Etc2 : IBlock { - /// - /// Texture compressed with ETC2. - /// - internal struct Etc2 : IBlock - { - /// - public int BitsPerPixel => 24; + /// + public readonly int BitsPerPixel => 24; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 1; + /// + public readonly byte DivSize => 1; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public bool Compressed => true; + /// + public readonly bool Compressed => true; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int extraX = 4 - (width % 4); - int extraY = 4 - (height % 4); - byte[] decompressedData = new byte[(width + extraX) * (height + extraY) * 3]; - byte[] decodedPixels = new byte[16 * 3]; - Span decodedPixelSpan = decodedPixels.AsSpan(); - var blockDataIdx = 0; + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + int extraX = 4 - (width % 4); + int extraY = 4 - (height % 4); + byte[] decompressedData = new byte[(width + extraX) * (height + extraY) * 3]; + byte[] decodedPixels = new byte[16 * 3]; + Span decodedPixelSpan = decodedPixels.AsSpan(); + int blockDataIdx = 0; - for (int y = 0; y < height; y += 4) + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) { - for (int x = 0; x < width; x += 4) - { - EtcDecoder.DecodeEtc2Block(blockData.AsSpan(blockDataIdx, 8), decodedPixelSpan); + EtcDecoder.DecodeEtc2Block(blockData.AsSpan(blockDataIdx, 8), decodedPixelSpan); - var decodedPixelSpanIdx = 0; - for (int b = 0; b < 4; b++) + int decodedPixelSpanIdx = 0; + for (int b = 0; b < 4; b++) + { + for (int a = 0; a < 4; a++) { - for (int a = 0; a < 4; a++) - { - var imageX = x + b; - var imageY = y + a; - var offset = (imageY * width * 3) + (imageX * 3); - decompressedData[offset] = decodedPixelSpan[decodedPixelSpanIdx++]; - decompressedData[offset + 1] = decodedPixelSpan[decodedPixelSpanIdx++]; - decompressedData[offset + 2] = decodedPixelSpan[decodedPixelSpanIdx++]; - } + int imageX = x + b; + int imageY = y + a; + int offset = (imageY * width * 3) + (imageX * 3); + decompressedData[offset] = decodedPixelSpan[decodedPixelSpanIdx++]; + decompressedData[offset + 1] = decodedPixelSpan[decodedPixelSpanIdx++]; + decompressedData[offset + 2] = decodedPixelSpan[decodedPixelSpanIdx++]; } - - blockDataIdx += 8; } - } - return decompressedData; + blockDataIdx += 8; + } } + + return decompressedData; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/EtcDecoder.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/EtcDecoder.cs index 32b1a65a..4804522c 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/EtcDecoder.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/EtcDecoder.cs @@ -1,386 +1,384 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Decoder for ETC (Ericsson Texture Compression) compressed textures. +/// Based on https://github.com/hglm/detex.git +/// +/// +/// See ktx specification: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#ETC1 +/// +internal static class EtcDecoder { - /// - /// Decoder for ETC (Ericsson Texture Compression) compressed textures. - /// Based on https://github.com/hglm/detex.git - /// - /// - /// See ktx specification: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#ETC1 - /// - internal static class EtcDecoder + private static readonly int[,] ModifierTable = { - private static readonly int[,] ModifierTable = - { - { 2, 8, -2, -8 }, - { 5, 17, -5, -17 }, - { 9, 29, -9, -29 }, - { 13, 42, -13, -42 }, - { 18, 60, -18, -60 }, - { 24, 80, -24, -80 }, - { 33, 106, -33, -106 }, - { 47, 183, -47, -183 } - }; - - private static readonly int[] Complement3BitShiftedTable = - { - 0, 8, 16, 24, -32, -24, -16, -8 - }; - - private static readonly int[] Etc2DistanceTable = { 3, 6, 11, 16, 23, 32, 41, 64 }; - - public static void DecodeEtc1Block(Span payload, Span decodedPixelSpan) - { - byte red = payload[0]; - byte green = payload[1]; - byte blue = payload[2]; - byte codeWordsWithFlags = payload[3]; - - bool diffFlag = (codeWordsWithFlags & 2) != 0; - bool flipFlag = (codeWordsWithFlags & 1) != 0; - byte codeword1 = (byte)((codeWordsWithFlags & 224) >> 5); - byte codeword2 = (byte)((codeWordsWithFlags & 28) >> 2); - - int c0r, c0g, c0b; - int c1r, c1g, c1b; + { 2, 8, -2, -8 }, + { 5, 17, -5, -17 }, + { 9, 29, -9, -29 }, + { 13, 42, -13, -42 }, + { 18, 60, -18, -60 }, + { 24, 80, -24, -80 }, + { 33, 106, -33, -106 }, + { 47, 183, -47, -183 } + }; + + private static readonly int[] Complement3BitShiftedTable = + [ + 0, 8, 16, 24, -32, -24, -16, -8 + ]; + + private static readonly int[] Etc2DistanceTable = [3, 6, 11, 16, 23, 32, 41, 64]; + + public static void DecodeEtc1Block(Span payload, Span decodedPixelSpan) + { + byte red = payload[0]; + byte green = payload[1]; + byte blue = payload[2]; + byte codeWordsWithFlags = payload[3]; - if (diffFlag) - { - int c0r5 = red & 0xF8; - c0r = FiveToEightBit(c0r5); - int c0g5 = green & 0xF8; - c0g = FiveToEightBit(c0g5); - int c0b5 = blue & 0xF8; - c0b = FiveToEightBit(c0b5); - - int rd3 = Complement3BitShifted(red & 0x7); - int gd3 = Complement3BitShifted(green & 0x7); - int bd3 = Complement3BitShifted(blue & 0x7); - c1r = FiveToEightBit(c0r5 + rd3); - c1g = FiveToEightBit(c0g5 + gd3); - c1b = FiveToEightBit(c0b5 + bd3); - } - else - { - int c0r4 = red & 0xF0; - c0r = c0r4 | (c0r4 >> 4); - int c0g4 = green & 0xF0; - c0g = c0g4 | (c0g4 >> 4); - int c0b4 = blue & 0xF0; - c0b = c0b4 | (c0b4 >> 4); - - int c1r4 = red & 0x0F; - c1r = c1r4 | (c1r4 << 4); - int c1g4 = green & 0x0F; - c1g = c1g4 | (c1g4 << 4); - int c1b4 = blue & 0x0F; - c1b = c1b4 | (c1b4 << 4); - } + bool diffFlag = (codeWordsWithFlags & 2) != 0; + bool flipFlag = (codeWordsWithFlags & 1) != 0; + byte codeword1 = (byte)((codeWordsWithFlags & 224) >> 5); + byte codeword2 = (byte)((codeWordsWithFlags & 28) >> 2); - uint pixelIndexWord = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(4, 4)); + int c0r, c0g, c0b; + int c1r, c1g, c1b; - // Check if the sub-blocks are horizontal or vertical. - if (!flipFlag) - { - // Flip bit indicates horizontal sub-blocks. - // 0000 - // 0000 - // 1111 - // 1111 - // Iterate over the pixels in each sub-block and set their final values in the image data. - DecompressEtc1BlockHorizontal(pixelIndexWord, codeword1, codeword2, (byte)c0r, (byte)c0g, (byte)c0b, (byte)c1r, (byte)c1g, (byte)c1b, decodedPixelSpan); - } - else - { - // Flip bit indicates vertical sub-blocks. - // 0011 - // 0011 - // 0011 - // 0011 - DecompressEtc1BlockVertical(pixelIndexWord, codeword1, codeword2, (byte)c0r, (byte)c0g, (byte)c0b, (byte)c1r, (byte)c1g, (byte)c1b, decodedPixelSpan); - } + if (diffFlag) + { + int c0r5 = red & 0xF8; + c0r = FiveToEightBit(c0r5); + int c0g5 = green & 0xF8; + c0g = FiveToEightBit(c0g5); + int c0b5 = blue & 0xF8; + c0b = FiveToEightBit(c0b5); + + int rd3 = Complement3BitShifted(red & 0x7); + int gd3 = Complement3BitShifted(green & 0x7); + int bd3 = Complement3BitShifted(blue & 0x7); + c1r = FiveToEightBit(c0r5 + rd3); + c1g = FiveToEightBit(c0g5 + gd3); + c1b = FiveToEightBit(c0b5 + bd3); } - - private static void DecompressEtc1BlockHorizontal( - uint pixelIndexWord, - uint tableCodeword1, - uint tableCodeword2, - byte redBaseColorSubBlock1, - byte greenBaseColorSubBlock1, - byte blueBaseColorSubBlock1, - byte redBaseColorSubBlock2, - byte greenBaseColorSubBlock2, - byte blueBaseColorSubBlock2, - Span pixelBuffer) + else { - ProcessPixelEtc1(0, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(0, 3)); - ProcessPixelEtc1(1, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(3, 3)); - ProcessPixelEtc1(2, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(6, 3)); - ProcessPixelEtc1(3, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(9, 3)); - ProcessPixelEtc1(4, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(12, 3)); - ProcessPixelEtc1(5, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(15, 3)); - ProcessPixelEtc1(6, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(18, 3)); - ProcessPixelEtc1(7, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(21, 3)); - - ProcessPixelEtc1(8, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(24, 3)); - ProcessPixelEtc1(9, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(27, 3)); - ProcessPixelEtc1(10, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(30, 3)); - ProcessPixelEtc1(11, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(33, 3)); - ProcessPixelEtc1(12, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(36, 3)); - ProcessPixelEtc1(13, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(39, 3)); - ProcessPixelEtc1(14, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(42, 3)); - ProcessPixelEtc1(15, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(45, 3)); + int c0r4 = red & 0xF0; + c0r = c0r4 | (c0r4 >> 4); + int c0g4 = green & 0xF0; + c0g = c0g4 | (c0g4 >> 4); + int c0b4 = blue & 0xF0; + c0b = c0b4 | (c0b4 >> 4); + + int c1r4 = red & 0x0F; + c1r = c1r4 | (c1r4 << 4); + int c1g4 = green & 0x0F; + c1g = c1g4 | (c1g4 << 4); + int c1b4 = blue & 0x0F; + c1b = c1b4 | (c1b4 << 4); } - private static void DecompressEtc1BlockVertical( - uint pixelIndexWord, - uint tableCodeword1, - uint tableCodeword2, - byte redBaseColorSubBlock1, - byte greenBaseColorSubBlock1, - byte blueBaseColorSubBlock1, - byte redBaseColorSubBlock2, - byte greenBaseColorSubBlock2, - byte blueBaseColorSubBlock2, - Span pixelBuffer) - { - ProcessPixelEtc1(0, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(0, 3)); - ProcessPixelEtc1(1, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(3, 3)); + uint pixelIndexWord = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(4, 4)); - ProcessPixelEtc1(2, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(6, 3)); - ProcessPixelEtc1(3, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(9, 3)); + // Check if the sub-blocks are horizontal or vertical. + if (!flipFlag) + { + // Flip bit indicates horizontal sub-blocks. + // 0000 + // 0000 + // 1111 + // 1111 + // Iterate over the pixels in each sub-block and set their final values in the image data. + DecompressEtc1BlockHorizontal(pixelIndexWord, codeword1, codeword2, (byte)c0r, (byte)c0g, (byte)c0b, (byte)c1r, (byte)c1g, (byte)c1b, decodedPixelSpan); + } + else + { + // Flip bit indicates vertical sub-blocks. + // 0011 + // 0011 + // 0011 + // 0011 + DecompressEtc1BlockVertical(pixelIndexWord, codeword1, codeword2, (byte)c0r, (byte)c0g, (byte)c0b, (byte)c1r, (byte)c1g, (byte)c1b, decodedPixelSpan); + } + } - ProcessPixelEtc1(4, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(12, 3)); - ProcessPixelEtc1(5, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(15, 3)); + private static void DecompressEtc1BlockHorizontal( + uint pixelIndexWord, + uint tableCodeword1, + uint tableCodeword2, + byte redBaseColorSubBlock1, + byte greenBaseColorSubBlock1, + byte blueBaseColorSubBlock1, + byte redBaseColorSubBlock2, + byte greenBaseColorSubBlock2, + byte blueBaseColorSubBlock2, + Span pixelBuffer) + { + ProcessPixelEtc1(0, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer[..3]); + ProcessPixelEtc1(1, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(3, 3)); + ProcessPixelEtc1(2, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(6, 3)); + ProcessPixelEtc1(3, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(9, 3)); + ProcessPixelEtc1(4, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(12, 3)); + ProcessPixelEtc1(5, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(15, 3)); + ProcessPixelEtc1(6, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(18, 3)); + ProcessPixelEtc1(7, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(21, 3)); + + ProcessPixelEtc1(8, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(24, 3)); + ProcessPixelEtc1(9, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(27, 3)); + ProcessPixelEtc1(10, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(30, 3)); + ProcessPixelEtc1(11, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(33, 3)); + ProcessPixelEtc1(12, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(36, 3)); + ProcessPixelEtc1(13, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(39, 3)); + ProcessPixelEtc1(14, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(42, 3)); + ProcessPixelEtc1(15, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(45, 3)); + } - ProcessPixelEtc1(6, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(18, 3)); - ProcessPixelEtc1(7, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(21, 3)); + private static void DecompressEtc1BlockVertical( + uint pixelIndexWord, + uint tableCodeword1, + uint tableCodeword2, + byte redBaseColorSubBlock1, + byte greenBaseColorSubBlock1, + byte blueBaseColorSubBlock1, + byte redBaseColorSubBlock2, + byte greenBaseColorSubBlock2, + byte blueBaseColorSubBlock2, + Span pixelBuffer) + { + ProcessPixelEtc1(0, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer[..3]); + ProcessPixelEtc1(1, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(3, 3)); - ProcessPixelEtc1(8, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(24, 3)); - ProcessPixelEtc1(9, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(27, 3)); + ProcessPixelEtc1(2, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(6, 3)); + ProcessPixelEtc1(3, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(9, 3)); - ProcessPixelEtc1(10, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(30, 3)); - ProcessPixelEtc1(11, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(33, 3)); + ProcessPixelEtc1(4, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(12, 3)); + ProcessPixelEtc1(5, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(15, 3)); - ProcessPixelEtc1(12, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(36, 3)); - ProcessPixelEtc1(13, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(39, 3)); + ProcessPixelEtc1(6, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(18, 3)); + ProcessPixelEtc1(7, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(21, 3)); - ProcessPixelEtc1(14, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(42, 3)); - ProcessPixelEtc1(15, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(45, 3)); - } + ProcessPixelEtc1(8, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(24, 3)); + ProcessPixelEtc1(9, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(27, 3)); - public static void DecodeEtc2Block(Span payload, Span decodedPixelSpan) - { - // Figure out the mode. - if ((payload[3] & 2) == 0) - { - // Individual mode. - DecodeEtc1Block(payload, decodedPixelSpan); - return; - } + ProcessPixelEtc1(10, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(30, 3)); + ProcessPixelEtc1(11, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(33, 3)); - int r = payload[0] & 0xF8; - r += Complement3BitShifted(payload[0] & 7); - int g = payload[1] & 0xF8; - g += Complement3BitShifted(payload[1] & 7); - int b = payload[2] & 0xF8; - b += Complement3BitShifted(payload[2] & 7); + ProcessPixelEtc1(12, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(36, 3)); + ProcessPixelEtc1(13, pixelIndexWord, tableCodeword1, redBaseColorSubBlock1, greenBaseColorSubBlock1, blueBaseColorSubBlock1, pixelBuffer.Slice(39, 3)); - decodedPixelSpan.Clear(); - if ((r & 0xFF07) != 0) - { - ProcessBlockEtc2TMode(payload, decodedPixelSpan); - return; - } + ProcessPixelEtc1(14, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(42, 3)); + ProcessPixelEtc1(15, pixelIndexWord, tableCodeword2, redBaseColorSubBlock2, greenBaseColorSubBlock2, blueBaseColorSubBlock2, pixelBuffer.Slice(45, 3)); + } - if ((g & 0xFF07) != 0) - { - ProcessBlockEtc2HMode(payload, decodedPixelSpan); - return; - } + public static void DecodeEtc2Block(Span payload, Span decodedPixelSpan) + { + // Figure out the mode. + if ((payload[3] & 2) == 0) + { + // Individual mode. + DecodeEtc1Block(payload, decodedPixelSpan); + return; + } - if ((b & 0xFF07) != 0) - { - // Planar mode. - ProcessBlockEtc2PlanarMode(payload, decodedPixelSpan); - return; - } + int r = payload[0] & 0xF8; + r += Complement3BitShifted(payload[0] & 7); + int g = payload[1] & 0xF8; + g += Complement3BitShifted(payload[1] & 7); + int b = payload[2] & 0xF8; + b += Complement3BitShifted(payload[2] & 7); - // Differential mode. - DecodeEtc1Block(payload, decodedPixelSpan); + decodedPixelSpan.Clear(); + if ((r & 0xFF07) != 0) + { + ProcessBlockEtc2TMode(payload, decodedPixelSpan); + return; } - private static void ProcessBlockEtc2PlanarMode(Span payload, Span decodedPixelSpan) + if ((g & 0xFF07) != 0) { - // Each color O, H and V is in 6-7-6 format. - int ro = (payload[0] & 0x7E) >> 1; - int go = ((payload[0] & 0x1) << 6) | ((payload[1] & 0x7E) >> 1); - int bo = ((payload[1] & 0x1) << 5) | (payload[2] & 0x18) | ((payload[2] & 0x03) << 1) | ((payload[3] & 0x80) >> 7); - int rh = ((payload[3] & 0x7C) >> 1) | (payload[3] & 0x1); - int gh = (payload[4] & 0xFE) >> 1; - int bh = ((payload[4] & 0x1) << 5) | ((payload[5] & 0xF8) >> 3); - int rv = ((payload[5] & 0x7) << 3) | ((payload[6] & 0xE0) >> 5); - int gv = ((payload[6] & 0x1F) << 2) | ((payload[7] & 0xC0) >> 6); - int bv = payload[7] & 0x3F; - - // Replicate bits. - ro = (ro << 2) | ((ro & 0x30) >> 4); - go = (go << 1) | ((go & 0x40) >> 6); - bo = (bo << 2) | ((bo & 0x30) >> 4); - rh = (rh << 2) | ((rh & 0x30) >> 4); - gh = (gh << 1) | ((gh & 0x40) >> 6); - bh = (bh << 2) | ((bh & 0x30) >> 4); - rv = (rv << 2) | ((rv & 0x30) >> 4); - gv = (gv << 1) | ((gv & 0x40) >> 6); - bv = (bv << 2) | ((bv & 0x30) >> 4); - - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - byte r = (byte)Helper.Clamp(((x * (rh - ro)) + (y * (rv - ro)) + (4 * ro) + 2) >> 2, 0, 255); - byte g = (byte)Helper.Clamp(((x * (gh - go)) + (y * (gv - go)) + (4 * go) + 2) >> 2, 0, 255); - byte b = (byte)Helper.Clamp(((x * (bh - bo)) + (y * (bv - bo)) + (4 * bo) + 2) >> 2, 0, 255); - int pixelIdx = ((y * 4) + x) * 3; - decodedPixelSpan[pixelIdx] = r; - decodedPixelSpan[pixelIdx + 1] = g; - decodedPixelSpan[pixelIdx + 2] = b; - } - } + ProcessBlockEtc2HMode(payload, decodedPixelSpan); + return; } - private static void ProcessBlockEtc2TMode(Span payload, Span decodedPixelSpan) + if ((b & 0xFF07) != 0) { - int[] paintColorR = new int[4]; - int[] paintColorG = new int[4]; - int[] paintColorB = new int[4]; - - int c0r = ((payload[0] & 0x18) >> 1) | (payload[0] & 0x3); - c0r |= c0r << 4; - int c0g = payload[1] & 0xF0; - c0g |= c0g >> 4; - int c0b = payload[1] & 0x0F; - c0b |= c0b << 4; - int c1r = payload[2] & 0xF0; - c1r |= c1r >> 4; - int c1g = payload[2] & 0x0F; - c1g |= c1g << 4; - int c1b = payload[3] & 0xF0; - c1b |= c1b >> 4; - - int distance = Etc2DistanceTable[((payload[3] & 0x0C) >> 1) | (payload[3] & 0x1)]; - paintColorR[0] = c0r; - paintColorG[0] = c0g; - paintColorB[0] = c0b; - paintColorR[2] = c1r; - paintColorG[2] = c1g; - paintColorB[2] = c1b; - paintColorR[1] = Helper.Clamp(c1r + distance, 0, 255); - paintColorG[1] = Helper.Clamp(c1g + distance, 0, 255); - paintColorB[1] = Helper.Clamp(c1b + distance, 0, 255); - paintColorR[3] = Helper.Clamp(c1r - distance, 0, 255); - paintColorG[3] = Helper.Clamp(c1g - distance, 0, 255); - paintColorB[3] = Helper.Clamp(c1b - distance, 0, 255); - - uint pixel_index_word = (uint)((payload[4] << 24) | (payload[5] << 16) | (payload[6] << 8) | payload[7]); - int decodedPixelIdx = 0; - for (int i = 0; i < 16; i++) - { - uint pixel_index = (uint)(((pixel_index_word & (1 << i)) >> i) | ((pixel_index_word & (0x10000 << i)) >> (16 + i - 1))); - int r = paintColorR[pixel_index]; - int g = paintColorG[pixel_index]; - int b = paintColorB[pixel_index]; - decodedPixelSpan[decodedPixelIdx++] = (byte)r; - decodedPixelSpan[decodedPixelIdx++] = (byte)g; - decodedPixelSpan[decodedPixelIdx++] = (byte)b; - } + // Planar mode. + ProcessBlockEtc2PlanarMode(payload, decodedPixelSpan); + return; } - private static void ProcessBlockEtc2HMode(Span payload, Span decodedPixelSpan) + // Differential mode. + DecodeEtc1Block(payload, decodedPixelSpan); + } + + private static void ProcessBlockEtc2PlanarMode(Span payload, Span decodedPixelSpan) + { + // Each color O, H and V is in 6-7-6 format. + int ro = (payload[0] & 0x7E) >> 1; + int go = ((payload[0] & 0x1) << 6) | ((payload[1] & 0x7E) >> 1); + int bo = ((payload[1] & 0x1) << 5) | (payload[2] & 0x18) | ((payload[2] & 0x03) << 1) | ((payload[3] & 0x80) >> 7); + int rh = ((payload[3] & 0x7C) >> 1) | (payload[3] & 0x1); + int gh = (payload[4] & 0xFE) >> 1; + int bh = ((payload[4] & 0x1) << 5) | ((payload[5] & 0xF8) >> 3); + int rv = ((payload[5] & 0x7) << 3) | ((payload[6] & 0xE0) >> 5); + int gv = ((payload[6] & 0x1F) << 2) | ((payload[7] & 0xC0) >> 6); + int bv = payload[7] & 0x3F; + + // Replicate bits. + ro = (ro << 2) | ((ro & 0x30) >> 4); + go = (go << 1) | ((go & 0x40) >> 6); + bo = (bo << 2) | ((bo & 0x30) >> 4); + rh = (rh << 2) | ((rh & 0x30) >> 4); + gh = (gh << 1) | ((gh & 0x40) >> 6); + bh = (bh << 2) | ((bh & 0x30) >> 4); + rv = (rv << 2) | ((rv & 0x30) >> 4); + gv = (gv << 1) | ((gv & 0x40) >> 6); + bv = (bv << 2) | ((bv & 0x30) >> 4); + + for (int y = 0; y < 4; y++) { - int[] paintColorR = new int[4]; - int[] paintColorG = new int[4]; - int[] paintColorB = new int[4]; - - int c0r = (payload[0] & 0x78) >> 3; - c0r |= c0r << 4; - int c0g = ((payload[0] & 0x07) << 1) | ((payload[1] & 0x10) >> 4); - c0g |= c0g << 4; - int c0b = (payload[1] & 0x08) | ((payload[1] & 0x03) << 1) | ((payload[2] & 0x80) >> 7); - c0b |= c0b << 4; - int c1r = (payload[2] & 0x78) >> 3; - c1r |= c1r << 4; - int c1g = ((payload[2] & 0x07) << 1) | ((payload[3] & 0x80) >> 7); - c1g |= c1g << 4; - int c1b = (payload[3] & 0x78) >> 3; - c1b |= c1b << 4; - - int baseColor0Value = (c0r << 16) + (c0g << 8) + c0b; - int baseColor1Value = (c1r << 16) + (c1g << 8) + c1b; - int bit = baseColor0Value >= baseColor1Value ? 1 : 0; - - int distance = Etc2DistanceTable[(payload[3] & 0x04) | ((payload[3] & 0x01) << 1) | bit]; - paintColorR[0] = Helper.Clamp(c0r + distance, 0, 255); - paintColorG[0] = Helper.Clamp(c0g + distance, 0, 255); - paintColorB[0] = Helper.Clamp(c0b + distance, 0, 255); - paintColorR[1] = Helper.Clamp(c0r - distance, 0, 255); - paintColorG[1] = Helper.Clamp(c0g - distance, 0, 255); - paintColorB[1] = Helper.Clamp(c0b - distance, 0, 255); - paintColorR[2] = Helper.Clamp(c1r + distance, 0, 255); - paintColorG[2] = Helper.Clamp(c1g + distance, 0, 255); - paintColorB[2] = Helper.Clamp(c1b + distance, 0, 255); - paintColorR[3] = Helper.Clamp(c1r - distance, 0, 255); - paintColorG[3] = Helper.Clamp(c1g - distance, 0, 255); - paintColorB[3] = Helper.Clamp(c1b - distance, 0, 255); - - uint pixel_index_word = (uint)((payload[4] << 24) | (payload[5] << 16) | (payload[6] << 8) | payload[7]); - int decodedPixelIdx = 0; - for (int i = 0; i < 16; i++) + for (int x = 0; x < 4; x++) { - uint pixel_index = (uint)(((pixel_index_word & (1 << i)) >> i) | ((pixel_index_word & (0x10000 << i)) >> (16 + i - 1))); - int r = paintColorR[pixel_index]; - int g = paintColorG[pixel_index]; - int b = paintColorB[pixel_index]; - decodedPixelSpan[decodedPixelIdx++] = (byte)r; - decodedPixelSpan[decodedPixelIdx++] = (byte)g; - decodedPixelSpan[decodedPixelIdx++] = (byte)b; + byte r = (byte)Helper.Clamp(((x * (rh - ro)) + (y * (rv - ro)) + (4 * ro) + 2) >> 2, 0, 255); + byte g = (byte)Helper.Clamp(((x * (gh - go)) + (y * (gv - go)) + (4 * go) + 2) >> 2, 0, 255); + byte b = (byte)Helper.Clamp(((x * (bh - bo)) + (y * (bv - bo)) + (4 * bo) + 2) >> 2, 0, 255); + int pixelIdx = ((y * 4) + x) * 3; + decodedPixelSpan[pixelIdx] = r; + decodedPixelSpan[pixelIdx + 1] = g; + decodedPixelSpan[pixelIdx + 2] = b; } } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FiveToEightBit(int color) + private static void ProcessBlockEtc2TMode(Span payload, Span decodedPixelSpan) + { + int[] paintColorR = new int[4]; + int[] paintColorG = new int[4]; + int[] paintColorB = new int[4]; + + int c0r = ((payload[0] & 0x18) >> 1) | (payload[0] & 0x3); + c0r |= c0r << 4; + int c0g = payload[1] & 0xF0; + c0g |= c0g >> 4; + int c0b = payload[1] & 0x0F; + c0b |= c0b << 4; + int c1r = payload[2] & 0xF0; + c1r |= c1r >> 4; + int c1g = payload[2] & 0x0F; + c1g |= c1g << 4; + int c1b = payload[3] & 0xF0; + c1b |= c1b >> 4; + + int distance = Etc2DistanceTable[((payload[3] & 0x0C) >> 1) | (payload[3] & 0x1)]; + paintColorR[0] = c0r; + paintColorG[0] = c0g; + paintColorB[0] = c0b; + paintColorR[2] = c1r; + paintColorG[2] = c1g; + paintColorB[2] = c1b; + paintColorR[1] = Helper.Clamp(c1r + distance, 0, 255); + paintColorG[1] = Helper.Clamp(c1g + distance, 0, 255); + paintColorB[1] = Helper.Clamp(c1b + distance, 0, 255); + paintColorR[3] = Helper.Clamp(c1r - distance, 0, 255); + paintColorG[3] = Helper.Clamp(c1g - distance, 0, 255); + paintColorB[3] = Helper.Clamp(c1b - distance, 0, 255); + + uint pixel_index_word = (uint)((payload[4] << 24) | (payload[5] << 16) | (payload[6] << 8) | payload[7]); + int decodedPixelIdx = 0; + for (int i = 0; i < 16; i++) { - int c0r = color | ((color & 0xE0) >> 5); - return c0r; + uint pixel_index = (uint)(((pixel_index_word & (1 << i)) >> i) | ((pixel_index_word & (0x10000 << i)) >> (16 + i - 1))); + int r = paintColorR[pixel_index]; + int g = paintColorG[pixel_index]; + int b = paintColorB[pixel_index]; + decodedPixelSpan[decodedPixelIdx++] = (byte)r; + decodedPixelSpan[decodedPixelIdx++] = (byte)g; + decodedPixelSpan[decodedPixelIdx++] = (byte)b; } + } - /// - /// This function calculates the 3-bit complement value in the range -4 to 3 of a three bit representation. - /// The result is arithmetically shifted 3 places to the left before returning. - /// - /// The value. - /// 3-bit complement value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Complement3BitShifted(int x) => Complement3BitShiftedTable[x]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ProcessPixelEtc1(int i, uint pixelIndexWord, uint tableCodeword, byte redBaseColorSubBlock, byte greenBaseColorSubBlock, byte blueBaseColorSubBlock, Span pixelBuffer) + private static void ProcessBlockEtc2HMode(Span payload, Span decodedPixelSpan) + { + int[] paintColorR = new int[4]; + int[] paintColorG = new int[4]; + int[] paintColorB = new int[4]; + + int c0r = (payload[0] & 0x78) >> 3; + c0r |= c0r << 4; + int c0g = ((payload[0] & 0x07) << 1) | ((payload[1] & 0x10) >> 4); + c0g |= c0g << 4; + int c0b = (payload[1] & 0x08) | ((payload[1] & 0x03) << 1) | ((payload[2] & 0x80) >> 7); + c0b |= c0b << 4; + int c1r = (payload[2] & 0x78) >> 3; + c1r |= c1r << 4; + int c1g = ((payload[2] & 0x07) << 1) | ((payload[3] & 0x80) >> 7); + c1g |= c1g << 4; + int c1b = (payload[3] & 0x78) >> 3; + c1b |= c1b << 4; + + int baseColor0Value = (c0r << 16) + (c0g << 8) + c0b; + int baseColor1Value = (c1r << 16) + (c1g << 8) + c1b; + int bit = baseColor0Value >= baseColor1Value ? 1 : 0; + + int distance = Etc2DistanceTable[(payload[3] & 0x04) | ((payload[3] & 0x01) << 1) | bit]; + paintColorR[0] = Helper.Clamp(c0r + distance, 0, 255); + paintColorG[0] = Helper.Clamp(c0g + distance, 0, 255); + paintColorB[0] = Helper.Clamp(c0b + distance, 0, 255); + paintColorR[1] = Helper.Clamp(c0r - distance, 0, 255); + paintColorG[1] = Helper.Clamp(c0g - distance, 0, 255); + paintColorB[1] = Helper.Clamp(c0b - distance, 0, 255); + paintColorR[2] = Helper.Clamp(c1r + distance, 0, 255); + paintColorG[2] = Helper.Clamp(c1g + distance, 0, 255); + paintColorB[2] = Helper.Clamp(c1b + distance, 0, 255); + paintColorR[3] = Helper.Clamp(c1r - distance, 0, 255); + paintColorG[3] = Helper.Clamp(c1g - distance, 0, 255); + paintColorB[3] = Helper.Clamp(c1b - distance, 0, 255); + + uint pixel_index_word = (uint)((payload[4] << 24) | (payload[5] << 16) | (payload[6] << 8) | payload[7]); + int decodedPixelIdx = 0; + for (int i = 0; i < 16; i++) { - long pixelIndex = ((pixelIndexWord & (1 << i)) >> i) | ((pixelIndexWord & (0x10000 << i)) >> (16 + i - 1)); - int modifier = ModifierTable[tableCodeword, pixelIndex]; - byte red = (byte)Helper.Clamp(0, redBaseColorSubBlock + modifier, 255); - byte green = (byte)Helper.Clamp(0, greenBaseColorSubBlock + modifier, 255); - byte blue = (byte)Helper.Clamp(0, blueBaseColorSubBlock + modifier, 255); - - pixelBuffer[0] = red; - pixelBuffer[1] = green; - pixelBuffer[2] = blue; + uint pixel_index = (uint)(((pixel_index_word & (1 << i)) >> i) | ((pixel_index_word & (0x10000 << i)) >> (16 + i - 1))); + int r = paintColorR[pixel_index]; + int g = paintColorG[pixel_index]; + int b = paintColorB[pixel_index]; + decodedPixelSpan[decodedPixelIdx++] = (byte)r; + decodedPixelSpan[decodedPixelIdx++] = (byte)g; + decodedPixelSpan[decodedPixelIdx++] = (byte)b; } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FiveToEightBit(int color) + { + int c0r = color | ((color & 0xE0) >> 5); + return c0r; + } + + /// + /// This function calculates the 3-bit complement value in the range -4 to 3 of a three bit representation. + /// The result is arithmetically shifted 3 places to the left before returning. + /// + /// The value. + /// 3-bit complement value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Complement3BitShifted(int x) => Complement3BitShiftedTable[x]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ProcessPixelEtc1(int i, uint pixelIndexWord, uint tableCodeword, byte redBaseColorSubBlock, byte greenBaseColorSubBlock, byte blueBaseColorSubBlock, Span pixelBuffer) + { + long pixelIndex = ((pixelIndexWord & (1 << i)) >> i) | ((pixelIndexWord & (0x10000 << i)) >> (16 + i - 1)); + int modifier = ModifierTable[tableCodeword, pixelIndex]; + byte red = (byte)Helper.Clamp(0, redBaseColorSubBlock + modifier, 255); + byte green = (byte)Helper.Clamp(0, greenBaseColorSubBlock + modifier, 255); + byte blue = (byte)Helper.Clamp(0, blueBaseColorSubBlock + modifier, 255); + + pixelBuffer[0] = red; + pixelBuffer[1] = green; + pixelBuffer[2] = blue; + } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Fp32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Fp32.cs index 2e5d5538..001e61c6 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Fp32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Fp32.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixel data for single channel 32 bit float values. - /// - internal struct Fp32 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture for pixel data for single channel 32 bit float values. +/// +internal struct Fp32 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Grgb32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Grgb32.cs index bf02c556..5da9cc85 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Grgb32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Grgb32.cs @@ -1,66 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture for the pixel format G8R8G8B8. +/// +internal struct Grgb32 : IBlock { - /// - /// Texture for the pixel format G8R8G8B8. - /// - internal struct Grgb32 : IBlock - { - /// - public int BitsPerPixel => 32; + /// + public readonly int BitsPerPixel => 32; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 1; + /// + public readonly byte DivSize => 1; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public bool Compressed => false; + /// + public readonly bool Compressed => false; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + int totalPixels = width * height; + byte[] decompressed = new byte[totalPixels * 3]; + Span rgb24Span = MemoryMarshal.Cast(decompressed); + + ImageSharp.PixelFormats.Rgb24 pixel = default(ImageSharp.PixelFormats.Rgb24); + int pixelIdx = 0; + for (int i = 0; i < blockData.Length; i += 4) { - int totalPixels = width * height; - byte[] decompressed = new byte[totalPixels * 3]; - Span rgb24Span = MemoryMarshal.Cast(decompressed); + byte g0 = blockData[i]; + byte r = blockData[i + 1]; + byte g1 = blockData[i + 2]; + byte b = blockData[i + 3]; - var pixel = default(ImageSharp.PixelFormats.Rgb24); - int pixelIdx = 0; - for (int i = 0; i < blockData.Length; i += 4) + pixel.FromRgb24(new ImageSharp.PixelFormats.Rgb24(r, g0, b)); + rgb24Span[pixelIdx++] = pixel; + if (pixelIdx >= totalPixels) { - byte g0 = blockData[i]; - byte r = blockData[i + 1]; - byte g1 = blockData[i + 2]; - byte b = blockData[i + 3]; - - pixel.FromRgb24(new ImageSharp.PixelFormats.Rgb24(r, g0, b)); - rgb24Span[pixelIdx++] = pixel; - if (pixelIdx >= totalPixels) - { - break; - } - - pixel.FromRgb24(new ImageSharp.PixelFormats.Rgb24(r, g1, b)); - rgb24Span[pixelIdx++] = pixel; + break; } - return decompressed; + pixel.FromRgb24(new ImageSharp.PixelFormats.Rgb24(r, g1, b)); + rgb24Span[pixelIdx++] = pixel; } + + return decompressed; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Helper.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Helper.cs index e755cc84..b01c8ae9 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Helper.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Helper.cs @@ -1,100 +1,98 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Helper methods for decoding textures. +/// +public static class Helper { /// - /// Helper methods for decoding textures. + /// Delegate to decode a texture. /// - public static class Helper - { - /// - /// Delegate to decode a texture. - /// - /// The stream containing the encoded texture. - /// The data array to decompress the texture data to. - /// Index of the stream. - /// Index of the data. - /// The stride. - /// Stream position after decompression. - public delegate int DecodeDelegate(byte[] stream, byte[] data, int streamIndex, int dataIndex, int stride); - - /// - /// Calculates the number of blocks. - /// - /// The number of pixels. - /// The number of blocks. - public static int CalcBlocks(int pixels) => Math.Max(1, (pixels + 3) / 4); + /// The stream containing the encoded texture. + /// The data array to decompress the texture data to. + /// Index of the stream. + /// Index of the data. + /// The stride. + /// Stream position after decompression. + public delegate int DecodeDelegate(byte[] stream, byte[] data, int streamIndex, int dataIndex, int stride); - /// - /// Decodes a block texture. - /// - /// The type of the block. - /// The memory buffer with the encoded texture data. - /// The width of the texture. - /// The height of the texture. - /// The decode delegate to use. - /// The decoded bytes of the texture. - public static byte[] InMemoryDecode(byte[] memBuffer, int width, int height, DecodeDelegate decode) - where TBlock : struct, IBlock - { - TBlock blockFormat = default; - int heightBlocks = CalcBlocks(height); - int widthBlocks = CalcBlocks(width); - int stridePixels = widthBlocks * blockFormat.DivSize; - int deflatedStrideBytes = stridePixels * blockFormat.PixelDepthBytes; - int dataLen = heightBlocks * blockFormat.DivSize * deflatedStrideBytes; - byte[] data = new byte[dataLen]; - - int dataIndex = 0; - int bIndex = 0; + /// + /// Calculates the number of blocks. + /// + /// The number of pixels. + /// The number of blocks. + public static int CalcBlocks(int pixels) => Math.Max(1, (pixels + 3) / 4); - int stride = stridePixels * blockFormat.PixelDepthBytes; - int blocksPerStride = widthBlocks; - int indexPixelsLeft = heightBlocks * blockFormat.DivSize * stride; + /// + /// Decodes a block texture. + /// + /// The type of the block. + /// The memory buffer with the encoded texture data. + /// The width of the texture. + /// The height of the texture. + /// The decode delegate to use. + /// The decoded bytes of the texture. + public static byte[] InMemoryDecode(byte[] memBuffer, int width, int height, DecodeDelegate decode) + where TBlock : struct, IBlock + { + TBlock blockFormat = default; + int heightBlocks = CalcBlocks(height); + int widthBlocks = CalcBlocks(width); + int stridePixels = widthBlocks * blockFormat.DivSize; + int deflatedStrideBytes = stridePixels * blockFormat.PixelDepthBytes; + int dataLen = heightBlocks * blockFormat.DivSize * deflatedStrideBytes; + byte[] data = new byte[dataLen]; - while (indexPixelsLeft > 0) - { - int origDataIndex = dataIndex; + int dataIndex = 0; + int bIndex = 0; - for (int i = 0; i < blocksPerStride; i++) - { - bIndex = decode.Invoke(memBuffer, data, bIndex, dataIndex, stridePixels); - dataIndex += blockFormat.DivSize * blockFormat.PixelDepthBytes; - } + int stride = stridePixels * blockFormat.PixelDepthBytes; + int blocksPerStride = widthBlocks; + int indexPixelsLeft = heightBlocks * blockFormat.DivSize * stride; - int filled = stride * blockFormat.DivSize; - indexPixelsLeft -= filled; + while (indexPixelsLeft > 0) + { + int origDataIndex = dataIndex; - // Jump down to the block that is exactly (divSize - 1) - // below the current row we are on - dataIndex = origDataIndex + filled; + for (int i = 0; i < blocksPerStride; i++) + { + bIndex = decode.Invoke(memBuffer, data, bIndex, dataIndex, stridePixels); + dataIndex += blockFormat.DivSize * blockFormat.PixelDepthBytes; } - return data; - } + int filled = stride * blockFormat.DivSize; + indexPixelsLeft -= filled; - /// - /// Clamps the specified value between a min and a max value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// The clamped value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint Clamp(uint val, uint min, uint max) => Math.Min(Math.Max(val, min), max); + // Jump down to the block that is exactly (divSize - 1) + // below the current row we are on + dataIndex = origDataIndex + filled; + } - /// - /// Clamps the specified value between a min and a max value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// The clamped value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Clamp(int val, int min, int max) => Math.Min(Math.Max(val, min), max); + return data; } + + /// + /// Clamps the specified value between a min and a max value. + /// + /// The value to clamp. + /// The minimum value. + /// The maximum value. + /// The clamped value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Clamp(uint val, uint min, uint max) => Math.Min(Math.Max(val, min), max); + + /// + /// Clamps the specified value between a min and a max value. + /// + /// The value to clamp. + /// The minimum value. + /// The maximum value. + /// The clamped value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Clamp(int val, int min, int max) => Math.Min(Math.Max(val, min), max); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/IBlock.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/IBlock.cs index 99edf380..cc63c174 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/IBlock.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/IBlock.cs @@ -1,54 +1,53 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Interface for a block texture. +/// +public interface IBlock { /// - /// Interface for a block texture. + /// Gets the bits per pixel. + /// + int BitsPerPixel { get; } + + /// + /// Gets the pixel depth in bytes. + /// + byte PixelDepthBytes { get; } + + /// + /// Gets the div size. + /// + byte DivSize { get; } + + /// + /// Gets the number of compressed bytes per block. + /// + byte CompressedBytesPerBlock { get; } + + /// + /// Gets a value indicating whether this block is compressed. + /// + bool Compressed { get; } + + /// + /// Gets the image from the (maybe compressed) block data. + /// + /// The block data bytes. + /// The width of the texture. + /// The height of the texture. + /// The Image. + Image GetImage(byte[] blockData, int width, int height); + + /// + /// Gets the decompressed data. /// - public interface IBlock - { - /// - /// Gets the bits per pixel. - /// - int BitsPerPixel { get; } - - /// - /// Gets the pixel depth in bytes. - /// - byte PixelDepthBytes { get; } - - /// - /// Gets the div size. - /// - byte DivSize { get; } - - /// - /// Gets the number of compressed bytes per block. - /// - byte CompressedBytesPerBlock { get; } - - /// - /// Gets a value indicating whether this block is compressed. - /// - bool Compressed { get; } - - /// - /// Gets the image from the (maybe compressed) block data. - /// - /// The block data bytes. - /// The width of the texture. - /// The height of the texture. - /// The Image. - Image GetImage(byte[] blockData, int width, int height); - - /// - /// Gets the decompressed data. - /// - /// The block data bytes. - /// The width of the texture. - /// The height of the texture. - /// The decompressed byte data of the texture. - byte[] Decompress(byte[] blockData, int width, int height); - } + /// The block data bytes. + /// The width of the texture. + /// The height of the texture. + /// The decompressed byte data of the texture. + byte[] Decompress(byte[] blockData, int width, int height); } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/IBlock{TSelf}.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/IBlock{TSelf}.cs index d656dc1f..f3932bac 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/IBlock{TSelf}.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/IBlock{TSelf}.cs @@ -1,14 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Interface for a block texture. +/// +/// The type of the texture. +public interface IBlock : IBlock + where TSelf : struct, IBlock { - /// - /// Interface for a block texture. - /// - /// The type of the texture. - public interface IBlock : IBlock - where TSelf : struct, IBlock - { - } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/L16.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/L16.cs index 8951fa6a..0a3dfde0 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/L16.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/L16.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixels with 16 bit luminance values. - /// - internal struct L16 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture for pixels with 16 bit luminance values. +/// +internal struct L16 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/L32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/L32.cs index 32813f6e..b7733196 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/L32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/L32.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for 32 bit luminance pixel data. - /// - internal struct L32 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture for 32 bit luminance pixel data. +/// +internal struct L32 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/L8.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/L8.cs index e43d7d8c..212db6af 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/L8.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/L8.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for 8 bit luminance pixels. - /// - internal struct L8 : IBlock - { - /// - public int BitsPerPixel => 8; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 1; +/// +/// Texture for 8 bit luminance pixels. +/// +internal struct L8 : IBlock +{ + /// + public readonly int BitsPerPixel => 8; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 1; - /// - public byte CompressedBytesPerBlock => 1; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 1; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/La16.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/La16.cs index 98aa5ff9..c295bfb1 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/La16.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/La16.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for 8 bit luminance and 8 bit alpha data. - /// - internal struct La16 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture for 8 bit luminance and 8 bit alpha data. +/// +internal struct La16 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/Constants.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/Constants.cs index 60fc7929..1655799d 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/Constants.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/Constants.cs @@ -2,325 +2,318 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; + +internal static class Constants { - internal static class Constants - { - public const int NumPixelsPerBlock = 16; + public const int NumPixelsPerBlock = 16; #pragma warning disable SA1310 // Field names should not contain underscore - public const int BC6H_MAX_REGIONS = 2; - public const int BC6H_MAX_INDICES = 16; - public const int BC7_MAX_REGIONS = 3; - public const int BC7_MAX_INDICES = 16; + public const int BC6H_MAX_REGIONS = 2; + public const int BC6H_MAX_INDICES = 16; + public const int BC7_MAX_REGIONS = 3; + public const int BC7_MAX_INDICES = 16; - public const ushort F16S_MASK = 0x8000; // f16 sign mask - public const ushort F16EM_MASK = 0x7fff; // f16 exp & mantissa mask - public const ushort F16MAX = 0x7bff; // MAXFLT bit pattern for XMHALF + public const ushort F16S_MASK = 0x8000; // f16 sign mask + public const ushort F16EM_MASK = 0x7fff; // f16 exp & mantissa mask + public const ushort F16MAX = 0x7bff; // MAXFLT bit pattern for XMHALF - public const int BC6H_NUM_CHANNELS = 3; - public const int BC6H_MAX_SHAPES = 32; + public const int BC6H_NUM_CHANNELS = 3; + public const int BC6H_MAX_SHAPES = 32; - public const int BC7_NUM_CHANNELS = 4; - public const int BC7_MAX_SHAPES = 64; + public const int BC7_NUM_CHANNELS = 4; + public const int BC7_MAX_SHAPES = 64; - public const int BC67_WEIGHT_MAX = 64; - public const int BC67_WEIGHT_SHIFT = 6; - public const int BC67_WEIGHT_ROUND = 32; + public const int BC67_WEIGHT_MAX = 64; + public const int BC67_WEIGHT_SHIFT = 6; + public const int BC67_WEIGHT_ROUND = 32; #pragma warning restore SA1310 // Field names should not contain underscore - /* - public const float fEpsilon = 0.25f / 64.0f * (0.25f / 64.0f); - public static readonly float[] pC3 = { 2.0f / 2.0f, 1.0f / 2.0f, 0.0f / 2.0f }; - public static readonly float[] pD3 = { 0.0f / 2.0f, 1.0f / 2.0f, 2.0f / 2.0f }; - public static readonly float[] pC4 = { 3.0f / 3.0f, 2.0f / 3.0f, 1.0f / 3.0f, 0.0f / 3.0f }; - public static readonly float[] pD4 = { 0.0f / 3.0f, 1.0f / 3.0f, 2.0f / 3.0f, 3.0f / 3.0f }; - */ + /* + public const float fEpsilon = 0.25f / 64.0f * (0.25f / 64.0f); + public static readonly float[] pC3 = { 2.0f / 2.0f, 1.0f / 2.0f, 0.0f / 2.0f }; + public static readonly float[] pD3 = { 0.0f / 2.0f, 1.0f / 2.0f, 2.0f / 2.0f }; + public static readonly float[] pC4 = { 3.0f / 3.0f, 2.0f / 3.0f, 1.0f / 3.0f, 0.0f / 3.0f }; + public static readonly float[] pD4 = { 0.0f / 3.0f, 1.0f / 3.0f, 2.0f / 3.0f, 3.0f / 3.0f }; + */ - // Partition, Shape, Pixel (index into 4x4 block) - public static readonly byte[][][] PartitionTable = - { - new[] - { - // 1 Region case has no subsets (all 0) - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } - }, + // Partition, Shape, Pixel (index into 4x4 block) + public static readonly byte[][][] PartitionTable = + [ + [ + // 1 Region case has no subsets (all 0) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ], - new[] - { - // BC6H/BC7 Partition Set for 2 Subsets - new byte[] { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1 }, // Shape 0 - new byte[] { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 }, // Shape 1 - new byte[] { 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1 }, // Shape 2 - new byte[] { 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1 }, // Shape 3 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1 }, // Shape 4 - new byte[] { 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, // Shape 5 - new byte[] { 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, // Shape 6 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1 }, // Shape 7 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1 }, // Shape 8 - new byte[] { 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // Shape 9 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, // Shape 10 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1 }, // Shape 11 - new byte[] { 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // Shape 12 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }, // Shape 13 - new byte[] { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // Shape 14 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // Shape 15 - new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1 }, // Shape 16 - new byte[] { 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // Shape 17 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 }, // Shape 18 - new byte[] { 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0 }, // Shape 19 - new byte[] { 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // Shape 20 - new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0 }, // Shape 21 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // Shape 22 - new byte[] { 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1 }, // Shape 23 - new byte[] { 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 }, // Shape 24 - new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // Shape 25 - new byte[] { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0 }, // Shape 26 - new byte[] { 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0 }, // Shape 27 - new byte[] { 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0 }, // Shape 28 - new byte[] { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, // Shape 29 - new byte[] { 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0 }, // Shape 30 - new byte[] { 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0 }, // Shape 31 + [ + // BC6H/BC7 Partition Set for 2 Subsets + [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1], // Shape 0 + [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1], // Shape 1 + [0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], // Shape 2 + [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1], // Shape 3 + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1], // Shape 4 + [0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], // Shape 5 + [0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], // Shape 6 + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1], // Shape 7 + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1], // Shape 8 + [0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // Shape 9 + [0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], // Shape 10 + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1], // Shape 11 + [0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // Shape 12 + [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], // Shape 13 + [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // Shape 14 + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], // Shape 15 + [0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1], // Shape 16 + [0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], // Shape 17 + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0], // Shape 18 + [0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0], // Shape 19 + [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], // Shape 20 + [0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0], // Shape 21 + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0], // Shape 22 + [0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1], // Shape 23 + [0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0], // Shape 24 + [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0], // Shape 25 + [0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0], // Shape 26 + [0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0], // Shape 27 + [0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0], // Shape 28 + [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], // Shape 29 + [0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0], // Shape 30 + [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0], // Shape 31 - // BC7 Partition Set for 2 Subsets (second-half) - new byte[] { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 }, // Shape 32 - new byte[] { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // Shape 33 - new byte[] { 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0 }, // Shape 34 - new byte[] { 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0 }, // Shape 35 - new byte[] { 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0 }, // Shape 36 - new byte[] { 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0 }, // Shape 37 - new byte[] { 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1 }, // Shape 38 - new byte[] { 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1 }, // Shape 39 - new byte[] { 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0 }, // Shape 40 - new byte[] { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0 }, // Shape 41 - new byte[] { 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0 }, // Shape 42 - new byte[] { 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0 }, // Shape 43 - new byte[] { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }, // Shape 44 - new byte[] { 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, // Shape 45 - new byte[] { 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1 }, // Shape 46 - new byte[] { 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // Shape 47 - new byte[] { 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // Shape 48 - new byte[] { 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, // Shape 49 - new byte[] { 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0 }, // Shape 50 - new byte[] { 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0 }, // Shape 51 - new byte[] { 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1 }, // Shape 52 - new byte[] { 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1 }, // Shape 53 - new byte[] { 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0 }, // Shape 54 - new byte[] { 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0 }, // Shape 55 - new byte[] { 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1 }, // Shape 56 - new byte[] { 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1 }, // Shape 57 - new byte[] { 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1 }, // Shape 58 - new byte[] { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1 }, // Shape 59 - new byte[] { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1 }, // Shape 60 - new byte[] { 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, // Shape 61 - new byte[] { 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0 }, // Shape 62 - new byte[] { 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1 } // Shape 63 - }, + // BC7 Partition Set for 2 Subsets (second-half) + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], // Shape 32 + [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1], // Shape 33 + [0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0], // Shape 34 + [0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0], // Shape 35 + [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0], // Shape 36 + [0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0], // Shape 37 + [0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1], // Shape 38 + [0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1], // Shape 39 + [0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0], // Shape 40 + [0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0], // Shape 41 + [0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0], // Shape 42 + [0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0], // Shape 43 + [0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0], // Shape 44 + [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1], // Shape 45 + [0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1], // Shape 46 + [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0], // Shape 47 + [0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0], // Shape 48 + [0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0], // Shape 49 + [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0], // Shape 50 + [0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0], // Shape 51 + [0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1], // Shape 52 + [0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1], // Shape 53 + [0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0], // Shape 54 + [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0], // Shape 55 + [0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1], // Shape 56 + [0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1], // Shape 57 + [0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1], // Shape 58 + [0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1], // Shape 59 + [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1], // Shape 60 + [0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], // Shape 61 + [0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0], // Shape 62 + [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1] // Shape 63 + ], - new[] - { - // BC7 Partition Set for 3 Subsets - new byte[] { 0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 1, 2, 2, 2, 2 }, // Shape 0 - new byte[] { 0, 0, 0, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1 }, // Shape 1 - new byte[] { 0, 0, 0, 0, 2, 0, 0, 1, 2, 2, 1, 1, 2, 2, 1, 1 }, // Shape 2 - new byte[] { 0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 1, 0, 1, 1, 1 }, // Shape 3 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2 }, // Shape 4 - new byte[] { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 2, 2 }, // Shape 5 - new byte[] { 0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1 }, // Shape 6 - new byte[] { 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1 }, // Shape 7 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2 }, // Shape 8 - new byte[] { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2 }, // Shape 9 - new byte[] { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2 }, // Shape 10 - new byte[] { 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2 }, // Shape 11 - new byte[] { 0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2 }, // Shape 12 - new byte[] { 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2 }, // Shape 13 - new byte[] { 0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 2 }, // Shape 14 - new byte[] { 0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0 }, // Shape 15 - new byte[] { 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2 }, // Shape 16 - new byte[] { 0, 1, 1, 1, 0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0 }, // Shape 17 - new byte[] { 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2 }, // Shape 18 - new byte[] { 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1 }, // Shape 19 - new byte[] { 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2 }, // Shape 20 - new byte[] { 0, 0, 0, 1, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 2, 1 }, // Shape 21 - new byte[] { 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2 }, // Shape 22 - new byte[] { 0, 0, 0, 0, 1, 1, 0, 0, 2, 2, 1, 0, 2, 2, 1, 0 }, // Shape 23 - new byte[] { 0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1, 0, 0, 0, 0 }, // Shape 24 - new byte[] { 0, 0, 1, 2, 0, 0, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2 }, // Shape 25 - new byte[] { 0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1, 0, 1, 1, 0 }, // Shape 26 - new byte[] { 0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1 }, // Shape 27 - new byte[] { 0, 0, 2, 2, 1, 1, 0, 2, 1, 1, 0, 2, 0, 0, 2, 2 }, // Shape 28 - new byte[] { 0, 1, 1, 0, 0, 1, 1, 0, 2, 0, 0, 2, 2, 2, 2, 2 }, // Shape 29 - new byte[] { 0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1 }, // Shape 30 - new byte[] { 0, 0, 0, 0, 2, 0, 0, 0, 2, 2, 1, 1, 2, 2, 2, 1 }, // Shape 31 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 2, 2, 2 }, // Shape 32 - new byte[] { 0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 2, 0, 0, 1, 1 }, // Shape 33 - new byte[] { 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 2, 2, 0, 2, 2, 2 }, // Shape 34 - new byte[] { 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0 }, // Shape 35 - new byte[] { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0 }, // Shape 36 - new byte[] { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 }, // Shape 37 - new byte[] { 0, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 0, 1, 2, 0 }, // Shape 38 - new byte[] { 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1 }, // Shape 39 - new byte[] { 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1 }, // Shape 40 - new byte[] { 0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2 }, // Shape 41 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1 }, // Shape 42 - new byte[] { 0, 0, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2, 1, 1, 2, 2 }, // Shape 43 - new byte[] { 0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 1, 1 }, // Shape 44 - new byte[] { 0, 2, 2, 0, 1, 2, 2, 1, 0, 2, 2, 0, 1, 2, 2, 1 }, // Shape 45 - new byte[] { 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 1 }, // Shape 46 - new byte[] { 0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1 }, // Shape 47 - new byte[] { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2 }, // Shape 48 - new byte[] { 0, 2, 2, 2, 0, 1, 1, 1, 0, 2, 2, 2, 0, 1, 1, 1 }, // Shape 49 - new byte[] { 0, 0, 0, 2, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 2 }, // Shape 50 - new byte[] { 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2 }, // Shape 51 - new byte[] { 0, 2, 2, 2, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2 }, // Shape 52 - new byte[] { 0, 0, 0, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2 }, // Shape 53 - new byte[] { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2 }, // Shape 54 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2 }, // Shape 55 - new byte[] { 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2 }, // Shape 56 - new byte[] { 0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2 }, // Shape 57 - new byte[] { 0, 0, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2 }, // Shape 58 - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2 }, // Shape 59 - new byte[] { 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1 }, // Shape 60 - new byte[] { 0, 2, 2, 2, 1, 2, 2, 2, 0, 2, 2, 2, 1, 2, 2, 2 }, // Shape 61 - new byte[] { 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, // Shape 62 - new byte[] { 0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 2, 0 } // Shape 63 - } - }; + [ + // BC7 Partition Set for 3 Subsets + [0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 1, 2, 2, 2, 2], // Shape 0 + [0, 0, 0, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1], // Shape 1 + [0, 0, 0, 0, 2, 0, 0, 1, 2, 2, 1, 1, 2, 2, 1, 1], // Shape 2 + [0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 1, 0, 1, 1, 1], // Shape 3 + [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2], // Shape 4 + [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 2, 2], // Shape 5 + [0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1], // Shape 6 + [0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1], // Shape 7 + [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2], // Shape 8 + [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2], // Shape 9 + [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2], // Shape 10 + [0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2], // Shape 11 + [0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2], // Shape 12 + [0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2], // Shape 13 + [0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 2], // Shape 14 + [0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0], // Shape 15 + [0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2], // Shape 16 + [0, 1, 1, 1, 0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0], // Shape 17 + [0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2], // Shape 18 + [0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1], // Shape 19 + [0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2], // Shape 20 + [0, 0, 0, 1, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 2, 1], // Shape 21 + [0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2], // Shape 22 + [0, 0, 0, 0, 1, 1, 0, 0, 2, 2, 1, 0, 2, 2, 1, 0], // Shape 23 + [0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1, 0, 0, 0, 0], // Shape 24 + [0, 0, 1, 2, 0, 0, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2], // Shape 25 + [0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1, 0, 1, 1, 0], // Shape 26 + [0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1], // Shape 27 + [0, 0, 2, 2, 1, 1, 0, 2, 1, 1, 0, 2, 0, 0, 2, 2], // Shape 28 + [0, 1, 1, 0, 0, 1, 1, 0, 2, 0, 0, 2, 2, 2, 2, 2], // Shape 29 + [0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1], // Shape 30 + [0, 0, 0, 0, 2, 0, 0, 0, 2, 2, 1, 1, 2, 2, 2, 1], // Shape 31 + [0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 2, 2, 2], // Shape 32 + [0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 2, 0, 0, 1, 1], // Shape 33 + [0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 2, 2, 0, 2, 2, 2], // Shape 34 + [0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0], // Shape 35 + [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0], // Shape 36 + [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0], // Shape 37 + [0, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 0, 1, 2, 0], // Shape 38 + [0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1], // Shape 39 + [0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1], // Shape 40 + [0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2], // Shape 41 + [0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1], // Shape 42 + [0, 0, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2, 1, 1, 2, 2], // Shape 43 + [0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 1, 1], // Shape 44 + [0, 2, 2, 0, 1, 2, 2, 1, 0, 2, 2, 0, 1, 2, 2, 1], // Shape 45 + [0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 1], // Shape 46 + [0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1], // Shape 47 + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2], // Shape 48 + [0, 2, 2, 2, 0, 1, 1, 1, 0, 2, 2, 2, 0, 1, 1, 1], // Shape 49 + [0, 0, 0, 2, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 2], // Shape 50 + [0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2], // Shape 51 + [0, 2, 2, 2, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2], // Shape 52 + [0, 0, 0, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2], // Shape 53 + [0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2], // Shape 54 + [0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2], // Shape 55 + [0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2], // Shape 56 + [0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2], // Shape 57 + [0, 0, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2], // Shape 58 + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2], // Shape 59 + [0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1], // Shape 60 + [0, 2, 2, 2, 1, 2, 2, 2, 0, 2, 2, 2, 1, 2, 2, 2], // Shape 61 + [0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], // Shape 62 + [0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 2, 0] // Shape 63 + ] + ]; - // Partition, Shape, Fixup - public static readonly byte[][][] FixUp = - { - new[] - { - // No fix-ups for 1st subset for BC6H or BC7 - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, - new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 } - }, + // Partition, Shape, Fixup + public static readonly byte[][][] FixUp = + [ + [ + // No fix-ups for 1st subset for BC6H or BC7 + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], + [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0] + ], - new[] - { - // BC6H/BC7 Partition Set Fixups for 2 Subsets - new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, - new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, - new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, - new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, - new byte[] { 0, 15, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 2, 0 }, - new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 15, 0 }, - new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, - new byte[] { 0, 8, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, + [ + // BC6H/BC7 Partition Set Fixups for 2 Subsets + [0, 15, 0], [0, 15, 0], [0, 15, 0], [0, 15, 0], + [0, 15, 0], [0, 15, 0], [0, 15, 0], [0, 15, 0], + [0, 15, 0], [0, 15, 0], [0, 15, 0], [0, 15, 0], + [0, 15, 0], [0, 15, 0], [0, 15, 0], [0, 15, 0], + [0, 15, 0], [0, 2, 0], [0, 8, 0], [0, 2, 0], + [0, 2, 0], [0, 8, 0], [0, 8, 0], [0, 15, 0], + [0, 2, 0], [0, 8, 0], [0, 2, 0], [0, 2, 0], + [0, 8, 0], [0, 8, 0], [0, 2, 0], [0, 2, 0], - // BC7 Partition Set Fixups for 2 Subsets (second-half) - new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 6, 0 }, new byte[] { 0, 8, 0 }, - new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, - new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, - new byte[] { 0, 2, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 6, 0 }, - new byte[] { 0, 6, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 6, 0 }, new byte[] { 0, 8, 0 }, - new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, - new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, - new byte[] { 0, 15, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 15, 0 } - }, + // BC7 Partition Set Fixups for 2 Subsets (second-half) + [0, 15, 0], [0, 15, 0], [0, 6, 0], [0, 8, 0], + [0, 2, 0], [0, 8, 0], [0, 15, 0], [0, 15, 0], + [0, 2, 0], [0, 8, 0], [0, 2, 0], [0, 2, 0], + [0, 2, 0], [0, 15, 0], [0, 15, 0], [0, 6, 0], + [0, 6, 0], [0, 2, 0], [0, 6, 0], [0, 8, 0], + [0, 15, 0], [0, 15, 0], [0, 2, 0], [0, 2, 0], + [0, 15, 0], [0, 15, 0], [0, 15, 0], [0, 15, 0], + [0, 15, 0], [0, 2, 0], [0, 2, 0], [0, 15, 0] + ], - new[] - { - // BC7 Partition Set Fixups for 3 Subsets - new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, new byte[] { 0, 15, 8 }, new byte[] { 0, 15, 3 }, - new byte[] { 0, 8, 15 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 15, 3 }, new byte[] { 0, 15, 8 }, - new byte[] { 0, 8, 15 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 6, 15 }, new byte[] { 0, 6, 15 }, - new byte[] { 0, 6, 15 }, new byte[] { 0, 5, 15 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, - new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 15, 3 }, - new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, new byte[] { 0, 6, 15 }, new byte[] { 0, 10, 8 }, - new byte[] { 0, 5, 3 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 8, 6 }, new byte[] { 0, 6, 10 }, - new byte[] { 0, 8, 15 }, new byte[] { 0, 5, 15 }, new byte[] { 0, 15, 10 }, new byte[] { 0, 15, 8 }, - new byte[] { 0, 8, 15 }, new byte[] { 0, 15, 3 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 5, 10 }, - new byte[] { 0, 6, 10 }, new byte[] { 0, 10, 8 }, new byte[] { 0, 8, 9 }, new byte[] { 0, 15, 10 }, - new byte[] { 0, 15, 6 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 15, 8 }, new byte[] { 0, 5, 15 }, - new byte[] { 0, 15, 3 }, new byte[] { 0, 15, 6 }, new byte[] { 0, 15, 6 }, new byte[] { 0, 15, 8 }, - new byte[] { 0, 3, 15 }, new byte[] { 0, 15, 3 }, new byte[] { 0, 5, 15 }, new byte[] { 0, 5, 15 }, - new byte[] { 0, 5, 15 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 5, 15 }, new byte[] { 0, 10, 15 }, - new byte[] { 0, 5, 15 }, new byte[] { 0, 10, 15 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 13, 15 }, - new byte[] { 0, 15, 3 }, new byte[] { 0, 12, 15 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 } - } - }; + [ + // BC7 Partition Set Fixups for 3 Subsets + [0, 3, 15], [0, 3, 8], [0, 15, 8], [0, 15, 3], + [0, 8, 15], [0, 3, 15], [0, 15, 3], [0, 15, 8], + [0, 8, 15], [0, 8, 15], [0, 6, 15], [0, 6, 15], + [0, 6, 15], [0, 5, 15], [0, 3, 15], [0, 3, 8], + [0, 3, 15], [0, 3, 8], [0, 8, 15], [0, 15, 3], + [0, 3, 15], [0, 3, 8], [0, 6, 15], [0, 10, 8], + [0, 5, 3], [0, 8, 15], [0, 8, 6], [0, 6, 10], + [0, 8, 15], [0, 5, 15], [0, 15, 10], [0, 15, 8], + [0, 8, 15], [0, 15, 3], [0, 3, 15], [0, 5, 10], + [0, 6, 10], [0, 10, 8], [0, 8, 9], [0, 15, 10], + [0, 15, 6], [0, 3, 15], [0, 15, 8], [0, 5, 15], + [0, 15, 3], [0, 15, 6], [0, 15, 6], [0, 15, 8], + [0, 3, 15], [0, 15, 3], [0, 5, 15], [0, 5, 15], + [0, 5, 15], [0, 8, 15], [0, 5, 15], [0, 10, 15], + [0, 5, 15], [0, 10, 15], [0, 8, 15], [0, 13, 15], + [0, 15, 3], [0, 12, 15], [0, 3, 15], [0, 3, 8] + ] + ]; - public static readonly int[] Weights2 = { 0, 21, 43, 64 }; - public static readonly int[] Weights3 = { 0, 9, 18, 27, 37, 46, 55, 64 }; - public static readonly int[] Weights4 = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; - } + public static readonly int[] Weights2 = [0, 21, 43, 64]; + public static readonly int[] Weights3 = [0, 9, 18, 27, 37, 46, 55, 64]; + public static readonly int[] Weights4 = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64]; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/Helpers.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/Helpers.cs index 0b3ce4f6..c765896d 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/Helpers.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/Helpers.cs @@ -1,75 +1,72 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats +internal static class Helpers { - internal static class Helpers + public static bool IsFixUpOffset(byte uPartitions, byte uShape, int uOffset) { - public static bool IsFixUpOffset(byte uPartitions, byte uShape, int uOffset) - { - Guard.MustBeLessThan(uPartitions, (byte)3, nameof(uPartitions)); - Guard.MustBeLessThan(uShape, (byte)64, nameof(uShape)); - Guard.MustBeBetweenOrEqualTo(uOffset, 0, 15, nameof(uOffset)); + Guard.MustBeLessThan(uPartitions, (byte)3, nameof(uPartitions)); + Guard.MustBeLessThan(uShape, (byte)64, nameof(uShape)); + Guard.MustBeBetweenOrEqualTo(uOffset, 0, 15, nameof(uOffset)); - for (byte p = 0; p <= uPartitions; p++) + for (byte p = 0; p <= uPartitions; p++) + { + if (uOffset == Constants.FixUp[uPartitions][uShape][p]) { - if (uOffset == Constants.FixUp[uPartitions][uShape][p]) - { - return true; - } + return true; } - - return false; } - public static void TransformInverseSigned(IntEndPntPair[] aEndPts, LdrColorA prec) - { - var wrapMask = new IntColor((1 << prec.R) - 1, (1 << prec.G) - 1, (1 << prec.B) - 1); - aEndPts[0].B += aEndPts[0].A; - aEndPts[0].B &= wrapMask; - aEndPts[1].A += aEndPts[0].A; - aEndPts[1].A &= wrapMask; - aEndPts[1].B += aEndPts[0].A; - aEndPts[1].B &= wrapMask; - aEndPts[0].B.SignExtend(prec); - aEndPts[1].A.SignExtend(prec); - aEndPts[1].B.SignExtend(prec); - } + return false; + } - public static void TransformInverseUnsigned(IntEndPntPair[] aEndPts, LdrColorA prec) - { - var wrapMask = new IntColor((1 << prec.R) - 1, (1 << prec.G) - 1, (1 << prec.B) - 1); - aEndPts[0].B += aEndPts[0].A; - aEndPts[0].B &= wrapMask; - aEndPts[1].A += aEndPts[0].A; - aEndPts[1].A &= wrapMask; - aEndPts[1].B += aEndPts[0].A; - aEndPts[1].B &= wrapMask; - } + public static void TransformInverseSigned(IntEndPntPair[] aEndPts, LdrColorA prec) + { + IntColor wrapMask = new IntColor((1 << prec.R) - 1, (1 << prec.G) - 1, (1 << prec.B) - 1); + aEndPts[0].B += aEndPts[0].A; + aEndPts[0].B &= wrapMask; + aEndPts[1].A += aEndPts[0].A; + aEndPts[1].A &= wrapMask; + aEndPts[1].B += aEndPts[0].A; + aEndPts[1].B &= wrapMask; + aEndPts[0].B.SignExtend(prec); + aEndPts[1].A.SignExtend(prec); + aEndPts[1].B.SignExtend(prec); + } - public static int DivRem(int a, int b, out int result) - { - int div = a / b; - result = a - (div * b); - return div; - } + public static void TransformInverseUnsigned(IntEndPntPair[] aEndPts, LdrColorA prec) + { + IntColor wrapMask = new IntColor((1 << prec.R) - 1, (1 << prec.G) - 1, (1 << prec.B) - 1); + aEndPts[0].B += aEndPts[0].A; + aEndPts[0].B &= wrapMask; + aEndPts[1].A += aEndPts[0].A; + aEndPts[1].A &= wrapMask; + aEndPts[1].B += aEndPts[0].A; + aEndPts[1].B &= wrapMask; + } - // Fill colors where each pixel is 4 bytes (rgba) - public static void FillWithErrorColors(Span pOut, ref int index, int numPixels, byte divSize, int stride) + public static int DivRem(int a, int b, out int result) + { + int div = a / b; + result = a - (div * b); + return div; + } + + // Fill colors where each pixel is 4 bytes (rgba) + public static void FillWithErrorColors(Span pOut, ref int index, int numPixels, byte divSize, int stride) + { + for (int i = 0; i < numPixels; ++i) { - for (int i = 0; i < numPixels; ++i) + pOut[index++] = 0; + pOut[index++] = 0; + pOut[index++] = 0; + pOut[index++] = 255; + DivRem(i + 1, divSize, out int rem); + if (rem == 0) { - pOut[index++] = 0; - pOut[index++] = 0; - pOut[index++] = 0; - pOut[index++] = 255; - DivRem(i + 1, divSize, out int rem); - if (rem == 0) - { - index += 4 * (stride - divSize); - } + index += 4 * (stride - divSize); } } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/IntColor.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/IntColor.cs index 7bceb861..870957f9 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/IntColor.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/IntColor.cs @@ -1,94 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; + +internal class IntColor { - internal class IntColor + public IntColor() { - public IntColor() - { - } + } - public IntColor(int nr, int ng, int nb) - { - this.R = nr; - this.G = ng; - this.B = nb; - this.Pad = 0; - } + public IntColor(int nr, int ng, int nb) + { + this.R = nr; + this.G = ng; + this.B = nb; + this.Pad = 0; + } - public int R { get; set; } + public int R { get; set; } - public int G { get; set; } + public int G { get; set; } - public int B { get; set; } + public int B { get; set; } - public int Pad { get; } + public int Pad { get; } - public static IntColor operator +(IntColor a, IntColor c) - { - a.R += c.R; - a.G += c.G; - a.B += c.B; - return a; - } + public static IntColor operator +(IntColor a, IntColor c) + { + a.R += c.R; + a.G += c.G; + a.B += c.B; + return a; + } - public static IntColor operator &(IntColor a, IntColor c) - { - a.R &= c.R; - a.G &= c.G; - a.B &= c.B; - return a; - } + public static IntColor operator &(IntColor a, IntColor c) + { + a.R &= c.R; + a.G &= c.G; + a.B &= c.B; + return a; + } - public IntColor SignExtend(LdrColorA prec) - { - this.R = SIGN_EXTEND(this.R, prec.R); - this.G = SIGN_EXTEND(this.G, prec.G); - this.B = SIGN_EXTEND(this.B, prec.B); - return this; - } + public IntColor SignExtend(LdrColorA prec) + { + this.R = SIGN_EXTEND(this.R, prec.R); + this.G = SIGN_EXTEND(this.G, prec.G); + this.B = SIGN_EXTEND(this.B, prec.B); + return this; + } - private static int SIGN_EXTEND(int x, int nb) - { - return ((x & 1 << (nb - 1)) != 0 ? ~0 ^ (1 << nb) - 1 : 0) | x; - } + private static int SIGN_EXTEND(int x, int nb) => ((x & (1 << (nb - 1))) != 0 ? ~0 ^ ((1 << nb) - 1) : 0) | x; - public void ToF16Signed(ushort[] aF16) - { - aF16[0] = Int2F16Signed(this.R); - aF16[1] = Int2F16Signed(this.G); - aF16[2] = Int2F16Signed(this.B); - } + public void ToF16Signed(ushort[] aF16) + { + aF16[0] = Int2F16Signed(this.R); + aF16[1] = Int2F16Signed(this.G); + aF16[2] = Int2F16Signed(this.B); + } - public void ToF16Unsigned(ushort[] aF16) - { - aF16[0] = Int2F16Unsigned(this.R); - aF16[1] = Int2F16Unsigned(this.G); - aF16[2] = Int2F16Unsigned(this.B); - } + public void ToF16Unsigned(ushort[] aF16) + { + aF16[0] = Int2F16Unsigned(this.R); + aF16[1] = Int2F16Unsigned(this.G); + aF16[2] = Int2F16Unsigned(this.B); + } - private static ushort Int2F16Unsigned(int input) - { - Guard.MustBeBetweenOrEqualTo(input, 0, Constants.F16MAX, nameof(input)); + private static ushort Int2F16Unsigned(int input) + { + Guard.MustBeBetweenOrEqualTo(input, 0, Constants.F16MAX, nameof(input)); - ushort res = (ushort)input; + ushort res = (ushort)input; - return res; - } + return res; + } - private static ushort Int2F16Signed(int input) + private static ushort Int2F16Signed(int input) + { + int s = 0; + if (input < 0) { - int s = 0; - if (input < 0) - { - s = Constants.F16S_MASK; - input = -input; - } + s = Constants.F16S_MASK; + input = -input; + } - ushort res = (ushort)(s | input); + ushort res = (ushort)(s | input); - return res; - } + return res; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/IntEndPntPair.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/IntEndPntPair.cs index 50dd10d2..2dd3ed97 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/IntEndPntPair.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/IntEndPntPair.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; + +internal struct IntEndPntPair { - internal struct IntEndPntPair - { - public IntColor A; - public IntColor B; + public IntColor A; + public IntColor B; - public IntEndPntPair(IntColor a, IntColor b) - { - this.A = a; - this.B = b; - } + public IntEndPntPair(IntColor a, IntColor b) + { + this.A = a; + this.B = b; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/LdrColorA.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/LdrColorA.cs index f7e1ce3f..84823486 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/LdrColorA.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/PixelFormats/LdrColorA.cs @@ -1,187 +1,179 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding.PixelFormats; + +internal class LdrColorA { - internal class LdrColorA + /// + /// Initializes a new instance of the class. + /// + public LdrColorA() { - /// - /// Initializes a new instance of the class. - /// - public LdrColorA() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The red color value. - /// The green color value. - /// The blue color value. - /// The alpha value. - public LdrColorA(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } + /// + /// Initializes a new instance of the class. + /// + /// The red color value. + /// The green color value. + /// The blue color value. + /// The alpha value. + public LdrColorA(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - /// - /// Gets or sets the red component. - /// - public byte R { get; set; } + /// + /// Gets or sets the red component. + /// + public byte R { get; set; } - /// - /// Gets or sets the green component. - /// - public byte G { get; set; } + /// + /// Gets or sets the green component. + /// + public byte G { get; set; } - /// - /// Gets or sets the blue component. - /// - public byte B { get; set; } + /// + /// Gets or sets the blue component. + /// + public byte B { get; set; } - /// - /// Gets or sets the alpha value. - /// - public byte A { get; set; } + /// + /// Gets or sets the alpha value. + /// + public byte A { get; set; } - public byte this[int uElement] + public byte this[int uElement] + { + get => uElement switch { - get => uElement switch - { - 0 => this.R, - 1 => this.G, - 2 => this.B, - 3 => this.A, - _ => throw new ArgumentOutOfRangeException(nameof(uElement)), - }; - - set + 0 => this.R, + 1 => this.G, + 2 => this.B, + 3 => this.A, + _ => throw new ArgumentOutOfRangeException(nameof(uElement)), + }; + + set + { + switch (uElement) { - switch (uElement) - { - case 0: - this.R = value; - break; - case 1: - this.G = value; - break; - case 2: - this.B = value; - break; - case 3: - this.A = value; - break; - default: - throw new ArgumentOutOfRangeException(nameof(uElement)); - } + case 0: + this.R = value; + break; + case 1: + this.G = value; + break; + case 2: + this.B = value; + break; + case 3: + this.A = value; + break; + default: + throw new ArgumentOutOfRangeException(nameof(uElement)); } } + } - public void SwapRedWithAlpha() - { - byte tmp = this.A; - this.A = this.R; - this.R = tmp; - } + public void SwapRedWithAlpha() + { + (this.R, this.A) = (this.A, this.R); + } - public void SwapBlueWithAlpha() - { - byte tmp = this.A; - this.A = this.B; - this.B = tmp; - } + public void SwapBlueWithAlpha() + { + (this.B, this.A) = (this.A, this.B); + } - public void SwapGreenWithAlpha() - { - byte tmp = this.A; - this.A = this.G; - this.G = tmp; - } + public void SwapGreenWithAlpha() + { + (this.G, this.A) = (this.A, this.G); + } - public static void InterpolateRgb(LdrColorA c0, LdrColorA c1, int wc, int wcprec, LdrColorA outt) - { - DebugGuard.MustBeBetweenOrEqualTo(wcprec, 2, 4, nameof(wcprec)); + public static void InterpolateRgb(LdrColorA c0, LdrColorA c1, int wc, int wcprec, LdrColorA outt) + { + DebugGuard.MustBeBetweenOrEqualTo(wcprec, 2, 4, nameof(wcprec)); - int[] aWeights; - switch (wcprec) + int[] aWeights; + switch (wcprec) + { + case 2: { - case 2: - { - aWeights = Constants.Weights2; - Debug.Assert(wc < 4, "wc is expected to be smaller then 4"); - break; - } - - case 3: - { - aWeights = Constants.Weights3; - Debug.Assert(wc < 8, "wc is expected to be smaller then 8"); - break; - } + aWeights = Constants.Weights2; + Debug.Assert(wc < 4, "wc is expected to be smaller then 4"); + break; + } - case 4: - { - aWeights = Constants.Weights4; - Debug.Assert(wc < 16, "wc is expected to be smaller then 16"); - break; - } + case 3: + { + aWeights = Constants.Weights3; + Debug.Assert(wc < 8, "wc is expected to be smaller then 8"); + break; + } - default: - outt.R = outt.G = outt.B = 0; - return; + case 4: + { + aWeights = Constants.Weights4; + Debug.Assert(wc < 16, "wc is expected to be smaller then 16"); + break; } - outt.R = (byte)(((c0.R * (uint)(Constants.BC67_WEIGHT_MAX - aWeights[wc])) + (c1.R * (uint)aWeights[wc]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT); - outt.G = (byte)(((c0.G * (uint)(Constants.BC67_WEIGHT_MAX - aWeights[wc])) + (c1.G * (uint)aWeights[wc]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT); - outt.B = (byte)(((c0.B * (uint)(Constants.BC67_WEIGHT_MAX - aWeights[wc])) + (c1.B * (uint)aWeights[wc]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT); + default: + outt.R = outt.G = outt.B = 0; + return; } - public static void InterpolateA(LdrColorA c0, LdrColorA c1, int wa, int waprec, LdrColorA outt) - { - DebugGuard.MustBeBetweenOrEqualTo(waprec, 2, 4, nameof(waprec)); + outt.R = (byte)(((c0.R * (uint)(Constants.BC67_WEIGHT_MAX - aWeights[wc])) + (c1.R * (uint)aWeights[wc]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT); + outt.G = (byte)(((c0.G * (uint)(Constants.BC67_WEIGHT_MAX - aWeights[wc])) + (c1.G * (uint)aWeights[wc]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT); + outt.B = (byte)(((c0.B * (uint)(Constants.BC67_WEIGHT_MAX - aWeights[wc])) + (c1.B * (uint)aWeights[wc]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT); + } - int[] aWeights; - switch (waprec) - { - case 2: - { - aWeights = Constants.Weights2; - Debug.Assert(wa < 4, "wc is expected to be smaller then 4"); - break; - } + public static void InterpolateA(LdrColorA c0, LdrColorA c1, int wa, int waprec, LdrColorA outt) + { + DebugGuard.MustBeBetweenOrEqualTo(waprec, 2, 4, nameof(waprec)); - case 3: - { - aWeights = Constants.Weights3; - Debug.Assert(wa < 8, "wc is expected to be smaller then 8"); - break; - } + int[] aWeights; + switch (waprec) + { + case 2: + { + aWeights = Constants.Weights2; + Debug.Assert(wa < 4, "wc is expected to be smaller then 4"); + break; + } - case 4: - { - aWeights = Constants.Weights4; - Debug.Assert(wa < 16, "wc is expected to be smaller then 16"); - break; - } + case 3: + { + aWeights = Constants.Weights3; + Debug.Assert(wa < 8, "wc is expected to be smaller then 8"); + break; + } - default: - outt.A = 0; - return; + case 4: + { + aWeights = Constants.Weights4; + Debug.Assert(wa < 16, "wc is expected to be smaller then 16"); + break; } - outt.A = (byte)(((c0.A * (uint)(Constants.BC67_WEIGHT_MAX - aWeights[wa])) + (c1.A * (uint)aWeights[wa]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT); + default: + outt.A = 0; + return; } - public static void Interpolate(LdrColorA c0, LdrColorA c1, int wc, int wa, int wcprec, int waprec, LdrColorA outt) - { - InterpolateRgb(c0, c1, wc, wcprec, outt); - InterpolateA(c0, c1, wa, waprec, outt); - } + outt.A = (byte)(((c0.A * (uint)(Constants.BC67_WEIGHT_MAX - aWeights[wa])) + (c1.A * (uint)aWeights[wa]) + Constants.BC67_WEIGHT_ROUND) >> Constants.BC67_WEIGHT_SHIFT); + } + + public static void Interpolate(LdrColorA c0, LdrColorA c1, int wc, int wa, int wcprec, int waprec, LdrColorA outt) + { + InterpolateRgb(c0, c1, wc, wcprec, outt); + InterpolateA(c0, c1, wa, waprec, outt); } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/R16Float.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/R16Float.cs index 6701581e..8a8be7bd 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/R16Float.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/R16Float.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for data with a single channel red channel as 16 bit float. - /// - internal struct R16Float : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture for data with a single channel red channel as 16 bit float. +/// +internal struct R16Float : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg16.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg16.cs index a9747f05..d1ae979c 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg16.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg16.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixels which have only the red and green channel and use 8 bit for each. - /// - internal struct Rg16 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture format for pixels which have only the red and green channel and use 8 bit for each. +/// +internal struct Rg16 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg32.cs index f3df0a7a..f734f40c 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg32.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixels which have only the red and green channel and use 16 bit for each. - /// - internal struct Rg32 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture format for pixels which have only the red and green channel and use 16 bit for each. +/// +internal struct Rg32 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg32Float.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg32Float.cs index b8253623..b2044efc 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg32Float.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg32Float.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixels which have only the red and green channel and use 16 bit for each as float. - /// - internal struct Rg32Float : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture format for pixels which have only the red and green channel and use 16 bit for each as float. +/// +internal struct Rg32Float : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg64.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg64.cs index ef5cacb5..58d83354 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg64.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg64.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixels which have only the red and green channel and use 32 bit for each. - /// - internal struct Rg64 : IBlock - { - /// - public int BitsPerPixel => 64; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 8; +/// +/// Texture format for pixels which have only the red and green channel and use 32 bit for each. +/// +internal struct Rg64 : IBlock +{ + /// + public readonly int BitsPerPixel => 64; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 8; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg64Float.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg64Float.cs index 216a1b98..8a89ba4d 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rg64Float.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rg64Float.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixels which have only the red and green channel and use 32 bit for each as float. - /// - internal struct Rg64Float : IBlock - { - /// - public int BitsPerPixel => 64; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 8; +/// +/// Texture format for pixels which have only the red and green channel and use 32 bit for each as float. +/// +internal struct Rg64Float : IBlock +{ + /// + public readonly int BitsPerPixel => 64; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 8; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb111110Float.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb111110Float.cs index 2eed5248..4e873ec3 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb111110Float.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb111110Float.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for pixel data with 11 bits for red and green and 10 bits for the blue channel as float. - /// - internal struct Rgb111110Float : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture for pixel data with 11 bits for red and green and 10 bits for the blue channel as float. +/// +internal struct Rgb111110Float : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb24.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb24.cs index 905cfd88..58dfb50f 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb24.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb24.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format with pixels which use 8 bits for each channel without an alpha channel (pixel format is Rgb24). - /// - internal struct Rgb24 : IBlock - { - /// - public int BitsPerPixel => 24; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 3; +/// +/// Texture format with pixels which use 8 bits for each channel without an alpha channel (pixel format is Rgb24). +/// +internal struct Rgb24 : IBlock +{ + /// + public readonly int BitsPerPixel => 24; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte CompressedBytesPerBlock => 3; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 3; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb32.cs index 76f4a805..2651a919 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb32.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format with pixels which have one byte for R, G, B channels and an unused alpha channel. - /// - internal struct Rgb32 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture format with pixels which have one byte for R, G, B channels and an unused alpha channel. +/// +internal struct Rgb32 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb48.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb48.cs index 70de61eb..88c340bd 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb48.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb48.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format with pixels which use 16 bits for each channel without an alpha channel (pixel format is Rgb48). - /// - internal struct Rgb48 : IBlock - { - /// - public int BitsPerPixel => 24; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 3; +/// +/// Texture format with pixels which use 16 bits for each channel without an alpha channel (pixel format is Rgb48). +/// +internal struct Rgb48 : IBlock +{ + /// + public readonly int BitsPerPixel => 24; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte CompressedBytesPerBlock => 3; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 3; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb565.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb565.cs index 7d245e86..51165fcc 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb565.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb565.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixel data with 5 bit for red and blue and 6 bits for the green color channel. - /// - internal struct Rgb565 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture format for pixel data with 5 bit for red and blue and 6 bits for the green color channel. +/// +internal struct Rgb565 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb96.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb96.cs index 5e3557b5..2c86cafe 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb96.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb96.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture which has 32 bits per color channel without a alpha channel (pixel format R32G32B32). - /// - internal struct Rgb96 : IBlock - { - /// - public int BitsPerPixel => 96; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 12; +/// +/// Texture which has 32 bits per color channel without a alpha channel (pixel format R32G32B32). +/// +internal struct Rgb96 : IBlock +{ + /// + public readonly int BitsPerPixel => 96; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 12; - /// - public byte CompressedBytesPerBlock => 12; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 12; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb96Float.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb96Float.cs index 0260a2f3..7c170ef6 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb96Float.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgb96Float.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixels which use 32 bit float values for the RGB channels. - /// - internal struct Rgb96Float : IBlock - { - /// - public int BitsPerPixel => 96; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 12; +/// +/// Texture format for pixels which use 32 bit float values for the RGB channels. +/// +internal struct Rgb96Float : IBlock +{ + /// + public readonly int BitsPerPixel => 96; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 12; - /// - public byte CompressedBytesPerBlock => 12; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 12; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba1010102.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba1010102.cs index c1fab15c..fc67373c 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba1010102.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba1010102.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for textures with 10 the RGB channel and 2 Bits for the alpha channel. - /// - internal struct Rgba1010102 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture format for textures with 10 the RGB channel and 2 Bits for the alpha channel. +/// +internal struct Rgba1010102 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba128.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba128.cs index 06bfe861..b9ddfd94 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba128.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba128.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture which has 32 bits per color channel including the alpha channel. - /// - internal struct Rgba128 : IBlock - { - /// - public int BitsPerPixel => 128; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 16; +/// +/// Texture which has 32 bits per color channel including the alpha channel. +/// +internal struct Rgba128 : IBlock +{ + /// + public readonly int BitsPerPixel => 128; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 16; - /// - public byte CompressedBytesPerBlock => 16; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba128Float.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba128Float.cs index 2e2329d1..121eceef 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba128Float.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba128Float.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixels which use 32 bit float values for the RGBA channels. - /// - internal struct Rgba128Float : IBlock - { - /// - public int BitsPerPixel => 128; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 16; +/// +/// Texture format for pixels which use 32 bit float values for the RGBA channels. +/// +internal struct Rgba128Float : IBlock +{ + /// + public readonly int BitsPerPixel => 128; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 16; - /// - public byte CompressedBytesPerBlock => 16; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 16; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba32.cs index af7859ec..e4dcd993 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba32.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture which has 8 bits per color channel (uses pixel format Rgba32). - /// - internal struct Rgba32 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture which has 8 bits per color channel (uses pixel format Rgba32). +/// +internal struct Rgba32 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba4444.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba4444.cs index 25a1c4d1..5d16362b 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba4444.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba4444.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixel data with 4 bit for each color channel (including alpha). - /// - internal struct Rgba4444 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture format for pixel data with 4 bit for each color channel (including alpha). +/// +internal struct Rgba4444 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba5551.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba5551.cs index 8e560232..6a5d7795 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba5551.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba5551.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format for pixel data with 5 bit for each color channel and 1 bit for the alpha channel. - /// - internal struct Rgba5551 : IBlock - { - /// - public int BitsPerPixel => 16; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 2; +/// +/// Texture format for pixel data with 5 bit for each color channel and 1 bit for the alpha channel. +/// +internal struct Rgba5551 : IBlock +{ + /// + public readonly int BitsPerPixel => 16; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 2; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba64.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba64.cs index 10228960..41a87fd2 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba64.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba64.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture format with 16 bits per color channel. - /// - internal struct Rgba64 : IBlock - { - /// - public int BitsPerPixel => 64; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 8; +/// +/// Texture format with 16 bits per color channel. +/// +internal struct Rgba64 : IBlock +{ + /// + public readonly int BitsPerPixel => 64; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 8; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba64Float.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba64Float.cs index ec517a81..a862854b 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba64Float.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgba64Float.cs @@ -1,38 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Textures.PixelFormats; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +/// +/// Texture format for pixels with 16 bit floating point values for each channel (including the alpha channel). +/// +internal struct Rgba64Float : IBlock { - /// - /// Texture format for pixels with 16 bit floating point values for each channel (including the alpha channel). - /// - internal struct Rgba64Float : IBlock - { - /// - public int BitsPerPixel => 64; - - /// - public byte PixelDepthBytes => 8; + /// + public readonly int BitsPerPixel => 64; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 8; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgbg32.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgbg32.cs index 71612c4e..412ad310 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Rgbg32.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Rgbg32.cs @@ -1,66 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture for pixel data with the R8G8B8G8 format. +/// +internal struct Rgbg32 : IBlock { - /// - /// Texture for pixel data with the R8G8B8G8 format. - /// - internal struct Rgbg32 : IBlock - { - /// - public int BitsPerPixel => 32; + /// + public readonly int BitsPerPixel => 32; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 1; + /// + public readonly byte DivSize => 1; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public bool Compressed => false; + /// + public readonly bool Compressed => false; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - /// - public byte[] Decompress(byte[] blockData, int width, int height) + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + int totalPixels = width * height; + byte[] decompressed = new byte[totalPixels * 3]; + Span rgb24Span = MemoryMarshal.Cast(decompressed); + + ImageSharp.PixelFormats.Rgb24 pixel = default(ImageSharp.PixelFormats.Rgb24); + int pixelIdx = 0; + for (int i = 0; i < blockData.Length; i += 4) { - int totalPixels = width * height; - byte[] decompressed = new byte[totalPixels * 3]; - Span rgb24Span = MemoryMarshal.Cast(decompressed); + byte r = blockData[i]; + byte g0 = blockData[i + 1]; + byte b = blockData[i + 2]; + byte g1 = blockData[i + 3]; - var pixel = default(ImageSharp.PixelFormats.Rgb24); - int pixelIdx = 0; - for (int i = 0; i < blockData.Length; i += 4) + pixel.FromRgb24(new ImageSharp.PixelFormats.Rgb24(r, g0, b)); + rgb24Span[pixelIdx++] = pixel; + if (pixelIdx >= totalPixels) { - byte r = blockData[i]; - byte g0 = blockData[i + 1]; - byte b = blockData[i + 2]; - byte g1 = blockData[i + 3]; - - pixel.FromRgb24(new ImageSharp.PixelFormats.Rgb24(r, g0, b)); - rgb24Span[pixelIdx++] = pixel; - if (pixelIdx >= totalPixels) - { - break; - } - - pixel.FromRgb24(new ImageSharp.PixelFormats.Rgb24(r, g1, b)); - rgb24Span[pixelIdx++] = pixel; + break; } - return decompressed; + pixel.FromRgb24(new ImageSharp.PixelFormats.Rgb24(r, g1, b)); + rgb24Span[pixelIdx++] = pixel; } + + return decompressed; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Y210.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Y210.cs index e924fc33..07835a4c 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Y210.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Y210.cs @@ -1,76 +1,74 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Textures.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture for 10-bit per channel packed YUV 4:2:2 video resource format. +/// +internal struct Y210 : IBlock { - /// - /// Texture for 10-bit per channel packed YUV 4:2:2 video resource format. - /// - internal struct Y210 : IBlock - { - /// - public int BitsPerPixel => 64; + /// + public readonly int BitsPerPixel => 64; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 1; + /// + public readonly byte DivSize => 1; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public bool Compressed => false; + /// + public readonly bool Compressed => false; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } - - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int totalPixels = width * height; - byte[] decompressed = new byte[totalPixels * 3]; - Span rgb24Span = MemoryMarshal.Cast(decompressed); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - // Same as Y216 with least significant 6 bits set to zero. - var pixel = default(ImageSharp.PixelFormats.Rgb24); - int pixelIdx = 0; - for (int i = 0; i < blockData.Length; i += 8) - { - uint y0 = BitConverter.ToUInt16(blockData, i); - uint u = BitConverter.ToUInt16(blockData, i + 2); - uint y1 = BitConverter.ToUInt16(blockData, i + 4); - uint v = BitConverter.ToUInt16(blockData, i + 6); + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + int totalPixels = width * height; + byte[] decompressed = new byte[totalPixels * 3]; + Span rgb24Span = MemoryMarshal.Cast(decompressed); - y0 = (y0 >> 6) - 64; - u = (u >> 6) - 512; - y1 = (y1 >> 6) - 64; - v = (v >> 6) - 512; + // Same as Y216 with least significant 6 bits set to zero. + ImageSharp.PixelFormats.Rgb24 pixel = default(ImageSharp.PixelFormats.Rgb24); + int pixelIdx = 0; + for (int i = 0; i < blockData.Length; i += 8) + { + uint y0 = BitConverter.ToUInt16(blockData, i); + uint u = BitConverter.ToUInt16(blockData, i + 2); + uint y1 = BitConverter.ToUInt16(blockData, i + 4); + uint v = BitConverter.ToUInt16(blockData, i + 6); - Vector4 rgbVec = ColorSpaceConversion.YuvToRgba10Bit(y0, u, v); - pixel.FromVector4(rgbVec); - rgb24Span[pixelIdx++] = pixel; - if (pixelIdx >= totalPixels) - { - break; - } + y0 = (y0 >> 6) - 64; + u = (u >> 6) - 512; + y1 = (y1 >> 6) - 64; + v = (v >> 6) - 512; - rgbVec = ColorSpaceConversion.YuvToRgba10Bit(y1, u, v); - pixel.FromVector4(rgbVec); - rgb24Span[pixelIdx++] = pixel; + Vector4 rgbVec = ColorSpaceConversion.YuvToRgba10Bit(y0, u, v); + pixel.FromVector4(rgbVec); + rgb24Span[pixelIdx++] = pixel; + if (pixelIdx >= totalPixels) + { + break; } - return decompressed; + rgbVec = ColorSpaceConversion.YuvToRgba10Bit(y1, u, v); + pixel.FromVector4(rgbVec); + rgb24Span[pixelIdx++] = pixel; } + + return decompressed; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Y216.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Y216.cs index 88e09459..8e3ac72c 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Y216.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Y216.cs @@ -1,75 +1,73 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Textures.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// Texture for 16-bit per channel packed YUV 4:2:2 video resource format. +/// +internal struct Y216 : IBlock { - /// - /// Texture for 16-bit per channel packed YUV 4:2:2 video resource format. - /// - internal struct Y216 : IBlock - { - /// - public int BitsPerPixel => 64; + /// + public readonly int BitsPerPixel => 64; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 1; + /// + public readonly byte DivSize => 1; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public bool Compressed => false; + /// + public readonly bool Compressed => false; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } - - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int totalPixels = width * height; - byte[] decompressed = new byte[totalPixels * 3]; - Span rgb24Span = MemoryMarshal.Cast(decompressed); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - var pixel = default(ImageSharp.PixelFormats.Rgb24); - int pixelIdx = 0; - for (int i = 0; i < blockData.Length; i += 8) - { - uint y0 = BitConverter.ToUInt16(blockData, i); - uint u = BitConverter.ToUInt16(blockData, i + 2); - uint y1 = BitConverter.ToUInt16(blockData, i + 4); - uint v = BitConverter.ToUInt16(blockData, i + 6); + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + int totalPixels = width * height; + byte[] decompressed = new byte[totalPixels * 3]; + Span rgb24Span = MemoryMarshal.Cast(decompressed); - y0 -= 4096; - u -= 32768; - y1 -= 4096; - v -= 32768; + ImageSharp.PixelFormats.Rgb24 pixel = default(ImageSharp.PixelFormats.Rgb24); + int pixelIdx = 0; + for (int i = 0; i < blockData.Length; i += 8) + { + uint y0 = BitConverter.ToUInt16(blockData, i); + uint u = BitConverter.ToUInt16(blockData, i + 2); + uint y1 = BitConverter.ToUInt16(blockData, i + 4); + uint v = BitConverter.ToUInt16(blockData, i + 6); - Vector4 rgbVec = ColorSpaceConversion.YuvToRgba16Bit(y0, u, v); - pixel.FromVector4(rgbVec); - rgb24Span[pixelIdx++] = pixel; - if (pixelIdx >= totalPixels) - { - break; - } + y0 -= 4096; + u -= 32768; + y1 -= 4096; + v -= 32768; - rgbVec = ColorSpaceConversion.YuvToRgba16Bit(y1, u, v); - pixel.FromVector4(rgbVec); - rgb24Span[pixelIdx++] = pixel; + Vector4 rgbVec = ColorSpaceConversion.YuvToRgba16Bit(y0, u, v); + pixel.FromVector4(rgbVec); + rgb24Span[pixelIdx++] = pixel; + if (pixelIdx >= totalPixels) + { + break; } - return decompressed; + rgbVec = ColorSpaceConversion.YuvToRgba16Bit(y1, u, v); + pixel.FromVector4(rgbVec); + rgb24Span[pixelIdx++] = pixel; } + + return decompressed; } } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Y410.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Y410.cs index 6679bb72..4c31d399 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Y410.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Y410.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for 10-bit per channel packed YUV 4:4:4 video resource format. - /// - internal struct Y410 : IBlock - { - /// - public int BitsPerPixel => 32; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 4; +/// +/// Texture for 10-bit per channel packed YUV 4:4:4 video resource format. +/// +internal struct Y410 : IBlock +{ + /// + public readonly int BitsPerPixel => 32; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 4; - /// - public byte CompressedBytesPerBlock => 4; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 4; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Y416.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Y416.cs index aee555b6..ff684ad0 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Y416.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Y416.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding -{ - /// - /// Texture for 16-bit per channel packed YUV 4:4:4 video resource format. - /// - internal struct Y416 : IBlock - { - /// - public int BitsPerPixel => 64; +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; - /// - public byte PixelDepthBytes => 8; +/// +/// Texture for 16-bit per channel packed YUV 4:4:4 video resource format. +/// +internal struct Y416 : IBlock +{ + /// + public readonly int BitsPerPixel => 64; - /// - public byte DivSize => 1; + /// + public readonly byte PixelDepthBytes => 8; - /// - public byte CompressedBytesPerBlock => 8; + /// + public readonly byte DivSize => 1; - /// - public bool Compressed => false; + /// + public readonly byte CompressedBytesPerBlock => 8; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } + /// + public readonly bool Compressed => false; - /// - public byte[] Decompress(byte[] blockData, int width, int height) => blockData; + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); } + + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) => blockData; } diff --git a/src/ImageSharp.Textures/TextureFormats/Decoding/Yuy2.cs b/src/ImageSharp.Textures/TextureFormats/Decoding/Yuy2.cs index 275e52e1..0412eaa3 100644 --- a/src/ImageSharp.Textures/TextureFormats/Decoding/Yuy2.cs +++ b/src/ImageSharp.Textures/TextureFormats/Decoding/Yuy2.cs @@ -1,75 +1,73 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Textures.PixelFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding +namespace SixLabors.ImageSharp.Textures.TextureFormats.Decoding; + +/// +/// A texture based on the YUV 4:2:2 video resource format. The pixel format will be decoded into Rgb24. +/// +internal struct Yuy2 : IBlock { - /// - /// A texture based on the YUV 4:2:2 video resource format. The pixel format will be decoded into Rgb24. - /// - internal struct Yuy2 : IBlock - { - /// - public int BitsPerPixel => 32; + /// + public readonly int BitsPerPixel => 32; - /// - public byte PixelDepthBytes => 3; + /// + public readonly byte PixelDepthBytes => 3; - /// - public byte DivSize => 1; + /// + public readonly byte DivSize => 1; - /// - public byte CompressedBytesPerBlock => 2; + /// + public readonly byte CompressedBytesPerBlock => 2; - /// - public bool Compressed => false; + /// + public readonly bool Compressed => false; - /// - public Image GetImage(byte[] blockData, int width, int height) - { - byte[] decompressedData = this.Decompress(blockData, width, height); - return Image.LoadPixelData(decompressedData, width, height); - } - - /// - public byte[] Decompress(byte[] blockData, int width, int height) - { - int totalPixels = width * height; - byte[] decompressed = new byte[totalPixels * 3]; - Span rgb24Span = MemoryMarshal.Cast(decompressed); + /// + public Image GetImage(byte[] blockData, int width, int height) + { + byte[] decompressedData = this.Decompress(blockData, width, height); + return Image.LoadPixelData(decompressedData, width, height); + } - var pixel = default(ImageSharp.PixelFormats.Rgb24); - int pixelIdx = 0; - for (int i = 0; i < blockData.Length; i += 4) - { - int y0 = blockData[i]; - int u = blockData[i + 1]; - int y1 = blockData[i + 2]; - int v = blockData[i + 3]; + /// + public readonly byte[] Decompress(byte[] blockData, int width, int height) + { + int totalPixels = width * height; + byte[] decompressed = new byte[totalPixels * 3]; + Span rgb24Span = MemoryMarshal.Cast(decompressed); - y0 -= 16; - u -= 128; - y1 -= 16; - v -= 128; + ImageSharp.PixelFormats.Rgb24 pixel = default(ImageSharp.PixelFormats.Rgb24); + int pixelIdx = 0; + for (int i = 0; i < blockData.Length; i += 4) + { + int y0 = blockData[i]; + int u = blockData[i + 1]; + int y1 = blockData[i + 2]; + int v = blockData[i + 3]; - Vector4 rgbVec = ColorSpaceConversion.YuvToRgba8Bit(y0, u, v); - pixel.FromVector4(rgbVec); - rgb24Span[pixelIdx++] = pixel; - if (pixelIdx >= totalPixels) - { - break; - } + y0 -= 16; + u -= 128; + y1 -= 16; + v -= 128; - rgbVec = ColorSpaceConversion.YuvToRgba8Bit(y1, u, v); - pixel.FromVector4(rgbVec); - rgb24Span[pixelIdx++] = pixel; + Vector4 rgbVec = ColorSpaceConversion.YuvToRgba8Bit(y0, u, v); + pixel.FromVector4(rgbVec); + rgb24Span[pixelIdx++] = pixel; + if (pixelIdx >= totalPixels) + { + break; } - return decompressed; + rgbVec = ColorSpaceConversion.YuvToRgba8Bit(y1, u, v); + pixel.FromVector4(rgbVec); + rgb24Span[pixelIdx++] = pixel; } + + return decompressed; } } diff --git a/src/ImageSharp.Textures/TextureFormats/FlatTexture.cs b/src/ImageSharp.Textures/TextureFormats/FlatTexture.cs index b1f0183f..0395512b 100644 --- a/src/ImageSharp.Textures/TextureFormats/FlatTexture.cs +++ b/src/ImageSharp.Textures/TextureFormats/FlatTexture.cs @@ -1,50 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.TextureFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats +/// +/// A flat texture. +/// +/// +public class FlatTexture : Texture { + private bool isDisposed; + /// - /// A flat texture. + /// Initializes a new instance of the class. /// - /// - public class FlatTexture : Texture - { - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - public FlatTexture() => this.MipMaps = new List(); + public FlatTexture() => this.MipMaps = []; - /// - /// Gets the list of mip maps of the texture. - /// - public List MipMaps { get; } + /// + /// Gets the list of mip maps of the texture. + /// + public List MipMaps { get; } - /// - protected override void Dispose(bool disposing) + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) { - if (this.isDisposed) - { - return; - } + return; + } - if (disposing) + if (disposing) + { + foreach (MipMap mipMap in this.MipMaps) { - foreach (MipMap mipMap in this.MipMaps) - { - // mipMap.Dispose(); - } + // mipMap.Dispose(); } - - this.isDisposed = true; } - /// - internal override void EnsureNotDisposed() - => ObjectDisposedException.ThrowIf(this.isDisposed, "Trying to execute an operation on a disposed image."); + this.isDisposed = true; } + + /// + internal override void EnsureNotDisposed() + => ObjectDisposedException.ThrowIf(this.isDisposed, "Trying to execute an operation on a disposed image."); } diff --git a/src/ImageSharp.Textures/TextureFormats/VolumeTexture.cs b/src/ImageSharp.Textures/TextureFormats/VolumeTexture.cs index 2a514e93..f90dad19 100644 --- a/src/ImageSharp.Textures/TextureFormats/VolumeTexture.cs +++ b/src/ImageSharp.Textures/TextureFormats/VolumeTexture.cs @@ -1,50 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.TextureFormats; -namespace SixLabors.ImageSharp.Textures.TextureFormats +/// +/// Represents a volume texture. +/// +/// +public class VolumeTexture : Texture { + private bool isDisposed; + /// - /// Represents a volume texture. + /// Initializes a new instance of the class. /// - /// - public class VolumeTexture : Texture - { - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - public VolumeTexture() => this.Slices = new List(); + public VolumeTexture() => this.Slices = []; - /// - /// Gets a list of flat textures from which the volume texture is composed of. - /// - public List Slices { get; } + /// + /// Gets a list of flat textures from which the volume texture is composed of. + /// + public List Slices { get; } - /// - protected override void Dispose(bool disposing) + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) { - if (this.isDisposed) - { - return; - } + return; + } - if (disposing) + if (disposing) + { + foreach (FlatTexture slice in this.Slices) { - foreach (FlatTexture slice in this.Slices) - { - slice.Dispose(); - } + slice.Dispose(); } - - this.isDisposed = true; } - /// - internal override void EnsureNotDisposed() - => ObjectDisposedException.ThrowIf(this.isDisposed, "Trying to execute an operation on a disposed image."); + this.isDisposed = true; } + + /// + internal override void EnsureNotDisposed() + => ObjectDisposedException.ThrowIf(this.isDisposed, "Trying to execute an operation on a disposed image."); } diff --git a/src/ImageSharp.Textures/TextureInfo.cs b/src/ImageSharp.Textures/TextureInfo.cs index 53f8e83b..cb944b1d 100644 --- a/src/ImageSharp.Textures/TextureInfo.cs +++ b/src/ImageSharp.Textures/TextureInfo.cs @@ -3,33 +3,32 @@ using SixLabors.ImageSharp.Textures.Formats; -namespace SixLabors.ImageSharp.Textures +namespace SixLabors.ImageSharp.Textures; + +/// +/// Contains information about the image including dimensions, pixel type information and additional metadata +/// +internal sealed class TextureInfo : ITextureInfo { /// - /// Contains information about the image including dimensions, pixel type information and additional metadata + /// Initializes a new instance of the class. /// - internal sealed class TextureInfo : ITextureInfo + /// The image pixel type information. + /// The width of the image in pixels. + /// The height of the image in pixels. + public TextureInfo(TextureTypeInfo pixelType, int width, int height) { - /// - /// Initializes a new instance of the class. - /// - /// The image pixel type information. - /// The width of the image in pixels. - /// The height of the image in pixels. - public TextureInfo(TextureTypeInfo pixelType, int width, int height) - { - this.PixelType = pixelType; - this.Width = width; - this.Height = height; - } + this.PixelType = pixelType; + this.Width = width; + this.Height = height; + } - /// - public TextureTypeInfo PixelType { get; } + /// + public TextureTypeInfo PixelType { get; } - /// - public int Width { get; } + /// + public int Width { get; } - /// - public int Height { get; } - } + /// + public int Height { get; } } diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs index ccbbb97f..bdb36ee9 100644 --- a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderHdrTests.cs @@ -41,15 +41,15 @@ public class ReferenceDecoderHdrTests [InlineData("ldr-tile")] public void DecompressHdr_WithHdrImage_ShouldMatch(string basename) { - var filePath = Path.Combine("TestData", "HDR", basename + ".astc"); + string filePath = Path.Combine("TestData", "HDR", basename + ".astc"); - var bytes = File.ReadAllBytes(filePath); - var astcFile = AstcFile.FromMemory(bytes); - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); + byte[] bytes = File.ReadAllBytes(filePath); + AstcFile astcFile = AstcFile.FromMemory(bytes); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); - var expected = ReferenceDecoder.DecompressHdr( + Half[] expected = ReferenceDecoder.DecompressHdr( astcFile.Blocks, astcFile.Width, astcFile.Height, blockX, blockY); - var actual = AstcDecoder.DecompressHdrImage( + Span actual = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); CompareF16(actual, expected, astcFile.Width, astcFile.Height, basename); @@ -62,14 +62,14 @@ public void DecompressHdr_WithHdrImage_ShouldMatch(string basename) [InlineData("atlas_small_8x8")] public void DecompressHdr_WithLdrImage_ShouldMatch(string basename) { - var filePath = Path.Combine("TestData", "Input", basename + ".astc"); - var bytes = File.ReadAllBytes(filePath); - var astcFile = AstcFile.FromMemory(bytes); - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); + string filePath = Path.Combine("TestData", "Input", basename + ".astc"); + byte[] bytes = File.ReadAllBytes(filePath); + AstcFile astcFile = AstcFile.FromMemory(bytes); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); - var expected = ReferenceDecoder.DecompressHdr( + Half[] expected = ReferenceDecoder.DecompressHdr( astcFile.Blocks, astcFile.Width, astcFile.Height, blockX, blockY); - var actual = AstcDecoder.DecompressHdrImage( + Span actual = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); CompareF16(actual, expected, astcFile.Width, astcFile.Height, basename); @@ -79,25 +79,25 @@ public void DecompressHdr_WithLdrImage_ShouldMatch(string basename) [MemberData(nameof(AllFootprintTypes))] public void DecompressHdr_SolidColor_ShouldMatch(FootprintType footprintType) { - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); int width = blockX; int height = blockY; // Single block: R=G=B=2.0, A=1.0 (above LDR range) - var pixels = new Half[width * height * 4]; + Half[] pixels = new Half[width * height * 4]; for (int index = 0; index < width * height; index++) { - pixels[index * 4 + 0] = (Half)2.0f; - pixels[index * 4 + 1] = (Half)2.0f; - pixels[index * 4 + 2] = (Half)2.0f; - pixels[index * 4 + 3] = (Half)1.0f; + pixels[(index * 4) + 0] = (Half)2.0f; + pixels[(index * 4) + 1] = (Half)2.0f; + pixels[(index * 4) + 2] = (Half)2.0f; + pixels[(index * 4) + 3] = (Half)1.0f; } - var compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); - var footprint = Footprint.FromFootprintType(footprintType); + byte[] compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); + Footprint footprint = Footprint.FromFootprintType(footprintType); - var expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); - var actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); + Half[] expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); + Span actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); CompareF16(actual, expected, width, height, $"BrightSolid_{footprintType}"); } @@ -106,20 +106,20 @@ public void DecompressHdr_SolidColor_ShouldMatch(FootprintType footprintType) [MemberData(nameof(AllFootprintTypes))] public void DecompressHdr_Gradient_ShouldMatch(FootprintType footprintType) { - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); // 2×2 blocks for HDR gradient int width = blockX * 2; int height = blockY * 2; // Gradient from 0.0 to 4.0 - var pixels = new Half[width * height * 4]; + Half[] pixels = new Half[width * height * 4]; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { - int idx = (row * width + col) * 4; - float fraction = (float)(row * width + col) / (width * height - 1); + int idx = ((row * width) + col) * 4; + float fraction = (float)((row * width) + col) / ((width * height) - 1); float value = fraction * 4.0f; pixels[idx + 0] = (Half)value; pixels[idx + 1] = (Half)value; @@ -128,11 +128,11 @@ public void DecompressHdr_Gradient_ShouldMatch(FootprintType footprintType) } } - var compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); - var footprint = Footprint.FromFootprintType(footprintType); + byte[] compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); + Footprint footprint = Footprint.FromFootprintType(footprintType); - var expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); - var actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); + Half[] expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); + Span actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); CompareF16(actual, expected, width, height, $"HdrGradient_{footprintType}"); } @@ -143,19 +143,19 @@ public void DecompressHdr_Gradient_ShouldMatch(FootprintType footprintType) " encoded with LDR modes and others with HDR modes, the encoder optimizes each block independently.")] public void DecompressHdr_MixedLdrHdr_ShouldMatch(FootprintType footprintType) { - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); // 2×2 blocks int width = blockX * 2; int height = blockY * 2; int halfWidth = width / 2; - var pixels = new Half[width * height * 4]; + Half[] pixels = new Half[width * height * 4]; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { - int idx = (row * width + col) * 4; + int idx = ((row * width) + col) * 4; if (col < halfWidth) { // LDR left half: values in 0.0-1.0 @@ -168,20 +168,20 @@ public void DecompressHdr_MixedLdrHdr_ShouldMatch(FootprintType footprintType) { // HDR right half: values above 1.0 float fraction = (float)row / (height - 1); - pixels[idx + 0] = (Half)(1.0f + fraction * 3.0f); - pixels[idx + 1] = (Half)(0.5f + fraction * 2.0f); - pixels[idx + 2] = (Half)(0.2f + fraction * 1.5f); + pixels[idx + 0] = (Half)(1.0f + (fraction * 3.0f)); + pixels[idx + 1] = (Half)(0.5f + (fraction * 2.0f)); + pixels[idx + 2] = (Half)(0.2f + (fraction * 1.5f)); } pixels[idx + 3] = (Half)1.0f; } } - var compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); - var footprint = Footprint.FromFootprintType(footprintType); + byte[] compressed = ReferenceDecoder.CompressHdr(pixels, width, height, blockX, blockY); + Footprint footprint = Footprint.FromFootprintType(footprintType); - var expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); - var actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); + Half[] expected = ReferenceDecoder.DecompressHdr(compressed, width, height, blockX, blockY); + Span actual = AstcDecoder.DecompressHdrImage(compressed, width, height, footprint); CompareF16(actual, expected, width, height, $"MixedLdrHdr_{footprintType}"); } @@ -209,7 +209,10 @@ private static void CompareF16(Span actual, Half[] expected, int width, i // Both NaN == match; one NaN == mismatch if (float.IsNaN(actualValue) && float.IsNaN(expectedValue)) + { continue; + } + if (float.IsNaN(actualValue) || float.IsNaN(expectedValue)) { mismatches++; @@ -241,8 +244,8 @@ private static void CompareF16(Span actual, Half[] expected, int width, i Assert.Fail( $"[{label}] {mismatches}/{channelCount} F16 channel mismatches. " + $"Worst: pixel ({pixelX},{pixelY}) channel {channelName}, " + - $"actual={actual[worstPixel * 4 + worstChannel]:G5} vs " + - $"expected={(float)expected[worstPixel * 4 + worstChannel]:G5} " + + $"actual={actual[(worstPixel * 4) + worstChannel]:G5} vs " + + $"expected={(float)expected[(worstPixel * 4) + worstChannel]:G5} " + $"(relDiff={worstRelDiff:P2})."); } } diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs index 822034b3..3f206630 100644 --- a/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ReferenceDecoderTests.cs @@ -73,14 +73,14 @@ public class ReferenceDecoderTests [InlineData("rgb_12x12")] public void DecompressLdr_WithImage_ShouldMatch(string basename) { - var filePath = Path.Combine("TestData", "Input", basename + ".astc"); - var bytes = File.ReadAllBytes(filePath); - var astcFile = AstcFile.FromMemory(bytes); - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); + string filePath = Path.Combine("TestData", "Input", basename + ".astc"); + byte[] bytes = File.ReadAllBytes(filePath); + AstcFile astcFile = AstcFile.FromMemory(bytes); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(astcFile.Footprint.Type); - var expected = ReferenceDecoder.DecompressLdr( + byte[] expected = ReferenceDecoder.DecompressLdr( astcFile.Blocks, astcFile.Width, astcFile.Height, blockX, blockY); - var actual = AstcDecoder.DecompressImage( + Span actual = AstcDecoder.DecompressImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); CompareRgba8(actual, expected, astcFile.Width, astcFile.Height, basename); @@ -90,25 +90,25 @@ public void DecompressLdr_WithImage_ShouldMatch(string basename) [MemberData(nameof(AllFootprintTypes))] public void DecompressLdr_SolidColor_ShouldMatch(FootprintType footprintType) { - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); int width = blockX; int height = blockY; // Single solid color block - var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; + byte[] pixels = new byte[width * height * RgbaColor.BytesPerPixel]; for (int index = 0; index < width * height; index++) { - pixels[index * 4 + 0] = 128; // R - pixels[index * 4 + 1] = 64; // G - pixels[index * 4 + 2] = 200; // B - pixels[index * 4 + 3] = 255; // A + pixels[(index * 4) + 0] = 128; // R + pixels[(index * 4) + 1] = 64; // G + pixels[(index * 4) + 2] = 200; // B + pixels[(index * 4) + 3] = 255; // A } - var compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); - var footprint = Footprint.FromFootprintType(footprintType); + byte[] compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); + Footprint footprint = Footprint.FromFootprintType(footprintType); - var expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); - var actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); + byte[] expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); + Span actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); CompareRgba8(actual, expected, width, height, $"SolidColor_{footprintType}"); } @@ -117,30 +117,30 @@ public void DecompressLdr_SolidColor_ShouldMatch(FootprintType footprintType) [MemberData(nameof(AllFootprintTypes))] public void DecompressLdr_Gradient_ShouldMatch(FootprintType footprintType) { - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); // 2×2 blocks for gradient int width = blockX * 2; int height = blockY * 2; - var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; + byte[] pixels = new byte[width * height * RgbaColor.BytesPerPixel]; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { - int idx = (row * width + col) * 4; + int idx = ((row * width) + col) * 4; pixels[idx + 0] = (byte)(255 * col / (width - 1)); // R: left-to-right pixels[idx + 1] = (byte)(255 * row / (height - 1)); // G: top-to-bottom - pixels[idx + 2] = (byte)(255 - 255 * col / (width - 1)); // B: inverse of R + pixels[idx + 2] = (byte)(255 - (255 * col / (width - 1))); // B: inverse of R pixels[idx + 3] = 255; } } - var compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); - var footprint = Footprint.FromFootprintType(footprintType); + byte[] compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); + Footprint footprint = Footprint.FromFootprintType(footprintType); - var expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); - var actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); + byte[] expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); + Span actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); CompareRgba8(actual, expected, width, height, $"Gradient_{footprintType}"); } @@ -149,25 +149,27 @@ public void DecompressLdr_Gradient_ShouldMatch(FootprintType footprintType) [MemberData(nameof(AllFootprintTypes))] public void DecompressLdr_RandomNoise_ShouldMatch(FootprintType footprintType) { - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); // 2×2 blocks int width = blockX * 2; int height = blockY * 2; - var rng = new Random(42); // Fixed seed for reproducibility - var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; + Random rng = new(42); // Fixed seed for reproducibility + byte[] pixels = new byte[width * height * RgbaColor.BytesPerPixel]; rng.NextBytes(pixels); // Force alpha to 255 so compression doesn't introduce alpha-related variance for (int index = 3; index < pixels.Length; index += RgbaColor.BytesPerPixel) + { pixels[index] = byte.MaxValue; + } - var compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); - var footprint = Footprint.FromFootprintType(footprintType); + byte[] compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); + Footprint footprint = Footprint.FromFootprintType(footprintType); - var expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); - var actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); + byte[] expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); + Span actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); CompareRgba8(actual, expected, width, height, $"RandomNoise_{footprintType}"); } @@ -176,23 +178,25 @@ public void DecompressLdr_RandomNoise_ShouldMatch(FootprintType footprintType) [MemberData(nameof(AllFootprintTypes))] public void DecompressLdr_NonBlockAlignedDimensions_ShouldMatch(FootprintType footprintType) { - var (blockX, blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); + (int blockX, int blockY) = ReferenceDecoder.ToBlockDimensions(footprintType); // Non-block-aligned dimensions: use dimensions that don't evenly divide by block size - int width = blockX + blockX / 2 + 1; // e.g. for 4x4: 7, for 8x8: 13 - int height = blockY + blockY / 2 + 1; + int width = blockX + (blockX / 2) + 1; // e.g. for 4x4: 7, for 8x8: 13 + int height = blockY + (blockY / 2) + 1; - var rng = new Random(123); - var pixels = new byte[width * height * RgbaColor.BytesPerPixel]; + Random rng = new(123); + byte[] pixels = new byte[width * height * RgbaColor.BytesPerPixel]; rng.NextBytes(pixels); for (int index = 3; index < pixels.Length; index += RgbaColor.BytesPerPixel) + { pixels[index] = byte.MaxValue; + } - var compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); - var footprint = Footprint.FromFootprintType(footprintType); + byte[] compressed = ReferenceDecoder.CompressLdr(pixels, width, height, blockX, blockY); + Footprint footprint = Footprint.FromFootprintType(footprintType); - var expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); - var actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); + byte[] expected = ReferenceDecoder.DecompressLdr(compressed, width, height, blockX, blockY); + Span actual = AstcDecoder.DecompressImage(compressed, width, height, footprint); CompareRgba8(actual, expected, width, height, $"NonAligned_{footprintType}"); } @@ -209,18 +213,18 @@ public void DecompressLdr_VoidExtentBlock_ShouldMatch() // Bits [80..95] = G (UNORM16) // Bits [96..111] = B (UNORM16) // Bits [112..127]= A (UNORM16) - var block = new byte[16]; + byte[] block = new byte[16]; ulong low = 0xFFFFFFFFFFFFFDFC; - ulong high = (0xFFFFUL << 48) | ((ulong)0xC000 << 32) | (0x4000UL << 16) | 0x8000; + ulong high = (0xFFFFUL << 48) | (0xC000UL << 32) | (0x4000UL << 16) | 0x8000; BitConverter.TryWriteBytes(block.AsSpan(0, 8), low); BitConverter.TryWriteBytes(block.AsSpan(8, 8), high); const int blockX = 4; const int blockY = 4; - var footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); + Footprint footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); - var expected = ReferenceDecoder.DecompressLdr(block, blockX, blockY, blockX, blockY); - var actual = AstcDecoder.DecompressImage(block, blockX, blockY, footprint); + byte[] expected = ReferenceDecoder.DecompressLdr(block, blockX, blockY, blockX, blockY); + Span actual = AstcDecoder.DecompressImage(block, blockX, blockY, footprint); CompareRgba8(actual, expected, blockX, blockY, "VoidExtent"); } @@ -262,7 +266,7 @@ private static void CompareRgba8(Span actual, byte[] expected, int width, Assert.Fail( $"[{label}] {mismatches} channel mismatches exceed tolerance ±{Ldr8BitTolerance}. " + $"Worst: pixel ({pixelX},{pixelY}) channel {channelName}, " + - $"actual={actual[worstPixel * 4 + worstChannel]} vs expected={expected[worstPixel * 4 + worstChannel]} (diff={worstDiff})"); + $"actual={actual[(worstPixel * 4) + worstChannel]} vs expected={expected[(worstPixel * 4) + worstChannel]} (diff={worstDiff})"); } } } diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/Utils/ReferenceDecoder.cs b/tests/ImageSharp.Textures.Astc.Reference.Tests/Utils/ReferenceDecoder.cs index 8214ecc5..5240ee8b 100644 --- a/tests/ImageSharp.Textures.Astc.Reference.Tests/Utils/ReferenceDecoder.cs +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/Utils/ReferenceDecoder.cs @@ -26,23 +26,25 @@ internal static class ReferenceDecoder /// public static byte[] DecompressLdr(ReadOnlySpan blocks, int w, int h, int blockX, int blockY) { - var error = Astcenc.AstcencConfigInit( + AstcencError error = Astcenc.AstcencConfigInit( AstcencProfile.AstcencPrfLdr, - (uint)blockX, (uint)blockY, 1, + (uint)blockX, + (uint)blockY, + 1, Astcenc.AstcencPreFastest, AstcencFlags.DecompressOnly, - out var config); + out AstcencConfig config); ThrowOnError(error, "ConfigInit(LDR)"); - error = Astcenc.AstcencContextAlloc(ref config, 1, out var context); + error = Astcenc.AstcencContextAlloc(ref config, 1, out AstcencContext context); ThrowOnError(error, "ContextAlloc(LDR)"); try { int pixelCount = w * h; - var outputBytes = new byte[pixelCount * 4]; // RGBA8 + byte[] outputBytes = new byte[pixelCount * 4]; // RGBA8 - var image = new AstcencImage + AstcencImage image = new() { dimX = (uint)w, dimY = (uint)h, @@ -52,7 +54,7 @@ public static byte[] DecompressLdr(ReadOnlySpan blocks, int w, int h, int }; // We need a mutable copy of blocks for the Span parameter - var blocksCopy = blocks.ToArray(); + byte[] blocksCopy = blocks.ToArray(); error = Astcenc.AstcencDecompressImage(context, blocksCopy, ref image, IdentitySwizzle, 0); ThrowOnError(error, "DecompressImage(LDR)"); @@ -69,24 +71,26 @@ public static byte[] DecompressLdr(ReadOnlySpan blocks, int w, int h, int /// public static Half[] DecompressHdr(ReadOnlySpan blocks, int w, int h, int blockX, int blockY) { - var error = Astcenc.AstcencConfigInit( + AstcencError error = Astcenc.AstcencConfigInit( AstcencProfile.AstcencPrfHdr, - (uint)blockX, (uint)blockY, 1, + (uint)blockX, + (uint)blockY, + 1, Astcenc.AstcencPreFastest, AstcencFlags.DecompressOnly, - out var config); + out AstcencConfig config); ThrowOnError(error, "ConfigInit(HDR)"); - error = Astcenc.AstcencContextAlloc(ref config, 1, out var context); + error = Astcenc.AstcencContextAlloc(ref config, 1, out AstcencContext context); ThrowOnError(error, "ContextAlloc(HDR)"); try { int pixelCount = w * h; - var outputHalves = new Half[pixelCount * 4]; // RGBA FP16 - var outputBytes = MemoryMarshal.AsBytes(outputHalves.AsSpan()).ToArray(); + Half[] outputHalves = new Half[pixelCount * 4]; // RGBA FP16 + byte[] outputBytes = MemoryMarshal.AsBytes(outputHalves.AsSpan()).ToArray(); - var image = new AstcencImage + AstcencImage image = new() { dimX = (uint)w, dimY = (uint)h, @@ -95,7 +99,7 @@ public static Half[] DecompressHdr(ReadOnlySpan blocks, int w, int h, int data = outputBytes, }; - var blocksCopy = blocks.ToArray(); + byte[] blocksCopy = blocks.ToArray(); error = Astcenc.AstcencDecompressImage(context, blocksCopy, ref image, IdentitySwizzle, 0); ThrowOnError(error, "DecompressImage(HDR)"); @@ -116,20 +120,22 @@ public static Half[] DecompressHdr(ReadOnlySpan blocks, int w, int h, int /// public static byte[] CompressLdr(byte[] pixels, int w, int h, int blockX, int blockY) { - var error = Astcenc.AstcencConfigInit( + AstcencError error = Astcenc.AstcencConfigInit( AstcencProfile.AstcencPrfLdr, - (uint)blockX, (uint)blockY, 1, + (uint)blockX, + (uint)blockY, + 1, Astcenc.AstcencPreMedium, 0, - out var config); + out AstcencConfig config); ThrowOnError(error, "ConfigInit(CompressLDR)"); - error = Astcenc.AstcencContextAlloc(ref config, 1, out var context); + error = Astcenc.AstcencContextAlloc(ref config, 1, out AstcencContext context); ThrowOnError(error, "ContextAlloc(CompressLDR)"); try { - var image = new AstcencImage + AstcencImage image = new() { dimX = (uint)w, dimY = (uint)h, @@ -140,7 +146,7 @@ public static byte[] CompressLdr(byte[] pixels, int w, int h, int blockX, int bl int blocksWide = (w + blockX - 1) / blockX; int blocksHigh = (h + blockY - 1) / blockY; - var compressedData = new byte[blocksWide * blocksHigh * 16]; + byte[] compressedData = new byte[blocksWide * blocksHigh * 16]; error = Astcenc.AstcencCompressImage(context, ref image, IdentitySwizzle, compressedData, 0); ThrowOnError(error, "CompressImage(LDR)"); @@ -158,22 +164,24 @@ public static byte[] CompressLdr(byte[] pixels, int w, int h, int blockX, int bl /// public static byte[] CompressHdr(Half[] pixels, int w, int h, int blockX, int blockY) { - var error = Astcenc.AstcencConfigInit( + AstcencError error = Astcenc.AstcencConfigInit( AstcencProfile.AstcencPrfHdr, - (uint)blockX, (uint)blockY, 1, + (uint)blockX, + (uint)blockY, + 1, Astcenc.AstcencPreMedium, 0, - out var config); + out AstcencConfig config); ThrowOnError(error, "ConfigInit(CompressHDR)"); - error = Astcenc.AstcencContextAlloc(ref config, 1, out var context); + error = Astcenc.AstcencContextAlloc(ref config, 1, out AstcencContext context); ThrowOnError(error, "ContextAlloc(CompressHDR)"); try { - var pixelBytes = MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray(); + byte[] pixelBytes = MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray(); - var image = new AstcencImage + AstcencImage image = new() { dimX = (uint)w, dimY = (uint)h, @@ -184,7 +192,7 @@ public static byte[] CompressHdr(Half[] pixels, int w, int h, int blockX, int bl int blocksWide = (w + blockX - 1) / blockX; int blocksHigh = (h + blockY - 1) / blockY; - var compressedData = new byte[blocksWide * blocksHigh * 16]; + byte[] compressedData = new byte[blocksWide * blocksHigh * 16]; error = Astcenc.AstcencCompressImage(context, ref image, IdentitySwizzle, compressedData, 0); ThrowOnError(error, "CompressImage(HDR)"); @@ -200,7 +208,7 @@ public static byte[] CompressHdr(Half[] pixels, int w, int h, int blockX, int bl /// /// Map a FootprintType to its (blockX, blockY) dimensions. /// - public static (int blockX, int blockY) ToBlockDimensions(FootprintType footprint) => footprint switch + public static (int BlockX, int BlockY) ToBlockDimensions(FootprintType footprint) => footprint switch { FootprintType.Footprint4x4 => (4, 4), FootprintType.Footprint5x4 => (5, 4), @@ -223,7 +231,7 @@ private static void ThrowOnError(AstcencError error, string operation) { if (error != AstcencError.AstcencSuccess) { - var message = Astcenc.GetErrorString(error) ?? error.ToString(); + string message = Astcenc.GetErrorString(error) ?? error.ToString(); throw new InvalidOperationException($"ARM ASTC encoder {operation} failed: {message}"); } } diff --git a/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs index 76f66499..6b939923 100644 --- a/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs +++ b/tests/ImageSharp.Textures.Benchmarks/AstcDecodingBenchmark.cs @@ -26,9 +26,9 @@ public bool ParseBlock() { ReadOnlySpan blocks = this.astcFile!.Blocks; Span blockBytes = stackalloc byte[16]; - blocks.Slice(0, 16).CopyTo(blockBytes); + blocks[..16].CopyTo(blockBytes); ulong low = BitConverter.ToUInt64(blockBytes); - ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + ulong high = BitConverter.ToUInt64(blockBytes[8..]); PhysicalBlock phyiscalBlock = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); return !phyiscalBlock.IsIllegalEncoding; @@ -39,9 +39,9 @@ public bool DecodeEndpoints() { ReadOnlySpan blocks = this.astcFile!.Blocks; Span blockBytes = stackalloc byte[16]; - blocks.Slice(0, 16).CopyTo(blockBytes); + blocks[..16].CopyTo(blockBytes); ulong low = BitConverter.ToUInt64(blockBytes); - ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + ulong high = BitConverter.ToUInt64(blockBytes[8..]); PhysicalBlock physicalBlock = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); IntermediateBlock.IntermediateBlockData? blockData = IntermediateBlock.UnpackIntermediateBlock(physicalBlock); @@ -54,9 +54,9 @@ public bool Partitioning() { ReadOnlySpan blocks = this.astcFile!.Blocks; Span blockBytes = stackalloc byte[16]; - blocks.Slice(0, 16).CopyTo(blockBytes); + blocks[..16].CopyTo(blockBytes); ulong low = BitConverter.ToUInt64(blockBytes); - ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + ulong high = BitConverter.ToUInt64(blockBytes[8..]); UInt128 bits = (UInt128)low | ((UInt128)high << 64); BlockInfo info = BlockInfo.Decode(bits); LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(Footprint.Get4x4(), bits, in info) diff --git a/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs b/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs index 396b6b80..00b446b0 100644 --- a/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs +++ b/tests/ImageSharp.Textures.Benchmarks/AstcImageDecodeBenchmark.cs @@ -30,9 +30,9 @@ public void ImageDecode() { blocks.Slice(i * 16, 16).CopyTo(blockBytes); ulong low = BitConverter.ToUInt64(blockBytes); - ulong high = BitConverter.ToUInt64(blockBytes.Slice(8)); + ulong high = BitConverter.ToUInt64(blockBytes[8..]); PhysicalBlock block = PhysicalBlock.Create((UInt128)low | ((UInt128)high << 64)); - IntermediateBlock.IntermediateBlockData? _ = IntermediateBlock.UnpackIntermediateBlock(block); + _ = IntermediateBlock.UnpackIntermediateBlock(block); } } } diff --git a/tests/ImageSharp.Textures.Benchmarks/Config.cs b/tests/ImageSharp.Textures.Benchmarks/Config.cs index f57970ad..06502c3f 100644 --- a/tests/ImageSharp.Textures.Benchmarks/Config.cs +++ b/tests/ImageSharp.Textures.Benchmarks/Config.cs @@ -6,23 +6,16 @@ using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; -namespace SixLabors.ImageSharp.Textures.Benchmarks +namespace SixLabors.ImageSharp.Textures.Benchmarks; + +public class Config : ManualConfig { - public class Config : ManualConfig - { - public Config() - { - this.AddDiagnoser(MemoryDiagnoser.Default); - } + public Config() => this.AddDiagnoser(MemoryDiagnoser.Default); - public class ShortRun : Config - { - public ShortRun() - { - this.AddJob( - Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); - } - } + public class ShortRun : Config + { + public ShortRun() => this.AddJob( + Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); } } diff --git a/tests/ImageSharp.Textures.Benchmarks/Program.cs b/tests/ImageSharp.Textures.Benchmarks/Program.cs index 287b56e1..d8ace843 100644 --- a/tests/ImageSharp.Textures.Benchmarks/Program.cs +++ b/tests/ImageSharp.Textures.Benchmarks/Program.cs @@ -4,10 +4,9 @@ using System.Reflection; using BenchmarkDotNet.Running; -namespace SixLabors.ImageSharp.Textures.Benchmarks +namespace SixLabors.ImageSharp.Textures.Benchmarks; + +public class Program { - public class Program - { - public static void Main(string[] args) => new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); - } + public static void Main(string[] args) => new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); } diff --git a/tests/ImageSharp.Textures.InteractiveTest/ApplicationManager.cs b/tests/ImageSharp.Textures.InteractiveTest/ApplicationManager.cs index f1e71471..0a8fa42e 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/ApplicationManager.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/ApplicationManager.cs @@ -1,47 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using SixLabors.ImageSharp.PixelFormats; using Veldrid; -namespace SixLabors.ImageSharp.Textures.InteractiveTest +namespace SixLabors.ImageSharp.Textures.InteractiveTest; + +public static class ApplicationManager { - public static class ApplicationManager - { - public static CommandList CommandList { get; set; } + public static CommandList CommandList { get; set; } - public static GraphicsDevice GraphicsDevice { get; set; } + public static GraphicsDevice GraphicsDevice { get; set; } - public static ImGuiRenderer Controller { get; set; } + public static ImGuiRenderer Controller { get; set; } - private static readonly object LockObject = new(); + private static readonly object LockObject = new(); - public static unsafe IntPtr Create(Image image) + public static unsafe IntPtr Create(Image image) + { + lock (LockObject) { - lock (LockObject) + Veldrid.Texture texture = GraphicsDevice.ResourceFactory.CreateTexture(TextureDescription.Texture2D((uint)image.Width, (uint)image.Height, 1, 1, PixelFormat.R8_G8_B8_A8_UNorm, TextureUsage.Sampled)); + bool gotPixelMemory = image.DangerousTryGetSinglePixelMemory(out Memory pixelsMemory); + if (gotPixelMemory) + { + System.Buffers.MemoryHandle pin = pixelsMemory.Pin(); + GraphicsDevice.UpdateTexture(texture, (IntPtr)pin.Pointer, (uint)(4 * image.Width * image.Height), 0, 0, 0, (uint)image.Width, (uint)image.Height, 1, 0, 0); + } + else { - Veldrid.Texture texture = GraphicsDevice.ResourceFactory.CreateTexture(TextureDescription.Texture2D((uint)image.Width, (uint)image.Height, 1, 1, PixelFormat.R8_G8_B8_A8_UNorm, TextureUsage.Sampled)); - bool gotPixelMemory = image.DangerousTryGetSinglePixelMemory(out Memory pixelsMemory); - if (gotPixelMemory) - { - System.Buffers.MemoryHandle pin = pixelsMemory.Pin(); - GraphicsDevice.UpdateTexture(texture, (IntPtr)pin.Pointer, (uint)(4 * image.Width * image.Height), 0, 0, 0, (uint)image.Width, (uint)image.Height, 1, 0, 0); - } - else - { - throw new Exception("DangerousTryGetSinglePixelMemory failed!"); - } - - return Controller.GetOrCreateImGuiBinding(GraphicsDevice.ResourceFactory, texture); + throw new Exception("DangerousTryGetSinglePixelMemory failed!"); } + + return Controller.GetOrCreateImGuiBinding(GraphicsDevice.ResourceFactory, texture); } + } - public static void ClearImageCache() => Controller.ClearCachedImageResources(); + public static void ClearImageCache() => Controller.ClearCachedImageResources(); - private static Dictionary datastore; + private static Dictionary datastore; - public static Dictionary DataStore => datastore ?? (datastore = new Dictionary()); - } + public static Dictionary DataStore => datastore ?? (datastore = []); } diff --git a/tests/ImageSharp.Textures.InteractiveTest/Extensions.cs b/tests/ImageSharp.Textures.InteractiveTest/Extensions.cs index f342e50d..1b2f715d 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/Extensions.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/Extensions.cs @@ -1,21 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Textures.InteractiveTest; -namespace SixLabors.ImageSharp.Textures.InteractiveTest +public static class Extensions { - public static class Extensions + public static void AddOrReplace(this Dictionary dictionary, string key, object value) { - public static void AddOrReplace(this Dictionary dictionary, string key, object value) + if (dictionary.ContainsKey(key)) { - if (dictionary.ContainsKey(key)) - { - dictionary.Remove(key); - } - - dictionary.Add(key, value); + dictionary.Remove(key); } + + dictionary.Add(key, value); } } diff --git a/tests/ImageSharp.Textures.InteractiveTest/Program.cs b/tests/ImageSharp.Textures.InteractiveTest/Program.cs index 759c1b70..676db80b 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/Program.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/Program.cs @@ -1,90 +1,88 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using ImGuiNET; using Veldrid; using Veldrid.Sdl2; using Veldrid.StartupUtilities; -namespace SixLabors.ImageSharp.Textures.InteractiveTest +namespace SixLabors.ImageSharp.Textures.InteractiveTest; + +public class Program { - public class Program - { - private static UiManager uiManager; - private static Sdl2Window window; - private static DateTime prevUpdateTime; + private static UiManager uiManager; + private static Sdl2Window window; + private static DateTime prevUpdateTime; - public static void Main() - { - uiManager = new UiManager(); + public static void Main() + { + uiManager = new UiManager(); - window = VeldridStartup.CreateWindow(new WindowCreateInfo(50, 50, 1280, 720, WindowState.Normal, "ImageSharp.Textures.InteractiveTest")); - ApplicationManager.GraphicsDevice = VeldridStartup.CreateGraphicsDevice(window, GraphicsBackend.OpenGL); + window = VeldridStartup.CreateWindow(new WindowCreateInfo(50, 50, 1280, 720, WindowState.Normal, "ImageSharp.Textures.InteractiveTest")); + ApplicationManager.GraphicsDevice = VeldridStartup.CreateGraphicsDevice(window, GraphicsBackend.OpenGL); - window.Resized += Window_Resized; + window.Resized += Window_Resized; - ApplicationManager.CommandList = ApplicationManager.GraphicsDevice.ResourceFactory.CreateCommandList(); - ApplicationManager.Controller = new ImGuiRenderer(ApplicationManager.GraphicsDevice, ApplicationManager.GraphicsDevice.MainSwapchain.Framebuffer.OutputDescription, window.Width, window.Height); + ApplicationManager.CommandList = ApplicationManager.GraphicsDevice.ResourceFactory.CreateCommandList(); + ApplicationManager.Controller = new ImGuiRenderer(ApplicationManager.GraphicsDevice, ApplicationManager.GraphicsDevice.MainSwapchain.Framebuffer.OutputDescription, window.Width, window.Height); - ImGui.StyleColorsDark(); + ImGui.StyleColorsDark(); - // Main application loop - while (window.Exists) + // Main application loop + while (window.Exists) + { + InputSnapshot snapshot = window.PumpEvents(); + if (!window.Exists) { - InputSnapshot snapshot = window.PumpEvents(); - if (!window.Exists) - { - break; - } - - DateTime curUpdateTime = DateTime.Now; - if (prevUpdateTime.Ticks == 0) - { - prevUpdateTime = curUpdateTime; - } - - float dt = (float)(curUpdateTime - prevUpdateTime).TotalSeconds; - if (dt <= 0) - { - dt = float.Epsilon; - } + break; + } + DateTime curUpdateTime = DateTime.Now; + if (prevUpdateTime.Ticks == 0) + { prevUpdateTime = curUpdateTime; + } - ApplicationManager.Controller.Update(dt, snapshot); - - SubmitUi(); - - ApplicationManager.CommandList.Begin(); - ApplicationManager.CommandList.SetFramebuffer(ApplicationManager.GraphicsDevice.MainSwapchain.Framebuffer); - ApplicationManager.CommandList.ClearColorTarget(0, new RgbaFloat(0.5f, 0.5f, 0.5f, 1f)); - try - { - ApplicationManager.Controller.Render(ApplicationManager.GraphicsDevice, ApplicationManager.CommandList); - } - catch (Exception) - { - // do nothing. - } - - ApplicationManager.CommandList.End(); - ApplicationManager.GraphicsDevice.SubmitCommands(ApplicationManager.CommandList); - ApplicationManager.GraphicsDevice.SwapBuffers(ApplicationManager.GraphicsDevice.MainSwapchain); + float dt = (float)(curUpdateTime - prevUpdateTime).TotalSeconds; + if (dt <= 0) + { + dt = float.Epsilon; } - ApplicationManager.GraphicsDevice.WaitForIdle(); - ApplicationManager.Controller.Dispose(); - ApplicationManager.CommandList.Dispose(); - ApplicationManager.GraphicsDevice.Dispose(); - } + prevUpdateTime = curUpdateTime; - private static void Window_Resized() - { - ApplicationManager.GraphicsDevice.MainSwapchain.Resize((uint)window.Width, (uint)window.Height); - ApplicationManager.Controller.WindowResized(window.Width, window.Height); + ApplicationManager.Controller.Update(dt, snapshot); + + SubmitUi(); + + ApplicationManager.CommandList.Begin(); + ApplicationManager.CommandList.SetFramebuffer(ApplicationManager.GraphicsDevice.MainSwapchain.Framebuffer); + ApplicationManager.CommandList.ClearColorTarget(0, new RgbaFloat(0.5f, 0.5f, 0.5f, 1f)); + try + { + ApplicationManager.Controller.Render(ApplicationManager.GraphicsDevice, ApplicationManager.CommandList); + } + catch (Exception) + { + // do nothing. + } + + ApplicationManager.CommandList.End(); + ApplicationManager.GraphicsDevice.SubmitCommands(ApplicationManager.CommandList); + ApplicationManager.GraphicsDevice.SwapBuffers(ApplicationManager.GraphicsDevice.MainSwapchain); } - private static void SubmitUi() => uiManager.Render(window.Width, window.Height); + ApplicationManager.GraphicsDevice.WaitForIdle(); + ApplicationManager.Controller.Dispose(); + ApplicationManager.CommandList.Dispose(); + ApplicationManager.GraphicsDevice.Dispose(); } + + private static void Window_Resized() + { + ApplicationManager.GraphicsDevice.MainSwapchain.Resize((uint)window.Width, (uint)window.Height); + ApplicationManager.Controller.WindowResized(window.Width, window.Height); + } + + private static void SubmitUi() => uiManager.Render(window.Width, window.Height); } diff --git a/tests/ImageSharp.Textures.InteractiveTest/ResourceLoader.cs b/tests/ImageSharp.Textures.InteractiveTest/ResourceLoader.cs index f7ab619a..fa5cd7bb 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/ResourceLoader.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/ResourceLoader.cs @@ -1,70 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Linq; using System.Reflection; -namespace SixLabors.ImageSharp.Textures.InteractiveTest +namespace SixLabors.ImageSharp.Textures.InteractiveTest; + +public static class ResourceLoader { - public static class ResourceLoader + public static bool GetEmbeddedResourceExists(string resourceFileName, Assembly assembly = null) { - public static bool GetEmbeddedResourceExists(string resourceFileName, Assembly assembly = null) + if (assembly == null) { - if (assembly == null) - { - assembly = typeof(ResourceLoader).GetTypeInfo().Assembly; - } + assembly = typeof(ResourceLoader).GetTypeInfo().Assembly; + } - string[] resourceNames = assembly.GetManifestResourceNames(); + string[] resourceNames = assembly.GetManifestResourceNames(); - string[] resourcePaths = resourceNames - .Where(x => x.EndsWith(resourceFileName, StringComparison.CurrentCultureIgnoreCase)) - .ToArray(); + string[] resourcePaths = [.. resourceNames.Where(x => x.EndsWith(resourceFileName, StringComparison.CurrentCultureIgnoreCase))]; - return resourcePaths.Any() && resourcePaths.Count() <= 1; - } + return resourcePaths.Any() && resourcePaths.Count() <= 1; + } - public static Stream GetEmbeddedResourceStream(string resourceFileName, Assembly assembly = null) + public static Stream GetEmbeddedResourceStream(string resourceFileName, Assembly assembly = null) + { + if (assembly == null) { - if (assembly == null) - { - assembly = typeof(ResourceLoader).GetTypeInfo().Assembly; - } - - string[] resourceNames = assembly.GetManifestResourceNames(); - - string[] resourcePaths = resourceNames - .Where(x => x.EndsWith(resourceFileName, StringComparison.CurrentCultureIgnoreCase)) - .ToArray(); + assembly = typeof(ResourceLoader).GetTypeInfo().Assembly; + } - if (!resourcePaths.Any()) - { - throw new Exception($"Resource ending with {resourceFileName} not found."); - } + string[] resourceNames = assembly.GetManifestResourceNames(); - if (resourcePaths.Length > 1) - { - throw new Exception($"Multiple resources ending with {resourceFileName} found: {System.Environment.NewLine}{string.Join(System.Environment.NewLine, resourcePaths)}"); - } + string[] resourcePaths = [.. resourceNames.Where(x => x.EndsWith(resourceFileName, StringComparison.CurrentCultureIgnoreCase))]; - return assembly.GetManifestResourceStream(resourcePaths.Single()); - } - - public static string GetEmbeddedResourceString(string resourceFileName, Assembly assembly = null) + if (!resourcePaths.Any()) { - Stream stream = GetEmbeddedResourceStream(resourceFileName, assembly); - using var streamReader = new StreamReader(stream); - return streamReader.ReadToEnd(); + throw new Exception($"Resource ending with {resourceFileName} not found."); } - public static byte[] GetEmbeddedResourceBytes(string resourceFileName, Assembly assembly = null) + if (resourcePaths.Length > 1) { - Stream stream = GetEmbeddedResourceStream(resourceFileName, assembly); - using var streamReader = new MemoryStream(); - stream.CopyTo(streamReader); - return streamReader.ToArray(); + throw new Exception($"Multiple resources ending with {resourceFileName} found: {System.Environment.NewLine}{string.Join(System.Environment.NewLine, resourcePaths)}"); } + + return assembly.GetManifestResourceStream(resourcePaths.Single()); + } + + public static string GetEmbeddedResourceString(string resourceFileName, Assembly assembly = null) + { + Stream stream = GetEmbeddedResourceStream(resourceFileName, assembly); + using StreamReader streamReader = new StreamReader(stream); + return streamReader.ReadToEnd(); + } + + public static byte[] GetEmbeddedResourceBytes(string resourceFileName, Assembly assembly = null) + { + Stream stream = GetEmbeddedResourceStream(resourceFileName, assembly); + using MemoryStream streamReader = new MemoryStream(); + stream.CopyTo(streamReader); + return streamReader.ToArray(); } } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UI/Button.cs b/tests/ImageSharp.Textures.InteractiveTest/UI/Button.cs index c163405e..cd6cb4fd 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UI/Button.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UI/Button.cs @@ -1,39 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using ImGuiNET; -namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI +namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI; + +public class Button { - public class Button - { - public Vector2 Size { get; set; } = new Vector2(100, 30); + public Vector2 Size { get; set; } = new Vector2(100, 30); - public string Title { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; - public bool Enabled { get; set; } = true; + public bool Enabled { get; set; } = true; - public bool Visible { get; set; } = true; + public bool Visible { get; set; } = true; - public void Render(Action clicked) + public void Render(Action clicked) + { + if (!this.Visible) { - if (!this.Visible) - { - return; - } + return; + } - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * (this.Enabled ? 1.0f : 0.5f)); - if (ImGui.Button(this.Title, this.Size)) + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * (this.Enabled ? 1.0f : 0.5f)); + if (ImGui.Button(this.Title, this.Size)) + { + if (this.Enabled) { - if (this.Enabled) - { - clicked?.Invoke(); - } + clicked?.Invoke(); } - - ImGui.PopStyleVar(); } + + ImGui.PopStyleVar(); } } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UI/MenuBar.cs b/tests/ImageSharp.Textures.InteractiveTest/UI/MenuBar.cs index 37b9606b..ce08cf6b 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UI/MenuBar.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UI/MenuBar.cs @@ -3,56 +3,55 @@ using ImGuiNET; -namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI +namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI; + +public class MenuBar { - public class MenuBar - { - public bool DarkMode { get; private set; } + public bool DarkMode { get; private set; } - public bool DemoMode { get; } + public bool DemoMode { get; } + + public MenuBar() + { + this.DarkMode = true; + this.DemoMode = false; + } - public MenuBar() + public void Render(out float menuHeight) + { + if (this.DarkMode) + { + ImGui.StyleColorsDark(); + } + else { - this.DarkMode = true; - this.DemoMode = false; + ImGui.StyleColorsLight(); } - public void Render(out float menuHeight) + menuHeight = 0.0f; + if (ImGui.BeginMainMenuBar()) { - if (this.DarkMode) + if (ImGui.BeginMenu("Theme")) { - ImGui.StyleColorsDark(); - } - else - { - ImGui.StyleColorsLight(); - } + if (ImGui.MenuItem("Light", string.Empty, !this.DarkMode, this.DarkMode)) + { + this.DarkMode = false; + } - menuHeight = 0.0f; - if (ImGui.BeginMainMenuBar()) - { - if (ImGui.BeginMenu("Theme")) + if (ImGui.MenuItem("Dark", string.Empty, this.DarkMode, !this.DarkMode)) { - if (ImGui.MenuItem("Light", string.Empty, !this.DarkMode, this.DarkMode)) - { - this.DarkMode = false; - } - - if (ImGui.MenuItem("Dark", string.Empty, this.DarkMode, !this.DarkMode)) - { - this.DarkMode = true; - } - - // if (ImGui.MenuItem("Demo", "", DemoMode)) - // { - // DemoMode = !DemoMode; - // } - ImGui.EndMenu(); + this.DarkMode = true; } - menuHeight = ImGui.GetWindowHeight(); - ImGui.EndMainMenuBar(); + // if (ImGui.MenuItem("Demo", "", DemoMode)) + // { + // DemoMode = !DemoMode; + // } + ImGui.EndMenu(); } + + menuHeight = ImGui.GetWindowHeight(); + ImGui.EndMainMenuBar(); } } } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UI/TitleBar.cs b/tests/ImageSharp.Textures.InteractiveTest/UI/TitleBar.cs index ad93a5f0..24461c4a 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UI/TitleBar.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UI/TitleBar.cs @@ -4,28 +4,27 @@ using System.Numerics; using ImGuiNET; -namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI +namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI; + +public class TitleBar { - public class TitleBar + public void Render(int page) { - public void Render(int page) - { - Vector2 position = ImGui.GetCursorScreenPos(); + Vector2 position = ImGui.GetCursorScreenPos(); - if (ImGui.BeginChild("TitleBar", new Vector2(0, 30), true, ImGuiWindowFlags.None)) - { - Vector2 size = ImGui.GetWindowSize(); + if (ImGui.BeginChild("TitleBar", new Vector2(0, 30), true, ImGuiWindowFlags.None)) + { + Vector2 size = ImGui.GetWindowSize(); - Vector4 inactiveColor = ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.Text)); - Vector4 activeColor = ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.ButtonActive)); + Vector4 inactiveColor = ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.Text)); + Vector4 activeColor = ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.ButtonActive)); - ImGui.TextColored(page == 0 ? activeColor : inactiveColor, "Welcome"); - ImGui.SameLine(0, 16); - ImGui.TextColored(page == 1 ? activeColor : inactiveColor, "Preview"); + ImGui.TextColored(page == 0 ? activeColor : inactiveColor, "Welcome"); + ImGui.SameLine(0, 16); + ImGui.TextColored(page == 1 ? activeColor : inactiveColor, "Preview"); - ImGui.EndChild(); - ImGui.GetWindowDrawList().AddRectFilled(position, position + size, ImGui.GetColorU32(ImGuiCol.TitleBgActive)); - } + ImGui.EndChild(); + ImGui.GetWindowDrawList().AddRectFilled(position, position + size, ImGui.GetColorU32(ImGuiCol.TitleBgActive)); } } } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UI/Widgets.cs b/tests/ImageSharp.Textures.InteractiveTest/UI/Widgets.cs index 453ae269..9f2849e8 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UI/Widgets.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UI/Widgets.cs @@ -1,35 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using ImGuiNET; -namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI +namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI; + +public static class Widgets { - public static class Widgets + public static void RenderSpinner(Vector2 position, float radius, int thickness) { - public static void RenderSpinner(Vector2 position, float radius, int thickness) - { - float time = (float)ImGui.GetTime(); - uint color = ImGui.ColorConvertFloat4ToU32(new Vector4(0.5f, 0.5f, 0.5f, 1)); - ImDrawListPtr drawList = ImGui.GetWindowDrawList(); - - int num_segments = 30; - int start = (int)MathF.Abs(MathF.Sin(time * 1.8f) * (num_segments - 5)); - float aMin = MathF.PI * 2.0f * start / num_segments; - float aMax = MathF.PI * 2.0f * ((float)num_segments - 3) / num_segments; - var centre = new Vector2(position.X + radius, position.Y + radius + ImGui.GetStyle().FramePadding.Y); + float time = (float)ImGui.GetTime(); + uint color = ImGui.ColorConvertFloat4ToU32(new Vector4(0.5f, 0.5f, 0.5f, 1)); + ImDrawListPtr drawList = ImGui.GetWindowDrawList(); - drawList.PathClear(); - for (int i = 0; i < num_segments; i++) - { - float a = aMin + (i / (float)num_segments * (aMax - aMin)); - var location = new Vector2(centre.X + (MathF.Cos(a + (time * 8)) * radius), centre.Y + (MathF.Sin(a + (time * 8)) * radius)); - drawList.PathLineTo(location); - } + int num_segments = 30; + int start = (int)MathF.Abs(MathF.Sin(time * 1.8f) * (num_segments - 5)); + float aMin = MathF.PI * 2.0f * start / num_segments; + float aMax = MathF.PI * 2.0f * ((float)num_segments - 3) / num_segments; + Vector2 centre = new Vector2(position.X + radius, position.Y + radius + ImGui.GetStyle().FramePadding.Y); - drawList.PathStroke(color, false, thickness); + drawList.PathClear(); + for (int i = 0; i < num_segments; i++) + { + float a = aMin + (i / (float)num_segments * (aMax - aMin)); + Vector2 location = new Vector2(centre.X + (MathF.Cos(a + (time * 8)) * radius), centre.Y + (MathF.Sin(a + (time * 8)) * radius)); + drawList.PathLineTo(location); } + + drawList.PathStroke(color, false, thickness); } } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UI/Wizard.cs b/tests/ImageSharp.Textures.InteractiveTest/UI/Wizard.cs index fc48ce38..9073b241 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UI/Wizard.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UI/Wizard.cs @@ -1,125 +1,123 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using ImGuiNET; -namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI +namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI; + +public class Wizard { - public class Wizard - { - public int Pages { get; set; } + public int Pages { get; set; } - public int CurrentPageIndex { get; set; } + public int CurrentPageIndex { get; set; } - public Action OnCancel { get; set; } + public Action OnCancel { get; set; } - public Action OnValidate { get; set; } + public Action OnValidate { get; set; } - public Func OnPrevious { get; set; } + public Func OnPrevious { get; set; } - public Func OnNext { get; set; } + public Func OnNext { get; set; } - public Button CancelButton { get; } + public Button CancelButton { get; } - public Button ValidateButton { get; } + public Button ValidateButton { get; } - public Button PreviousButton { get; } + public Button PreviousButton { get; } - public Button NextButton { get; } + public Button NextButton { get; } - public Wizard() + public Wizard() + { + this.CancelButton = new Button() { - this.CancelButton = new Button() - { - Title = "Cancel", - Enabled = true, - Visible = false, - Size = new Vector2(100, 30) - }; - - this.ValidateButton = new Button() - { - Title = "Validate", - Enabled = true, - Visible = false, - Size = new Vector2(100, 30) - }; - - this.PreviousButton = new Button() - { - Title = "Previous", - Enabled = true, - Visible = true, - Size = new Vector2(100, 30) - }; - - this.NextButton = new Button() - { - Title = "Next", - Enabled = true, - Visible = true, - Size = new Vector2(100, 30) - }; - - this.Pages = 1; - this.CurrentPageIndex = 0; - } + Title = "Cancel", + Enabled = true, + Visible = false, + Size = new Vector2(100, 30) + }; - public void Render(Action renderPage) + this.ValidateButton = new Button() { - ImGui.BeginChild("Wizard", new Vector2(0, ImGui.GetWindowSize().Y - 88), true, ImGuiWindowFlags.None); - renderPage?.Invoke(); - ImGui.EndChild(); + Title = "Validate", + Enabled = true, + Visible = false, + Size = new Vector2(100, 30) + }; - ImGui.SetCursorPos(new Vector2(8, ImGui.GetWindowSize().Y - 38)); - this.CancelButton.Render(this.CancelAction); + this.PreviousButton = new Button() + { + Title = "Previous", + Enabled = true, + Visible = true, + Size = new Vector2(100, 30) + }; - ImGui.SetCursorPos(new Vector2(ImGui.GetWindowSize().X - 324, ImGui.GetWindowSize().Y - 38)); - this.ValidateButton.Render(this.ValidateAction); + this.NextButton = new Button() + { + Title = "Next", + Enabled = true, + Visible = true, + Size = new Vector2(100, 30) + }; + + this.Pages = 1; + this.CurrentPageIndex = 0; + } - ImGui.SetCursorPos(new Vector2(ImGui.GetWindowSize().X - 216, ImGui.GetWindowSize().Y - 38)); - this.PreviousButton.Render(this.PreviousAction); + public void Render(Action renderPage) + { + ImGui.BeginChild("Wizard", new Vector2(0, ImGui.GetWindowSize().Y - 88), true, ImGuiWindowFlags.None); + renderPage?.Invoke(); + ImGui.EndChild(); - ImGui.SetCursorPos(new Vector2(ImGui.GetWindowSize().X - 108, ImGui.GetWindowSize().Y - 38)); - this.NextButton.Render(this.NextAction); - } + ImGui.SetCursorPos(new Vector2(8, ImGui.GetWindowSize().Y - 38)); + this.CancelButton.Render(this.CancelAction); + + ImGui.SetCursorPos(new Vector2(ImGui.GetWindowSize().X - 324, ImGui.GetWindowSize().Y - 38)); + this.ValidateButton.Render(this.ValidateAction); + + ImGui.SetCursorPos(new Vector2(ImGui.GetWindowSize().X - 216, ImGui.GetWindowSize().Y - 38)); + this.PreviousButton.Render(this.PreviousAction); - private void CancelAction() => this.OnCancel?.Invoke(); + ImGui.SetCursorPos(new Vector2(ImGui.GetWindowSize().X - 108, ImGui.GetWindowSize().Y - 38)); + this.NextButton.Render(this.NextAction); + } + + private void CancelAction() => this.OnCancel?.Invoke(); - private void ValidateAction() => this.OnValidate?.Invoke(); + private void ValidateAction() => this.OnValidate?.Invoke(); - private void PreviousAction() + private void PreviousAction() + { + int pageIndex = this.CurrentPageIndex; + if (pageIndex > 0) { - int pageIndex = this.CurrentPageIndex; - if (pageIndex > 0) - { - pageIndex -= 1; - } - - if (this.OnPrevious?.Invoke(pageIndex) ?? false) - { - this.CurrentPageIndex = pageIndex; - } + pageIndex -= 1; } - private void NextAction() + if (this.OnPrevious?.Invoke(pageIndex) ?? false) { - int pageIndex = this.CurrentPageIndex; - if (pageIndex < (this.Pages - 1)) - { - pageIndex += 1; - } - - if (this.OnNext?.Invoke(pageIndex) ?? false) - { - this.CurrentPageIndex = pageIndex; - } + this.CurrentPageIndex = pageIndex; } + } - public void GoHome() => this.CurrentPageIndex = 0; + private void NextAction() + { + int pageIndex = this.CurrentPageIndex; + if (pageIndex < (this.Pages - 1)) + { + pageIndex += 1; + } - public void GoNext() => this.NextAction(); + if (this.OnNext?.Invoke(pageIndex) ?? false) + { + this.CurrentPageIndex = pageIndex; + } } + + public void GoHome() => this.CurrentPageIndex = 0; + + public void GoNext() => this.NextAction(); } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPage.cs b/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPage.cs index 2563785f..91f3dcb9 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPage.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPage.cs @@ -1,38 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI +namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI; + +public abstract class WizardPage { - public abstract class WizardPage - { - public Wizard Wizard { get; } + public Wizard Wizard { get; } - protected WizardPage(Wizard wizard) - { - this.Wizard = wizard; - } + protected WizardPage(Wizard wizard) => this.Wizard = wizard; - public virtual void Cancel() - { - this.Wizard.GoHome(); - } + public virtual void Cancel() => this.Wizard.GoHome(); - public virtual void Validate() - { - } + public virtual void Validate() + { + } - public virtual bool Previous(WizardPage newWizardPage) - { - return true; - } + public virtual bool Previous(WizardPage newWizardPage) => true; - public virtual bool Next(WizardPage newWizardPage) - { - return true; - } + public virtual bool Next(WizardPage newWizardPage) => true; - public abstract void Initialize(); + public abstract void Initialize(); - public abstract void Render(); - } + public abstract void Render(); } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPages/Preview.cs b/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPages/Preview.cs index 81384eee..ec07101b 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPages/Preview.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPages/Preview.cs @@ -1,10 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Numerics; using System.Runtime.InteropServices; using ImGuiNET; @@ -13,244 +10,243 @@ using SixLabors.ImageSharp.Textures.Formats.Dds; using SixLabors.ImageSharp.Textures.TextureFormats; -namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI.WizardPages +namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI.WizardPages; + +public class Preview : WizardPage { - public class Preview : WizardPage + public struct ImageInfo { - public struct ImageInfo - { - public IntPtr TexturePtr; - public Vector2 Size; - public string FilePath; - public string TempFilePath; - public string ErrorMessage; - } + public IntPtr TexturePtr; + public Vector2 Size; + public string FilePath; + public string TempFilePath; + public string ErrorMessage; + } - private readonly string rootFolder; - private string currentFolder; - private string currentFile; + private readonly string rootFolder; + private string currentFolder; + private string currentFile; - private ImageInfo expectedImageInfo; - private ImageInfo actualImageInfo; + private ImageInfo expectedImageInfo; + private ImageInfo actualImageInfo; - public Preview(Wizard wizard) - : base(wizard) - { - this.rootFolder = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "Dds"); - this.currentFolder = this.rootFolder; - } + public Preview(Wizard wizard) + : base(wizard) + { + this.rootFolder = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "Dds"); + this.currentFolder = this.rootFolder; + } - public void OpenCompare(string filePath1, string filePath2) + public void OpenCompare(string filePath1, string filePath2) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - string command = @"C:\Program Files\Beyond Compare 4\bcomp.exe"; - Process.Start(new ProcessStartInfo(command, $"\"{filePath1}\" \"{filePath2}\" /fv=\"Picture Compare\"")); - } + string command = @"C:\Program Files\Beyond Compare 4\bcomp.exe"; + Process.Start(new ProcessStartInfo(command, $"\"{filePath1}\" \"{filePath2}\" /fv=\"Picture Compare\"")); } + } - public string DecompressDds(string filePath) + public string DecompressDds(string filePath) + { + string command = Path.Combine(TestEnvironment.ToolsDirectoryFullPath, "TexConv.exe"); + Process process = new Process { - string command = Path.Combine(TestEnvironment.ToolsDirectoryFullPath, "TexConv.exe"); - var process = new Process - { - StartInfo = - { - FileName = command, - Arguments = $"-ft PNG \"{filePath}\" -f rgba -o {Path.GetTempPath()}", - RedirectStandardOutput = true - } - }; - process.Start(); - process.WaitForExit(); - string sourceFile = Path.Combine(Path.GetTempPath(), $"{Path.GetFileNameWithoutExtension(filePath)}.png"); - if (!File.Exists(sourceFile)) + StartInfo = { - throw new Exception(process.StandardOutput.ReadToEnd()); + FileName = command, + Arguments = $"-ft PNG \"{filePath}\" -f rgba -o {Path.GetTempPath()}", + RedirectStandardOutput = true } - - string saveFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.png"); - File.Move(sourceFile, saveFilePath); - return saveFilePath; + }; + process.Start(); + process.WaitForExit(); + string sourceFile = Path.Combine(Path.GetTempPath(), $"{Path.GetFileNameWithoutExtension(filePath)}.png"); + if (!File.Exists(sourceFile)) + { + throw new Exception(process.StandardOutput.ReadToEnd()); } - public ImageInfo LoadExpected(string filePath) + string saveFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.png"); + File.Move(sourceFile, saveFilePath); + return saveFilePath; + } + + public ImageInfo LoadExpected(string filePath) + { + try { - try - { - string ddsSaveFilePath = this.DecompressDds(filePath); - using var clone = Image.Load(ddsSaveFilePath); - return new ImageInfo { TexturePtr = ApplicationManager.Create(clone), Size = new Vector2(clone.Width, clone.Height), FilePath = filePath, TempFilePath = ddsSaveFilePath }; - } - catch (Exception ex) - { - return new ImageInfo { TexturePtr = IntPtr.Zero, Size = Vector2.Zero, FilePath = filePath, TempFilePath = string.Empty, ErrorMessage = ex.ToString() }; - } + string ddsSaveFilePath = this.DecompressDds(filePath); + using Image clone = Image.Load(ddsSaveFilePath); + return new ImageInfo { TexturePtr = ApplicationManager.Create(clone), Size = new Vector2(clone.Width, clone.Height), FilePath = filePath, TempFilePath = ddsSaveFilePath }; } - - public ImageInfo LoadActualImage(string filePath) + catch (Exception ex) { - try - { - string saveFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.png"); - var decoder = new DdsDecoder(); - using FileStream fileStream = File.OpenRead(filePath); - using var result = (FlatTexture)decoder.DecodeTexture(Configuration.Default, fileStream); - using Image ddsImage = result.MipMaps[0].GetImage(); - using Image clone = ddsImage.CloneAs(); - clone.Save(saveFilePath); - return new ImageInfo { TexturePtr = ApplicationManager.Create(clone), Size = new Vector2(clone.Width, clone.Height), FilePath = filePath, TempFilePath = saveFilePath }; - } - catch (Exception ex) - { - return new ImageInfo { TexturePtr = IntPtr.Zero, Size = Vector2.Zero, FilePath = filePath, TempFilePath = string.Empty, ErrorMessage = ex.ToString() }; - } + return new ImageInfo { TexturePtr = IntPtr.Zero, Size = Vector2.Zero, FilePath = filePath, TempFilePath = string.Empty, ErrorMessage = ex.ToString() }; } + } - public override void Initialize() => ApplicationManager.ClearImageCache(); - - private static void DrawLines(IReadOnlyList points, Vector2 location, float size) + public ImageInfo LoadActualImage(string filePath) + { + try { - uint iconColor = ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1)); - ImDrawListPtr drawList = ImGui.GetWindowDrawList(); - for (int i = 0; i < points.Count; i += 2) - { - Vector2 vector1 = points[i] / 100 * size; - Vector2 vector2 = points[i + 1] / 100 * size; - drawList.AddLine(location + vector1, location + vector2, iconColor); - } + string saveFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.png"); + DdsDecoder decoder = new DdsDecoder(); + using FileStream fileStream = File.OpenRead(filePath); + using FlatTexture result = (FlatTexture)decoder.DecodeTexture(Configuration.Default, fileStream); + using Image ddsImage = result.MipMaps[0].GetImage(); + using Image clone = ddsImage.CloneAs(); + clone.Save(saveFilePath); + return new ImageInfo { TexturePtr = ApplicationManager.Create(clone), Size = new Vector2(clone.Width, clone.Height), FilePath = filePath, TempFilePath = saveFilePath }; } - - private static void GenerateFolderIcon(Vector2 location, float size) + catch (Exception ex) { - Vector2[] points = new[] - { - new Vector2(0.0f, 0.0f), new Vector2(45.0f, 0.0f), - new Vector2(45.0f, 0.0f), new Vector2(55.0f, 22.5f), - new Vector2(55.0f, 22.5f), new Vector2(100.0f, 22.5f), - new Vector2(100.0f, 22.5f), new Vector2(100.0f, 87.5f), - new Vector2(100.0f, 87.5f), new Vector2(0.0f, 87.5f), - new Vector2(0.0f, 87.5f), new Vector2(0.0f, 0.0f) - }; - DrawLines(points, location, size); + return new ImageInfo { TexturePtr = IntPtr.Zero, Size = Vector2.Zero, FilePath = filePath, TempFilePath = string.Empty, ErrorMessage = ex.ToString() }; } + } - private static void GenerateFileIcon(Vector2 location, float size) + public override void Initialize() => ApplicationManager.ClearImageCache(); + + private static void DrawLines(IReadOnlyList points, Vector2 location, float size) + { + uint iconColor = ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1)); + ImDrawListPtr drawList = ImGui.GetWindowDrawList(); + for (int i = 0; i < points.Count; i += 2) { - Vector2[] points = new[] - { - new Vector2(12.5f, 0.0f), new Vector2(62.5f, 0.0f), - new Vector2(62.5f, 0.0f), new Vector2(87.5f, 50.0f), - new Vector2(87.5f, 50.0f), new Vector2(87.5f, 100.0f), - new Vector2(87.5f, 100.0f), new Vector2(12.5f, 100.0f), - new Vector2(12.5f, 100.0f), new Vector2(12.5f, 0.0f), - new Vector2(62.5f, 0.0f), new Vector2(62.5f, 50.0f), - new Vector2(62.5f, 50.0f), new Vector2(87.5f, 50.0f) - }; - DrawLines(points, location, size); + Vector2 vector1 = points[i] / 100 * size; + Vector2 vector2 = points[i + 1] / 100 * size; + drawList.AddLine(location + vector1, location + vector2, iconColor); } + } - public override void Render() - { - this.Wizard.NextButton.Enabled = true; - this.Wizard.PreviousButton.Visible = false; - this.Wizard.NextButton.Title = "Home"; + private static void GenerateFolderIcon(Vector2 location, float size) + { + Vector2[] points = + [ + new Vector2(0.0f, 0.0f), new Vector2(45.0f, 0.0f), + new Vector2(45.0f, 0.0f), new Vector2(55.0f, 22.5f), + new Vector2(55.0f, 22.5f), new Vector2(100.0f, 22.5f), + new Vector2(100.0f, 22.5f), new Vector2(100.0f, 87.5f), + new Vector2(100.0f, 87.5f), new Vector2(0.0f, 87.5f), + new Vector2(0.0f, 87.5f), new Vector2(0.0f, 0.0f) + ]; + DrawLines(points, location, size); + } - Vector2 size = ImGui.GetWindowSize(); + private static void GenerateFileIcon(Vector2 location, float size) + { + Vector2[] points = + [ + new Vector2(12.5f, 0.0f), new Vector2(62.5f, 0.0f), + new Vector2(62.5f, 0.0f), new Vector2(87.5f, 50.0f), + new Vector2(87.5f, 50.0f), new Vector2(87.5f, 100.0f), + new Vector2(87.5f, 100.0f), new Vector2(12.5f, 100.0f), + new Vector2(12.5f, 100.0f), new Vector2(12.5f, 0.0f), + new Vector2(62.5f, 0.0f), new Vector2(62.5f, 50.0f), + new Vector2(62.5f, 50.0f), new Vector2(87.5f, 50.0f) + ]; + DrawLines(points, location, size); + } - ImGui.PushItemWidth(size.X - 16); - ImGui.PopItemWidth(); - ImGui.Spacing(); + public override void Render() + { + this.Wizard.NextButton.Enabled = true; + this.Wizard.PreviousButton.Visible = false; + this.Wizard.NextButton.Title = "Home"; - if (ImGui.BeginChildFrame(1, new Vector2(200, size.Y - 24), ImGuiWindowFlags.None)) - { - var directories = new List(); - if (!this.currentFolder.Equals(this.rootFolder, StringComparison.CurrentCultureIgnoreCase)) - { - directories.Add(".."); - } + Vector2 size = ImGui.GetWindowSize(); - directories.AddRange(Directory.GetDirectories(this.currentFolder)); - foreach (string directory in directories) - { - Vector2 iconPosition = ImGui.GetWindowPos() + ImGui.GetCursorPos(); - iconPosition.Y -= ImGui.GetScrollY(); - float lineHeight = ImGui.GetTextLineHeight(); - ImGui.SetCursorPosX(lineHeight * 2); - if (ImGui.Selectable(Path.GetFileName(directory), false, ImGuiSelectableFlags.DontClosePopups)) - { - this.currentFile = null; - this.currentFolder = directory.Equals("..") ? Path.GetFullPath(Path.Combine(this.currentFolder, "..")) : directory; - } - - GenerateFolderIcon(iconPosition, lineHeight); - } + ImGui.PushItemWidth(size.X - 16); + ImGui.PopItemWidth(); + ImGui.Spacing(); - string[] files = Directory.GetFiles(this.currentFolder); - foreach (string file in files) + if (ImGui.BeginChildFrame(1, new Vector2(200, size.Y - 24), ImGuiWindowFlags.None)) + { + List directories = new List(); + if (!this.currentFolder.Equals(this.rootFolder, StringComparison.CurrentCultureIgnoreCase)) + { + directories.Add(".."); + } + + directories.AddRange(Directory.GetDirectories(this.currentFolder)); + foreach (string directory in directories) + { + Vector2 iconPosition = ImGui.GetWindowPos() + ImGui.GetCursorPos(); + iconPosition.Y -= ImGui.GetScrollY(); + float lineHeight = ImGui.GetTextLineHeight(); + ImGui.SetCursorPosX(lineHeight * 2); + if (ImGui.Selectable(Path.GetFileName(directory), false, ImGuiSelectableFlags.DontClosePopups)) { - Vector2 iconPosition = ImGui.GetWindowPos() + ImGui.GetCursorPos(); - iconPosition.Y -= ImGui.GetScrollY(); - float lineHeight = ImGui.GetTextLineHeight(); - ImGui.SetCursorPosX(lineHeight * 2); - if (ImGui.Selectable(Path.GetFileName(file), string.Equals(file, this.currentFile, StringComparison.CurrentCultureIgnoreCase), ImGuiSelectableFlags.DontClosePopups)) - { - ApplicationManager.ClearImageCache(); - this.currentFile = file; - this.expectedImageInfo = this.LoadExpected(file); - this.actualImageInfo = this.LoadActualImage(file); - } - - GenerateFileIcon(iconPosition, lineHeight); + this.currentFile = null; + this.currentFolder = directory.Equals("..") ? Path.GetFullPath(Path.Combine(this.currentFolder, "..")) : directory; } - ImGui.EndChildFrame(); + GenerateFolderIcon(iconPosition, lineHeight); } - ImGui.SameLine(); - if (ImGui.BeginChildFrame(2, new Vector2(size.X - 224, size.Y - 24), ImGuiWindowFlags.None)) + string[] files = Directory.GetFiles(this.currentFolder); + foreach (string file in files) { - ImGui.Text("Expected Image"); - if (this.expectedImageInfo.TexturePtr != IntPtr.Zero) - { - ImGui.Image(this.expectedImageInfo.TexturePtr, this.expectedImageInfo.Size); - } - else if (this.expectedImageInfo.ErrorMessage != null) + Vector2 iconPosition = ImGui.GetWindowPos() + ImGui.GetCursorPos(); + iconPosition.Y -= ImGui.GetScrollY(); + float lineHeight = ImGui.GetTextLineHeight(); + ImGui.SetCursorPosX(lineHeight * 2); + if (ImGui.Selectable(Path.GetFileName(file), string.Equals(file, this.currentFile, StringComparison.CurrentCultureIgnoreCase), ImGuiSelectableFlags.DontClosePopups)) { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1, 0, 0, 1)); - ImGui.TextWrapped(this.expectedImageInfo.ErrorMessage); - ImGui.PopStyleColor(); + ApplicationManager.ClearImageCache(); + this.currentFile = file; + this.expectedImageInfo = this.LoadExpected(file); + this.actualImageInfo = this.LoadActualImage(file); } - ImGui.Spacing(); + GenerateFileIcon(iconPosition, lineHeight); + } - ImGui.Text("Actual Image"); - if (this.actualImageInfo.TexturePtr != IntPtr.Zero) - { - ImGui.Image(this.actualImageInfo.TexturePtr, this.actualImageInfo.Size); - } - else if (this.actualImageInfo.ErrorMessage != null) - { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1, 0, 0, 1)); - ImGui.TextWrapped(this.actualImageInfo.ErrorMessage); - ImGui.PopStyleColor(); - } + ImGui.EndChildFrame(); + } - ImGui.Spacing(); + ImGui.SameLine(); + if (ImGui.BeginChildFrame(2, new Vector2(size.X - 224, size.Y - 24), ImGuiWindowFlags.None)) + { + ImGui.Text("Expected Image"); + if (this.expectedImageInfo.TexturePtr != IntPtr.Zero) + { + ImGui.Image(this.expectedImageInfo.TexturePtr, this.expectedImageInfo.Size); + } + else if (this.expectedImageInfo.ErrorMessage != null) + { + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1, 0, 0, 1)); + ImGui.TextWrapped(this.expectedImageInfo.ErrorMessage); + ImGui.PopStyleColor(); + } - if (ImGui.Button("Compare")) - { - this.OpenCompare(this.expectedImageInfo.TempFilePath, this.actualImageInfo.TempFilePath); - } + ImGui.Spacing(); - ImGui.EndChildFrame(); + ImGui.Text("Actual Image"); + if (this.actualImageInfo.TexturePtr != IntPtr.Zero) + { + ImGui.Image(this.actualImageInfo.TexturePtr, this.actualImageInfo.Size); + } + else if (this.actualImageInfo.ErrorMessage != null) + { + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1, 0, 0, 1)); + ImGui.TextWrapped(this.actualImageInfo.ErrorMessage); + ImGui.PopStyleColor(); } - } - public override bool Next(WizardPage newWizardPage) - { - this.Wizard.GoHome(); - return false; + ImGui.Spacing(); + + if (ImGui.Button("Compare")) + { + this.OpenCompare(this.expectedImageInfo.TempFilePath, this.actualImageInfo.TempFilePath); + } + + ImGui.EndChildFrame(); } } + + public override bool Next(WizardPage newWizardPage) + { + this.Wizard.GoHome(); + return false; + } } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPages/Welcome.cs b/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPages/Welcome.cs index 893a9cd7..8944f45c 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPages/Welcome.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UI/WizardPages/Welcome.cs @@ -3,28 +3,27 @@ using ImGuiNET; -namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI.WizardPages +namespace SixLabors.ImageSharp.Textures.InteractiveTest.UI.WizardPages; + +public class Welcome : WizardPage { - public class Welcome : WizardPage + public Welcome(Wizard wizard) + : base(wizard) { - public Welcome(Wizard wizard) - : base(wizard) - { - } + } - public override void Initialize() - { - } + public override void Initialize() + { + } - public override void Render() - { - this.Wizard.PreviousButton.Visible = false; - this.Wizard.CancelButton.Visible = false; + public override void Render() + { + this.Wizard.PreviousButton.Visible = false; + this.Wizard.CancelButton.Visible = false; - ImGui.TextWrapped("Welcome"); - ImGui.Spacing(); - ImGui.Spacing(); - ImGui.TextWrapped("Welcome to the ImageSharp Textures Test tool."); - } + ImGui.TextWrapped("Welcome"); + ImGui.Spacing(); + ImGui.Spacing(); + ImGui.TextWrapped("Welcome to the ImageSharp Textures Test tool."); } } diff --git a/tests/ImageSharp.Textures.InteractiveTest/UIManager.cs b/tests/ImageSharp.Textures.InteractiveTest/UIManager.cs index 9abaf4df..e000dc03 100644 --- a/tests/ImageSharp.Textures.InteractiveTest/UIManager.cs +++ b/tests/ImageSharp.Textures.InteractiveTest/UIManager.cs @@ -6,97 +6,96 @@ using SixLabors.ImageSharp.Textures.InteractiveTest.UI; using SixLabors.ImageSharp.Textures.InteractiveTest.UI.WizardPages; -namespace SixLabors.ImageSharp.Textures.InteractiveTest +namespace SixLabors.ImageSharp.Textures.InteractiveTest; + +public class UiManager { - public class UiManager + private readonly MenuBar menuBar; + private readonly TitleBar titleBar; + private readonly Wizard wizard; + private readonly WizardPage[] wizardPages; + + public UiManager() + { + this.menuBar = new MenuBar(); + this.titleBar = new TitleBar(); + + this.wizard = new Wizard(); + this.wizardPages = + [ + new Welcome(this.wizard), + new Preview(this.wizard) + ]; + this.wizard.Pages = this.wizardPages.Length; + + this.wizard.OnCancel += this.CancelButton_Action; + this.wizard.OnValidate += this.ValidateButton_Action; + this.wizard.OnPrevious += this.PreviousButton_Action; + this.wizard.OnNext += this.NextButton_Action; + } + + public void Render(float width, float height) { - private readonly MenuBar menuBar; - private readonly TitleBar titleBar; - private readonly Wizard wizard; - private readonly WizardPage[] wizardPages; + uint backgroundColor = ImGui.GetColorU32(ImGuiCol.WindowBg); + Vector4 newBackgroundColor = ImGui.ColorConvertU32ToFloat4(backgroundColor); + newBackgroundColor.W = 1.0f; + ImGui.PushStyleColor(ImGuiCol.WindowBg, newBackgroundColor); - public UiManager() + this.menuBar.Render(out float menuHeight); + if (this.menuBar.DemoMode) { - this.menuBar = new MenuBar(); - this.titleBar = new TitleBar(); - - this.wizard = new Wizard(); - this.wizardPages = new WizardPage[] - { - new Welcome(this.wizard), - new Preview(this.wizard) - }; - this.wizard.Pages = this.wizardPages.Length; - - this.wizard.OnCancel += this.CancelButton_Action; - this.wizard.OnValidate += this.ValidateButton_Action; - this.wizard.OnPrevious += this.PreviousButton_Action; - this.wizard.OnNext += this.NextButton_Action; + ImGui.ShowDemoWindow(); + return; } - public void Render(float width, float height) + ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0); + if (ImGui.Begin(string.Empty, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)) { - uint backgroundColor = ImGui.GetColorU32(ImGuiCol.WindowBg); - Vector4 newBackgroundColor = ImGui.ColorConvertU32ToFloat4(backgroundColor); - newBackgroundColor.W = 1.0f; - ImGui.PushStyleColor(ImGuiCol.WindowBg, newBackgroundColor); - - this.menuBar.Render(out float menuHeight); - if (this.menuBar.DemoMode) - { - ImGui.ShowDemoWindow(); - return; - } - - ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0); - if (ImGui.Begin(string.Empty, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)) - { - ImGui.SetWindowPos(new Vector2(0.0f, menuHeight)); - ImGui.SetWindowSize(new Vector2(width, height - menuHeight)); - - this.titleBar.Render(this.wizard.CurrentPageIndex); - - this.wizard.CancelButton.Visible = false; - this.wizard.CancelButton.Enabled = true; - this.wizard.CancelButton.Title = "Cancel"; - - this.wizard.ValidateButton.Visible = false; - this.wizard.ValidateButton.Enabled = true; - this.wizard.ValidateButton.Title = "Validate"; - - this.wizard.PreviousButton.Visible = true; - this.wizard.PreviousButton.Enabled = this.wizard.CurrentPageIndex > 0; - this.wizard.PreviousButton.Title = "Previous"; - - this.wizard.NextButton.Visible = true; - this.wizard.NextButton.Enabled = this.wizard.CurrentPageIndex < (this.wizard.Pages - 1); - this.wizard.NextButton.Title = "Next"; - this.wizard.Render(this.RenderPage_Action); - - ImGui.End(); - } - - ImGui.PopStyleVar(); - ImGui.PopStyleColor(); + ImGui.SetWindowPos(new Vector2(0.0f, menuHeight)); + ImGui.SetWindowSize(new Vector2(width, height - menuHeight)); + + this.titleBar.Render(this.wizard.CurrentPageIndex); + + this.wizard.CancelButton.Visible = false; + this.wizard.CancelButton.Enabled = true; + this.wizard.CancelButton.Title = "Cancel"; + + this.wizard.ValidateButton.Visible = false; + this.wizard.ValidateButton.Enabled = true; + this.wizard.ValidateButton.Title = "Validate"; + + this.wizard.PreviousButton.Visible = true; + this.wizard.PreviousButton.Enabled = this.wizard.CurrentPageIndex > 0; + this.wizard.PreviousButton.Title = "Previous"; + + this.wizard.NextButton.Visible = true; + this.wizard.NextButton.Enabled = this.wizard.CurrentPageIndex < (this.wizard.Pages - 1); + this.wizard.NextButton.Title = "Next"; + this.wizard.Render(this.RenderPage_Action); + + ImGui.End(); } - private void RenderPage_Action() => this.wizardPages[this.wizard.CurrentPageIndex].Render(); + ImGui.PopStyleVar(); + ImGui.PopStyleColor(); + } + + private void RenderPage_Action() => this.wizardPages[this.wizard.CurrentPageIndex].Render(); - private void CancelButton_Action() => this.wizardPages[this.wizard.CurrentPageIndex].Cancel(); + private void CancelButton_Action() => this.wizardPages[this.wizard.CurrentPageIndex].Cancel(); - private void ValidateButton_Action() => this.wizardPages[this.wizard.CurrentPageIndex].Validate(); + private void ValidateButton_Action() => this.wizardPages[this.wizard.CurrentPageIndex].Validate(); - private bool PreviousButton_Action(int newPageIndex) => this.wizardPages[this.wizard.CurrentPageIndex].Previous(this.wizardPages[newPageIndex]); + private bool PreviousButton_Action(int newPageIndex) => this.wizardPages[this.wizard.CurrentPageIndex].Previous(this.wizardPages[newPageIndex]); - private bool NextButton_Action(int newPageIndex) + private bool NextButton_Action(int newPageIndex) + { + bool value = this.wizardPages[this.wizard.CurrentPageIndex].Next(this.wizardPages[newPageIndex]); + if (value) { - bool value = this.wizardPages[this.wizard.CurrentPageIndex].Next(this.wizardPages[newPageIndex]); - if (value) - { - this.wizardPages[newPageIndex].Initialize(); - } - - return value; + this.wizardPages[newPageIndex].Initialize(); } + + return value; } } diff --git a/tests/ImageSharp.Textures.Tests/Enums/TestTextureFormat.cs b/tests/ImageSharp.Textures.Tests/Enums/TestTextureFormat.cs index f737c793..def8dc7b 100644 --- a/tests/ImageSharp.Textures.Tests/Enums/TestTextureFormat.cs +++ b/tests/ImageSharp.Textures.Tests/Enums/TestTextureFormat.cs @@ -1,23 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Tests.Enums +namespace SixLabors.ImageSharp.Textures.Tests.Enums; + +public enum TestTextureFormat { - public enum TestTextureFormat - { - /// - /// DirectDraw Surface Textures. - /// - Dds, + /// + /// DirectDraw Surface Textures. + /// + Dds, - /// - /// Khronos Texture, version 1. - /// - Ktx, + /// + /// Khronos Texture, version 1. + /// + Ktx, - /// - /// Khronos Texture, version 2. - /// - Ktx2, - } + /// + /// Khronos Texture, version 2. + /// + Ktx2, } diff --git a/tests/ImageSharp.Textures.Tests/Enums/TestTextureTool.cs b/tests/ImageSharp.Textures.Tests/Enums/TestTextureTool.cs index 169d4daf..b74a8972 100644 --- a/tests/ImageSharp.Textures.Tests/Enums/TestTextureTool.cs +++ b/tests/ImageSharp.Textures.Tests/Enums/TestTextureTool.cs @@ -1,28 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Tests.Enums +namespace SixLabors.ImageSharp.Textures.Tests.Enums; + +public enum TestTextureTool { - public enum TestTextureTool - { - /// - /// TexConv. - /// - TexConv, + /// + /// TexConv. + /// + TexConv, - /// - /// NvDxt. - /// - NvDxt, + /// + /// NvDxt. + /// + NvDxt, - /// - /// ToKtx. - /// - ToKtx, + /// + /// ToKtx. + /// + ToKtx, - /// - /// The PVR tex tool cli. - /// - PvrTexToolCli - } + /// + /// The PVR tex tool cli. + /// + PvrTexToolCli } diff --git a/tests/ImageSharp.Textures.Tests/Enums/TestTextureType.cs b/tests/ImageSharp.Textures.Tests/Enums/TestTextureType.cs index d550f166..40858bac 100644 --- a/tests/ImageSharp.Textures.Tests/Enums/TestTextureType.cs +++ b/tests/ImageSharp.Textures.Tests/Enums/TestTextureType.cs @@ -1,23 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Tests.Enums +namespace SixLabors.ImageSharp.Textures.Tests.Enums; + +public enum TestTextureType { - public enum TestTextureType - { - /// - /// FlatTexture - /// - Flat, + /// + /// FlatTexture + /// + Flat, - /// - /// VolumeTexture - /// - Volume, + /// + /// VolumeTexture + /// + Volume, - /// - /// Cubemap - /// - Cubemap, - } + /// + /// Cubemap + /// + Cubemap, } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs index 923d69b4..d719c4bf 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitOperationsTests.cs @@ -11,9 +11,9 @@ public class BitOperationsTests [Fact] public void GetBits_UInt128WithLowBits_ShouldExtractCorrectly() { - UInt128 value = new UInt128(0x1234567890ABCDEF, 0xFEDCBA0987654321); + UInt128 value = new(0x1234567890ABCDEF, 0xFEDCBA0987654321); - var result = BitOperations.GetBits(value, 0, 8); + UInt128 result = BitOperations.GetBits(value, 0, 8); result.Low().Should().Be(0x21UL); } @@ -21,9 +21,9 @@ public void GetBits_UInt128WithLowBits_ShouldExtractCorrectly() [Fact] public void GetBits_UInt128WithZeroLength_ShouldReturnZero() { - UInt128 value = new UInt128(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF); + UInt128 value = new(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF); - var result = BitOperations.GetBits(value, 0, 0); + UInt128 result = BitOperations.GetBits(value, 0, 0); result.Should().Be(UInt128.Zero); } @@ -33,7 +33,7 @@ public void GetBits_ULongWithLowBits_ShouldExtractCorrectly() { ulong value = 0xFEDCBA0987654321; - var result = BitOperations.GetBits(value, 0, 8); + ulong result = BitOperations.GetBits(value, 0, 8); result.Should().Be(0x21UL); } @@ -43,7 +43,7 @@ public void GetBits_ULongWithZeroLength_ShouldReturnZero() { ulong value = 0xFFFFFFFFFFFFFFFF; - var result = BitOperations.GetBits(value, 0, 0); + ulong result = BitOperations.GetBits(value, 0, 0); result.Should().Be(0UL); } @@ -56,8 +56,8 @@ public void GetBits_ULongWithZeroLength_ShouldReturnZero() [InlineData(64, 64)] public void TransferPrecision_WithSameInput_ShouldBeDeterministic(int inputA, int inputB) { - var (a1, b1) = BitOperations.TransferPrecision(inputA, inputB); - var (a2, b2) = BitOperations.TransferPrecision(inputA, inputB); + (int a1, int b1) = BitOperations.TransferPrecision(inputA, inputB); + (int a2, int b2) = BitOperations.TransferPrecision(inputA, inputB); a1.Should().Be(a2); b1.Should().Be(b2); @@ -70,7 +70,7 @@ public void TransferPrecision_WithAllValidByteInputs_ShouldNotThrow() { for (int b = byte.MinValue; b <= byte.MaxValue; b++) { - var action = () => BitOperations.TransferPrecision(a, b); + Action action = () => BitOperations.TransferPrecision(a, b); action.Should().NotThrow(); } } @@ -85,8 +85,8 @@ public void TransferPrecision_WithAllValidByteInputs_ShouldNotThrow() [InlineData(-1, 100)] public void TransferPrecisionInverse_WithSameInput_ShouldBeDeterministic(int inputA, int inputB) { - var (a1, b1) = BitOperations.TransferPrecisionInverse(inputA, inputB); - var (a2, b2) = BitOperations.TransferPrecisionInverse(inputA, inputB); + (int a1, int b1) = BitOperations.TransferPrecisionInverse(inputA, inputB); + (int a2, int b2) = BitOperations.TransferPrecisionInverse(inputA, inputB); a1.Should().Be(a2); b1.Should().Be(b2); @@ -99,7 +99,7 @@ public void TransferPrecisionInverse_WithSameInput_ShouldBeDeterministic(int inp [InlineData(0, 256)] // b too large public void TransferPrecisionInverse_WithInvalidInput_ShouldThrowArgumentOutOfRangeException(int a, int b) { - var action = () => BitOperations.TransferPrecisionInverse(a, b); + Action action = () => BitOperations.TransferPrecisionInverse(a, b); action.Should().Throw(); } @@ -112,10 +112,10 @@ public void TransferPrecisionInverse_WithInvalidInput_ShouldThrowArgumentOutOfRa [InlineData(-1, 200)] public void TransferPrecision_AfterInverse_ShouldReturnOriginalValues(int originalA, int originalB) { - var (encodedA, encodedB) = BitOperations.TransferPrecisionInverse(originalA, originalB); + (int encodedA, int encodedB) = BitOperations.TransferPrecisionInverse(originalA, originalB); // Apply regular to decode - var (decodedA, decodedB) = BitOperations.TransferPrecision(encodedA, encodedB); + (int decodedA, int decodedB) = BitOperations.TransferPrecision(encodedA, encodedB); decodedA.Should().Be(originalA); decodedB.Should().Be(originalB); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs index 4e187a0e..1e7dd250 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/BitStreamTests.cs @@ -11,7 +11,7 @@ public class BitStreamTests [Fact] public void Constructor_WithBitsAndLength_ShouldInitializeCorrectly() { - var stream = new BitStream(0b1010101010101010UL, 32); + BitStream stream = new(0b1010101010101010UL, 32); stream.Bits.Should().Be(32); } @@ -19,7 +19,7 @@ public void Constructor_WithBitsAndLength_ShouldInitializeCorrectly() [Fact] public void Constructor_WithoutParameters_ShouldInitializeEmpty() { - var stream = default(BitStream); + BitStream stream = default; stream.Bits.Should().Be(0); } @@ -27,9 +27,9 @@ public void Constructor_WithoutParameters_ShouldInitializeEmpty() [Fact] public void TryGetBits_WithSingleBitFromZero_ShouldReturnZero() { - var stream = new BitStream(0UL, 1); + BitStream stream = new(0UL, 1); - var success = stream.TryGetBits(1, out var bits); + bool success = stream.TryGetBits(1, out uint bits); success.Should().BeTrue(); bits.Should().Be(0U); @@ -38,10 +38,10 @@ public void TryGetBits_WithSingleBitFromZero_ShouldReturnZero() [Fact] public void TryGetBits_StreamEnd_ShouldReturnFalse() { - var stream = new BitStream(0UL, 1); + BitStream stream = new(0UL, 1); stream.TryGetBits(1, out _); - var success = stream.TryGetBits(1, out var _); + bool success = stream.TryGetBits(1, out uint _); success.Should().BeFalse(); } @@ -49,20 +49,20 @@ public void TryGetBits_StreamEnd_ShouldReturnFalse() [Fact] public void TryGetBits_WithAlternatingBitPattern_ShouldExtractCorrectly() { - var stream = new BitStream(0b1010101010101010UL, 32); + BitStream stream = new(0b1010101010101010UL, 32); - stream.TryGetBits(1, out var bits1).Should().BeTrue(); + stream.TryGetBits(1, out uint bits1).Should().BeTrue(); bits1.Should().Be(0U); - stream.TryGetBits(3, out var bits2).Should().BeTrue(); + stream.TryGetBits(3, out uint bits2).Should().BeTrue(); bits2.Should().Be(0b101U); - stream.TryGetBits(8, out var bits3).Should().BeTrue(); + stream.TryGetBits(8, out uint bits3).Should().BeTrue(); bits3.Should().Be(0b10101010U); stream.Bits.Should().Be(20); - stream.TryGetBits(20, out var bits4).Should().BeTrue(); + stream.TryGetBits(20, out uint bits4).Should().BeTrue(); bits4.Should().Be(0b1010U); stream.Bits.Should().Be(0); } @@ -71,12 +71,12 @@ public void TryGetBits_WithAlternatingBitPattern_ShouldExtractCorrectly() public void TryGetBits_With64BitsOfOnes_ShouldReturnAllOnes() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; - var stream = new BitStream(allBits, 64); + BitStream stream = new(allBits, 64); // Check initial state stream.Bits.Should().Be(64); - var success = stream.TryGetBits(64, out var bits); + bool success = stream.TryGetBits(64, out ulong bits); success.Should().BeTrue(); bits.Should().Be(allBits); @@ -88,12 +88,12 @@ public void TryGetBits_With40BitsFromFullBits_ShouldReturnLower40Bits() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; const ulong expected40Bits = 0x000000FFFFFFFFFFUL; - var stream = new BitStream(allBits, 64); + BitStream stream = new(allBits, 64); // Check initial state stream.Bits.Should().Be(64); - var success = stream.TryGetBits(40, out var bits); + bool success = stream.TryGetBits(40, out ulong bits); success.Should().BeTrue(); bits.Should().Be(expected40Bits); @@ -105,15 +105,15 @@ public void TryGetBits_WithZeroBits_ShouldReturnZeroAndNotConsume() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; const ulong expected40Bits = 0x000000FFFFFFFFFFUL; - var stream = new BitStream(allBits, 32); + BitStream stream = new(allBits, 32); - stream.TryGetBits(0, out var bits1).Should().BeTrue(); + stream.TryGetBits(0, out ulong bits1).Should().BeTrue(); bits1.Should().Be(0UL); - stream.TryGetBits(32, out var bits2).Should().BeTrue(); + stream.TryGetBits(32, out ulong bits2).Should().BeTrue(); bits2.Should().Be(expected40Bits & 0xFFFFFFFFUL); - stream.TryGetBits(0, out var bits3).Should().BeTrue(); + stream.TryGetBits(0, out ulong bits3).Should().BeTrue(); bits3.Should().Be(0UL); stream.Bits.Should().Be(0); } @@ -121,13 +121,13 @@ public void TryGetBits_WithZeroBits_ShouldReturnZeroAndNotConsume() [Fact] public void PutBits_WithSmallValues_ShouldAccumulateCorrectly() { - var stream = default(BitStream); + BitStream stream = default; stream.PutBits(0U, 1); stream.PutBits(0b11U, 2); stream.Bits.Should().Be(3); - stream.TryGetBits(3, out var bits).Should().BeTrue(); + stream.TryGetBits(3, out uint bits).Should().BeTrue(); bits.Should().Be(0b110U); } @@ -135,12 +135,12 @@ public void PutBits_WithSmallValues_ShouldAccumulateCorrectly() public void PutBits_With64BitsOfOnes_ShouldStoreCorrectly() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; - var stream = default(BitStream); + BitStream stream = default; stream.PutBits(allBits, 64); stream.Bits.Should().Be(64); - stream.TryGetBits(64, out var bits).Should().BeTrue(); + stream.TryGetBits(64, out ulong bits).Should().BeTrue(); bits.Should().Be(allBits); stream.Bits.Should().Be(0); } @@ -150,11 +150,11 @@ public void PutBits_With40BitsOfOnes_ShouldMaskTo40Bits() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; const ulong expected40Bits = 0x000000FFFFFFFFFFUL; - var stream = default(BitStream); + BitStream stream = default; stream.PutBits(allBits, 40); - stream.TryGetBits(40, out var bits).Should().BeTrue(); + stream.TryGetBits(40, out ulong bits).Should().BeTrue(); bits.Should().Be(expected40Bits); stream.Bits.Should().Be(0); } @@ -164,13 +164,13 @@ public void PutBits_WithZeroBitsInterspersed_ShouldReturnValue() { const ulong allBits = 0xFFFFFFFFFFFFFFFFUL; const ulong expected40Bits = 0x000000FFFFFFFFFFUL; - var stream = default(BitStream); + BitStream stream = default; stream.PutBits(0U, 0); stream.PutBits((uint)(allBits & 0xFFFFFFFFUL), 32); stream.PutBits(0U, 0); - stream.TryGetBits(32, out var bits).Should().BeTrue(); + stream.TryGetBits(32, out ulong bits).Should().BeTrue(); bits.Should().Be(expected40Bits & 0xFFFFFFFFUL); stream.Bits.Should().Be(0); } @@ -178,16 +178,16 @@ public void PutBits_WithZeroBitsInterspersed_ShouldReturnValue() [Fact] public void PutBits_ThenGetBits_ShouldReturnValue() { - var stream = default(BitStream); + BitStream stream = default; const uint value1 = 0b101; const uint value2 = 0b11001100; stream.PutBits(value1, 3); stream.PutBits(value2, 8); - stream.TryGetBits(3, out var retrieved1).Should().BeTrue(); + stream.TryGetBits(3, out uint retrieved1).Should().BeTrue(); retrieved1.Should().Be(value1); - stream.TryGetBits(8, out var retrieved2).Should().BeTrue(); + stream.TryGetBits(8, out uint retrieved2).Should().BeTrue(); retrieved2.Should().Be(value2); } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs index e063a664..ae4b7730 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/CodecTests.cs @@ -12,15 +12,16 @@ namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; +#nullable enable public class CodecTests { [Fact] public void ASTCDecompressToRGBA_WithZeroWidth_ShouldReturnEmpty() { - var data = new byte[256]; + byte[] data = new byte[256]; const int height = 16; - var result = AstcDecoder.DecompressImage(data, 0, height, FootprintType.Footprint4x4); + Span result = AstcDecoder.DecompressImage(data, 0, height, FootprintType.Footprint4x4); result.ToArray().Should().BeEmpty(); } @@ -28,10 +29,10 @@ public void ASTCDecompressToRGBA_WithZeroWidth_ShouldReturnEmpty() [Fact] public void ASTCDecompressToRGBA_WithZeroHeight_ShouldReturnEmpty() { - var data = new byte[256]; + byte[] data = new byte[256]; const int width = 16; - var result = AstcDecoder.DecompressImage(data, width, 0, FootprintType.Footprint4x4); + Span result = AstcDecoder.DecompressImage(data, width, 0, FootprintType.Footprint4x4); result.ToArray().Should().BeEmpty(); } @@ -39,12 +40,12 @@ public void ASTCDecompressToRGBA_WithZeroHeight_ShouldReturnEmpty() [Fact] public void ASTCDecompressToRGBA_WithDataSizeNotMultipleOfBlockSize_ShouldReturnEmpty() { - var data = new byte[256]; + byte[] data = new byte[256]; const int width = 16; const int height = 16; - var invalidData = data.AsSpan(0, data.Length - 1).ToArray(); + byte[] invalidData = data.AsSpan(0, data.Length - 1).ToArray(); - var result = AstcDecoder.DecompressImage(invalidData, width, height, FootprintType.Footprint4x4); + Span result = AstcDecoder.DecompressImage(invalidData, width, height, FootprintType.Footprint4x4); result.ToArray().Should().BeEmpty(); } @@ -52,12 +53,12 @@ public void ASTCDecompressToRGBA_WithDataSizeNotMultipleOfBlockSize_ShouldReturn [Fact] public void ASTCDecompressToRGBA_WithMismatchedBlockCount_ShouldReturnEmpty() { - var data = new byte[256]; + byte[] data = new byte[256]; const int width = 16; const int height = 16; - var mismatchedData = data.AsSpan(0, data.Length - PhysicalBlock.SizeInBytes).ToArray(); + byte[] mismatchedData = data.AsSpan(0, data.Length - PhysicalBlock.SizeInBytes).ToArray(); - var result = AstcDecoder.DecompressImage(mismatchedData, width, height, FootprintType.Footprint4x4); + Span result = AstcDecoder.DecompressImage(mismatchedData, width, height, FootprintType.Footprint4x4); result.ToArray().Should().BeEmpty(); } @@ -73,8 +74,8 @@ public void ASTCDecompressToRGBA_WithValidData_ShouldMatchExpected( int width, int height) { - var astcData = TestFile.Create(Path.Combine(TestImages.Astc.InputFolder, imageName + ".astc")).Bytes[16..]; - var footprint = Footprint.FromFootprintType(footprintType); + byte[] astcData = TestFile.Create(Path.Combine(TestImages.Astc.InputFolder, imageName + ".astc")).Bytes[16..]; + Footprint footprint = Footprint.FromFootprintType(footprintType); int blockWidth = footprint.Width; int blockHeight = footprint.Height; int blocksWide = (width + blockWidth - 1) / blockWidth; @@ -88,10 +89,10 @@ public void ASTCDecompressToRGBA_WithValidData_ShouldMatchExpected( // Verify all blocks can be unpacked for (int i = 0; i < astcData.Length; i += PhysicalBlock.SizeInBytes) { - var block = astcData.AsSpan(i, PhysicalBlock.SizeInBytes).ToArray(); - var bits = new UInt128(BitConverter.ToUInt64(block, 8), BitConverter.ToUInt64(block, 0)); - var info = BlockInfo.Decode(bits); - var logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, bits, in info); + byte[] block = astcData.AsSpan(i, PhysicalBlock.SizeInBytes).ToArray(); + UInt128 bits = new(BitConverter.ToUInt64(block, 8), BitConverter.ToUInt64(block, 0)); + BlockInfo info = BlockInfo.Decode(bits); + LogicalBlock? logicalBlock = LogicalBlock.UnpackLogicalBlock(footprint, bits, in info); logicalBlock.Should().NotBeNull("all blocks should unpack successfully"); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs index 3a54151c..0aaea912 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/EndpointCodecTests.cs @@ -20,13 +20,13 @@ public class EndpointCodecTests [InlineData(EndpointEncodingMode.DirectRgba)] internal void EncodeColorsForMode_WithVariousRanges_ShouldProduceValidQuantizedValues(EndpointEncodingMode mode) { - var low = new RgbaColor(0, 0, 0, 0); - var high = new RgbaColor(255, 255, 255, 255); + RgbaColor low = new(0, 0, 0, 0); + RgbaColor high = new(255, 255, 255, 255); for (int quantRange = 5; quantRange < 256; quantRange++) { - var values = new List(); - EndpointEncoder.EncodeColorsForMode(low, high, quantRange, mode, out var _, values); + List values = []; + EndpointEncoder.EncodeColorsForMode(low, high, quantRange, mode, out ColorEndpointMode _, values); // Assert value count matches expected values.Should().HaveCount(mode.GetValuesCount()); @@ -45,12 +45,12 @@ internal void EncodeColorsForMode_WithVariousRanges_ShouldProduceValidQuantizedV [InlineData(EndpointEncodingMode.DirectRgba)] internal void EncodeDecodeColors_WithBlackAndWhite_ShouldPreserveColors(EndpointEncodingMode mode) { - var white = new RgbaColor(255, 255, 255, 255); - var black = new RgbaColor(0, 0, 0, 255); + RgbaColor white = new(255, 255, 255, 255); + RgbaColor black = new(0, 0, 0, 255); for (int quantRange = 5; quantRange < 256; ++quantRange) { - var (low, high) = EncodeAndDecodeColors(white, black, quantRange, mode); + (RgbaColor low, RgbaColor high) = EncodeAndDecodeColors(white, black, quantRange, mode); (low == white).Should().BeTrue(); (high == black).Should().BeTrue(); @@ -60,7 +60,7 @@ internal void EncodeDecodeColors_WithBlackAndWhite_ShouldPreserveColors(Endpoint [Fact] public void UsesBlueContract_WithDirectModes_ShouldDetectCorrectly() { - var values = new List { 132, 127, 116, 112, 183, 180, 31, 22 }; + List values = [132, 127, 116, 112, 183, 180, 31, 22]; EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbDirect, values).Should().BeTrue(); EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbaDirect, values).Should().BeTrue(); @@ -69,9 +69,9 @@ public void UsesBlueContract_WithDirectModes_ShouldDetectCorrectly() [Fact] public void UsesBlueContract_WithOffsetModes_ShouldDetectBasedOnBitFlags() { - var baseValues = new List { 132, 127, 116, 112, 183, 180, 31, 22 }; + List baseValues = [132, 127, 116, 112, 183, 180, 31, 22]; - var valuesClearedBit6 = new List(baseValues); + List valuesClearedBit6 = [.. baseValues]; valuesClearedBit6[1] &= 0xBF; valuesClearedBit6[3] &= 0xBF; valuesClearedBit6[5] &= 0xBF; @@ -80,7 +80,7 @@ public void UsesBlueContract_WithOffsetModes_ShouldDetectBasedOnBitFlags() EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbBaseOffset, valuesClearedBit6).Should().BeFalse(); EndpointEncoder.UsesBlueContract(255, ColorEndpointMode.LdrRgbaBaseOffset, valuesClearedBit6).Should().BeFalse(); - var valuesSetBit6 = new List(baseValues); + List valuesSetBit6 = [.. baseValues]; valuesSetBit6[1] |= 0x40; valuesSetBit6[3] |= 0x40; valuesSetBit6[5] |= 0x40; @@ -93,19 +93,19 @@ public void UsesBlueContract_WithOffsetModes_ShouldDetectBasedOnBitFlags() [Fact] public void EncodeColorsForMode_WithRgbDirectAndSpecificPairs_ShouldUseBlueContract() { - var pairs = new[] - { + (RgbaColor, RgbaColor)[] pairs = + [ (new RgbaColor(22, 18, 30, 59), new RgbaColor(162, 148, 155, 59)), (new RgbaColor(22, 30, 27, 36), new RgbaColor(228, 221, 207, 36)), (new RgbaColor(54, 60, 55, 255), new RgbaColor(23, 30, 27, 255)) - }; + ]; const int endpointRange = 31; - foreach (var (low, high) in pairs) + foreach ((RgbaColor low, RgbaColor high) in pairs) { - var values = new List(); - EndpointEncoder.EncodeColorsForMode(low, high, endpointRange, EndpointEncodingMode.DirectRbg, out var astcMode, values); + List values = []; + EndpointEncoder.EncodeColorsForMode(low, high, endpointRange, EndpointEncodingMode.DirectRbg, out ColorEndpointMode astcMode, values); EndpointEncoder.UsesBlueContract(endpointRange, astcMode, values).Should().BeTrue(); } @@ -114,78 +114,84 @@ public void EncodeColorsForMode_WithRgbDirectAndSpecificPairs_ShouldUseBlueContr [Fact] public void EncodeDecodeColors_WithLumaDirect_ShouldProduceLumaValues() { - var mode = EndpointEncodingMode.DirectLuma; + EndpointEncodingMode mode = EndpointEncodingMode.DirectLuma; - var result1 = EncodeAndDecodeColors( + (RgbaColor low, RgbaColor high) = EncodeAndDecodeColors( new RgbaColor(247, 248, 246, 255), new RgbaColor(2, 3, 1, 255), - 255, mode); + 255, + mode); - (result1.Low == new RgbaColor(247, 247, 247, 255)).Should().BeTrue(); - (result1.High == new RgbaColor(2, 2, 2, 255)).Should().BeTrue(); + (low == new RgbaColor(247, 247, 247, 255)).Should().BeTrue(); + (high == new RgbaColor(2, 2, 2, 255)).Should().BeTrue(); - var result2 = EncodeAndDecodeColors( + (RgbaColor low2, RgbaColor high2) = EncodeAndDecodeColors( new RgbaColor(80, 80, 50, 255), new RgbaColor(99, 255, 6, 255), - 255, mode); + 255, + mode); - (result2.Low == new RgbaColor(70, 70, 70, 255)).Should().BeTrue(); - (result2.High == new RgbaColor(120, 120, 120, 255)).Should().BeTrue(); + (low2 == new RgbaColor(70, 70, 70, 255)).Should().BeTrue(); + (high2 == new RgbaColor(120, 120, 120, 255)).Should().BeTrue(); - var result3 = EncodeAndDecodeColors( + (RgbaColor low3, RgbaColor high3) = EncodeAndDecodeColors( new RgbaColor(247, 248, 246, 255), new RgbaColor(2, 3, 1, 255), - 15, mode); + 15, + mode); - (result3.Low == new RgbaColor(255, 255, 255, 255)).Should().BeTrue(); - (result3.High == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); + (low3 == new RgbaColor(255, 255, 255, 255)).Should().BeTrue(); + (high3 == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); - var result4 = EncodeAndDecodeColors( + (RgbaColor low4, RgbaColor high4) = EncodeAndDecodeColors( new RgbaColor(64, 127, 192, 255), new RgbaColor(0, 0, 0, 255), - 63, mode); + 63, + mode); - (result4.Low == new RgbaColor(130, 130, 130, 255)).Should().BeTrue(); - (result4.High == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); + (low4 == new RgbaColor(130, 130, 130, 255)).Should().BeTrue(); + (high4 == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); } [Fact] public void EncodeDecodeColors_WithLumaAlphaDirect_ShouldPreserveLumaAndAlpha() { - var mode = EndpointEncodingMode.DirectLumaAlpha; + EndpointEncodingMode mode = EndpointEncodingMode.DirectLumaAlpha; // Grey with varying alpha - var result1 = EncodeAndDecodeColors( + (RgbaColor low, RgbaColor high) = EncodeAndDecodeColors( new RgbaColor(64, 127, 192, 127), new RgbaColor(0, 0, 0, 20), - 63, mode); + 63, + mode); - ((result1.Low == new RgbaColor(130, 130, 130, 125)) || - result1.Low.IsCloseTo(new RgbaColor(130, 130, 130, 125), 1)).Should().BeTrue(); - ((result1.High == new RgbaColor(0, 0, 0, 20)) || - result1.High.IsCloseTo(new RgbaColor(0, 0, 0, 20), 1)).Should().BeTrue(); + ((low == new RgbaColor(130, 130, 130, 125)) || + low.IsCloseTo(new RgbaColor(130, 130, 130, 125), 1)).Should().BeTrue(); + ((high == new RgbaColor(0, 0, 0, 20)) || + high.IsCloseTo(new RgbaColor(0, 0, 0, 20), 1)).Should().BeTrue(); // Different alpha values - var result2 = EncodeAndDecodeColors( + (RgbaColor low2, RgbaColor high2) = EncodeAndDecodeColors( new RgbaColor(247, 248, 246, 250), new RgbaColor(2, 3, 1, 172), - 255, mode); + 255, + mode); - (result2.Low == new RgbaColor(247, 247, 247, 250)).Should().BeTrue(); - (result2.High == new RgbaColor(2, 2, 2, 172)).Should().BeTrue(); + (low2 == new RgbaColor(247, 247, 247, 250)).Should().BeTrue(); + (high2 == new RgbaColor(2, 2, 2, 172)).Should().BeTrue(); } [Fact] public void EncodeDecodeColors_WithRgbDirectAndRandomColors_ShouldPreserveColors() { - var mode = EndpointEncodingMode.DirectRbg; - var random = new Random(unchecked((int)0xdeadbeef)); + EndpointEncodingMode mode = EndpointEncodingMode.DirectRbg; + Random random = new(unchecked((int)0xdeadbeef)); for (int i = 0; i < 100; ++i) { - var low = new RgbaColor(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); - var high = new RgbaColor(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); - var (low1, high1) = EncodeAndDecodeColors(low, high, 255, mode); + RgbaColor low = new(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); + RgbaColor high = new(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); + (RgbaColor low1, RgbaColor high1) = EncodeAndDecodeColors(low, high, 255, mode); (low1 == low).Should().BeTrue(); (high1 == high).Should().BeTrue(); @@ -195,71 +201,75 @@ public void EncodeDecodeColors_WithRgbDirectAndRandomColors_ShouldPreserveColors [Fact] public void EncodeDecodeColors_WithRgbDirectAndSpecificColors_ShouldMatchExpected() { - var mode = EndpointEncodingMode.DirectRbg; + EndpointEncodingMode mode = EndpointEncodingMode.DirectRbg; - var result1 = EncodeAndDecodeColors( + (RgbaColor low, RgbaColor high) = EncodeAndDecodeColors( new RgbaColor(64, 127, 192, 255), new RgbaColor(0, 0, 0, 255), - 63, mode); + 63, + mode); - (result1.Low == new RgbaColor(65, 125, 190, 255)).Should().BeTrue(); - (result1.High == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); + (low == new RgbaColor(65, 125, 190, 255)).Should().BeTrue(); + (high == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); - var result2 = EncodeAndDecodeColors( + (RgbaColor low2, RgbaColor high2) = EncodeAndDecodeColors( new RgbaColor(0, 0, 0, 255), new RgbaColor(64, 127, 192, 255), - 63, mode); + 63, + mode); - (result2.Low == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); - (result2.High == new RgbaColor(65, 125, 190, 255)).Should().BeTrue(); + (low2 == new RgbaColor(0, 0, 0, 255)).Should().BeTrue(); + (high2 == new RgbaColor(65, 125, 190, 255)).Should().BeTrue(); } [Fact] public void EncodeDecodeColors_WithRgbBaseScaleAndIdenticalColors_ShouldBeCloseToOriginal() { - var mode = EndpointEncodingMode.BaseScaleRgb; - var random = new Random(unchecked((int)0xdeadbeef)); + EndpointEncodingMode mode = EndpointEncodingMode.BaseScaleRgb; + Random random = new(unchecked((int)0xdeadbeef)); for (int i = 0; i < 100; ++i) { - var color = new RgbaColor(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); - var result = EncodeAndDecodeColors(color, color, 255, mode); + RgbaColor color = new(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256), 255); + (RgbaColor low, RgbaColor high) = EncodeAndDecodeColors(color, color, 255, mode); - result.Low.IsCloseTo(color, 1).Should().BeTrue(); - result.High.IsCloseTo(color, 1).Should().BeTrue(); + low.IsCloseTo(color, 1).Should().BeTrue(); + high.IsCloseTo(color, 1).Should().BeTrue(); } } [Fact] public void EncodeDecodeColors_WithRgbBaseScaleAndDifferentColors_ShouldMatchExpected() { - var mode = EndpointEncodingMode.BaseScaleRgb; - var low = new RgbaColor(20, 4, 40, 255); - var high = new RgbaColor(80, 16, 160, 255); + EndpointEncodingMode mode = EndpointEncodingMode.BaseScaleRgb; + RgbaColor low = new(20, 4, 40, 255); + RgbaColor high = new(80, 16, 160, 255); - var result1 = EncodeAndDecodeColors(low, high, 255, mode); - result1.Low.IsCloseTo(low, 0).Should().BeTrue(); - result1.High.IsCloseTo(high, 0).Should().BeTrue(); + (RgbaColor decodedLow, RgbaColor decodedHigh) = EncodeAndDecodeColors(low, high, 255, mode); + decodedLow.IsCloseTo(low, 0).Should().BeTrue(); + decodedHigh.IsCloseTo(high, 0).Should().BeTrue(); - var result2 = EncodeAndDecodeColors(low, high, 127, mode); - result2.Low.IsCloseTo(low, 1).Should().BeTrue(); - result2.High.IsCloseTo(high, 1).Should().BeTrue(); + (RgbaColor low2, RgbaColor high2) = EncodeAndDecodeColors(low, high, 127, mode); + low2.IsCloseTo(low, 1).Should().BeTrue(); + high2.IsCloseTo(high, 1).Should().BeTrue(); } - public static IEnumerable RgbBaseOffsetColorPairs() + internal static TheoryData RgbBaseOffsetColorPairs() => new() { - yield return new object[] { new RgbaColor(80, 16, 112, 255), new RgbaColor(87, 18, 132, 255) }; - yield return new object[] { new RgbaColor(80, 74, 82, 255), new RgbaColor(90, 92, 110, 255) }; - yield return new object[] { new RgbaColor(0, 0, 0, 255), new RgbaColor(2, 2, 2, 255) }; - } + { new RgbaColor(80, 16, 112, 255), new RgbaColor(87, 18, 132, 255) }, + { new RgbaColor(80, 74, 82, 255), new RgbaColor(90, 92, 110, 255) }, + { new RgbaColor(0, 0, 0, 255), new RgbaColor(2, 2, 2, 255) }, + }; [Theory] +#pragma warning disable xUnit1016 // MemberData is internal because RgbaColor is internal [MemberData(nameof(RgbBaseOffsetColorPairs))] +#pragma warning restore xUnit1016 internal void DecodeColorsForMode_WithRgbBaseOffset_AndSpecificColorPairs_ShouldDecodeCorrectly( RgbaColor expectedLow, RgbaColor expectedHigh) { - var values = EncodeRgbBaseOffset(expectedLow, expectedHigh); - var (decLow, decHigh) = EndpointCodec.DecodeColorsForMode(values, 255, ColorEndpointMode.LdrRgbBaseOffset); + int[] values = EncodeRgbBaseOffset(expectedLow, expectedHigh); + (RgbaColor decLow, RgbaColor decHigh) = EndpointCodec.DecodeColorsForMode(values, 255, ColorEndpointMode.LdrRgbBaseOffset); (decLow == expectedLow).Should().BeTrue(); (decHigh == expectedHigh).Should().BeTrue(); @@ -268,7 +278,7 @@ internal void DecodeColorsForMode_WithRgbBaseOffset_AndSpecificColorPairs_Should [Fact] public void DecodeColorsForMode_WithRgbBaseOffset_AndIdenticalColors_ShouldDecodeCorrectly() { - var random = new Random(unchecked((int)0xdeadbeef)); + Random random = new(unchecked((int)0xdeadbeef)); for (int i = 0; i < 100; ++i) { @@ -278,11 +288,13 @@ public void DecodeColorsForMode_WithRgbBaseOffset_AndIdenticalColors_ShouldDecod // Ensure even channels (reference test skips odd) if (((r | g | b) & 1) != 0) + { continue; + } - var color = new RgbaColor(r, g, b, 255); - var values = EncodeRgbBaseOffset(color, color); - var (decLow, decHigh) = EndpointCodec.DecodeColorsForMode(values, 255, ColorEndpointMode.LdrRgbBaseOffset); + RgbaColor color = new(r, g, b, 255); + int[] values = EncodeRgbBaseOffset(color, color); + (RgbaColor decLow, RgbaColor decHigh) = EndpointCodec.DecodeColorsForMode(values, 255, ColorEndpointMode.LdrRgbBaseOffset); (decLow == color).Should().BeTrue(); (decHigh == color).Should().BeTrue(); @@ -291,18 +303,21 @@ public void DecodeColorsForMode_WithRgbBaseOffset_AndIdenticalColors_ShouldDecod private static int[] EncodeRgbBaseOffset(RgbaColor low, RgbaColor high) { - var values = new List(); + List values = []; for (int i = 0; i < 3; ++i) { bool isLarge = low[i] >= 128; values.Add((low[i] * 2) & 0xFF); int diff = (high[i] - low[i]) * 2; if (isLarge) + { diff |= 0x80; + } + values.Add(diff); } - return values.ToArray(); + return [.. values]; } [Fact] @@ -317,12 +332,12 @@ public void DecodeCheckerboard_ShouldDecodeToGrayscaleEndpoints() { // Read block bytes UInt128 blockData = BinaryPrimitives.ReadUInt128LittleEndian(astcData.AsSpan(i, PhysicalBlock.SizeInBytes)); - var physicalBlock = PhysicalBlock.Create(blockData); + PhysicalBlock physicalBlock = PhysicalBlock.Create(blockData); // Unpack to intermediate block - var intermediateBlock = IntermediateBlock.UnpackIntermediateBlock(physicalBlock); + IntermediateBlock.IntermediateBlockData? intermediateBlock = IntermediateBlock.UnpackIntermediateBlock(physicalBlock); intermediateBlock.Should().NotBeNull("checkerboard blocks should not be void extent"); - var ib = intermediateBlock!.Value; + IntermediateBlock.IntermediateBlockData ib = intermediateBlock!.Value; // Verify endpoints exist ib.EndpointCount.Should().BeGreaterThan(0, "block should have endpoints"); @@ -333,9 +348,9 @@ public void DecodeCheckerboard_ShouldDecodeToGrayscaleEndpoints() // Check all endpoint pairs decode successfully to grayscale colors for (int ep = 0; ep < ib.EndpointCount; ep++) { - var endpoints = ib.Endpoints[ep]; + IntermediateBlock.IntermediateEndpointData endpoints = ib.Endpoints[ep]; ReadOnlySpan colorSpan = ((ReadOnlySpan)endpoints.Colors)[..endpoints.ColorCount]; - var (low, high) = EndpointCodec.DecodeColorsForMode( + (RgbaColor low, RgbaColor high) = EndpointCodec.DecodeColorsForMode( colorSpan, colorRange, endpoints.Mode); @@ -360,9 +375,9 @@ private static (RgbaColor Low, RgbaColor High) EncodeAndDecodeColors( int quantRange, EndpointEncodingMode mode) { - var values = new List(); - var needsSwap = EndpointEncoder.EncodeColorsForMode(low, high, quantRange, mode, out var astcMode, values); - var (decLow, decHigh) = EndpointCodec.DecodeColorsForMode(values.ToArray(), quantRange, astcMode); + List values = []; + bool needsSwap = EndpointEncoder.EncodeColorsForMode(low, high, quantRange, mode, out ColorEndpointMode astcMode, values); + (RgbaColor decLow, RgbaColor decHigh) = EndpointCodec.DecodeColorsForMode(values.ToArray(), quantRange, astcMode); return needsSwap ? (decHigh, decLow) : (decLow, decHigh); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs index d57c4acb..df95f691 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/FootprintTests.cs @@ -26,7 +26,7 @@ public class FootprintTests public void FromFootprintType_WithValidType_ShouldReturnCorrectDimensions( FootprintType type, int expectedWidth, int expectedHeight) { - var footprint = Footprint.FromFootprintType(type); + Footprint footprint = Footprint.FromFootprintType(type); footprint.Type.Should().Be(type); footprint.Width.Should().Be(expectedWidth); @@ -37,16 +37,16 @@ public void FromFootprintType_WithValidType_ShouldReturnCorrectDimensions( [Fact] public void FromFootprintType_WithAllValidTypes_ShouldReturnUniqueFootprints() { - var allTypes = new[] - { + FootprintType[] allTypes = + [ FootprintType.Footprint4x4, FootprintType.Footprint5x4, FootprintType.Footprint5x5, FootprintType.Footprint6x5, FootprintType.Footprint6x6, FootprintType.Footprint8x5, FootprintType.Footprint8x6, FootprintType.Footprint8x8, FootprintType.Footprint10x5, FootprintType.Footprint10x6, FootprintType.Footprint10x8, FootprintType.Footprint10x10, FootprintType.Footprint12x10, FootprintType.Footprint12x12 - }; + ]; - var footprints = allTypes.Select(Footprint.FromFootprintType).ToList(); + List footprints = [.. allTypes.Select(Footprint.FromFootprintType)]; footprints.Should().HaveCount(allTypes.Length); footprints.Should().OnlyHaveUniqueItems(); @@ -55,7 +55,7 @@ public void FromFootprintType_WithAllValidTypes_ShouldReturnUniqueFootprints() [Fact] public void Footprint_PixelCount_ShouldEqualWidthTimesHeight() { - var footprint = Footprint.FromFootprintType(FootprintType.Footprint10x8); + Footprint footprint = Footprint.FromFootprintType(FootprintType.Footprint10x8); footprint.PixelCount.Should().Be(footprint.Width * footprint.Height); footprint.PixelCount.Should().Be(80); @@ -64,8 +64,8 @@ public void Footprint_PixelCount_ShouldEqualWidthTimesHeight() [Fact] public void Footprint_ValueEquality_WithSameType_ShouldBeEqual() { - var footprint1 = Footprint.FromFootprintType(FootprintType.Footprint6x6); - var footprint2 = Footprint.FromFootprintType(FootprintType.Footprint6x6); + Footprint footprint1 = Footprint.FromFootprintType(FootprintType.Footprint6x6); + Footprint footprint2 = Footprint.FromFootprintType(FootprintType.Footprint6x6); footprint1.Should().Be(footprint2); (footprint1 == footprint2).Should().BeTrue(); @@ -74,8 +74,8 @@ public void Footprint_ValueEquality_WithSameType_ShouldBeEqual() [Fact] public void Footprint_ValueEquality_WithDifferentType_ShouldNotBeEqual() { - var footprint1 = Footprint.FromFootprintType(FootprintType.Footprint6x6); - var footprint2 = Footprint.FromFootprintType(FootprintType.Footprint8x8); + Footprint footprint1 = Footprint.FromFootprintType(FootprintType.Footprint6x6); + Footprint footprint2 = Footprint.FromFootprintType(FootprintType.Footprint8x8); footprint1.Should().NotBe(footprint2); (footprint1 != footprint2).Should().BeTrue(); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs index 7644c261..5ad72fc9 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrComparisonTests.cs @@ -17,20 +17,20 @@ public class HdrComparisonTests public void HdrFile_DecodedWithHdrApi_ShouldPreserveExtendedRange() { // HDR files should decode to values potentially exceeding 1.0 - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // Decode with HDR API - var hdrResult = AstcDecoder.DecompressHdrImage( + Span hdrResult = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); // Verify we get Float16 output hdrResult.Length.Should().Be(4); // 1 pixel, 4 channels // HDR content can have values > 1.0 (this file may or may not, but should allow it) - foreach (var value in hdrResult) + foreach (float value in hdrResult) { float.IsNaN(value).Should().BeFalse(); float.IsInfinity(value).Should().BeFalse(); @@ -42,19 +42,19 @@ public void HdrFile_DecodedWithHdrApi_ShouldPreserveExtendedRange() public void LdrFile_DecodedWithHdrApi_ShouldUpscaleToHdrRange() { // LDR files decoded with HDR API should produce values in 0.0-1.0 range - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // Decode with HDR API - var hdrResult = AstcDecoder.DecompressHdrImage( + Span hdrResult = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); hdrResult.Length.Should().Be(4); // LDR content should map to 0.0-1.0 range when decoded with HDR API - foreach (var value in hdrResult) + foreach (float value in hdrResult) { value.Should().BeGreaterThanOrEqualTo(0.0f); value.Should().BeLessThanOrEqualTo(1.0f); @@ -65,21 +65,21 @@ public void LdrFile_DecodedWithHdrApi_ShouldUpscaleToHdrRange() public void HdrFile_DecodedWithLdrApi_ShouldClampToByteRange() { // HDR files decoded with LDR API should clamp to 0-255 - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // Decode with LDR API - var ldrResult = AstcDecoder.DecompressImage(astcFile); + Span ldrResult = AstcDecoder.DecompressImage(astcFile); ldrResult.Length.Should().Be(4); // All values must be in LDR range - foreach (var value in ldrResult) + foreach (byte value in ldrResult) { - value.Should().BeGreaterThanOrEqualTo((byte)0); - value.Should().BeLessThanOrEqualTo((byte)255); + value.Should().BeGreaterThanOrEqualTo(0); + value.Should().BeLessThanOrEqualTo(255); } } @@ -87,14 +87,14 @@ public void HdrFile_DecodedWithLdrApi_ShouldClampToByteRange() public void LdrFile_DecodedWithBothApis_ShouldProduceConsistentValues() { // LDR content should produce equivalent results with both APIs - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // Decode with both APIs - var ldrResult = AstcDecoder.DecompressImage(astcFile); - var hdrResult = AstcDecoder.DecompressHdrImage( + Span ldrResult = AstcDecoder.DecompressImage(astcFile); + Span hdrResult = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); // Compare results - LDR byte should map to HDR float / 255.0 @@ -113,18 +113,18 @@ public void LdrFile_DecodedWithBothApis_ShouldProduceConsistentValues() public void HdrTile_ShouldDecodeSuccessfully() { // Test larger HDR tile decoding - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "hdr-tile.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "hdr-tile.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); - var hdrResult = AstcDecoder.DecompressHdrImage( + Span hdrResult = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); // Should produce Width * Height * 4 values hdrResult.Length.Should().Be(astcFile.Width * astcFile.Height * 4); - foreach (var value in hdrResult) + foreach (float value in hdrResult) { float.IsNaN(value).Should().BeFalse(); float.IsInfinity(value).Should().BeFalse(); @@ -135,14 +135,14 @@ public void HdrTile_ShouldDecodeSuccessfully() public void LdrTile_ShouldDecodeSuccessfully() { // Test larger LDR tile decoding - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "ldr-tile.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "ldr-tile.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // Decode with both APIs - var ldrResult = AstcDecoder.DecompressImage(astcFile); - var hdrResult = AstcDecoder.DecompressHdrImage( + Span ldrResult = AstcDecoder.DecompressImage(astcFile); + Span hdrResult = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); // Both should produce correct output sizes @@ -154,14 +154,14 @@ public void LdrTile_ShouldDecodeSuccessfully() public void SameFootprint_HdrVsLdr_ShouldBothDecode() { // Verify files with same footprint decode correctly - var hdrPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); - var ldrPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); + string hdrPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); + string ldrPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); - var hdrData = File.ReadAllBytes(hdrPath); - var ldrData = File.ReadAllBytes(ldrPath); + byte[] hdrData = File.ReadAllBytes(hdrPath); + byte[] ldrData = File.ReadAllBytes(ldrPath); - var hdrFile = AstcFile.FromMemory(hdrData); - var ldrFile = AstcFile.FromMemory(ldrData); + AstcFile hdrFile = AstcFile.FromMemory(hdrData); + AstcFile ldrFile = AstcFile.FromMemory(ldrData); // Both are 1x1 with 6x6 footprint hdrFile.Width.Should().Be(ldrFile.Width); @@ -170,9 +170,9 @@ public void SameFootprint_HdrVsLdr_ShouldBothDecode() hdrFile.Footprint.Height.Should().Be(ldrFile.Footprint.Height); // Both should decode successfully with HDR API - var hdrDecoded = AstcDecoder.DecompressHdrImage( + Span hdrDecoded = AstcDecoder.DecompressHdrImage( hdrFile.Blocks, hdrFile.Width, hdrFile.Height, hdrFile.Footprint); - var ldrDecoded = AstcDecoder.DecompressHdrImage( + Span ldrDecoded = AstcDecoder.DecompressHdrImage( ldrFile.Blocks, ldrFile.Width, ldrFile.Height, ldrFile.Footprint); hdrDecoded.Length.Should().Be(4); @@ -183,20 +183,20 @@ public void SameFootprint_HdrVsLdr_ShouldBothDecode() public void HdrColor_FromLdr_ShouldMatchLdrToHdrApiConversion() { // Verify that HdrColor.FromRgba() produces same results as decoding LDR with HDR API - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "LDR-A-1x1.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // Decode with LDR API to get byte values - var ldrBytes = AstcDecoder.DecompressImage(astcFile); + Span ldrBytes = AstcDecoder.DecompressImage(astcFile); // Convert LDR bytes to HDR using HdrColor - var ldrColor = new RgbaColor(ldrBytes[0], ldrBytes[1], ldrBytes[2], ldrBytes[3]); - var hdrFromLdr = RgbaHdrColor.FromRgba(ldrColor); + RgbaColor ldrColor = new(ldrBytes[0], ldrBytes[1], ldrBytes[2], ldrBytes[3]); + RgbaHdrColor hdrFromLdr = RgbaHdrColor.FromRgba(ldrColor); // Decode with HDR API - var hdrDirect = AstcDecoder.DecompressHdrImage( + Span hdrDirect = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); // Compare: UNORM16 normalized values should match HDR API output diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs index 96a1a29e..50cf2594 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrDecoderTests.cs @@ -13,17 +13,17 @@ public class HdrDecoderTests public void DecompressToFloat16_WithValidBlock_ShouldProduceCorrectOutputSize() { // Create a simple 4x4 block (16 bytes) - var astcData = new byte[16]; + byte[] astcData = new byte[16]; - var footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); + Footprint footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); // Decompress using HDR API - var hdrResult = AstcDecoder.DecompressHdrImage(astcData, 4, 4, footprint); + Span hdrResult = AstcDecoder.DecompressHdrImage(astcData, 4, 4, footprint); // Verify output size: 4x4 pixels, 4 Half values (RGBA) per pixel hdrResult.Length.Should().Be(4 * 4 * 4); // 64 Half values total - foreach (var value in hdrResult) + foreach (float value in hdrResult) { float.IsNaN(value).Should().BeFalse(); float.IsInfinity(value).Should().BeFalse(); @@ -38,21 +38,21 @@ public void DecompressToFloat16_WithValidBlock_ShouldProduceCorrectOutputSize() public void DecompressToFloat16_WithDifferentFootprints_ShouldWork() { // Test that HDR API works with various footprint types - var footprints = new[] - { + FootprintType[] footprints = + [ FootprintType.Footprint4x4, FootprintType.Footprint5x5, FootprintType.Footprint6x6, FootprintType.Footprint8x8 - }; + ]; - foreach (var footprint in footprints) + foreach (FootprintType footprint in footprints) { // Create a simple test: 1 block (footprint size) of zeros - var fp = Footprint.FromFootprintType(footprint); - var astcData = new byte[16]; // One ASTC block (all zeros = void extent block) + Footprint fp = Footprint.FromFootprintType(footprint); + byte[] astcData = new byte[16]; // One ASTC block (all zeros = void extent block) - var result = AstcDecoder.DecompressHdrImage(astcData, fp.Width, fp.Height, footprint); + Span result = AstcDecoder.DecompressHdrImage(astcData, fp.Width, fp.Height, footprint); // Should produce footprint.Width * footprint.Height pixels, each with 4 Half values result.Length.Should().Be(fp.Width * fp.Height * 4); @@ -62,9 +62,9 @@ public void DecompressToFloat16_WithDifferentFootprints_ShouldWork() [Fact] public void ASTCDecompressToFloat16_WithInvalidData_ShouldReturnEmpty() { - var emptyData = Array.Empty(); + byte[] emptyData = []; - var result = AstcDecoder.DecompressHdrImage(emptyData, 64, 64, FootprintType.Footprint4x4); + Span result = AstcDecoder.DecompressHdrImage(emptyData, 64, 64, FootprintType.Footprint4x4); result.Length.Should().Be(0); } @@ -72,10 +72,10 @@ public void ASTCDecompressToFloat16_WithInvalidData_ShouldReturnEmpty() [Fact] public void DecompressToFloat16_WithZeroDimensions_ShouldReturnEmpty() { - var astcData = new byte[16]; - var footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); + byte[] astcData = new byte[16]; + Footprint footprint = Footprint.FromFootprintType(FootprintType.Footprint4x4); - var result = AstcDecoder.DecompressHdrImage(astcData, 0, 0, footprint); + Span result = AstcDecoder.DecompressHdrImage(astcData, 0, 0, footprint); result.Length.Should().Be(0); } @@ -83,10 +83,10 @@ public void DecompressToFloat16_WithZeroDimensions_ShouldReturnEmpty() [Fact] public void HdrColor_LdrRoundTrip_ShouldPreserveValues() { - var ldrColor = new RgbaColor(50, 100, 150, 200); + RgbaColor ldrColor = new(50, 100, 150, 200); - var hdrColor = RgbaHdrColor.FromRgba(ldrColor); - var backToLdr = hdrColor.ToLowDynamicRange(); + RgbaHdrColor hdrColor = RgbaHdrColor.FromRgba(ldrColor); + RgbaColor backToLdr = hdrColor.ToLowDynamicRange(); backToLdr.R.Should().Be(ldrColor.R); backToLdr.G.Should().Be(ldrColor.G); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs index 9d5e15ee..f308c25b 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/HdrImageTests.cs @@ -20,10 +20,10 @@ public class HdrImageTests [Description("Verify that the ASTC file header is correctly parsed for HDR content, including footprint detection")] public void DecodeHdrFile_VerifyFootprintDetection() { - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // The HDR-A-1x1.astc file has a 6x6 footprint based on the header astcFile.Footprint.Width.Should().Be(6); @@ -34,12 +34,12 @@ public void DecodeHdrFile_VerifyFootprintDetection() [Fact] public void DecodeHdrAstcFile_1x1Pixel_ShouldProduceValidHdrOutput() { - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); - var hdrResult = AstcDecoder.DecompressHdrImage( + Span hdrResult = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, @@ -50,7 +50,7 @@ public void DecodeHdrAstcFile_1x1Pixel_ShouldProduceValidHdrOutput() // HDR values can exceed 1.0 // Just verify they're in a reasonable range (0.0 to 10.0) - foreach (var value in hdrResult) + foreach (float value in hdrResult) { value.Should().BeGreaterThanOrEqualTo(0.0f); value.Should().BeLessThan(10.0f); @@ -60,12 +60,12 @@ public void DecodeHdrAstcFile_1x1Pixel_ShouldProduceValidHdrOutput() [Fact] public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() { - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "hdr-tile.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "hdr-tile.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); - var hdrResult = AstcDecoder.DecompressHdrImage( + Span hdrResult = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, @@ -76,10 +76,12 @@ public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() // Verify at least some HDR values exceed 1.0 (typical for HDR content) int valuesGreaterThanOne = 0; - foreach (var v in hdrResult) + foreach (float v in hdrResult) { if (v > 1.0f) + { valuesGreaterThanOne++; + } } valuesGreaterThanOne.Should().Be(64); @@ -89,24 +91,24 @@ public void DecodeHdrAstcFile_Tile_ShouldProduceValidHdrOutput() [Description("Verify that HDR ASTC files can be decoded with the LDR API, producing clamped values")] public void DecodeHdrAstcFile_WithLdrApi_ShouldClampValues() { - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); if (!File.Exists(astcPath)) { return; } - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // Decode using LDR API - var ldrResult = AstcDecoder.DecompressImage(astcFile); + Span ldrResult = AstcDecoder.DecompressImage(astcFile); // Should produce 1 pixel with 4 bytes (RGBA) ldrResult.Length.Should().Be(RgbaColor.BytesPerPixel); // All values should be in LDR range - foreach (var value in ldrResult) + foreach (byte value in ldrResult) { value.Should().BeGreaterThanOrEqualTo(byte.MinValue); value.Should().BeLessThanOrEqualTo(byte.MaxValue); @@ -117,15 +119,15 @@ public void DecodeHdrAstcFile_WithLdrApi_ShouldClampValues() [Description("Verify that HDR and LDR APIs produce consistent relative channel values for the same HDR ASTC file")] public void HdrAndLdrApis_OnSameHdrFile_ShouldProduceConsistentRelativeValues() { - var astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); + string astcPath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.HdrFolder, "HDR-A-1x1.astc")); - var astcData = File.ReadAllBytes(astcPath); - var astcFile = AstcFile.FromMemory(astcData); + byte[] astcData = File.ReadAllBytes(astcPath); + AstcFile astcFile = AstcFile.FromMemory(astcData); // Decode with both APIs - var hdrResult = AstcDecoder.DecompressHdrImage( + Span hdrResult = AstcDecoder.DecompressHdrImage( astcFile.Blocks, astcFile.Width, astcFile.Height, astcFile.Footprint); - var ldrResult = AstcDecoder.DecompressImage(astcFile); + Span ldrResult = AstcDecoder.DecompressImage(astcFile); // Both should produce output for 1 pixel hdrResult.Length.Should().Be(4); @@ -139,9 +141,13 @@ public void HdrAndLdrApis_OnSameHdrFile_ShouldProduceConsistentRelativeValues() for (int j = i + 1; j < 3; j++) { if (hdrResult[i] > hdrResult[j]) + { ldrResult[i].Should().BeGreaterThanOrEqualTo(ldrResult[j]); + } else if (hdrResult[i] < hdrResult[j]) + { ldrResult[i].Should().BeLessThanOrEqualTo(ldrResult[j]); + } } } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs index 225eb8d9..dc1a8c82 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/HDR/RgbaHdrColorTests.cs @@ -11,7 +11,7 @@ public class RgbaHdrColorTests [Fact] public void Constructor_WithValidValues_ShouldInitializeCorrectly() { - var color = new RgbaHdrColor(1000, 2000, 3000, 4000); + RgbaHdrColor color = new(1000, 2000, 3000, 4000); color.R.Should().Be(1000); color.G.Should().Be(2000); @@ -22,7 +22,7 @@ public void Constructor_WithValidValues_ShouldInitializeCorrectly() [Fact] public void Indexer_WithValidIndices_ShouldReturnCorrectChannels() { - var color = new RgbaHdrColor(1000, 2000, 3000, 4000); + RgbaHdrColor color = new(1000, 2000, 3000, 4000); color[0].Should().Be(1000); color[1].Should().Be(2000); @@ -33,7 +33,7 @@ public void Indexer_WithValidIndices_ShouldReturnCorrectChannels() [Fact] public void Indexer_WithInvalidIndex_ShouldThrowException() { - var color = new RgbaHdrColor(1000, 2000, 3000, 4000); + RgbaHdrColor color = new(1000, 2000, 3000, 4000); Action act = () => _ = color[4]; @@ -43,9 +43,9 @@ public void Indexer_WithInvalidIndex_ShouldThrowException() [Fact] public void FromLdr_WithMinMaxValues_ShouldScaleCorrectly() { - var ldrColor = new RgbaColor(0, 127, 255, 200); + RgbaColor ldrColor = new(0, 127, 255, 200); - var hdrColor = RgbaHdrColor.FromRgba(ldrColor); + RgbaHdrColor hdrColor = RgbaHdrColor.FromRgba(ldrColor); hdrColor.R.Should().Be(0); // 0 * 257 = 0 hdrColor.G.Should().Be(32639); // 127 * 257 = 32639 @@ -56,9 +56,9 @@ public void FromLdr_WithMinMaxValues_ShouldScaleCorrectly() [Fact] public void ToLdr_WithHdrValues_ShouldDownscaleCorrectly() { - var hdrColor = new RgbaHdrColor(0, 32639, 65535, 51400); + RgbaHdrColor hdrColor = new(0, 32639, 65535, 51400); - var ldrColor = hdrColor.ToLowDynamicRange(); + RgbaColor ldrColor = hdrColor.ToLowDynamicRange(); ldrColor.R.Should().Be(0); // 0 >> 8 = 0 ldrColor.G.Should().Be(127); // 32639 >> 8 = 127 @@ -69,10 +69,10 @@ public void ToLdr_WithHdrValues_ShouldDownscaleCorrectly() [Fact] public void FromLdr_ToLdr_RoundTrip_ShouldPreserveValues() { - var original = new RgbaColor(50, 100, 150, 200); + RgbaColor original = new(50, 100, 150, 200); - var hdrColor = RgbaHdrColor.FromRgba(original); - var result = hdrColor.ToLowDynamicRange(); + RgbaHdrColor hdrColor = RgbaHdrColor.FromRgba(original); + RgbaColor result = hdrColor.ToLowDynamicRange(); result.R.Should().Be(original.R); result.G.Should().Be(original.G); @@ -83,10 +83,10 @@ public void FromLdr_ToLdr_RoundTrip_ShouldPreserveValues() [Fact] public void IsCloseTo_WithSimilarColors_ShouldReturnTrue() { - var color1 = new RgbaHdrColor(1000, 2000, 3000, 4000); - var color2 = new RgbaHdrColor(1005, 1995, 3002, 3998); + RgbaHdrColor color1 = new(1000, 2000, 3000, 4000); + RgbaHdrColor color2 = new(1005, 1995, 3002, 3998); - var result = color1.IsCloseTo(color2, 10); + bool result = color1.IsCloseTo(color2, 10); result.Should().BeTrue(); } @@ -94,10 +94,10 @@ public void IsCloseTo_WithSimilarColors_ShouldReturnTrue() [Fact] public void IsCloseTo_WithDifferentColors_ShouldReturnFalse() { - var color1 = new RgbaHdrColor(1000, 2000, 3000, 4000); - var color2 = new RgbaHdrColor(1020, 2000, 3000, 4000); + RgbaHdrColor color1 = new(1000, 2000, 3000, 4000); + RgbaHdrColor color2 = new(1020, 2000, 3000, 4000); - var result = color1.IsCloseTo(color2, 10); + bool result = color1.IsCloseTo(color2, 10); result.Should().BeFalse(); } @@ -105,7 +105,7 @@ public void IsCloseTo_WithDifferentColors_ShouldReturnFalse() [Fact] public void Empty_ShouldReturnBlackTransparent() { - var empty = RgbaHdrColor.Empty; + RgbaHdrColor empty = RgbaHdrColor.Empty; empty.R.Should().Be(0); empty.G.Should().Be(0); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs index 98b908cd..757bec77 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegerSequenceCodecTests.cs @@ -16,7 +16,7 @@ public void GetPackingModeBitCount_ForValidRange_ShouldNotReturnUnknownMode() { for (int i = 1; i < 32; ++i) { - var (mode, _) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(i); + (BiseEncodingMode mode, int _) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(i); mode.Should().NotBe(BiseEncodingMode.Unknown, $"Range {i} should not yield Unknown encoding mode"); } } @@ -61,8 +61,8 @@ public void GetPackingModeBitCount_ForValidRange_ShouldMatchExpectedValues() for (int i = 1; i < 32; ++i) { - var (mode, bitCount) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(i); - var (expectedMode, expectedBitCount) = expected[i - 1]; + (BiseEncodingMode mode, int bitCount) = BoundedIntegerSequenceCodec.GetPackingModeBitCount(i); + (BiseEncodingMode expectedMode, int expectedBitCount) = expected[i - 1]; mode.Should().Be(expectedMode, $"range {i} mode should match"); bitCount.Should().Be(expectedBitCount, $"range {i} bit count should match"); @@ -74,7 +74,7 @@ public void GetPackingModeBitCount_ForValidRange_ShouldMatchExpectedValues() [InlineData(256)] public void GetPackingModeBitCount_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int range) { - var action = () => BoundedIntegerSequenceCodec.GetPackingModeBitCount(range); + Action action = () => BoundedIntegerSequenceCodec.GetPackingModeBitCount(range); action.Should().Throw(); } @@ -86,8 +86,8 @@ public void GetPackingModeBitCount_WithInvalidRange_ShouldThrowArgumentOutOfRang [InlineData(63)] public void GetBitCount_WithBitEncodingMode1Bit_ShouldReturnValueCount(int valueCount) { - var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.BitEncoding, valueCount, 1); - var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 1); + int bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.BitEncoding, valueCount, 1); + int bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 1); bitCount.Should().Be(valueCount); bitCountForRange.Should().Be(valueCount); @@ -100,8 +100,8 @@ public void GetBitCount_WithBitEncodingMode1Bit_ShouldReturnValueCount(int value [InlineData(32, 64)] public void GetBitCount_WithBitEncodingMode2Bits_ShouldReturnTwiceValueCount(int valueCount, int expected) { - var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.BitEncoding, valueCount, 2); - var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 3); + int bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.BitEncoding, valueCount, 2); + int bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 3); bitCount.Should().Be(expected); bitCountForRange.Should().Be(expected); @@ -112,10 +112,10 @@ public void GetBitCount_WithTritEncoding15Values_ShouldReturnExpectedBitCount() { const int valueCount = 15; const int bits = 3; - int expectedBitCount = 8 * 3 + 15 * 3; // 69 bits + int expectedBitCount = (8 * 3) + (15 * 3); // 69 bits - var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.TritEncoding, valueCount, bits); - var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 23); + int bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.TritEncoding, valueCount, bits); + int bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 23); bitCount.Should().Be(expectedBitCount); bitCountForRange.Should().Be(bitCount); @@ -128,8 +128,8 @@ public void GetBitCount_WithTritEncoding13Values_ShouldReturnExpectedBitCount() const int bits = 2; const int expectedBitCount = 47; - var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.TritEncoding, valueCount, bits); - var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 11); + int bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.TritEncoding, valueCount, bits); + int bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 11); bitCount.Should().Be(expectedBitCount); bitCountForRange.Should().Be(bitCount); @@ -140,10 +140,10 @@ public void GetBitCount_WithQuintEncoding6Values_ShouldReturnExpectedBitCount() { const int valueCount = 6; const int bits = 4; - int expectedBitCount = 7 * 2 + 6 * 4; // 38 bits + int expectedBitCount = (7 * 2) + (6 * 4); // 38 bits - var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.QuintEncoding, valueCount, bits); - var bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 79); + int bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.QuintEncoding, valueCount, bits); + int bitCountForRange = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, 79); bitCount.Should().Be(expectedBitCount); bitCountForRange.Should().Be(bitCount); @@ -154,12 +154,12 @@ public void GetBitCount_WithQuintEncoding7Values_ShouldReturnExpectedBitCount() { const int valueCount = 7; const int bits = 3; - int expectedBitCount = 7 * 2 + // First two quint blocks - 6 * 3 + // First two blocks of bits + int expectedBitCount = (7 * 2) + // First two quint blocks + (6 * 3) + // First two blocks of bits 3 + // Last quint block without high order four bits 3; // Last block with one set of three bits - var bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.QuintEncoding, valueCount, bits); + int bitCount = BoundedIntegerSequenceCodec.GetBitCount(BiseEncodingMode.QuintEncoding, valueCount, bits); bitCount.Should().Be(expectedBitCount); } @@ -168,25 +168,27 @@ public void GetBitCount_WithQuintEncoding7Values_ShouldReturnExpectedBitCount() public void EncodeDecode_WithQuintValues_ShouldEncodeAndDecodeExpectedValues() { const int valueRange = 79; - var encoder = new BoundedIntegerSequenceEncoder(valueRange); - var values = new[] { 3, 79, 37 }; + BoundedIntegerSequenceEncoder encoder = new(valueRange); + int[] values = [3, 79, 37]; - foreach (var value in values) + foreach (int value in values) + { encoder.AddValue(value); + } // Encode - var bitSink = default(BitStream); + BitStream bitSink = default; encoder.Encode(ref bitSink); // Verify encoded data bitSink.Bits.Should().Be(19); - bitSink.TryGetBits(19, out var encoded).Should().BeTrue(); + bitSink.TryGetBits(19, out ulong encoded).Should().BeTrue(); encoded.Should().Be(0x4A7D3UL); // Decode - var bitSrc = new BitStream(encoded, 19); - var decoder = new BoundedIntegerSequenceDecoder(valueRange); - var decoded = decoder.Decode(3, ref bitSrc); + BitStream bitSrc = new(encoded, 19); + BoundedIntegerSequenceDecoder decoder = new(valueRange); + int[] decoded = decoder.Decode(3, ref bitSrc); decoded.Should().Equal(values); } @@ -194,29 +196,32 @@ public void EncodeDecode_WithQuintValues_ShouldEncodeAndDecodeExpectedValues() [Fact] public void DecodeThenEncode_WithQuintValues_ShouldPreserveEncoding() { - var expectedValues = new[] { 16, 18, 17, 4, 7, 14, 10, 0 }; + int[] expectedValues = [16, 18, 17, 4, 7, 14, 10, 0]; const ulong encoding = 0x2b9c83dc; const int range = 19; // Decode - var bitSrc = new BitStream(encoding, 64); - var decoder = new BoundedIntegerSequenceDecoder(range); - var decoded = decoder.Decode(expectedValues.Length, ref bitSrc); + BitStream bitSrc = new(encoding, 64); + BoundedIntegerSequenceDecoder decoder = new(range); + int[] decoded = decoder.Decode(expectedValues.Length, ref bitSrc); // Check decoded values decoded.Should().HaveCount(expectedValues.Length); decoded.Should().Equal(expectedValues); // Re-encode - var bitSink = default(BitStream); - var encoder = new BoundedIntegerSequenceEncoder(range); - foreach (var value in expectedValues) + BitStream bitSink = default; + BoundedIntegerSequenceEncoder encoder = new(range); + foreach (int value in expectedValues) + { encoder.AddValue(value); + } + encoder.Encode(ref bitSink); // Re-encoded should match original bitSink.Bits.Should().Be(35); - bitSink.TryGetBits(35, out var reencoded).Should().BeTrue(); + bitSink.TryGetBits(35, out ulong reencoded).Should().BeTrue(); reencoded.Should().Be(encoding); } @@ -224,25 +229,27 @@ public void DecodeThenEncode_WithQuintValues_ShouldPreserveEncoding() public void EncodeDecode_WithTritValues_ShouldEncodeAndDecodeExpectedValues() { const int valueRange = 11; - var encoder = new BoundedIntegerSequenceEncoder(valueRange); - var values = new[] { 7, 5, 3, 6, 10 }; + BoundedIntegerSequenceEncoder encoder = new(valueRange); + int[] values = [7, 5, 3, 6, 10]; - foreach (var value in values) + foreach (int value in values) + { encoder.AddValue(value); + } // Encode - var bitSink = default(BitStream); + BitStream bitSink = default; encoder.Encode(ref bitSink); // Verify encoded data bitSink.Bits.Should().Be(18); - bitSink.TryGetBits(18, out var encoded).Should().BeTrue(); + bitSink.TryGetBits(18, out ulong encoded).Should().BeTrue(); encoded.Should().Be(0x37357UL); // Decode - var bitSrc = new BitStream(encoded, 19); - var decoder = new BoundedIntegerSequenceDecoder(valueRange); - var decoded = decoder.Decode(5, ref bitSrc); + BitStream bitSrc = new(encoded, 19); + BoundedIntegerSequenceDecoder decoder = new(valueRange); + int[] decoded = decoder.Decode(5, ref bitSrc); decoded.Should().Equal(values); } @@ -250,66 +257,75 @@ public void EncodeDecode_WithTritValues_ShouldEncodeAndDecodeExpectedValues() [Fact] public void DecodeThenEncode_WithTritValues_ShouldPreserveEncoding() { - var expectedValues = new[] { 6, 0, 0, 2, 0, 0, 0, 0, 8, 0, 0, 0, 0, 8, 8, 0 }; + int[] expectedValues = [6, 0, 0, 2, 0, 0, 0, 0, 8, 0, 0, 0, 0, 8, 8, 0]; const ulong encoding = 0x0004c0100001006UL; const int range = 11; // Decode - var bitSrc = new BitStream(encoding, 64); - var decoder = new BoundedIntegerSequenceDecoder(range); - var decoded = decoder.Decode(expectedValues.Length, ref bitSrc); + BitStream bitSrc = new(encoding, 64); + BoundedIntegerSequenceDecoder decoder = new(range); + int[] decoded = decoder.Decode(expectedValues.Length, ref bitSrc); // Check decoded values decoded.Should().HaveCount(expectedValues.Length); decoded.Should().Equal(expectedValues); // Re-encode - var bitSink = default(BitStream); - var encoder = new BoundedIntegerSequenceEncoder(range); - foreach (var value in expectedValues) + BitStream bitSink = default; + BoundedIntegerSequenceEncoder encoder = new(range); + foreach (int value in expectedValues) + { encoder.AddValue(value); + } + encoder.Encode(ref bitSink); // Assert re-encoded matches original bitSink.Bits.Should().Be(58); - bitSink.TryGetBits(58, out var reencoded).Should().BeTrue(); + bitSink.TryGetBits(58, out ulong reencoded).Should().BeTrue(); reencoded.Should().Be(encoding); } [Fact] public void EncodeDecode_WithRandomValues_ShouldAlwaysRoundTripCorrectly() { - var random = new Random(unchecked(0xbad7357)); + Random random = new(unchecked(0xbad7357)); const int testCount = 1600; for (int test = 0; test < testCount; test++) { - int valueCount = 4 + random.Next(0, 256) % 44; - int range = 1 + random.Next(0, 256) % 63; + int valueCount = 4 + (random.Next(0, 256) % 44); + int range = 1 + (random.Next(0, 256) % 63); int bitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(valueCount, range); if (bitCount >= 64) + { continue; + } // Generate random values - var generated = new List(valueCount); + List generated = new(valueCount); for (int i = 0; i < valueCount; i++) + { generated.Add(random.Next(range + 1)); + } // Encode - var bitSink = default(BitStream); - var encoder = new BoundedIntegerSequenceEncoder(range); - foreach (var value in generated) + BitStream bitSink = default; + BoundedIntegerSequenceEncoder encoder = new(range); + foreach (int value in generated) + { encoder.AddValue(value); + } encoder.Encode(ref bitSink); - bitSink.TryGetBits((int)bitSink.Bits, out var encoded).Should().BeTrue(); + bitSink.TryGetBits((int)bitSink.Bits, out ulong encoded).Should().BeTrue(); // Decode - var bitSrc = new BitStream(encoded, 64); - var decoder = new BoundedIntegerSequenceDecoder(range); - var decoded = decoder.Decode(valueCount, ref bitSrc); + BitStream bitSrc = new(encoded, 64); + BoundedIntegerSequenceDecoder decoder = new(range); + int[] decoded = decoder.Decode(valueCount, ref bitSrc); decoded.Should().HaveCount(generated.Count); decoded.Should().Equal(generated); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs index 7b2576e8..b03cc039 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntegrationTests.cs @@ -45,11 +45,11 @@ public class IntegrationTests [InlineData("rgb_12x12")] public void DecompressToImage_WithTestdataFile_ShouldDecodeSuccessfully(string basename) { - var filePath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.InputFolder, basename + ".astc")); - var bytes = File.ReadAllBytes(filePath); - var astc = AstcFile.FromMemory(bytes); + string filePath = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.InputFolder, basename + ".astc")); + byte[] bytes = File.ReadAllBytes(filePath); + AstcFile astc = AstcFile.FromMemory(bytes); - var result = AstcDecoder.DecompressImage(astc); + Span result = AstcDecoder.DecompressImage(astc); result.Length.Should().BeGreaterThan(0, because: $"decoding should succeed for {basename}"); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs index bb107c6a..7281d7ef 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/IntermediateBlockTests.cs @@ -8,6 +8,7 @@ namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; +#nullable enable public class IntermediateBlockTests { private static readonly UInt128 ErrorBlock = UInt128.Zero; @@ -15,9 +16,9 @@ public class IntermediateBlockTests [Fact] public void UnpackVoidExtent_WithErrorBlock_ShouldReturnNull() { - var errorBlock = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock errorBlock = PhysicalBlock.Create(ErrorBlock); - var result = IntermediateBlock.UnpackVoidExtent(errorBlock); + IntermediateBlock.VoidExtentData? result = IntermediateBlock.UnpackVoidExtent(errorBlock); result.Should().BeNull(); } @@ -25,9 +26,9 @@ public void UnpackVoidExtent_WithErrorBlock_ShouldReturnNull() [Fact] public void UnpackIntermediateBlock_WithErrorBlock_ShouldReturnNull() { - var errorBlock = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock errorBlock = PhysicalBlock.Create(ErrorBlock); - var result = IntermediateBlock.UnpackIntermediateBlock(errorBlock); + IntermediateBlock.IntermediateBlockData? result = IntermediateBlock.UnpackIntermediateBlock(errorBlock); result.Should().BeNull(); } @@ -35,14 +36,14 @@ public void UnpackIntermediateBlock_WithErrorBlock_ShouldReturnNull() [Fact] public void EndpointRangeForBlock_WithoutWeights_ShouldReturnNegativeOne() { - var data = new IntermediateBlock.IntermediateBlockData + IntermediateBlock.IntermediateBlockData data = new() { WeightRange = 15, WeightGridX = 6, WeightGridY = 6 }; - var result = IntermediateBlock.EndpointRangeForBlock(data); + int result = IntermediateBlock.EndpointRangeForBlock(data); result.Should().Be(-1); } @@ -50,14 +51,14 @@ public void EndpointRangeForBlock_WithoutWeights_ShouldReturnNegativeOne() [Fact] public void Pack_WithIncorrectNumberOfWeights_ShouldReturnError() { - var data = new IntermediateBlock.IntermediateBlockData + IntermediateBlock.IntermediateBlockData data = new() { WeightRange = 15, WeightGridX = 6, WeightGridY = 6 }; - var (error, _) = IntermediateBlockPacker.Pack(data); + (string? error, UInt128 _) = IntermediateBlockPacker.Pack(data); error.Should().NotBeNull(); error.Should().Contain("Incorrect number of weights"); @@ -66,7 +67,7 @@ public void Pack_WithIncorrectNumberOfWeights_ShouldReturnError() [Fact] public void EndpointRangeForBlock_WithNotEnoughBits_ShouldReturnNegativeTwo() { - var data = new IntermediateBlock.IntermediateBlockData + IntermediateBlock.IntermediateBlockData data = new() { WeightRange = 1, PartitionId = 0, @@ -78,7 +79,7 @@ public void EndpointRangeForBlock_WithNotEnoughBits_ShouldReturnNegativeTwo() data.Endpoints[1] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; data.Endpoints[2] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; - var result = IntermediateBlock.EndpointRangeForBlock(data); + int result = IntermediateBlock.EndpointRangeForBlock(data); result.Should().Be(-2); } @@ -86,7 +87,7 @@ public void EndpointRangeForBlock_WithNotEnoughBits_ShouldReturnNegativeTwo() [Fact] public void Pack_WithNotEnoughBitsForColors_ShouldReturnError() { - var data = new IntermediateBlock.IntermediateBlockData + IntermediateBlock.IntermediateBlockData data = new() { WeightRange = 1, PartitionId = 0, @@ -99,7 +100,7 @@ public void Pack_WithNotEnoughBitsForColors_ShouldReturnError() data.Endpoints[1] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; data.Endpoints[2] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; - var (error, _) = IntermediateBlockPacker.Pack(data); + (string? error, UInt128 _) = IntermediateBlockPacker.Pack(data); error.Should().NotBeNull(); error.Should().Contain("illegal color range"); @@ -108,7 +109,7 @@ public void Pack_WithNotEnoughBitsForColors_ShouldReturnError() [Fact] public void EndpointRangeForBlock_WithIncreasingWeightGrid_ShouldDecreaseColorRange() { - var data = new IntermediateBlock.IntermediateBlockData + IntermediateBlock.IntermediateBlockData data = new() { WeightRange = 2, DualPlaneChannel = null, @@ -117,15 +118,19 @@ public void EndpointRangeForBlock_WithIncreasingWeightGrid_ShouldDecreaseColorRa data.Endpoints[0] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; data.Endpoints[1] = new() { Mode = ColorEndpointMode.LdrRgbDirect }; - var weightParams = new List<(int w, int h)>(); + List<(int W, int H)> weightParams = []; for (int y = 2; y < 8; ++y) + { for (int x = 2; x < 8; ++x) + { weightParams.Add((x, y)); + } + } - weightParams.Sort((a, b) => (a.w * a.h).CompareTo(b.w * b.h)); + weightParams.Sort((a, b) => (a.W * a.H).CompareTo(b.W * b.H)); int lastColorRange = byte.MaxValue; - foreach (var (w, h) in weightParams) + foreach ((int w, int h) in weightParams) { data.WeightGridX = w; data.WeightGridY = h; @@ -141,13 +146,13 @@ public void EndpointRangeForBlock_WithIncreasingWeightGrid_ShouldDecreaseColorRa [Fact] public void EndpointRange_WithStandardBlock_ShouldBe255() { - var block = PhysicalBlock.Create((UInt128)0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create((UInt128)0x0000000001FE000173UL); - var data = IntermediateBlock.UnpackIntermediateBlock(block); + IntermediateBlock.IntermediateBlockData? data = IntermediateBlock.UnpackIntermediateBlock(block); block.GetColorValuesRange().Should().Be(255); data.Should().NotBeNull(); - var ib = data!.Value; + IntermediateBlock.IntermediateBlockData ib = data!.Value; ib.EndpointCount.Should().Be(1); ib.Endpoints[0].Mode.Should().Be(ColorEndpointMode.LdrLumaDirect); ib.Endpoints[0].Colors[0].Should().Be(byte.MinValue); @@ -159,12 +164,12 @@ public void EndpointRange_WithStandardBlock_ShouldBe255() [Fact] public void UnpackIntermediateBlock_WithStandardBlock_ShouldReturnCorrectData() { - var block = PhysicalBlock.Create((UInt128)0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create((UInt128)0x0000000001FE000173UL); - var result = IntermediateBlock.UnpackIntermediateBlock(block); + IntermediateBlock.IntermediateBlockData? result = IntermediateBlock.UnpackIntermediateBlock(block); result.Should().NotBeNull(); - var data = result!.Value; + IntermediateBlock.IntermediateBlockData data = result!.Value; data.WeightGridX.Should().Be(6); data.WeightGridY.Should().Be(5); @@ -176,7 +181,7 @@ public void UnpackIntermediateBlock_WithStandardBlock_ShouldReturnCorrectData() data.Weights.AsSpan(0, data.WeightsCount).ToArray().Should().AllBeEquivalentTo(0); data.EndpointCount.Should().Be(1); - var endpoint = data.Endpoints[0]; + IntermediateBlock.IntermediateEndpointData endpoint = data.Endpoints[0]; endpoint.Mode.Should().Be(ColorEndpointMode.LdrLumaDirect); endpoint.ColorCount.Should().Be(2); endpoint.Colors[0].Should().Be(byte.MinValue); @@ -186,7 +191,7 @@ public void UnpackIntermediateBlock_WithStandardBlock_ShouldReturnCorrectData() [Fact] public void Pack_WithStandardBlockData_ShouldProduceExpectedBits() { - var data = new IntermediateBlock.IntermediateBlockData + IntermediateBlock.IntermediateBlockData data = new() { WeightGridX = 6, WeightGridY = 5, @@ -196,7 +201,7 @@ public void Pack_WithStandardBlockData_ShouldProduceExpectedBits() Weights = new int[30] }; - var endpoint = new IntermediateBlock.IntermediateEndpointData + IntermediateBlock.IntermediateEndpointData endpoint = new() { Mode = ColorEndpointMode.LdrLumaDirect, ColorCount = 2 @@ -206,7 +211,7 @@ public void Pack_WithStandardBlockData_ShouldProduceExpectedBits() data.Endpoints[0] = endpoint; data.EndpointCount = 1; - var (error, packed) = IntermediateBlockPacker.Pack(data); + (string? error, UInt128 packed) = IntermediateBlockPacker.Pack(data); error.Should().BeNull(); packed.Should().Be((UInt128)0x0000000001FE000173UL); @@ -215,12 +220,12 @@ public void Pack_WithStandardBlockData_ShouldProduceExpectedBits() [Fact] public void Pack_WithLargeGapInBits_ShouldPreserveOriginalEncoding() { - var original = new UInt128(0xBEDEAD0000000000UL, 0x0000000001FE032EUL); - var block = PhysicalBlock.Create(original); - var data = IntermediateBlock.UnpackIntermediateBlock(block); + UInt128 original = new(0xBEDEAD0000000000UL, 0x0000000001FE032EUL); + PhysicalBlock block = PhysicalBlock.Create(original); + IntermediateBlock.IntermediateBlockData? data = IntermediateBlock.UnpackIntermediateBlock(block); data.Should().NotBeNull(); - var intermediate = data!.Value; + IntermediateBlock.IntermediateBlockData intermediate = data!.Value; // Check unpacked values intermediate.WeightGridX.Should().Be(2); @@ -235,7 +240,7 @@ public void Pack_WithLargeGapInBits_ShouldPreserveOriginalEncoding() intermediate.Endpoints[0].Colors[1].Should().Be(0); // Repack - var (error, repacked) = IntermediateBlockPacker.Pack(intermediate); + (string? error, UInt128 repacked) = IntermediateBlockPacker.Pack(intermediate); error.Should().BeNull(); repacked.Should().Be(original); @@ -244,12 +249,12 @@ public void Pack_WithLargeGapInBits_ShouldPreserveOriginalEncoding() [Fact] public void UnpackVoidExtent_WithAllOnesPattern_ShouldReturnZeroColors() { - var block = PhysicalBlock.Create((UInt128)0xFFFFFFFFFFFFFDFCUL); + PhysicalBlock block = PhysicalBlock.Create((UInt128)0xFFFFFFFFFFFFFDFCUL); - var result = IntermediateBlock.UnpackVoidExtent(block); + IntermediateBlock.VoidExtentData? result = IntermediateBlock.UnpackVoidExtent(block); result.Should().NotBeNull(); - var data = result!.Value; + IntermediateBlock.VoidExtentData data = result!.Value; data.R.Should().Be(0); data.G.Should().Be(0); @@ -262,13 +267,13 @@ public void UnpackVoidExtent_WithAllOnesPattern_ShouldReturnZeroColors() [Fact] public void UnpackVoidExtent_WithColorData_ShouldReturnCorrectColors() { - var blockBits = new UInt128(0xdeadbeefdeadbeefUL, 0xFFF8003FFE000DFCUL); - var block = PhysicalBlock.Create(blockBits); + UInt128 blockBits = new(0xdeadbeefdeadbeefUL, 0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(blockBits); - var result = IntermediateBlock.UnpackVoidExtent(block); + IntermediateBlock.VoidExtentData? result = IntermediateBlock.UnpackVoidExtent(block); result.Should().NotBeNull(); - var data = result!.Value; + IntermediateBlock.VoidExtentData data = result!.Value; data.R.Should().Be(0xbeef); data.G.Should().Be(0xdead); @@ -284,7 +289,7 @@ public void UnpackVoidExtent_WithColorData_ShouldReturnCorrectColors() [Fact] public void Pack_WithZeroColorVoidExtent_ShouldProduceAllOnesPattern() { - var data = new IntermediateBlock.VoidExtentData + IntermediateBlock.VoidExtentData data = new() { R = 0, G = 0, @@ -294,9 +299,11 @@ public void Pack_WithZeroColorVoidExtent_ShouldProduceAllOnesPattern() }; for (int i = 0; i < 4; ++i) - data.Coords[i] = (ushort)((1 << 13) - 1); + { + data.Coords[i] = (1 << 13) - 1; + } - var (error, packed) = IntermediateBlockPacker.Pack(data); + (string? error, UInt128 packed) = IntermediateBlockPacker.Pack(data); error.Should().BeNull(); packed.Should().Be((UInt128)0xFFFFFFFFFFFFFDFCUL); @@ -305,16 +312,16 @@ public void Pack_WithZeroColorVoidExtent_ShouldProduceAllOnesPattern() [Fact] public void Pack_WithColorVoidExtent_ShouldProduceExpectedBits() { - var data = new IntermediateBlock.VoidExtentData + IntermediateBlock.VoidExtentData data = new() { R = 0xbeef, G = 0xdead, B = 0xbeef, A = 0xdead, - Coords = new ushort[4] { 0, 8191, 0, 8191 } + Coords = [0, 8191, 0, 8191] }; - var (error, packed) = IntermediateBlockPacker.Pack(data); + (string? error, UInt128 packed) = IntermediateBlockPacker.Pack(data); error.Should().BeNull(); packed.Should().Be(new UInt128(0xdeadbeefdeadbeefUL, 0xFFF8003FFE000DFCUL)); @@ -325,15 +332,15 @@ public void Pack_WithColorVoidExtent_ShouldProduceExpectedBits() [InlineData(0x3300c30700cb01c5UL, 0x0573907b8c0f6879UL)] public void PackUnpack_WithSameCEM_ShouldRoundTripCorrectly(ulong high, ulong low) { - var original = new UInt128(high, low); - var block = PhysicalBlock.Create(original); + UInt128 original = new(high, low); + PhysicalBlock block = PhysicalBlock.Create(original); - var unpacked = IntermediateBlock.UnpackIntermediateBlock(block); + IntermediateBlock.IntermediateBlockData? unpacked = IntermediateBlock.UnpackIntermediateBlock(block); unpacked.Should().NotBeNull(); - var ib = unpacked!.Value; + IntermediateBlock.IntermediateBlockData ib = unpacked!.Value; - var (error, repacked) = IntermediateBlockPacker.Pack(ib); + (string? error, UInt128 repacked) = IntermediateBlockPacker.Pack(ib); error.Should().BeNull(); repacked.Should().Be(original); @@ -353,48 +360,48 @@ public void PackUnpack_WithTestDataBlocks_ShouldPreserveBlockProperties(string i { const int astcDim = 8; int imgDim = checkeredDim * astcDim; - var astcData = LoadASTCFile(imageName); + byte[] astcData = LoadASTCFile(imageName); int numBlocks = (imgDim / astcDim) * (imgDim / astcDim); (astcData.Length % PhysicalBlock.SizeInBytes).Should().Be(0); for (int i = 0; i < numBlocks; ++i) { - var slice = new ReadOnlySpan(astcData, i * PhysicalBlock.SizeInBytes, PhysicalBlock.SizeInBytes); - var blockBits = new UInt128( + ReadOnlySpan slice = new(astcData, i * PhysicalBlock.SizeInBytes, PhysicalBlock.SizeInBytes); + UInt128 blockBits = new( BitConverter.ToUInt64(slice.Slice(8, 8)), - BitConverter.ToUInt64(slice.Slice(0, 8))); - var originalBlock = PhysicalBlock.Create(blockBits); + BitConverter.ToUInt64(slice[..8])); + PhysicalBlock originalBlock = PhysicalBlock.Create(blockBits); // Unpack and repack UInt128 repacked; if (originalBlock.IsVoidExtent) { - var voidData = IntermediateBlock.UnpackVoidExtent(originalBlock); + IntermediateBlock.VoidExtentData? voidData = IntermediateBlock.UnpackVoidExtent(originalBlock); voidData.Should().NotBeNull(); - var (error, packed) = IntermediateBlockPacker.Pack(voidData!.Value); + (string? error, UInt128 packed) = IntermediateBlockPacker.Pack(voidData!.Value); error.Should().BeNull(); repacked = packed; } else { - var intermediateData = IntermediateBlock.UnpackIntermediateBlock(originalBlock); + IntermediateBlock.IntermediateBlockData? intermediateData = IntermediateBlock.UnpackIntermediateBlock(originalBlock); intermediateData.Should().NotBeNull(); - var ibData = intermediateData!.Value; + IntermediateBlock.IntermediateBlockData ibData = intermediateData!.Value; // Verify endpoint range was set ibData.EndpointRange.Should().Be(originalBlock.GetColorValuesRange()); // Clear endpoint range before repacking (to test calculation) ibData.EndpointRange = null; - var (error, packed) = IntermediateBlockPacker.Pack(ibData); + (string? error, UInt128 packed) = IntermediateBlockPacker.Pack(ibData); error.Should().BeNull(); repacked = packed; } // Verify repacked block - var repackedBlock = PhysicalBlock.Create(repacked); + PhysicalBlock repackedBlock = PhysicalBlock.Create(repacked); VerifyBlockPropertiesMatch(repackedBlock, originalBlock); } } @@ -404,13 +411,13 @@ private static void VerifyBlockPropertiesMatch(PhysicalBlock repacked, PhysicalB repacked.IsIllegalEncoding.Should().BeFalse(); // Verify color bits match - var repackedColorBitCount = repacked.GetColorBitCount().Value; - var repackedColorMask = UInt128Extensions.OnesMask(repackedColorBitCount); - var repackedColorBits = (repacked.BlockBits >> repacked.GetColorStartBit().Value) & repackedColorMask; + int repackedColorBitCount = repacked.GetColorBitCount().Value; + UInt128 repackedColorMask = UInt128Extensions.OnesMask(repackedColorBitCount); + UInt128 repackedColorBits = (repacked.BlockBits >> repacked.GetColorStartBit().Value) & repackedColorMask; - var originalColorBitCount = original.GetColorBitCount().Value; - var originalColorMask = UInt128Extensions.OnesMask(originalColorBitCount); - var originalColorBits = (original.BlockBits >> original.GetColorStartBit().Value) & originalColorMask; + int originalColorBitCount = original.GetColorBitCount().Value; + UInt128 originalColorMask = UInt128Extensions.OnesMask(originalColorBitCount); + UInt128 originalColorBits = (original.BlockBits >> original.GetColorStartBit().Value) & originalColorMask; repackedColorMask.Should().Be(originalColorMask); repackedColorBits.Should().Be(originalColorBits); @@ -438,7 +445,7 @@ private static void VerifyBlockPropertiesMatch(PhysicalBlock repacked, PhysicalB repacked.GetColorValuesRange().Should().Be(original.GetColorValuesRange()); // Verify endpoint modes for all partitions - var numParts = repacked.GetPartitionsCount().GetValueOrDefault(0); + int numParts = repacked.GetPartitionsCount().GetValueOrDefault(0); for (int j = 0; j < numParts; ++j) { repacked.GetEndpointMode(j).Should().Be(original.GetEndpointMode(j)); @@ -447,10 +454,10 @@ private static void VerifyBlockPropertiesMatch(PhysicalBlock repacked, PhysicalB private static byte[] LoadASTCFile(string basename) { - var filename = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.InputFolder, basename + ".astc")); + string filename = TestFile.GetInputFileFullPath(Path.Combine(TestImages.Astc.InputFolder, basename + ".astc")); File.Exists(filename).Should().BeTrue($"Testdata missing: {filename}"); - var data = File.ReadAllBytes(filename); + byte[] data = File.ReadAllBytes(filename); data.Length.Should().BeGreaterThanOrEqualTo(16, "ASTC file too small"); - return data.Skip(16).ToArray(); + return [.. data.Skip(16)]; } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs index 570a4ca1..4e72c74c 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/LogicalAstcBlockTests.cs @@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Textures.Tests.Formats.Astc; +#nullable enable public class LogicalAstcBlockTests { [Theory] @@ -20,8 +21,8 @@ public class LogicalAstcBlockTests [InlineData(FootprintType.Footprint12x12)] public void Constructor_WithValidFootprintType_ShoulReturnExpectedFootprint(FootprintType footprintType) { - var footprint = Footprint.FromFootprintType(footprintType); - var logicalBlock = new LogicalBlock(footprint); + Footprint footprint = Footprint.FromFootprintType(footprintType); + LogicalBlock logicalBlock = new(footprint); logicalBlock.GetFootprint().Should().Be(footprint); logicalBlock.GetFootprint().Type.Should().Be(footprintType); @@ -30,10 +31,10 @@ public void Constructor_WithValidFootprintType_ShoulReturnExpectedFootprint(Foot [Fact] public void GetFootprint_AfterConstruction_ShouldReturnOriginalFootprint() { - var footprint = Footprint.Get8x8(); - var logicalBlock = new LogicalBlock(footprint); + Footprint footprint = Footprint.Get8x8(); + LogicalBlock logicalBlock = new(footprint); - var result = logicalBlock.GetFootprint(); + Footprint result = logicalBlock.GetFootprint(); result.Should().Be(footprint); } @@ -44,7 +45,7 @@ public void GetFootprint_AfterConstruction_ShouldReturnOriginalFootprint() [InlineData(64)] public void SetWeightAt_WithValidWeight_ShouldStoreCorrectly(int weight) { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); logicalBlock.SetWeightAt(1, 1, weight); @@ -57,9 +58,9 @@ public void SetWeightAt_WithValidWeight_ShouldStoreCorrectly(int weight) [InlineData(100)] public void SetWeightAt_WithInvalidWeight_ShouldThrowArgumentOutOfRangeException(int weight) { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); - var action = () => logicalBlock.SetWeightAt(0, 0, weight); + Action action = () => logicalBlock.SetWeightAt(0, 0, weight); action.Should().Throw(); } @@ -67,9 +68,9 @@ public void SetWeightAt_WithInvalidWeight_ShouldThrowArgumentOutOfRangeException [Fact] public void WeightAt_WithDefaultWeights_ShouldReturnZero() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); - var weight = logicalBlock.WeightAt(2, 2); + int weight = logicalBlock.WeightAt(2, 2); weight.Should().Be(0); } @@ -77,9 +78,9 @@ public void WeightAt_WithDefaultWeights_ShouldReturnZero() [Fact] public void IsDualPlane_ByDefault_ShouldBeFalse() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); - var result = logicalBlock.IsDualPlane(); + bool result = logicalBlock.IsDualPlane(); result.Should().BeFalse(); } @@ -87,7 +88,7 @@ public void IsDualPlane_ByDefault_ShouldBeFalse() [Fact] public void SetDualPlaneChannel_WithValidChannel_ShouldEnableDualPlane() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); logicalBlock.SetDualPlaneChannel(0); @@ -97,7 +98,7 @@ public void SetDualPlaneChannel_WithValidChannel_ShouldEnableDualPlane() [Fact] public void SetDualPlaneChannel_WithNegativeValue_ShouldDisableDualPlane() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); logicalBlock.SetDualPlaneChannel(0); logicalBlock.SetDualPlaneChannel(-1); @@ -108,9 +109,9 @@ public void SetDualPlaneChannel_WithNegativeValue_ShouldDisableDualPlane() [Fact] public void SetDualPlaneWeightAt_WhenNotDualPlane_ShouldThrowInvalidOperationException() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); - var action = () => logicalBlock.SetDualPlaneWeightAt(0, 2, 3, 1); + Action action = () => logicalBlock.SetDualPlaneWeightAt(0, 2, 3, 1); action.Should().Throw() .WithMessage("Not a dual plane block"); @@ -119,7 +120,7 @@ public void SetDualPlaneWeightAt_WhenNotDualPlane_ShouldThrowInvalidOperationExc [Fact] public void SetDualPlaneWeightAt_AfterEnablingDualPlane_ShouldPreserveOriginalWeight() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); logicalBlock.SetWeightAt(2, 3, 2); logicalBlock.SetDualPlaneChannel(0); @@ -132,7 +133,7 @@ public void SetDualPlaneWeightAt_AfterEnablingDualPlane_ShouldPreserveOriginalWe [Fact] public void DualPlaneWeightAt_ForNonDualPlaneChannel_ShouldReturnOriginalWeight() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); logicalBlock.SetWeightAt(2, 3, 2); logicalBlock.SetDualPlaneChannel(0); logicalBlock.SetDualPlaneWeightAt(0, 2, 3, 1); @@ -146,10 +147,10 @@ public void DualPlaneWeightAt_ForNonDualPlaneChannel_ShouldReturnOriginalWeight( [Fact] public void DualPlaneWeightAt_WhenNotDualPlane_ShouldReturnWeightAt() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); logicalBlock.SetWeightAt(2, 3, 42); - var result = logicalBlock.DualPlaneWeightAt(0, 2, 3); + int result = logicalBlock.DualPlaneWeightAt(0, 2, 3); result.Should().Be(42); } @@ -157,7 +158,7 @@ public void DualPlaneWeightAt_WhenNotDualPlane_ShouldReturnWeightAt() [Fact] public void SetDualPlaneWeightAt_ThenDisableDualPlane_ShouldResetToOriginalWeight() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); logicalBlock.SetWeightAt(2, 3, 2); logicalBlock.SetDualPlaneChannel(0); logicalBlock.SetDualPlaneWeightAt(0, 2, 3, 1); @@ -175,9 +176,9 @@ public void SetDualPlaneWeightAt_ThenDisableDualPlane_ShouldResetToOriginalWeigh [Fact] public void SetEndpoints_WithValidColors_ShouldStoreCorrectly() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); - var color1 = new RgbaColor(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); - var color2 = new RgbaColor(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); + RgbaColor color1 = new(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); + RgbaColor color2 = new(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); logicalBlock.SetEndpoints(color1, color2, 0); @@ -185,8 +186,8 @@ public void SetEndpoints_WithValidColors_ShouldStoreCorrectly() logicalBlock.SetWeightAt(0, 0, 0); logicalBlock.SetWeightAt(1, 1, 64); - var colorAtMinWeight = logicalBlock.ColorAt(0, 0); - var colorAtMaxWeight = logicalBlock.ColorAt(1, 1); + RgbaColor colorAtMinWeight = logicalBlock.ColorAt(0, 0); + RgbaColor colorAtMaxWeight = logicalBlock.ColorAt(1, 1); colorAtMinWeight.R.Should().Be(color1.R); colorAtMaxWeight.R.Should().BeCloseTo(color2.R, 1); @@ -195,7 +196,7 @@ public void SetEndpoints_WithValidColors_ShouldStoreCorrectly() [Fact] public void ColorAt_WithCheckerboardWeights_ShouldInterpolateCorrectly() { - var logicalBlock = new LogicalBlock(Footprint.Get8x8()); + LogicalBlock logicalBlock = new(Footprint.Get8x8()); // Create checkerboard weight pattern for (int j = 0; j < 8; ++j) @@ -203,21 +204,25 @@ public void ColorAt_WithCheckerboardWeights_ShouldInterpolateCorrectly() for (int i = 0; i < 8; ++i) { if (((i ^ j) & 1) == 1) + { logicalBlock.SetWeightAt(i, j, 0); + } else + { logicalBlock.SetWeightAt(i, j, 64); + } } } - var endpointA = new RgbaColor(123, 45, 67, 89); - var endpointB = new RgbaColor(101, 121, 31, 41); + RgbaColor endpointA = new(123, 45, 67, 89); + RgbaColor endpointB = new(101, 121, 31, 41); logicalBlock.SetEndpoints(endpointA, endpointB, 0); for (int j = 0; j < 8; ++j) { for (int i = 0; i < 8; ++i) { - var color = logicalBlock.ColorAt(i, j); + RgbaColor color = logicalBlock.ColorAt(i, j); if (((i ^ j) & 1) == 1) { // Weight 0 = first endpoint @@ -245,9 +250,9 @@ public void ColorAt_WithCheckerboardWeights_ShouldInterpolateCorrectly() [InlineData(0, 4)] public void ColorAt_WithOutOfBoundsCoordinates_ShouldThrowArgumentOutOfRangeException(int x, int y) { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); - var action = () => logicalBlock.ColorAt(x, y); + Action action = () => logicalBlock.ColorAt(x, y); action.Should().Throw(); } @@ -255,11 +260,11 @@ public void ColorAt_WithOutOfBoundsCoordinates_ShouldThrowArgumentOutOfRangeExce [Fact] public void SetPartition_WithValidPartition_ShouldUpdateCorrectly() { - var footprint = Footprint.Get8x8(); - var logicalBlock = new LogicalBlock(footprint); + Footprint footprint = Footprint.Get8x8(); + LogicalBlock logicalBlock = new(footprint); // Create partition with 2 subsets, all pixels assigned to subset 0 - var newPartition = new Partition(footprint, 2, 5) + Partition newPartition = new(footprint, 2, 5) { Assignment = new int[footprint.PixelCount] }; @@ -267,31 +272,31 @@ public void SetPartition_WithValidPartition_ShouldUpdateCorrectly() logicalBlock.SetPartition(newPartition); // Should be able to set endpoints for both valid partitions (0 and 1) - var redEndpoint = new RgbaColor(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); - var blackEndpoint = new RgbaColor(byte.MinValue, byte.MinValue, byte.MinValue, byte.MaxValue); - var greenEndpoint = new RgbaColor(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); + RgbaColor redEndpoint = new(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); + RgbaColor blackEndpoint = new(byte.MinValue, byte.MinValue, byte.MinValue, byte.MaxValue); + RgbaColor greenEndpoint = new(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); - var setEndpoint0 = () => logicalBlock.SetEndpoints(redEndpoint, blackEndpoint, 0); - var setEndpoint1 = () => logicalBlock.SetEndpoints(greenEndpoint, blackEndpoint, 1); + Action setEndpoint0 = () => logicalBlock.SetEndpoints(redEndpoint, blackEndpoint, 0); + Action setEndpoint1 = () => logicalBlock.SetEndpoints(greenEndpoint, blackEndpoint, 1); setEndpoint0.Should().NotThrow(); setEndpoint1.Should().NotThrow(); // Should not be able to set endpoints for non-existent partition 2 - var setEndpoint2 = () => logicalBlock.SetEndpoints(redEndpoint, blackEndpoint, 2); + Action setEndpoint2 = () => logicalBlock.SetEndpoints(redEndpoint, blackEndpoint, 2); setEndpoint2.Should().Throw(); } [Fact] public void SetPartition_WithDifferentFootprint_ShouldThrowInvalidOperationException() { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); - var wrongPartition = new Partition(Footprint.Get8x8(), 1, 0) + LogicalBlock logicalBlock = new(Footprint.Get4x4()); + Partition wrongPartition = new(Footprint.Get8x8(), 1, 0) { Assignment = new int[64] }; - var action = () => logicalBlock.SetPartition(wrongPartition); + Action action = () => logicalBlock.SetPartition(wrongPartition); action.Should() .Throw() @@ -303,11 +308,11 @@ public void SetPartition_WithDifferentFootprint_ShouldThrowInvalidOperationExcep [InlineData(2)] public void SetEndpoints_WithInvalidSubset_ShouldThrowArgumentOutOfRangeException(int subset) { - var logicalBlock = new LogicalBlock(Footprint.Get4x4()); - var color1 = new RgbaColor(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); - var color2 = new RgbaColor(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); + LogicalBlock logicalBlock = new(Footprint.Get4x4()); + RgbaColor color1 = new(byte.MaxValue, byte.MinValue, byte.MinValue, byte.MaxValue); + RgbaColor color2 = new(byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue); - var action = () => logicalBlock.SetEndpoints(color1, color2, subset); + Action action = () => logicalBlock.SetEndpoints(color1, color2, subset); action.Should().Throw(); } @@ -315,10 +320,10 @@ public void SetEndpoints_WithInvalidSubset_ShouldThrowArgumentOutOfRangeExceptio [Fact] public void UnpackLogicalBlock_WithErrorBlock_ShouldReturnNull() { - var bits = UInt128.Zero; - var info = BlockInfo.Decode(bits); + UInt128 bits = UInt128.Zero; + BlockInfo info = BlockInfo.Decode(bits); - var result = LogicalBlock.UnpackLogicalBlock(Footprint.Get8x8(), bits, in info); + LogicalBlock? result = LogicalBlock.UnpackLogicalBlock(Footprint.Get8x8(), bits, in info); result.Should().BeNull(); } @@ -326,10 +331,10 @@ public void UnpackLogicalBlock_WithErrorBlock_ShouldReturnNull() [Fact] public void UnpackLogicalBlock_WithVoidExtentBlock_ShouldReturnLogicalBlock() { - var bits = (UInt128)0xFFFFFFFFFFFFFDFCUL; - var info = BlockInfo.Decode(bits); + UInt128 bits = (UInt128)0xFFFFFFFFFFFFFDFCUL; + BlockInfo info = BlockInfo.Decode(bits); - var result = LogicalBlock.UnpackLogicalBlock(Footprint.Get8x8(), bits, in info); + LogicalBlock? result = LogicalBlock.UnpackLogicalBlock(Footprint.Get8x8(), bits, in info); result.Should().NotBeNull(); result!.GetFootprint().Should().Be(Footprint.Get8x8()); @@ -338,10 +343,10 @@ public void UnpackLogicalBlock_WithVoidExtentBlock_ShouldReturnLogicalBlock() [Fact] public void UnpackLogicalBlock_WithStandardBlock_ShouldReturnLogicalBlock() { - var bits = (UInt128)0x0000000001FE000173UL; - var info = BlockInfo.Decode(bits); + UInt128 bits = (UInt128)0x0000000001FE000173UL; + BlockInfo info = BlockInfo.Decode(bits); - var result = LogicalBlock.UnpackLogicalBlock(Footprint.Get6x5(), bits, in info); + LogicalBlock? result = LogicalBlock.UnpackLogicalBlock(Footprint.Get6x5(), bits, in info); result.Should().NotBeNull(); result!.GetFootprint().Should().Be(Footprint.Get6x5()); @@ -384,6 +389,7 @@ public void UnpackLogicalBlock_FromImage_ShouldDecodeCorrectly( int width, int height) { + _ = hasAlpha; Footprint footprint = Footprint.FromFootprintType(footprintType); byte[] astcData = TestFile.Create(Path.Combine(TestImages.Astc.InputFolder, imageName + ".astc")).Bytes[16..]; @@ -397,7 +403,7 @@ public void UnpackLogicalBlock_FromImage_ShouldDecodeCorrectly( private static Image DecodeAstcBlocksToImage(Footprint footprint, byte[] astcData, int width, int height) { // ASTC uses x/y ordering, so we flip Y to match ImageSharp's row/column origin. - var image = new Image(width, height); + Image image = new(width, height); int blockWidth = footprint.Width; int blockHeight = footprint.Height; int blocksWide = (width + blockWidth - 1) / blockWidth; @@ -409,7 +415,7 @@ private static Image DecodeAstcBlocksToImage(Footprint footprint, byte[] int blockY = blockIndex / blocksWide; byte[] blockSpan = astcData.AsSpan(i, PhysicalBlock.SizeInBytes).ToArray(); - var bits = new UInt128( + UInt128 bits = new( BitConverter.ToUInt64(blockSpan, 8), BitConverter.ToUInt64(blockSpan, 0)); BlockInfo info = BlockInfo.Decode(bits); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs index 6807a6d2..eeff44a7 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/PartitionTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Globalization; using AwesomeAssertions; using SixLabors.ImageSharp.Textures.Astc.ColorEncoding; using SixLabors.ImageSharp.Textures.Astc.Core; @@ -12,7 +13,7 @@ public class PartitionTests [Fact] public void PartitionMetric_WithSimplePartitions_ShouldCalculateCorrectDistance() { - var partitionA = new Partition(Footprint.Get6x6(), 2) + Partition partitionA = new(Footprint.Get6x6(), 2) { Assignment = [ @@ -25,7 +26,7 @@ public void PartitionMetric_WithSimplePartitions_ShouldCalculateCorrectDistance( ] }; - var partitionB = new Partition(Footprint.Get6x6(), 2) + Partition partitionB = new(Footprint.Get6x6(), 2) { Assignment = [ @@ -46,7 +47,7 @@ public void PartitionMetric_WithSimplePartitions_ShouldCalculateCorrectDistance( [Fact] public void PartitionMetric_WithDifferentPartCounts_ShouldCalculateCorrectDistance() { - var partitionA = new Partition(Footprint.Get4x4(), 2) + Partition partitionA = new(Footprint.Get4x4(), 2) { Assignment = [ @@ -57,7 +58,7 @@ public void PartitionMetric_WithDifferentPartCounts_ShouldCalculateCorrectDistan ] }; - var partitionB = new Partition(Footprint.Get4x4(), 3) + Partition partitionB = new(Footprint.Get4x4(), 3) { Assignment = [ @@ -76,7 +77,7 @@ public void PartitionMetric_WithDifferentPartCounts_ShouldCalculateCorrectDistan [Fact] public void PartitionMetric_WithDifferentMapping_ShouldCalculateCorrectDistance() { - var partitionA = new Partition(Footprint.Get4x4(), 2) + Partition partitionA = new(Footprint.Get4x4(), 2) { Assignment = [ @@ -87,7 +88,7 @@ public void PartitionMetric_WithDifferentMapping_ShouldCalculateCorrectDistance( ] }; - var partitionB = new Partition(Footprint.Get4x4(), 3) + Partition partitionB = new(Footprint.Get4x4(), 3) { Assignment = [ @@ -116,7 +117,7 @@ public void GetASTCPartition_WithSpecificParameters_ShouldReturnExpectedAssignme 0, 0, 0, 0, 1, 1, 1, 2, 2, 2 ]; - var partition = Partition.GetASTCPartition(Footprint.Get10x6(), 3, 557); + Partition partition = Partition.GetASTCPartition(Footprint.Get10x6(), 3, 557); partition.Assignment.Should().Equal(expected); } @@ -124,8 +125,8 @@ public void GetASTCPartition_WithSpecificParameters_ShouldReturnExpectedAssignme [Fact] public void GetASTCPartition_WithDifferentIds_ShouldProduceUniqueAssignments() { - var partition0 = Partition.GetASTCPartition(Footprint.Get6x6(), 2, 0); - var partition1 = Partition.GetASTCPartition(Footprint.Get6x6(), 2, 1); + Partition partition0 = Partition.GetASTCPartition(Footprint.Get6x6(), 2, 0); + Partition partition1 = Partition.GetASTCPartition(Footprint.Get6x6(), 2, 1); partition0.Assignment.Should().NotEqual(partition1.Assignment); } @@ -133,7 +134,7 @@ public void GetASTCPartition_WithDifferentIds_ShouldProduceUniqueAssignments() [Fact] public void FindClosestASTCPartition_ShouldPreservePartitionCount() { - var partition = new Partition(Footprint.Get6x6(), 2) + Partition partition = new(Footprint.Get6x6(), 2) { Assignment = [ @@ -146,7 +147,7 @@ public void FindClosestASTCPartition_ShouldPreservePartitionCount() ] }; - var closestAstcPartition = Partition.FindClosestASTCPartition(partition); + Partition closestAstcPartition = Partition.FindClosestASTCPartition(partition); closestAstcPartition.PartitionCount.Should().Be(partition.PartitionCount); } @@ -154,15 +155,15 @@ public void FindClosestASTCPartition_ShouldPreservePartitionCount() [Fact] public void FindClosestASTCPartition_WithModifiedPartition_ShouldReturnValidASTCPartition() { - var astcPartition = Partition.GetASTCPartition(Footprint.Get12x12(), 3, 0x3CB); - var modifiedPartition = new Partition(astcPartition.Footprint, astcPartition.PartitionCount) + Partition astcPartition = Partition.GetASTCPartition(Footprint.Get12x12(), 3, 0x3CB); + Partition modifiedPartition = new(astcPartition.Footprint, astcPartition.PartitionCount) { Assignment = [.. astcPartition.Assignment] }; modifiedPartition.Assignment[0]++; // Find closest ASTC partition - var closestPartition = Partition.FindClosestASTCPartition(modifiedPartition); + Partition closestPartition = Partition.FindClosestASTCPartition(modifiedPartition); // The closest partition should be a valid ASTC partition with the same footprint and number of parts closestPartition.Footprint.Should().Be(astcPartition.Footprint); @@ -170,7 +171,7 @@ public void FindClosestASTCPartition_WithModifiedPartition_ShouldReturnValidASTC closestPartition.PartitionId.Should().HaveValue("returned partition should have a valid ID"); // Verify we can retrieve the same partition again using its ID - var verifyPartition = Partition.GetASTCPartition( + Partition verifyPartition = Partition.GetASTCPartition( closestPartition.Footprint, closestPartition.PartitionCount, closestPartition.PartitionId!.Value); @@ -194,33 +195,33 @@ public void FindClosestASTCPartition_WithModifiedPartition_ShouldReturnValidASTC [InlineData(FootprintType.Footprint12x12)] public void FindClosestASTCPartition_WithRandomPartitions_ShouldReturnFewerOrEqualSubsets(FootprintType footprintType) { - var footprint = Footprint.FromFootprintType(footprintType); - var random = new Random(unchecked((int)0xdeadbeef)); + Footprint footprint = Footprint.FromFootprintType(footprintType); + Random random = new(unchecked((int)0xdeadbeef)); const int numTests = 15; // Tests per footprint type for (int i = 0; i < numTests; i++) { // Create random partition int numParts = 2 + random.Next(3); // 2, 3, or 4 parts - var assignment = new int[footprint.PixelCount]; + int[] assignment = new int[footprint.PixelCount]; for (int j = 0; j < footprint.PixelCount; j++) { assignment[j] = random.Next(numParts); } - var partition = new Partition(footprint, numParts) + Partition partition = new(footprint, numParts) { Assignment = assignment }; - var astcPartition = Partition.FindClosestASTCPartition(partition); + Partition astcPartition = Partition.FindClosestASTCPartition(partition); // Matched partition should have fewer or equal subsets astcPartition.PartitionCount .Should() .BeLessThanOrEqualTo( partition.PartitionCount, - $"Footprint {footprintType}, Test #{i}: Selected partition with ID {astcPartition.PartitionId?.ToString() ?? "null"}"); + $"Footprint {footprintType}, Test #{i}: Selected partition with ID {astcPartition.PartitionId?.ToString(CultureInfo.InvariantCulture) ?? "null"}"); } } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs index 9798f287..7fa383d9 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/PhysicalAstcBlockTests.cs @@ -16,7 +16,7 @@ public void Create_WithUInt64_ShouldRoundTripBlockBits() { const ulong expectedLow = 0x0000000001FE000173UL; - var block = PhysicalBlock.Create(expectedLow); + PhysicalBlock block = PhysicalBlock.Create(expectedLow); block.BlockBits.Should().Be((UInt128)expectedLow); } @@ -24,9 +24,9 @@ public void Create_WithUInt64_ShouldRoundTripBlockBits() [Fact] public void Create_WithUInt128_ShouldRoundTripBlockBits() { - var expected = (UInt128)0x12345678ABCDEF00UL | ((UInt128)0xCAFEBABEDEADBEEFUL << 64); + UInt128 expected = (UInt128)0x12345678ABCDEF00UL | ((UInt128)0xCAFEBABEDEADBEEFUL << 64); - var block = PhysicalBlock.Create(expected); + PhysicalBlock block = PhysicalBlock.Create(expected); block.BlockBits.Should().Be(expected); } @@ -36,8 +36,8 @@ public void Create_WithMatchingUInt64AndUInt128_ShouldProduceIdenticalBlocks() { const ulong value = 0x0000000001FE000173UL; - var block1 = PhysicalBlock.Create(value); - var block2 = PhysicalBlock.Create((UInt128)value); + PhysicalBlock block1 = PhysicalBlock.Create(value); + PhysicalBlock block2 = PhysicalBlock.Create((UInt128)value); block1.BlockBits.Should().Be(block2.BlockBits); } @@ -45,7 +45,7 @@ public void Create_WithMatchingUInt64AndUInt128_ShouldProduceIdenticalBlocks() [Fact] public void IsVoidExtent_WithKnownVoidExtentPattern_ShouldReturnTrue() { - var block = PhysicalBlock.Create((UInt128)0xFFFFFFFFFFFFFDFCUL); + PhysicalBlock block = PhysicalBlock.Create((UInt128)0xFFFFFFFFFFFFFDFCUL); block.IsVoidExtent.Should().BeTrue(); } @@ -53,7 +53,7 @@ public void IsVoidExtent_WithKnownVoidExtentPattern_ShouldReturnTrue() [Fact] public void IsVoidExtent_WithStandardBlock_ShouldReturnFalse() { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); block.IsVoidExtent.Should().BeFalse(); } @@ -61,7 +61,7 @@ public void IsVoidExtent_WithStandardBlock_ShouldReturnFalse() [Fact] public void IsVoidExtent_WithErrorBlock_ShouldReturnFalse() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); block.IsVoidExtent.Should().BeFalse(); } @@ -69,9 +69,9 @@ public void IsVoidExtent_WithErrorBlock_ShouldReturnFalse() [Fact] public void GetVoidExtentCoordinates_WithValidVoidExtentBlock_ShouldReturnExpectedCoordinates() { - var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); - var coords = block.GetVoidExtentCoordinates(); + int[] coords = block.GetVoidExtentCoordinates(); coords.Should().NotBeNull(); coords.Should().HaveCount(4); @@ -84,9 +84,9 @@ public void GetVoidExtentCoordinates_WithValidVoidExtentBlock_ShouldReturnExpect [Fact] public void GetVoidExtentCoordinates_WithAllOnesPattern_ShouldReturnNull() { - var block = PhysicalBlock.Create(0xFFFFFFFFFFFFFDFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFFFFFFFFFFFFDFCUL); - var coords = block.GetVoidExtentCoordinates(); + int[] coords = block.GetVoidExtentCoordinates(); block.IsVoidExtent.Should().BeTrue(); coords.Should().BeNull(); @@ -95,8 +95,8 @@ public void GetVoidExtentCoordinates_WithAllOnesPattern_ShouldReturnNull() [Fact] public void Create_WithInvalidVoidExtentCoordinates_ShouldBeIllegalEncoding() { - var block1 = PhysicalBlock.Create(0x0008004002001DFCUL); - var block2 = PhysicalBlock.Create(0x0007FFC001FFFDFCUL); + PhysicalBlock block1 = PhysicalBlock.Create(0x0008004002001DFCUL); + PhysicalBlock block2 = PhysicalBlock.Create(0x0007FFC001FFFDFCUL); block1.IsIllegalEncoding.Should().BeTrue(); block2.IsIllegalEncoding.Should().BeTrue(); @@ -105,8 +105,8 @@ public void Create_WithInvalidVoidExtentCoordinates_ShouldBeIllegalEncoding() [Fact] public void Create_WithModifiedHighBitsOnVoidExtent_ShouldStillBeValid() { - var original = PhysicalBlock.Create(0xFFF8003FFE000DFCUL, 0UL); - var modified = PhysicalBlock.Create(0xFFF8003FFE000DFCUL, 0xdeadbeefdeadbeef); + PhysicalBlock original = PhysicalBlock.Create(0xFFF8003FFE000DFCUL, 0UL); + PhysicalBlock modified = PhysicalBlock.Create(0xFFF8003FFE000DFCUL, 0xdeadbeefdeadbeef); original.IsIllegalEncoding.Should().BeFalse(); original.IsVoidExtent.Should().BeTrue(); @@ -117,9 +117,9 @@ public void Create_WithModifiedHighBitsOnVoidExtent_ShouldStillBeValid() [Fact] public void GetWeightRange_WithValidBlock_ShouldReturn7() { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); - var weightRange = block.GetWeightRange(); + int? weightRange = block.GetWeightRange(); weightRange.Should().HaveValue(); weightRange.Should().Be(7); @@ -128,9 +128,9 @@ public void GetWeightRange_WithValidBlock_ShouldReturn7() [Fact] public void GetWeightRange_WithTooManyBits_ShouldReturnNull() { - var block = PhysicalBlock.Create(0x0000000001FE000373UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000373UL); - var weightRange = block.GetWeightRange(); + int? weightRange = block.GetWeightRange(); weightRange.Should().BeNull(); } @@ -138,9 +138,9 @@ public void GetWeightRange_WithTooManyBits_ShouldReturnNull() [Fact] public void GetWeightRange_WithOneBitPerWeight_ShouldReturn1() { - var block = PhysicalBlock.Create(0x4000000000800D44UL); + PhysicalBlock block = PhysicalBlock.Create(0x4000000000800D44UL); - var weightRange = block.GetWeightRange(); + int? weightRange = block.GetWeightRange(); weightRange.Should().HaveValue(); weightRange.Should().Be(1); @@ -149,9 +149,9 @@ public void GetWeightRange_WithOneBitPerWeight_ShouldReturn1() [Fact] public void GetWeightRange_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var weightRange = block.GetWeightRange(); + int? weightRange = block.GetWeightRange(); weightRange.Should().BeNull(); } @@ -159,9 +159,9 @@ public void GetWeightRange_WithErrorBlock_ShouldReturnNull() [Fact] public void GetWeightGridDimensions_WithValidBlock_ShouldReturn6x5() { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); - var dims = block.GetWeightGridDimensions(); + (int Width, int Height)? dims = block.GetWeightGridDimensions(); dims.Should().NotBeNull(); dims!.Value.Width.Should().Be(6); @@ -171,21 +171,21 @@ public void GetWeightGridDimensions_WithValidBlock_ShouldReturn6x5() [Fact] public void GetWeightGridDimensions_WithTooManyBitsForGrid_ShouldReturnNull() { - var block = PhysicalBlock.Create(0x0000000001FE000373UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000373UL); - var dims = block.GetWeightGridDimensions(); + (int Width, int Height)? dims = block.GetWeightGridDimensions(); dims.Should().BeNull(); - var error = block.IdentifyInvalidEncodingIssues(); + string error = block.IdentifyInvalidEncodingIssues(); error.Should().Contain("Invalid block encoding"); } [Fact] public void GetWeightGridDimensions_WithDualPlaneBlock_ShouldReturn3x5() { - var block = PhysicalBlock.Create(0x0000000001FE0005FFUL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE0005FFUL); - var dims = block.GetWeightGridDimensions(); + (int Width, int Height)? dims = block.GetWeightGridDimensions(); dims.Should().NotBeNull(); dims!.Value.Width.Should().Be(3); @@ -195,9 +195,9 @@ public void GetWeightGridDimensions_WithDualPlaneBlock_ShouldReturn3x5() [Fact] public void GetWeightGridDimensions_WithNonSharedCEM_ShouldReturn8x8() { - var block = PhysicalBlock.Create(0x4000000000800D44UL); + PhysicalBlock block = PhysicalBlock.Create(0x4000000000800D44UL); - var dims = block.GetWeightGridDimensions(); + (int Width, int Height)? dims = block.GetWeightGridDimensions(); dims.Should().NotBeNull(); dims!.Value.Width.Should().Be(8); @@ -207,9 +207,9 @@ public void GetWeightGridDimensions_WithNonSharedCEM_ShouldReturn8x8() [Fact] public void GetWeightGridDimensions_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var dims = block.GetWeightGridDimensions(); + (int Width, int Height)? dims = block.GetWeightGridDimensions(); dims.Should().BeNull(); } @@ -217,7 +217,7 @@ public void GetWeightGridDimensions_WithErrorBlock_ShouldReturnNull() [Fact] public void IsDualPlane_WithSinglePlaneBlock_ShouldReturnFalse() { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); block.IsDualPlane.Should().BeFalse(); } @@ -225,7 +225,7 @@ public void IsDualPlane_WithSinglePlaneBlock_ShouldReturnFalse() [Fact] public void IsDualPlane_WithDualPlaneBlock_ShouldReturnTrue() { - var block = PhysicalBlock.Create(0x0000000001FE0005FFUL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE0005FFUL); block.IsDualPlane.Should().BeTrue(); } @@ -233,7 +233,7 @@ public void IsDualPlane_WithDualPlaneBlock_ShouldReturnTrue() [Fact] public void IsDualPlane_WithErrorBlock_ShouldReturnFalse() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); block.IsDualPlane.Should().BeFalse(); } @@ -241,7 +241,7 @@ public void IsDualPlane_WithErrorBlock_ShouldReturnFalse() [Fact] public void IsDualPlane_WithInvalidEncoding_ShouldReturnFalse() { - var block = PhysicalBlock.Create(0x0000000001FE000573UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000573UL); block.IsDualPlane.Should().BeFalse(); block.GetWeightGridDimensions().Should().BeNull(); @@ -251,7 +251,7 @@ public void IsDualPlane_WithInvalidEncoding_ShouldReturnFalse() [Fact] public void IsDualPlane_WithValidSinglePlaneBlock_ShouldHaveValidEncoding() { - var block = PhysicalBlock.Create(0x0000000001FE000108UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000108UL); block.IsDualPlane.Should().BeFalse(); block.IsIllegalEncoding.Should().BeFalse(); @@ -260,9 +260,9 @@ public void IsDualPlane_WithValidSinglePlaneBlock_ShouldHaveValidEncoding() [Fact] public void GetWeightBitCount_WithStandardBlock_ShouldReturn90() { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); - var bitCount = block.GetWeightBitCount(); + int? bitCount = block.GetWeightBitCount(); bitCount.Should().Be(90); } @@ -270,9 +270,9 @@ public void GetWeightBitCount_WithStandardBlock_ShouldReturn90() [Fact] public void GetWeightBitCount_WithDualPlaneBlock_ShouldReturn90() { - var block = PhysicalBlock.Create(0x0000000001FE0005FFUL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE0005FFUL); - var bitCount = block.GetWeightBitCount(); + int? bitCount = block.GetWeightBitCount(); bitCount.Should().Be(90); } @@ -280,9 +280,9 @@ public void GetWeightBitCount_WithDualPlaneBlock_ShouldReturn90() [Fact] public void GetWeightBitCount_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var bitCount = block.GetWeightBitCount(); + int? bitCount = block.GetWeightBitCount(); bitCount.Should().BeNull(); } @@ -290,9 +290,9 @@ public void GetWeightBitCount_WithErrorBlock_ShouldReturnNull() [Fact] public void GetWeightBitCount_WithVoidExtent_ShouldReturnNull() { - var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); - var bitCount = block.GetWeightBitCount(); + int? bitCount = block.GetWeightBitCount(); bitCount.Should().BeNull(); } @@ -300,9 +300,9 @@ public void GetWeightBitCount_WithVoidExtent_ShouldReturnNull() [Fact] public void GetWeightBitCount_WithInvalidBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(0x0000000001FE000573UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000573UL); - var bitCount = block.GetWeightBitCount(); + int? bitCount = block.GetWeightBitCount(); bitCount.Should().BeNull(); } @@ -310,9 +310,9 @@ public void GetWeightBitCount_WithInvalidBlock_ShouldReturnNull() [Fact] public void GetWeightStartBit_WithNonSharedCEM_ShouldReturn64() { - var block = PhysicalBlock.Create(0x4000000000800D44UL); + PhysicalBlock block = PhysicalBlock.Create(0x4000000000800D44UL); - var startBit = block.GetWeightStartBit(); + int? startBit = block.GetWeightStartBit(); startBit.Should().Be(64); } @@ -320,9 +320,9 @@ public void GetWeightStartBit_WithNonSharedCEM_ShouldReturn64() [Fact] public void GetWeightStartBit_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var startBit = block.GetWeightStartBit(); + int? startBit = block.GetWeightStartBit(); startBit.Should().BeNull(); } @@ -330,9 +330,9 @@ public void GetWeightStartBit_WithErrorBlock_ShouldReturnNull() [Fact] public void GetWeightStartBit_WithVoidExtent_ShouldReturnNull() { - var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); - var startBit = block.GetWeightStartBit(); + int? startBit = block.GetWeightStartBit(); startBit.Should().BeNull(); } @@ -348,9 +348,9 @@ public void IsIllegalEncoding_WithValidBlocks_ShouldReturnFalse() [Fact] public void IdentifyInvalidEncodingIssues_WithZeroBlock_ShouldReturnReservedBlockModeError() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var error = block.IdentifyInvalidEncodingIssues(); + string error = block.IdentifyInvalidEncodingIssues(); error.Should().NotBeNull(); error.Should().Contain("Invalid block encoding"); @@ -359,9 +359,9 @@ public void IdentifyInvalidEncodingIssues_WithZeroBlock_ShouldReturnReservedBloc [Fact] public void IdentifyInvalidEncodingIssues_WithTooManyWeightBits_ShouldReturnError() { - var block = PhysicalBlock.Create(0x0000000001FE000573UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000573UL); - var error = block.IdentifyInvalidEncodingIssues(); + string error = block.IdentifyInvalidEncodingIssues(); error.Should().NotBeNull(); error.Should().Contain("Invalid block encoding"); @@ -373,9 +373,9 @@ public void IdentifyInvalidEncodingIssues_WithTooManyWeightBits_ShouldReturnErro [InlineData(0x0000000001FE00002UL)] public void IdentifyInvalidEncodingIssues_WithInvalidBlocks_ShouldReturnError(ulong blockBits) { - var block = PhysicalBlock.Create(blockBits); + PhysicalBlock block = PhysicalBlock.Create(blockBits); - var error = block.IdentifyInvalidEncodingIssues(); + string error = block.IdentifyInvalidEncodingIssues(); error.Should().NotBeNull(); } @@ -383,9 +383,9 @@ public void IdentifyInvalidEncodingIssues_WithInvalidBlocks_ShouldReturnError(ul [Fact] public void IdentifyInvalidEncodingIssues_WithDualPlaneFourPartitions_ShouldReturnError() { - var block = PhysicalBlock.Create(0x000000000000001D1FUL); + PhysicalBlock block = PhysicalBlock.Create(0x000000000000001D1FUL); - var error = block.IdentifyInvalidEncodingIssues(); + string error = block.IdentifyInvalidEncodingIssues(); block.GetPartitionsCount().Should().BeNull(); error.Should().NotBeNull(); @@ -398,9 +398,9 @@ public void IdentifyInvalidEncodingIssues_WithDualPlaneFourPartitions_ShouldRetu [InlineData(0x000000000000001973UL)] public void GetPartitionsCount_WithInvalidPartitionConfig_ShouldReturnNull(ulong blockBits) { - var block = PhysicalBlock.Create(blockBits); + PhysicalBlock block = PhysicalBlock.Create(blockBits); - var partitions = block.GetPartitionsCount(); + int? partitions = block.GetPartitionsCount(); partitions.Should().BeNull(); } @@ -412,9 +412,9 @@ public void GetPartitionsCount_WithInvalidPartitionConfig_ShouldReturnNull(ulong [InlineData(0x4000000000800D44UL, 2)] public void GetPartitionsCount_WithValidBlock_ShouldReturnExpectedCount(ulong blockBits, int expectedCount) { - var block = PhysicalBlock.Create(blockBits); + PhysicalBlock block = PhysicalBlock.Create(blockBits); - var count = block.GetPartitionsCount(); + int? count = block.GetPartitionsCount(); count.Should().Be(expectedCount); } @@ -424,9 +424,9 @@ public void GetPartitionsCount_WithValidBlock_ShouldReturnExpectedCount(ulong bl [InlineData(0x4000000000AAAD44UL, 0x155)] public void GetPartitionId_WithValidMultiPartitionBlock_ShouldReturnExpectedId(ulong blockBits, int expectedId) { - var block = PhysicalBlock.Create(blockBits); + PhysicalBlock block = PhysicalBlock.Create(blockBits); - var partitionId = block.GetPartitionId(); + int? partitionId = block.GetPartitionId(); partitionId.Should().Be(expectedId); } @@ -434,9 +434,9 @@ public void GetPartitionId_WithValidMultiPartitionBlock_ShouldReturnExpectedId(u [Fact] public void GetPartitionId_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var partitionId = block.GetPartitionId(); + int? partitionId = block.GetPartitionId(); partitionId.Should().BeNull(); } @@ -444,9 +444,9 @@ public void GetPartitionId_WithErrorBlock_ShouldReturnNull() [Fact] public void GetPartitionId_WithVoidExtent_ShouldReturnNull() { - var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); - var partitionId = block.GetPartitionId(); + int? partitionId = block.GetPartitionId(); partitionId.Should().BeNull(); } @@ -454,11 +454,11 @@ public void GetPartitionId_WithVoidExtent_ShouldReturnNull() [Fact] public void GetEndpointMode_WithFourPartitionBlock_ShouldReturnSameModeForAll() { - var block = PhysicalBlock.Create(0x000000000000001961UL); + PhysicalBlock block = PhysicalBlock.Create(0x000000000000001961UL); for (int i = 0; i < 4; ++i) { - var mode = block.GetEndpointMode(i); + ColorEndpointMode? mode = block.GetEndpointMode(i); mode.Should().Be(ColorEndpointMode.LdrLumaDirect); } } @@ -466,10 +466,10 @@ public void GetEndpointMode_WithFourPartitionBlock_ShouldReturnSameModeForAll() [Fact] public void GetEndpointMode_WithNonSharedCEM_ShouldReturnDifferentModes() { - var block = PhysicalBlock.Create(0x4000000000800D44UL); + PhysicalBlock block = PhysicalBlock.Create(0x4000000000800D44UL); - var mode0 = block.GetEndpointMode(0); - var mode1 = block.GetEndpointMode(1); + ColorEndpointMode? mode0 = block.GetEndpointMode(0); + ColorEndpointMode? mode1 = block.GetEndpointMode(1); mode0.Should().Be(ColorEndpointMode.LdrLumaDirect); mode1.Should().Be(ColorEndpointMode.LdrLumaBaseOffset); @@ -478,9 +478,9 @@ public void GetEndpointMode_WithNonSharedCEM_ShouldReturnDifferentModes() [Fact] public void GetEndpointMode_WithVoidExtent_ShouldReturnNull() { - var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); - var mode = block.GetEndpointMode(0); + ColorEndpointMode? mode = block.GetEndpointMode(0); mode.Should().BeNull(); } @@ -491,9 +491,9 @@ public void GetEndpointMode_WithVoidExtent_ShouldReturnNull() [InlineData(100)] public void GetEndpointMode_WithInvalidPartitionIndex_ShouldReturnNull(int index) { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); - var mode = block.GetEndpointMode(index); + ColorEndpointMode? mode = block.GetEndpointMode(index); mode.Should().BeNull(); } @@ -501,9 +501,9 @@ public void GetEndpointMode_WithInvalidPartitionIndex_ShouldReturnNull(int index [Fact] public void GetColorValuesCount_WithStandardBlock_ShouldReturn2() { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); - var count = block.GetColorValuesCount(); + int? count = block.GetColorValuesCount(); count.Should().Be(2); } @@ -511,9 +511,9 @@ public void GetColorValuesCount_WithStandardBlock_ShouldReturn2() [Fact] public void GetColorValuesCount_WithVoidExtent_ShouldReturn4() { - var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); - var count = block.GetColorValuesCount(); + int? count = block.GetColorValuesCount(); count.Should().Be(4); } @@ -521,9 +521,9 @@ public void GetColorValuesCount_WithVoidExtent_ShouldReturn4() [Fact] public void GetColorValuesCount_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var count = block.GetColorValuesCount(); + int? count = block.GetColorValuesCount(); count.Should().BeNull(); } @@ -531,9 +531,9 @@ public void GetColorValuesCount_WithErrorBlock_ShouldReturnNull() [Fact] public void GetColorBitCount_WithStandardBlock_ShouldReturn16() { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); - var bitCount = block.GetColorBitCount(); + int? bitCount = block.GetColorBitCount(); bitCount.Should().Be(16); } @@ -541,9 +541,9 @@ public void GetColorBitCount_WithStandardBlock_ShouldReturn16() [Fact] public void GetColorBitCount_WithVoidExtent_ShouldReturn64() { - var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); - var bitCount = block.GetColorBitCount(); + int? bitCount = block.GetColorBitCount(); bitCount.Should().Be(64); } @@ -551,9 +551,9 @@ public void GetColorBitCount_WithVoidExtent_ShouldReturn64() [Fact] public void GetColorBitCount_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var bitCount = block.GetColorBitCount(); + int? bitCount = block.GetColorBitCount(); bitCount.Should().BeNull(); } @@ -561,9 +561,9 @@ public void GetColorBitCount_WithErrorBlock_ShouldReturnNull() [Fact] public void GetColorValuesRange_WithStandardBlock_ShouldReturn255() { - var block = PhysicalBlock.Create(0x0000000001FE000173UL); + PhysicalBlock block = PhysicalBlock.Create(0x0000000001FE000173UL); - var range = block.GetColorValuesRange(); + int? range = block.GetColorValuesRange(); range.Should().Be(255); } @@ -571,9 +571,9 @@ public void GetColorValuesRange_WithStandardBlock_ShouldReturn255() [Fact] public void GetColorValuesRange_WithVoidExtent_ShouldReturnMaxUInt16() { - var block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); + PhysicalBlock block = PhysicalBlock.Create(0xFFF8003FFE000DFCUL); - var range = block.GetColorValuesRange(); + int? range = block.GetColorValuesRange(); range.Should().Be((1 << 16) - 1); } @@ -581,9 +581,9 @@ public void GetColorValuesRange_WithVoidExtent_ShouldReturnMaxUInt16() [Fact] public void GetColorValuesRange_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var range = block.GetColorValuesRange(); + int? range = block.GetColorValuesRange(); range.Should().BeNull(); } @@ -597,9 +597,9 @@ public void GetColorValuesRange_WithErrorBlock_ShouldReturnNull() [InlineData(0xFFF8003FFE000DFCUL, 64)] public void GetColorStartBit_WithVariousBlocks_ShouldReturnExpectedValue(ulong blockBits, int expectedStartBit) { - var block = PhysicalBlock.Create(blockBits); + PhysicalBlock block = PhysicalBlock.Create(blockBits); - var startBit = block.GetColorStartBit(); + int? startBit = block.GetColorStartBit(); startBit.Should().Be(expectedStartBit); } @@ -607,9 +607,9 @@ public void GetColorStartBit_WithVariousBlocks_ShouldReturnExpectedValue(ulong b [Fact] public void GetColorStartBit_WithErrorBlock_ShouldReturnNull() { - var block = PhysicalBlock.Create(ErrorBlock); + PhysicalBlock block = PhysicalBlock.Create(ErrorBlock); - var startBit = block.GetColorStartBit(); + int? startBit = block.GetColorStartBit(); startBit.Should().BeNull(); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs index 14e4a602..e261b4a7 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/QuantizationTests.cs @@ -30,12 +30,12 @@ public void QuantizeWeightToRange_WithMaxValue_ShouldNotExceedRange() [Fact] public void QuantizeCEValueToRange_WithVariousValues_ShouldNotExceedRange() { - var ranges = BoundedIntegerSequenceCodec.MaxRanges; - var testValues = new[] { 0, 4, 15, 22, 66, 91, 126 }; + int[] ranges = BoundedIntegerSequenceCodec.MaxRanges; + int[] testValues = [0, 4, 15, 22, 66, 91, 126]; - foreach (var range in ranges.Where(r => r >= Quantization.EndpointRangeMinValue)) + foreach (int range in ranges.Where(r => r >= Quantization.EndpointRangeMinValue)) { - foreach (var value in testValues) + foreach (int value in testValues) { Quantization.QuantizeCEValueToRange(value, range).Should().BeLessThanOrEqualTo(range); } @@ -45,12 +45,12 @@ public void QuantizeCEValueToRange_WithVariousValues_ShouldNotExceedRange() [Fact] public void QuantizeWeightToRange_WithVariousValues_ShouldNotExceedRange() { - var ranges = BoundedIntegerSequenceCodec.MaxRanges; - var testValues = new[] { 0, 4, 15, 22 }; + int[] ranges = BoundedIntegerSequenceCodec.MaxRanges; + int[] testValues = [0, 4, 15, 22]; - foreach (var range in ranges.Where(r => r <= Quantization.WeightRangeMaxValue)) + foreach (int range in ranges.Where(r => r <= Quantization.WeightRangeMaxValue)) { - foreach (var value in testValues) + foreach (int value in testValues) { Quantization.QuantizeWeightToRange(value, range).Should().BeLessThanOrEqualTo(range); } @@ -60,14 +60,14 @@ public void QuantizeWeightToRange_WithVariousValues_ShouldNotExceedRange() [Fact] public void QuantizeWeight_ThenUnquantize_ShouldReturnOriginalQuantizedValue() { - var ranges = BoundedIntegerSequenceCodec.MaxRanges; + int[] ranges = BoundedIntegerSequenceCodec.MaxRanges; - foreach (var range in ranges.Where(r => r <= Quantization.WeightRangeMaxValue)) + foreach (int range in ranges.Where(r => r <= Quantization.WeightRangeMaxValue)) { for (int quantizedValue = 0; quantizedValue <= range; ++quantizedValue) { - var unquantized = Quantization.UnquantizeWeightFromRange(quantizedValue, range); - var requantized = Quantization.QuantizeWeightToRange(unquantized, range); + int unquantized = Quantization.UnquantizeWeightFromRange(quantizedValue, range); + int requantized = Quantization.QuantizeWeightToRange(unquantized, range); requantized.Should().Be(quantizedValue); } @@ -77,14 +77,14 @@ public void QuantizeWeight_ThenUnquantize_ShouldReturnOriginalQuantizedValue() [Fact] public void QuantizeCEValue_ThenUnquantize_ShouldReturnOriginalQuantizedValue() { - var ranges = BoundedIntegerSequenceCodec.MaxRanges; + int[] ranges = BoundedIntegerSequenceCodec.MaxRanges; - foreach (var range in ranges.Where(r => r >= Quantization.EndpointRangeMinValue)) + foreach (int range in ranges.Where(r => r >= Quantization.EndpointRangeMinValue)) { for (int quantizedValue = 0; quantizedValue <= range; ++quantizedValue) { - var unquantized = Quantization.UnquantizeCEValueFromRange(quantizedValue, range); - var requantized = Quantization.QuantizeCEValueToRange(unquantized, range); + int unquantized = Quantization.UnquantizeCEValueFromRange(quantizedValue, range); + int requantized = Quantization.QuantizeCEValueToRange(unquantized, range); requantized.Should().Be(quantizedValue); } @@ -101,7 +101,7 @@ public void QuantizeCEValue_ThenUnquantize_ShouldReturnOriginalQuantizedValue() [InlineData(255, 255)] public void UnquantizeCEValueFromRange_ShouldProduceValidByteValue(int quantizedValue, int range) { - var result = Quantization.UnquantizeCEValueFromRange(quantizedValue, range); + int result = Quantization.UnquantizeCEValueFromRange(quantizedValue, range); result.Should().BeLessThan(256); } @@ -113,7 +113,7 @@ public void UnquantizeCEValueFromRange_ShouldProduceValidByteValue(int quantized [InlineData(29, 31)] public void UnquantizeWeightFromRange_ShouldNotExceed64(int quantizedValue, int range) { - var result = Quantization.UnquantizeWeightFromRange(quantizedValue, range); + int result = Quantization.UnquantizeWeightFromRange(quantizedValue, range); result.Should().BeLessThanOrEqualTo(64); } @@ -121,21 +121,23 @@ public void UnquantizeWeightFromRange_ShouldNotExceed64(int quantizedValue, int [Fact] public void Quantize_WithDesiredRange_ShouldMatchExpectedRangeOutput() { - var ranges = BoundedIntegerSequenceCodec.MaxRanges; + int[] ranges = BoundedIntegerSequenceCodec.MaxRanges; int rangeIndex = 0; for (int desiredRange = 1; desiredRange <= byte.MaxValue; ++desiredRange) { while (rangeIndex + 1 < ranges.Length && ranges[rangeIndex + 1] <= desiredRange) + { ++rangeIndex; + } int expectedRange = ranges[rangeIndex]; // Test CE values if (desiredRange >= Quantization.EndpointRangeMinValue) { - var testValues = new[] { 0, 13, 173, 208, 255 }; - foreach (var value in testValues) + int[] testValues = [0, 13, 173, 208, 255]; + foreach (int value in testValues) { Quantization.QuantizeCEValueToRange(value, desiredRange) .Should().Be(Quantization.QuantizeCEValueToRange(value, expectedRange)); @@ -145,8 +147,8 @@ public void Quantize_WithDesiredRange_ShouldMatchExpectedRangeOutput() // Test weight values if (desiredRange <= Quantization.WeightRangeMaxValue) { - var testValues = new[] { 0, 12, 23, 63 }; - foreach (var value in testValues) + int[] testValues = [0, 12, 23, 63]; + foreach (int value in testValues) { Quantization.QuantizeWeightToRange(value, desiredRange) .Should().Be(Quantization.QuantizeWeightToRange(value, expectedRange)); @@ -194,7 +196,9 @@ public void QuantizeWeightToRange_ShouldBeMonotonicallyIncreasing() int range = (1 << numBits) - 1; if (range > Quantization.WeightRangeMaxValue) + { continue; + } int lastQuantizedValue = -1; @@ -218,7 +222,9 @@ public void QuantizeCEValueToRange_WithSmallBitRanges_ShouldQuantizeLowValuesToZ int range = (1 << numBits) - 1; if (range < Quantization.EndpointRangeMinValue) + { continue; + } const int cevBits = 8; int halfMaxQuantBits = Math.Max(0, cevBits - numBits - 1); @@ -236,7 +242,9 @@ public void QuantizeWeightToRange_WithSmallBitRanges_ShouldQuantizeLowValuesToZe int range = (1 << numBits) - 1; if (range > Quantization.WeightRangeMaxValue) + { continue; + } const int weightBits = 6; int halfMaxQuantBits = Math.Max(0, weightBits - numBits - 1); @@ -249,10 +257,10 @@ public void QuantizeWeightToRange_WithSmallBitRanges_ShouldQuantizeLowValuesToZe [Fact] public void UnquantizeWeightFromRange_WithQuintRange_ShouldMatchExpected() { - var values = new List { 4, 6, 4, 6, 7, 5, 7, 5 }; - var quintExpected = new List { 14, 21, 14, 21, 43, 50, 43, 50 }; + List values = [4, 6, 4, 6, 7, 5, 7, 5]; + List quintExpected = [14, 21, 14, 21, 43, 50, 43, 50]; - var quantized = values.Select(v => Quantization.UnquantizeWeightFromRange(v, 9)).ToList(); + List quantized = [.. values.Select(v => Quantization.UnquantizeWeightFromRange(v, 9))]; quantized.Should().Equal(quintExpected); } @@ -260,10 +268,10 @@ public void UnquantizeWeightFromRange_WithQuintRange_ShouldMatchExpected() [Fact] public void UnquantizeWeightFromRange_WithTritRange_ShouldMatchExpected() { - var values = new List { 4, 6, 4, 6, 7, 5, 7, 5 }; - var tritExpected = new List { 5, 23, 5, 23, 41, 59, 41, 59 }; + List values = [4, 6, 4, 6, 7, 5, 7, 5]; + List tritExpected = [5, 23, 5, 23, 41, 59, 41, 59]; - var quantized = values.Select(v => Quantization.UnquantizeWeightFromRange(v, 11)).ToList(); + List quantized = [.. values.Select(v => Quantization.UnquantizeWeightFromRange(v, 11))]; quantized.Should().Equal(tritExpected); } @@ -273,7 +281,7 @@ public void QuantizeCEValueToRange_WithInvalidMinRange_ShouldThrowArgumentOutOfR { for (int range = 0; range < Quantization.EndpointRangeMinValue; range++) { - var action = () => Quantization.QuantizeCEValueToRange(0, range); + Action action = () => Quantization.QuantizeCEValueToRange(0, range); action.Should().Throw(); } } @@ -283,7 +291,7 @@ public void UnquantizeCEValueFromRange_WithInvalidMinRange_ShouldThrowArgumentOu { for (int range = 0; range < Quantization.EndpointRangeMinValue; range++) { - var action = () => Quantization.UnquantizeCEValueFromRange(0, range); + Action action = () => Quantization.UnquantizeCEValueFromRange(0, range); action.Should().Throw(); } } @@ -291,7 +299,7 @@ public void UnquantizeCEValueFromRange_WithInvalidMinRange_ShouldThrowArgumentOu [Fact] public void QuantizeWeightToRange_WithZeroRange_ShouldThrowArgumentOutOfRangeException() { - var action = () => Quantization.QuantizeWeightToRange(0, 0); + Action action = () => Quantization.QuantizeWeightToRange(0, 0); action.Should().Throw(); } @@ -299,7 +307,7 @@ public void QuantizeWeightToRange_WithZeroRange_ShouldThrowArgumentOutOfRangeExc [Fact] public void UnquantizeWeightFromRange_WithZeroRange_ShouldThrowArgumentOutOfRangeException() { - var action = () => Quantization.UnquantizeWeightFromRange(0, 0); + Action action = () => Quantization.UnquantizeWeightFromRange(0, 0); action.Should().Throw(); } @@ -310,7 +318,7 @@ public void UnquantizeWeightFromRange_WithZeroRange_ShouldThrowArgumentOutOfRang [InlineData(10000, 17)] public void QuantizeCEValueToRange_WithInvalidValue_ShouldThrowArgumentOutOfRangeException(int value, int range) { - var action = () => Quantization.QuantizeCEValueToRange(value, range); + Action action = () => Quantization.QuantizeCEValueToRange(value, range); action.Should().Throw(); } @@ -321,7 +329,7 @@ public void QuantizeCEValueToRange_WithInvalidValue_ShouldThrowArgumentOutOfRang [InlineData(-1000, 17)] public void UnquantizeCEValueFromRange_WithInvalidValue_ShouldThrowArgumentOutOfRangeException(int value, int range) { - var action = () => Quantization.UnquantizeCEValueFromRange(value, range); + Action action = () => Quantization.UnquantizeCEValueFromRange(value, range); action.Should().Throw(); } @@ -331,7 +339,7 @@ public void UnquantizeCEValueFromRange_WithInvalidValue_ShouldThrowArgumentOutOf [InlineData(0, 257)] public void QuantizeCEValueToRange_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int value, int range) { - var action = () => Quantization.QuantizeCEValueToRange(value, range); + Action action = () => Quantization.QuantizeCEValueToRange(value, range); action.Should().Throw(); } @@ -341,7 +349,7 @@ public void QuantizeCEValueToRange_WithInvalidRange_ShouldThrowArgumentOutOfRang [InlineData(0, 256)] public void UnquantizeCEValueFromRange_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int value, int range) { - var action = () => Quantization.UnquantizeCEValueFromRange(value, range); + Action action = () => Quantization.UnquantizeCEValueFromRange(value, range); action.Should().Throw(); } @@ -352,7 +360,7 @@ public void UnquantizeCEValueFromRange_WithInvalidRange_ShouldThrowArgumentOutOf [InlineData(10000, 17)] public void QuantizeWeightToRange_WithInvalidValue_ShouldThrowArgumentOutOfRangeException(int value, int range) { - var action = () => Quantization.QuantizeWeightToRange(value, range); + Action action = () => Quantization.QuantizeWeightToRange(value, range); action.Should().Throw(); } @@ -363,7 +371,7 @@ public void QuantizeWeightToRange_WithInvalidValue_ShouldThrowArgumentOutOfRange [InlineData(-1000, 17)] public void UnquantizeWeightFromRange_WithInvalidValue_ShouldThrowArgumentOutOfRangeException(int value, int range) { - var action = () => Quantization.UnquantizeWeightFromRange(value, range); + Action action = () => Quantization.UnquantizeWeightFromRange(value, range); action.Should().Throw(); } @@ -373,7 +381,7 @@ public void UnquantizeWeightFromRange_WithInvalidValue_ShouldThrowArgumentOutOfR [InlineData(0, 32)] public void QuantizeWeightToRange_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int value, int range) { - var action = () => Quantization.QuantizeWeightToRange(value, range); + Action action = () => Quantization.QuantizeWeightToRange(value, range); action.Should().Throw(); } @@ -383,7 +391,7 @@ public void QuantizeWeightToRange_WithInvalidRange_ShouldThrowArgumentOutOfRange [InlineData(0, 64)] public void UnquantizeWeightFromRange_WithInvalidRange_ShouldThrowArgumentOutOfRangeException(int value, int range) { - var action = () => Quantization.UnquantizeWeightFromRange(value, range); + Action action = () => Quantization.UnquantizeWeightFromRange(value, range); action.Should().Throw(); } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs index 2d224434..a71c8e97 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Astc/WeightInfillTests.cs @@ -26,7 +26,7 @@ public class WeightInfillTests public void CountBitsForWeights_WithVariousParameters_ShouldReturnCorrectBitCount( int width, int height, int range, int expectedBitCount) { - var bitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(width * height, range); + int bitCount = BoundedIntegerSequenceCodec.GetBitCountForRange(width * height, range); bitCount.Should().Be(expectedBitCount); } @@ -37,9 +37,9 @@ public void InfillWeights_With3x3Grid_ShouldBilinearlyInterpolateTo5x5() int[] weights = [1, 3, 5, 3, 5, 7, 5, 7, 9]; int[] expected = [1, 2, 3, 4, 5, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 4, 5, 6, 7, 8, 5, 6, 7, 8, 9]; - var footprint = Footprint.Get5x5(); - var di = DecimationTable.Get(footprint, 3, 3); - var result = new int[footprint.PixelCount]; + Footprint footprint = Footprint.Get5x5(); + DecimationInfo di = DecimationTable.Get(footprint, 3, 3); + int[] result = new int[footprint.PixelCount]; DecimationTable.InfillWeights(weights, di, result); result.Should().HaveCount(expected.Length); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderCubemapTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderCubemapTests.cs index aed5e795..f81e64a9 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderCubemapTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderCubemapTests.cs @@ -5,30 +5,28 @@ using SixLabors.ImageSharp.Textures.Tests.Enums; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Dds; + +[Trait("Format", "Dds")] +public class DdsDecoderCubemapTests { - [Trait("Format", "Dds")] - public class DdsDecoderCubemapTests - { - private static readonly DdsDecoder DdsDecoder = new DdsDecoder(); + private static readonly DdsDecoder DdsDecoder = new(); - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Cubemap, TestTextureTool.NvDxt, "cubemap has-mips.dds")] - public void DdsDecoder_CanDecode_Cubemap_With_Mips(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Cubemap, TestTextureTool.NvDxt, "cubemap has-mips.dds")] + public void DdsDecoder_CanDecode_Cubemap_With_Mips(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Cubemap, TestTextureTool.NvDxt, "cubemap no-mips.dds")] - public void DdsDecoder_CanDecode_Cubemap_Without_Mips(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Cubemap, TestTextureTool.NvDxt, "cubemap no-mips.dds")] + public void DdsDecoder_CanDecode_Cubemap_Without_Mips(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderFlatTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderFlatTests.cs index 1186142b..1c9d0e88 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderFlatTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderFlatTests.cs @@ -1,355 +1,352 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Textures.Formats.Dds; using SixLabors.ImageSharp.Textures.Tests.Enums; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Dds; + +[Trait("Format", "Dds")] +public class DdsDecoderFlatTests { - [Trait("Format", "Dds")] - public class DdsDecoderFlatTests - { - private static readonly DdsDecoder DdsDecoder = new DdsDecoder(); - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips 3DC.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_3DC(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips 3DC.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_3DC(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips A8.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_A8(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips A8.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_A8(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips A8L8.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_A8L8(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips A8L8.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_A8L8(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips CXV8U8.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_CXV8U8(TestTextureProvider provider) => - Assert.Throws(() => - { - using Texture texture = provider.GetTexture(DdsDecoder); - }); - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips CXV8U8.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_CXV8U8(TestTextureProvider provider) => - Assert.Throws(() => - { - using Texture texture = provider.GetTexture(DdsDecoder); - }); - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT1A.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT1A(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT1A.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT1A(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT1C.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT1C(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT1C.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT1C(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT3.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT3(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT3.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT3(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT5.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT5(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT5.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT5(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT5NM.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT5NM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT5NM.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT5NM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips FP16X4.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_FP16X4(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips FP16X4.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_FP16X4(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips FP32.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_FP32(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips FP32.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_FP32(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips FP32X4.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_FP32X4(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips FP32X4.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_FP32X4(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips G16R16.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_G16R16(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips G16R16.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_G16R16(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips G16R16F.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_G16R16F(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips G16R16F.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_G16R16F(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U1555.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U1555(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U1555.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U1555(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U4444.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U4444(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U4444.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U4444(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U555.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U555(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U555.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U555(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U565.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U565(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U565.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U565(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U888.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U888(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U888.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U888(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U8888.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U8888(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U8888.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U8888(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips V8U8.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_V8U8(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips V8U8.dds")] - public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_V8U8(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + private static readonly DdsDecoder DdsDecoder = new(); + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips 3DC.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_3DC(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips 3DC.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_3DC(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips A8.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_A8(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips A8.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_A8(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips A8L8.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_A8L8(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips A8L8.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_A8L8(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips CXV8U8.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_CXV8U8(TestTextureProvider provider) => + Assert.Throws(() => + { + using Texture texture = provider.GetTexture(DdsDecoder); + }); + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips CXV8U8.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_CXV8U8(TestTextureProvider provider) => + Assert.Throws(() => + { + using Texture texture = provider.GetTexture(DdsDecoder); + }); + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT1A.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT1A(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT1A.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT1A(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT1C.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT1C(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT1C.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT1C(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT3.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT3(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT3.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT3(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT5.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT5(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT5.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT5(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips DXT5NM.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_DXT5NM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips DXT5NM.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_DXT5NM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips FP16X4.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_FP16X4(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips FP16X4.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_FP16X4(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips FP32.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_FP32(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips FP32.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_FP32(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips FP32X4.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_FP32X4(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips FP32X4.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_FP32X4(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips G16R16.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_G16R16(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips G16R16.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_G16R16(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips G16R16F.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_G16R16F(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips G16R16F.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_G16R16F(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U1555.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U1555(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U1555.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U1555(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U4444.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U4444(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U4444.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U4444(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U555.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U555(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U555.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U555(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U565.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U565(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U565.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U565(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U888.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U888(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U888.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U888(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips U8888.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_U8888(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips U8888.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_U8888(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat has-mips V8U8.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_Has_Mips_V8U8(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.NvDxt, "flat no-mips V8U8.dds")] + public void DdsDecoder_CanDecode_Flat_NvDxt_No_Mips_V8U8(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderTexConvFlatTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderTexConvFlatTests.cs index e2ad3379..daba9a91 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderTexConvFlatTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderTexConvFlatTests.cs @@ -1,707 +1,704 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Textures.Formats.Dds; using SixLabors.ImageSharp.Textures.Tests.Enums; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Dds; + +[Trait("Format", "Dds")] +public class DdsDecoderTexConvFlatTests { - [Trait("Format", "Dds")] - public class DdsDecoderTexConvFlatTests + private static readonly DdsDecoder DdsDecoder = new(); + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat A8_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_A8_UNORM(TestTextureProvider provider) { - private static readonly DdsDecoder DdsDecoder = new DdsDecoder(); + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat A8_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_A8_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat AYUV.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_AYUV(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat AYUV.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_AYUV(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B4G4R4A4_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_B4G4R4A4_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B4G4R4A4_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_B4G4R4A4_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B5G5R5A1_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_B5G5R5A1_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B5G5R5A1_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_B5G5R5A1_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B5G6R5_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_B5G6R5_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B5G6R5_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_B5G6R5_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B8G8R8A8_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_B8G8R8A8_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B8G8R8A8_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_B8G8R8A8_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B8G8R8A8_UNORM_SRGB.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_B8G8R8A8_UNORM_SRGB(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B8G8R8A8_UNORM_SRGB.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_B8G8R8A8_UNORM_SRGB(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B8G8R8X8_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_B8G8R8X8_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B8G8R8X8_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_B8G8R8X8_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B8G8R8X8_UNORM_SRGB.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_B8G8R8X8_UNORM_SRGB(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat B8G8R8X8_UNORM_SRGB.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_B8G8R8X8_UNORM_SRGB(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC1_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC1_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC1_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC1_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC1_UNORM_SRGB.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC1_UNORM_SRGB(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC1_UNORM_SRGB.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC1_UNORM_SRGB(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC2_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC2_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC2_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC2_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC2_UNORM_SRGB.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC2_UNORM_SRGB(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC2_UNORM_SRGB.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC2_UNORM_SRGB(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC3_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC3_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC3_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC3_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC3_UNORM_SRGB.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC3_UNORM_SRGB(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC3_UNORM_SRGB.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC3_UNORM_SRGB(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC4_SNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC4_SNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC4_SNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC4_SNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC4_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC4_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC4_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC4_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC5_SNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC5_SNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC5_SNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC5_SNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC5_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC5_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC5_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC5_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC6H_SF16.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC6H_SF16(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC6H_SF16.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC6H_SF16(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC6H_UF16.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC6H_UF16(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC6H_UF16.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC6H_UF16(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC7_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC7_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC7_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC7_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC7_UNORM_SRGB.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BC7_UNORM_SRGB(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BC7_UNORM_SRGB.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BC7_UNORM_SRGB(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BGRA.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BGRA(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BGRA.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BGRA(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BPTC.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BPTC(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BPTC.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BPTC(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BPTC_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_BPTC_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat BPTC_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_BPTC_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT1.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_DXT1(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT1.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_DXT1(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT2.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_DXT2(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT2.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_DXT2(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT3.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_DXT3(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT3.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_DXT3(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT4.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_DXT4(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT4.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_DXT4(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT5.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_DXT5(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat DXT5.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_DXT5(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat FP16.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_FP16(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat FP16.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_FP16(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat FP32.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_FP32(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat FP32.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_FP32(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat G8R8_G8B8_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_G8R8_G8B8_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat G8R8_G8B8_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_G8R8_G8B8_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R10G10B10_XR_BIAS_A2_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R10G10B10_XR_BIAS_A2_UNORM(TestTextureProvider provider) => - Assert.Throws(() => - { - using Texture texture = provider.GetTexture(DdsDecoder); - }); - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R10G10B10A2_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R10G10B10A2_UINT(TestTextureProvider provider) + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R10G10B10_XR_BIAS_A2_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R10G10B10_XR_BIAS_A2_UNORM(TestTextureProvider provider) => + Assert.Throws(() => { using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + }); - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R10G10B10A2_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R10G10B10A2_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R10G10B10A2_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R10G10B10A2_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R11G11B10_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R11G11B10_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R10G10B10A2_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R10G10B10A2_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R11G11B10_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R11G11B10_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_SNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16_SNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_SNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16_SNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_SNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_SNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_SNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_SNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_SNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_SNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_SNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_SNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R16G16B16A16_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R16G16B16A16_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32A32_FLOAT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32A32_FLOAT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32A32_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32A32_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32A32_FLOAT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32A32_FLOAT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32A32_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32A32_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32A32_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32A32_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R32G32B32A32_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R32G32B32A32_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8_SNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8_SNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8_SNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8_SNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_B8G8_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_B8G8_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_B8G8_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_B8G8_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_SNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_SNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_SNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_SNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_SINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_SINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_SNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_SNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_SINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_SINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_UINT.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_UINT(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_SNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_SNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_UNORM.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_UNORM(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_UINT.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_UINT(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_UNORM_SRGB.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_UNORM_SRGB(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R9G9B9E5_SHAREDEXP.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_R9G9B9E5_SHAREDEXP(TestTextureProvider provider) => - Assert.Throws(() => - { - using Texture texture = provider.GetTexture(DdsDecoder); - }); - - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat RGBA.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_RGBA(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_UNORM.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_UNORM(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat Y210.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_Y210(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R8G8B8A8_UNORM_SRGB.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R8G8B8A8_UNORM_SRGB(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat Y216.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_Y216(TestTextureProvider provider) + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat R9G9B9E5_SHAREDEXP.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_R9G9B9E5_SHAREDEXP(TestTextureProvider provider) => + Assert.Throws(() => { using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + }); - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat Y410.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_Y410(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat RGBA.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_RGBA(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat Y416.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_Y416(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat Y210.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_Y210(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat YUY2.DDS")] - public void DdsDecoder_CanDecode_Flat_TexConv_YUY2(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(DdsDecoder); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat Y216.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_Y216(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat Y410.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_Y410(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat Y416.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_Y416(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); + } + + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Flat, TestTextureTool.TexConv, "flat YUY2.DDS")] + public void DdsDecoder_CanDecode_Flat_TexConv_YUY2(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(DdsDecoder); + provider.SaveTextures(texture); } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderVolumeTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderVolumeTests.cs index 0a3f897b..dd426104 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderVolumeTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Dds/DdsDecoderVolumeTests.cs @@ -5,27 +5,25 @@ using SixLabors.ImageSharp.Textures.Tests.Enums; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; -using Xunit; -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Dds +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Dds; + +[Trait("Format", "Dds")] +public class DdsDecoderVolumeTests { - [Trait("Format", "Dds")] - public class DdsDecoderVolumeTests + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Volume, TestTextureTool.NvDxt, "volume has-mips.dds")] + public void DdsDecoder_CanDecode_Volume_NvDxt_Has_Mips(TestTextureProvider provider) { - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Volume, TestTextureTool.NvDxt, "volume has-mips.dds")] - public void DdsDecoder_CanDecode_Volume_NvDxt_Has_Mips(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(new DdsDecoder()); - provider.SaveTextures(texture); - } + using Texture texture = provider.GetTexture(new DdsDecoder()); + provider.SaveTextures(texture); + } - [Theory] - [WithFile(TestTextureFormat.Dds, TestTextureType.Volume, TestTextureTool.NvDxt, "volume no-mips.dds")] - public void DdsDecoder_CanDecode_Volume_NvDxt_No_Mips(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(new DdsDecoder()); - provider.SaveTextures(texture); - } + [Theory] + [WithFile(TestTextureFormat.Dds, TestTextureType.Volume, TestTextureTool.NvDxt, "volume no-mips.dds")] + public void DdsDecoder_CanDecode_Volume_NvDxt_No_Mips(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(new DdsDecoder()); + provider.SaveTextures(texture); } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs index 1fbacc67..3d28c5c7 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx/KtxDecoderTests.cs @@ -8,45 +8,43 @@ using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; using SixLabors.ImageSharp.Textures.TextureFormats; -using Xunit; -namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx; + +[Trait("Format", "Ktx")] +public class KtxDecoderTests { - [Trait("Format", "Ktx")] - public class KtxDecoderTests - { - private static readonly KtxDecoder KtxDecoder = new KtxDecoder(); + private static readonly KtxDecoder KtxDecoder = new(); - [Theory] - [WithFile(TestTextureFormat.Ktx, TestTextureType.Flat, TestTextureTool.PvrTexToolCli, TestImages.Ktx.Rgba)] - public void KtxDecoder_CanDecode_Rgba8888(TestTextureProvider provider) - { - using Texture texture = provider.GetTexture(KtxDecoder); - provider.SaveTextures(texture); - var flatTexture = texture as FlatTexture; + [Theory] + [WithFile(TestTextureFormat.Ktx, TestTextureType.Flat, TestTextureTool.PvrTexToolCli, TestImages.Ktx.Rgba)] + public void KtxDecoder_CanDecode_Rgba8888(TestTextureProvider provider) + { + using Texture texture = provider.GetTexture(KtxDecoder); + provider.SaveTextures(texture); + FlatTexture flatTexture = texture as FlatTexture; - Assert.NotNull(flatTexture?.MipMaps); - Assert.Equal(8, flatTexture.MipMaps.Count); - Assert.Equal(200, flatTexture.MipMaps[0].GetImage().Height); - Assert.Equal(200, flatTexture.MipMaps[0].GetImage().Width); - Assert.Equal(100, flatTexture.MipMaps[1].GetImage().Height); - Assert.Equal(100, flatTexture.MipMaps[1].GetImage().Width); - Assert.Equal(50, flatTexture.MipMaps[2].GetImage().Height); - Assert.Equal(50, flatTexture.MipMaps[2].GetImage().Width); - Assert.Equal(25, flatTexture.MipMaps[3].GetImage().Height); - Assert.Equal(25, flatTexture.MipMaps[3].GetImage().Width); - Assert.Equal(12, flatTexture.MipMaps[4].GetImage().Height); - Assert.Equal(12, flatTexture.MipMaps[4].GetImage().Width); - Assert.Equal(6, flatTexture.MipMaps[5].GetImage().Height); - Assert.Equal(6, flatTexture.MipMaps[5].GetImage().Width); - Assert.Equal(3, flatTexture.MipMaps[6].GetImage().Height); - Assert.Equal(3, flatTexture.MipMaps[6].GetImage().Width); - Assert.Equal(1, flatTexture.MipMaps[7].GetImage().Height); - Assert.Equal(1, flatTexture.MipMaps[7].GetImage().Width); - Image firstMipMap = flatTexture.MipMaps[0].GetImage(); - Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); - var firstMipMapImage = firstMipMap as Image; - firstMipMapImage.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); - } + Assert.NotNull(flatTexture?.MipMaps); + Assert.Equal(8, flatTexture.MipMaps.Count); + Assert.Equal(200, flatTexture.MipMaps[0].GetImage().Height); + Assert.Equal(200, flatTexture.MipMaps[0].GetImage().Width); + Assert.Equal(100, flatTexture.MipMaps[1].GetImage().Height); + Assert.Equal(100, flatTexture.MipMaps[1].GetImage().Width); + Assert.Equal(50, flatTexture.MipMaps[2].GetImage().Height); + Assert.Equal(50, flatTexture.MipMaps[2].GetImage().Width); + Assert.Equal(25, flatTexture.MipMaps[3].GetImage().Height); + Assert.Equal(25, flatTexture.MipMaps[3].GetImage().Width); + Assert.Equal(12, flatTexture.MipMaps[4].GetImage().Height); + Assert.Equal(12, flatTexture.MipMaps[4].GetImage().Width); + Assert.Equal(6, flatTexture.MipMaps[5].GetImage().Height); + Assert.Equal(6, flatTexture.MipMaps[5].GetImage().Width); + Assert.Equal(3, flatTexture.MipMaps[6].GetImage().Height); + Assert.Equal(3, flatTexture.MipMaps[6].GetImage().Width); + Assert.Equal(1, flatTexture.MipMaps[7].GetImage().Height); + Assert.Equal(1, flatTexture.MipMaps[7].GetImage().Width); + Image firstMipMap = flatTexture.MipMaps[0].GetImage(); + Assert.Equal(32, firstMipMap.PixelType.BitsPerPixel); + Image firstMipMapImage = firstMipMap as Image; + firstMipMapImage.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); } } diff --git a/tests/ImageSharp.Textures.Tests/Formats/PixelFormat/PixelFormatTests.cs b/tests/ImageSharp.Textures.Tests/Formats/PixelFormat/PixelFormatTests.cs index 68103752..e19c7bcf 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/PixelFormat/PixelFormatTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/PixelFormat/PixelFormatTests.cs @@ -2,126 +2,126 @@ // Licensed under the Six Labors Split License. using System.Globalization; +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.PixelFormats; -using Xunit; using Rg16 = SixLabors.ImageSharp.Textures.PixelFormats.Rg16; -namespace SixLabors.ImageSharp.Textures.Tests.Formats.PixelFormat +namespace SixLabors.ImageSharp.Textures.Tests.Formats.PixelFormat; + +[Trait("Category", "PixelFormats")] +public class PixelFormatTests { - [Trait("Category", "PixelFormats")] - public class PixelFormatTests + [Fact] + public void Test_Rg16() { - [Fact] - public void Test_Rg16() + string[] hexValues = ["FF", "FF00"]; + for (int i = 0; i < 2; i++) { - string[] hexValues = { "FF", "FF00" }; - for (int i = 0; i < 2; i++) - { - int x = i == 0 ? 1 : 0; - int y = i == 1 ? 1 : 0; + int x = i == 0 ? 1 : 0; + int y = i == 1 ? 1 : 0; - var testPixel = new Rg16(x, y); + Rg16 testPixel = new(x, y); - Assert.Equal(string.Format(CultureInfo.InvariantCulture, "Rg16({0}, {1})", x, y), testPixel.ToString()); + Assert.Equal(string.Format(CultureInfo.InvariantCulture, "Rg16({0}, {1})", x, y), testPixel.ToString()); - var destPixel = new ImageSharp.PixelFormats.Rgba32(0); - testPixel.ToRgba32(ref destPixel); + Rgba32 destPixel = new(0); + testPixel.ToRgba32(ref destPixel); - Assert.Equal(hexValues[i], testPixel.PackedValue.ToString("X", CultureInfo.InvariantCulture)); + Assert.Equal(hexValues[i], testPixel.PackedValue.ToString("X", CultureInfo.InvariantCulture)); - Assert.Equal(i == 0 ? 255 : 0, destPixel.R); - Assert.Equal(i == 1 ? 255 : 0, destPixel.G); - Assert.Equal(0, destPixel.B); - Assert.Equal(255, destPixel.A); + Assert.Equal(i == 0 ? 255 : 0, destPixel.R); + Assert.Equal(i == 1 ? 255 : 0, destPixel.G); + Assert.Equal(0, destPixel.B); + Assert.Equal(255, destPixel.A); - var vector4 = testPixel.ToVector4(); - testPixel.FromVector4(vector4); - Assert.Equal(testPixel.ToVector4(), vector4); - } + Vector4 vector4 = testPixel.ToVector4(); + testPixel.FromVector4(vector4); + Assert.Equal(testPixel.ToVector4(), vector4); } + } - [Fact] - public void Test_Bgr555() + [Fact] + public void Test_Bgr555() + { + string[] hexValues = ["7C00", "3E0", "1F"]; + for (int i = 0; i < 3; i++) { - var hexValues = new[] { "7C00", "3E0", "1F" }; - for (int i = 0; i < 3; i++) - { - int x = i == 0 ? 1 : 0; - int y = i == 1 ? 1 : 0; - int z = i == 2 ? 1 : 0; + int x = i == 0 ? 1 : 0; + int y = i == 1 ? 1 : 0; + int z = i == 2 ? 1 : 0; - var testPixel = new Bgr555(x, y, z); + Bgr555 testPixel = new(x, y, z); - var destPixel = new ImageSharp.PixelFormats.Rgba32(0); - testPixel.ToRgba32(ref destPixel); + Rgba32 destPixel = new(0); + testPixel.ToRgba32(ref destPixel); - Assert.Equal(hexValues[i], testPixel.PackedValue.ToString("X", CultureInfo.InvariantCulture)); + Assert.Equal(hexValues[i], testPixel.PackedValue.ToString("X", CultureInfo.InvariantCulture)); - Assert.Equal(i == 0 ? 255 : 0, destPixel.R); - Assert.Equal(i == 1 ? 255 : 0, destPixel.G); - Assert.Equal(i == 2 ? 255 : 0, destPixel.B); - Assert.Equal(255, destPixel.A); + Assert.Equal(i == 0 ? 255 : 0, destPixel.R); + Assert.Equal(i == 1 ? 255 : 0, destPixel.G); + Assert.Equal(i == 2 ? 255 : 0, destPixel.B); + Assert.Equal(255, destPixel.A); - var vector4 = testPixel.ToVector4(); - testPixel.FromVector4(vector4); - Assert.Equal(testPixel.ToVector4(), vector4); - } + Vector4 vector4 = testPixel.ToVector4(); + testPixel.FromVector4(vector4); + Assert.Equal(testPixel.ToVector4(), vector4); } + } - [Fact] - public void Test_Bgr32() + [Fact] + public void Test_Bgr32() + { + string[] hexValues = ["FF0000", "FF00", "FF"]; + for (int i = 0; i < 3; i++) { - string[] hexValues = { "FF0000", "FF00", "FF" }; - for (int i = 0; i < 3; i++) - { - int x = i == 0 ? 1 : 0; - int y = i == 1 ? 1 : 0; - int z = i == 2 ? 1 : 0; + int x = i == 0 ? 1 : 0; + int y = i == 1 ? 1 : 0; + int z = i == 2 ? 1 : 0; - var testPixel = new Bgr32(x, y, z); + Bgr32 testPixel = new(x, y, z); - var destPixel = new ImageSharp.PixelFormats.Rgba32(0); - testPixel.ToRgba32(ref destPixel); + Rgba32 destPixel = new(0); + testPixel.ToRgba32(ref destPixel); - Assert.Equal(hexValues[i], testPixel.PackedValue.ToString("X", CultureInfo.InvariantCulture)); + Assert.Equal(hexValues[i], testPixel.PackedValue.ToString("X", CultureInfo.InvariantCulture)); - Assert.Equal(i == 0 ? 255 : 0, destPixel.R); - Assert.Equal(i == 1 ? 255 : 0, destPixel.G); - Assert.Equal(i == 2 ? 255 : 0, destPixel.B); - Assert.Equal(255, destPixel.A); + Assert.Equal(i == 0 ? 255 : 0, destPixel.R); + Assert.Equal(i == 1 ? 255 : 0, destPixel.G); + Assert.Equal(i == 2 ? 255 : 0, destPixel.B); + Assert.Equal(255, destPixel.A); - var vector4 = testPixel.ToVector4(); - testPixel.FromVector4(vector4); - Assert.Equal(testPixel.ToVector4(), vector4); - } + Vector4 vector4 = testPixel.ToVector4(); + testPixel.FromVector4(vector4); + Assert.Equal(testPixel.ToVector4(), vector4); } + } - [Fact] - public void Test_Rgb32() + [Fact] + public void Test_Rgb32() + { + string[] hexValues = ["FF", "FF00", "FF0000"]; + for (int i = 0; i < 3; i++) { - string[] hexValues = { "FF", "FF00", "FF0000" }; - for (int i = 0; i < 3; i++) - { - int x = i == 0 ? 1 : 0; - int y = i == 1 ? 1 : 0; - int z = i == 2 ? 1 : 0; + int x = i == 0 ? 1 : 0; + int y = i == 1 ? 1 : 0; + int z = i == 2 ? 1 : 0; - var testPixel = new Rgb32(x, y, z); + Rgb32 testPixel = new(x, y, z); - var destPixel = new ImageSharp.PixelFormats.Rgba32(0); - testPixel.ToRgba32(ref destPixel); + Rgba32 destPixel = new(0); + testPixel.ToRgba32(ref destPixel); - Assert.Equal(hexValues[i], testPixel.PackedValue.ToString("X", CultureInfo.InvariantCulture)); + Assert.Equal(hexValues[i], testPixel.PackedValue.ToString("X", CultureInfo.InvariantCulture)); - Assert.Equal(i == 0 ? 255 : 0, destPixel.R); - Assert.Equal(i == 1 ? 255 : 0, destPixel.G); - Assert.Equal(i == 2 ? 255 : 0, destPixel.B); - Assert.Equal(255, destPixel.A); + Assert.Equal(i == 0 ? 255 : 0, destPixel.R); + Assert.Equal(i == 1 ? 255 : 0, destPixel.G); + Assert.Equal(i == 2 ? 255 : 0, destPixel.B); + Assert.Equal(255, destPixel.A); - var vector4 = testPixel.ToVector4(); - testPixel.FromVector4(vector4); - Assert.Equal(testPixel.ToVector4(), vector4); - } + Vector4 vector4 = testPixel.ToVector4(); + testPixel.FromVector4(vector4); + Assert.Equal(testPixel.ToVector4(), vector4); } } } diff --git a/tests/ImageSharp.Textures.Tests/TestFile.cs b/tests/ImageSharp.Textures.Tests/TestFile.cs index bc4c0ce5..a2ba51f3 100644 --- a/tests/ImageSharp.Textures.Tests/TestFile.cs +++ b/tests/ImageSharp.Textures.Tests/TestFile.cs @@ -1,138 +1,134 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections.Concurrent; -using System.IO; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.Tests +namespace SixLabors.ImageSharp.Textures.Tests; + +/// +/// A test image file. +/// +public class TestFile { /// - /// A test image file. + /// The test file cache. + /// + private static readonly ConcurrentDictionary Cache = new(); + + /// + /// The "Formats" directory, as lazy value + /// + // ReSharper disable once InconsistentNaming + private static readonly Lazy InputImagesDirectoryValue = new(() => TestEnvironment.InputImagesDirectoryFullPath); + + /// + /// The image (lazy initialized value) + /// + private Image image; + + /// + /// The image bytes + /// + private byte[] bytes; + + /// + /// Initializes a new instance of the class. + /// + /// The file. + private TestFile(string file) => this.FullPath = file; + + /// + /// Gets the image bytes. + /// + public byte[] Bytes => this.bytes ??= File.ReadAllBytes(this.FullPath); + + /// + /// Gets the full path to file. + /// + public string FullPath { get; } + + /// + /// Gets the file name. + /// + public string FileName => Path.GetFileName(this.FullPath); + + /// + /// Gets the file name without extension. /// - public class TestFile + public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); + + /// + /// Gets the image with lazy initialization. + /// + private Image Image => this.image ??= ImageSharp.Image.Load(this.Bytes); + + /// + /// Gets the input image directory. + /// + private static string InputImagesDirectory => InputImagesDirectoryValue.Value; + + /// + /// Gets the full qualified path to the input test file. + /// + /// + /// The file path. + /// + /// + /// The . + /// + public static string GetInputFileFullPath(string file) => Path.Combine(InputImagesDirectory, file).Replace('\\', Path.DirectorySeparatorChar); + + /// + /// Creates a new test file or returns one from the cache. + /// + /// The file path. + /// + /// The . + /// + public static TestFile Create(string file) => Cache.GetOrAdd(file, (string fileName) => new TestFile(GetInputFileFullPath(file))); + + /// + /// Gets the file name. + /// + /// The value. + /// + /// The . + /// + public string GetFileName(object value) => $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; + + /// + /// Gets the file name without extension. + /// + /// The value. + /// + /// The . + /// + public string GetFileNameWithoutExtension(object value) => this.FileNameWithoutExtension + "-" + value; + + /// + /// Creates a new image. + /// + /// + /// The . + /// + public Image CreateRgba32Image() => this.Image.Clone(); + + /// + /// Creates a new image. + /// + /// + /// The . + /// + public Image CreateRgba32Image(IImageFormat format, IImageDecoder decoder) { - /// - /// The test file cache. - /// - private static readonly ConcurrentDictionary Cache = new(); - - /// - /// The "Formats" directory, as lazy value - /// - // ReSharper disable once InconsistentNaming - private static readonly Lazy InputImagesDirectoryValue = new(() => TestEnvironment.InputImagesDirectoryFullPath); - - /// - /// The image (lazy initialized value) - /// - private Image image; - - /// - /// The image bytes - /// - private byte[] bytes; - - /// - /// Initializes a new instance of the class. - /// - /// The file. - private TestFile(string file) => this.FullPath = file; - - /// - /// Gets the image bytes. - /// - public byte[] Bytes => this.bytes ??= File.ReadAllBytes(this.FullPath); - - /// - /// Gets the full path to file. - /// - public string FullPath { get; } - - /// - /// Gets the file name. - /// - public string FileName => Path.GetFileName(this.FullPath); - - /// - /// Gets the file name without extension. - /// - public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); - - /// - /// Gets the image with lazy initialization. - /// - private Image Image => this.image ??= ImageSharp.Image.Load(this.Bytes); - - /// - /// Gets the input image directory. - /// - private static string InputImagesDirectory => InputImagesDirectoryValue.Value; - - /// - /// Gets the full qualified path to the input test file. - /// - /// - /// The file path. - /// - /// - /// The . - /// - public static string GetInputFileFullPath(string file) => Path.Combine(InputImagesDirectory, file).Replace('\\', Path.DirectorySeparatorChar); - - /// - /// Creates a new test file or returns one from the cache. - /// - /// The file path. - /// - /// The . - /// - public static TestFile Create(string file) => Cache.GetOrAdd(file, (string fileName) => new TestFile(GetInputFileFullPath(file))); - - /// - /// Gets the file name. - /// - /// The value. - /// - /// The . - /// - public string GetFileName(object value) => $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; - - /// - /// Gets the file name without extension. - /// - /// The value. - /// - /// The . - /// - public string GetFileNameWithoutExtension(object value) => this.FileNameWithoutExtension + "-" + value; - - /// - /// Creates a new image. - /// - /// - /// The . - /// - public Image CreateRgba32Image() => this.Image.Clone(); - - /// - /// Creates a new image. - /// - /// - /// The . - /// - public Image CreateRgba32Image(IImageFormat format, IImageDecoder decoder) + DecoderOptions options = new() { - var options = new DecoderOptions - { - Configuration = this.Image.Configuration - }; - options.Configuration.ImageFormatsManager.SetDecoder(format, decoder); - - return ImageSharp.Image.Load(options, this.Bytes); - } + Configuration = this.Image.Configuration + }; + options.Configuration.ImageFormatsManager.SetDecoder(format, decoder); + + return ImageSharp.Image.Load(options, this.Bytes); } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs index 6888d67f..ecd3a044 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs @@ -1,18 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes +/// +/// The output produced by this test class should be grouped into the specified subfolder. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public class GroupOutputAttribute : Attribute { - /// - /// The output produced by this test class should be grouped into the specified subfolder. - /// - [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] - public class GroupOutputAttribute : Attribute - { - public GroupOutputAttribute(string subfolder) => this.Subfolder = subfolder; + public GroupOutputAttribute(string subfolder) => this.Subfolder = subfolder; - public string Subfolder { get; } - } + public string Subfolder { get; } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/Attributes/WithFileAttribute.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/Attributes/WithFileAttribute.cs index b8eed784..dec5951a 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/Attributes/WithFileAttribute.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/Attributes/WithFileAttribute.cs @@ -1,57 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using SixLabors.ImageSharp.Textures.Tests.Enums; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; using Xunit.Sdk; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; + +public class WithFileAttribute : DataAttribute { - public class WithFileAttribute : DataAttribute + private readonly TestTextureFormat textureFormat; + private readonly TestTextureType textureType; + private readonly TestTextureTool textureTool; + private readonly string inputFile; + private readonly bool isRegex; + + public WithFileAttribute(TestTextureFormat textureFormat, TestTextureType textureType, TestTextureTool textureTool, string inputFile, bool isRegex = false) { - private readonly TestTextureFormat textureFormat; - private readonly TestTextureType textureType; - private readonly TestTextureTool textureTool; - private readonly string inputFile; - private readonly bool isRegex; + this.textureFormat = textureFormat; + this.textureType = textureType; + this.textureTool = textureTool; + this.inputFile = inputFile; + this.isRegex = isRegex; + } - public WithFileAttribute(TestTextureFormat textureFormat, TestTextureType textureType, TestTextureTool textureTool, string inputFile, bool isRegex = false) - { - this.textureFormat = textureFormat; - this.textureType = textureType; - this.textureTool = textureTool; - this.inputFile = inputFile; - this.isRegex = isRegex; - } + public override IEnumerable GetData(MethodInfo testMethod) + { + ArgumentNullException.ThrowIfNull(testMethod); + + string[] featureLevels = this.textureTool == TestTextureTool.TexConv ? ["9.1", "9.2", "9.3", "10.0", "10.1", "11.0", "11.1", "12.0", "12.1"] : [string.Empty]; - public override IEnumerable GetData(MethodInfo testMethod) + foreach (string featureLevel in featureLevels) { - ArgumentNullException.ThrowIfNull(testMethod); + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.textureFormat.ToString()); - string[] featureLevels = this.textureTool == TestTextureTool.TexConv ? new[] { "9.1", "9.2", "9.3", "10.0", "10.1", "11.0", "11.1", "12.0", "12.1" } : new[] { string.Empty }; + if (!string.IsNullOrEmpty(featureLevel)) + { + path = Path.Combine(path, featureLevel); + } - foreach (string featureLevel in featureLevels) + string[] files = Directory.GetFiles(path); + string[] filteredFiles = [.. files.Where(f => this.isRegex ? new Regex(this.inputFile).IsMatch(Path.GetFileName(f)) : Path.GetFileName(f).Equals(this.inputFile, StringComparison.OrdinalIgnoreCase))]; + foreach (string file in filteredFiles) { - string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.textureFormat.ToString()); - - if (!string.IsNullOrEmpty(featureLevel)) - { - path = Path.Combine(path, featureLevel); - } - - string[] files = Directory.GetFiles(path); - string[] filteredFiles = files.Where(f => this.isRegex ? new Regex(this.inputFile).IsMatch(Path.GetFileName(f)) : Path.GetFileName(f).Equals(this.inputFile, StringComparison.OrdinalIgnoreCase)).ToArray(); - foreach (string file in filteredFiles) - { - var testTextureProvider = new TestTextureProvider(testMethod.Name, this.textureFormat, this.textureType, this.textureTool, file, false); - yield return new object[] { testTextureProvider }; - } + TestTextureProvider testTextureProvider = new(testMethod.Name, this.textureFormat, this.textureType, this.textureTool, file, false); + yield return new object[] { testTextureProvider }; } } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index 3035fad6..6c6b2cfa 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -1,62 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Numerics; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; + +public class ExactImageComparer : ImageComparer { - public class ExactImageComparer : ImageComparer - { - public static ExactImageComparer Instance { get; } = new ExactImageComparer(); + public static ExactImageComparer Instance { get; } = new ExactImageComparer(); - public override ImageSimilarityReport CompareImages( - Image expected, - Image actual) + public override ImageSimilarityReport CompareImages( + Image expected, + Image actual) + { + if (expected.Size != actual.Size) { - if (expected.Size != actual.Size) - { - throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); - } + throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); + } - int width = actual.Width; + int width = actual.Width; - // TODO: Comparing through Rgba64 may not be robust enough because of the existence of super high precision pixel types. - var aBuffer = new Vector4[width]; - var bBuffer = new Vector4[width]; + // TODO: Comparing through Rgba64 may not be robust enough because of the existence of super high precision pixel types. + Vector4[] aBuffer = new Vector4[width]; + Vector4[] bBuffer = new Vector4[width]; - Buffer2D expectedBuffer = expected.Frames.RootFrame.PixelBuffer; - Buffer2D actualBuffer = actual.Frames.RootFrame.PixelBuffer; + Buffer2D expectedBuffer = expected.Frames.RootFrame.PixelBuffer; + Buffer2D actualBuffer = actual.Frames.RootFrame.PixelBuffer; - var differences = new List(); - ImageSharp.Configuration configuration = expected.Configuration; + List differences = new(); + ImageSharp.Configuration configuration = expected.Configuration; - for (int y = 0; y < actual.Height; y++) - { - Span aSpan = expectedBuffer.DangerousGetRowSpan(y); - Span bSpan = actualBuffer.DangerousGetRowSpan(y); + for (int y = 0; y < actual.Height; y++) + { + Span aSpan = expectedBuffer.DangerousGetRowSpan(y); + Span bSpan = actualBuffer.DangerousGetRowSpan(y); + + PixelOperations.Instance.ToVector4(configuration, aSpan, aBuffer); + PixelOperations.Instance.ToVector4(configuration, bSpan, bBuffer); - PixelOperations.Instance.ToVector4(configuration, aSpan, aBuffer); - PixelOperations.Instance.ToVector4(configuration, bSpan, bBuffer); + for (int x = 0; x < width; x++) + { + Vector4 aPixel = aBuffer[x]; + Vector4 bPixel = bBuffer[x]; - for (int x = 0; x < width; x++) + if (aPixel != bPixel) { - Vector4 aPixel = aBuffer[x]; - Vector4 bPixel = bBuffer[x]; - - if (aPixel != bPixel) - { - var diff = new PixelDifference(new Point(x, y), aPixel, bPixel); - differences.Add(diff); - } + PixelDifference diff = new(new Point(x, y), aPixel, bPixel); + differences.Add(diff); } } - - return new ImageSimilarityReport(expected, actual, differences); } + + return new ImageSimilarityReport(expected, actual, differences); } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs index f59af42a..d8c53eac 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison.Exceptions +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison.Exceptions; + +public class ImageDimensionsMismatchException : ImagesSimilarityException { - public class ImageDimensionsMismatchException : ImagesSimilarityException + public ImageDimensionsMismatchException(Size expectedSize, Size actualSize) + : base($"The image dimensions {actualSize} do not match the expected {expectedSize}!") { - public ImageDimensionsMismatchException(Size expectedSize, Size actualSize) - : base($"The image dimensions {actualSize} do not match the expected {expectedSize}!") - { - this.ExpectedSize = expectedSize; - this.ActualSize = actualSize; - } + this.ExpectedSize = expectedSize; + this.ActualSize = actualSize; + } - public Size ExpectedSize { get; } + public Size ExpectedSize { get; } - public Size ActualSize { get; } - } + public Size ActualSize { get; } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs index 38c0454a..42286988 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs @@ -1,15 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison.Exceptions -{ - using System; +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison.Exceptions; + +using System; - public class ImagesSimilarityException : Exception +public class ImagesSimilarityException : Exception +{ + public ImagesSimilarityException(string message) + : base(message) { - public ImagesSimilarityException(string message) - : base(message) - { - } } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ImageComparer.cs index c2b9d745..8eae6e18 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -1,107 +1,103 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.Globalization; -using System.Linq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison.Exceptions; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; + +public abstract class ImageComparer { - public abstract class ImageComparer - { - public static ImageComparer Exact { get; } = Tolerant(0, 0); + public static ImageComparer Exact { get; } = Tolerant(0, 0); - /// - /// Returns an instance of . - /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. - /// - /// The comparer. - public static ImageComparer Tolerant( - float imageThreshold = TolerantImageComparer.DefaultImageThreshold, - int perPixelManhattanThreshold = 0) => new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold); + /// + /// Returns an instance of . + /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. + /// + /// The comparer. + public static ImageComparer Tolerant( + float imageThreshold = TolerantImageComparer.DefaultImageThreshold, + int perPixelManhattanThreshold = 0) => new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold); - /// - /// Returns Tolerant(imageThresholdInPercents/100) - /// - /// The comparer. - public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0) - => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold); + /// + /// Returns Tolerant(imageThresholdInPercents/100) + /// + /// The comparer. + public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0) + => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold); - public abstract ImageSimilarityReport CompareImages( - Image expected, - Image actual) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel; - } + public abstract ImageSimilarityReport CompareImages( + Image expected, + Image actual) + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel; +} - public static class ImageComparerExtensions +public static class ImageComparerExtensions +{ + public static ImageSimilarityReport CompareImages( + this ImageComparer comparer, + Image expected, + Image actual) + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel => comparer.CompareImages(expected, actual); + + public static void VerifySimilarity( + this ImageComparer comparer, + Image expected, + Image actual) + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { - public static ImageSimilarityReport CompareImages( - this ImageComparer comparer, - Image expected, - Image actual) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel => comparer.CompareImages(expected, actual); + if (expected.Size != actual.Size) + { + throw new ImageDimensionsMismatchException(expected.Size, actual.Size); + } - public static void VerifySimilarity( - this ImageComparer comparer, - Image expected, - Image actual) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel + if (expected.Frames.Count != actual.Frames.Count) { - if (expected.Size != actual.Size) - { - throw new ImageDimensionsMismatchException(expected.Size, actual.Size); - } + throw new ImagesSimilarityException("Image frame count does not match!"); + } - if (expected.Frames.Count != actual.Frames.Count) - { - throw new ImagesSimilarityException("Image frame count does not match!"); - } + ImageSimilarityReport report = comparer.CompareImages(expected, actual); + if ((report.TotalNormalizedDifference ?? 0F) != 0F) + { + throw new ImagesSimilarityException(report.ToString()); + } + } - ImageSimilarityReport report = comparer.CompareImages(expected, actual); - if ((report.TotalNormalizedDifference ?? 0F) != 0F) - { - throw new ImagesSimilarityException(report.ToString()); - } + public static void VerifySimilarityIgnoreRegion( + this ImageComparer comparer, + Image expected, + Image actual, + Rectangle ignoredRegion) + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel + { + if (expected.Size != actual.Size) + { + throw new ImageDimensionsMismatchException(expected.Size, actual.Size); } - public static void VerifySimilarityIgnoreRegion( - this ImageComparer comparer, - Image expected, - Image actual, - Rectangle ignoredRegion) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel + if (expected.Frames.Count != actual.Frames.Count) { - if (expected.Size != actual.Size) - { - throw new ImageDimensionsMismatchException(expected.Size, actual.Size); - } + throw new ImagesSimilarityException("Image frame count does not match!"); + } - if (expected.Frames.Count != actual.Frames.Count) - { - throw new ImagesSimilarityException("Image frame count does not match!"); - } + ImageSimilarityReport report = comparer.CompareImages(expected, actual); + if ((report.TotalNormalizedDifference ?? 0F) != 0F) + { + IEnumerable outsideChanges = report.Differences.Where( + x => + !(ignoredRegion.X <= x.Position.X + && x.Position.X <= ignoredRegion.Right + && ignoredRegion.Y <= x.Position.Y + && x.Position.Y <= ignoredRegion.Bottom)); - ImageSimilarityReport report = comparer.CompareImages(expected, actual); - if ((report.TotalNormalizedDifference ?? 0F) != 0F) + if (outsideChanges.Any()) { - IEnumerable outsideChanges = report.Differences.Where( - x => - !(ignoredRegion.X <= x.Position.X - && x.Position.X <= ignoredRegion.Right - && ignoredRegion.Y <= x.Position.Y - && x.Position.Y <= ignoredRegion.Bottom)); - - if (outsideChanges.Any()) - { - var cleanedReport = new ImageSimilarityReport(expected, actual, outsideChanges, null); - throw new ImagesSimilarityException(cleanedReport.ToString()); - } + ImageSimilarityReport cleanedReport = new(expected, actual, outsideChanges, null); + throw new ImagesSimilarityException(cleanedReport.ToString()); } } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 0a1e4d41..23999f91 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -1,111 +1,104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; + +public class ImageSimilarityReport { - public class ImageSimilarityReport + // Provide an empty non-generic instance to avoid CA1000 in generic type. + public static ImageSimilarityReport Empty => new(null, null, Array.Empty(), 0f); + + protected ImageSimilarityReport( + object expectedImage, + object actualImage, + IEnumerable differences, + float? totalNormalizedDifference = null) { - // Provide an empty non-generic instance to avoid CA1000 in generic type. - public static ImageSimilarityReport Empty => new ImageSimilarityReport(null, null, Array.Empty(), 0f); - - protected ImageSimilarityReport( - object expectedImage, - object actualImage, - IEnumerable differences, - float? totalNormalizedDifference = null) - { - this.ExpectedImage = expectedImage; - this.ActualImage = actualImage; - this.TotalNormalizedDifference = totalNormalizedDifference; - this.Differences = differences.ToArray(); - } + this.ExpectedImage = expectedImage; + this.ActualImage = actualImage; + this.TotalNormalizedDifference = totalNormalizedDifference; + this.Differences = [.. differences]; + } - public object ExpectedImage { get; } + public object ExpectedImage { get; } - public object ActualImage { get; } + public object ActualImage { get; } - // TODO: This should not be a nullable value! - public float? TotalNormalizedDifference { get; } + // TODO: This should not be a nullable value! + public float? TotalNormalizedDifference { get; } - public string DifferencePercentageString + public string DifferencePercentageString + { + get { - get + if (!this.TotalNormalizedDifference.HasValue) + { + return "?"; + } + else if (this.TotalNormalizedDifference == 0) { - if (!this.TotalNormalizedDifference.HasValue) - { - return "?"; - } - else if (this.TotalNormalizedDifference == 0) - { - return "0%"; - } - else - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0000}%", this.TotalNormalizedDifference.Value * 100); - } + return "0%"; + } + else + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0000}%", this.TotalNormalizedDifference.Value * 100); } } + } - public PixelDifference[] Differences { get; } + public PixelDifference[] Differences { get; } - public bool IsEmpty => this.Differences.Length == 0; + public bool IsEmpty => this.Differences.Length == 0; - public override string ToString() - { - return this.IsEmpty ? "[SimilarImages]" : this.PrintDifference(); - } + public override string ToString() => this.IsEmpty ? "[SimilarImages]" : this.PrintDifference(); - private string PrintDifference() + private string PrintDifference() + { + StringBuilder sb = new(); + if (this.TotalNormalizedDifference.HasValue) { - var sb = new StringBuilder(); - if (this.TotalNormalizedDifference.HasValue) - { - sb.AppendLine(); - sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "Total difference: {0}", this.DifferencePercentageString)); - } - - int max = Math.Min(5, this.Differences.Length); + sb.AppendLine(); + sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "Total difference: {0}", this.DifferencePercentageString)); + } - for (int i = 0; i < max; i++) - { - sb.Append(this.Differences[i]); - if (i < max - 1) - { - sb.AppendFormat(CultureInfo.InvariantCulture, ";{0}", Environment.NewLine); - } - } + int max = Math.Min(5, this.Differences.Length); - if (this.Differences.Length >= 5) + for (int i = 0; i < max; i++) + { + sb.Append(this.Differences[i]); + if (i < max - 1) { - sb.Append("..."); + sb.AppendFormat(CultureInfo.InvariantCulture, ";{0}", Environment.NewLine); } - - return sb.ToString(); } - } - public class ImageSimilarityReport : ImageSimilarityReport - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel - { - public ImageSimilarityReport( - Image expectedImage, - Image actualImage, - IEnumerable differences, - float? totalNormalizedDifference = null) - : base(expectedImage, actualImage, differences, totalNormalizedDifference) + if (this.Differences.Length >= 5) { + sb.Append("..."); } - public new Image ExpectedImage => (Image)base.ExpectedImage; + return sb.ToString(); + } +} - public new Image ActualImage => (Image)base.ActualImage; +public class ImageSimilarityReport : ImageSimilarityReport + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel +{ + public ImageSimilarityReport( + Image expectedImage, + Image actualImage, + IEnumerable differences, + float? totalNormalizedDifference = null) + : base(expectedImage, actualImage, differences, totalNormalizedDifference) + { } + + public new Image ExpectedImage => (Image)base.ExpectedImage; + + public new Image ActualImage => (Image)base.ActualImage; } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/PixelDifference.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/PixelDifference.cs index 46153a6f..3faf66ac 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/PixelDifference.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/PixelDifference.cs @@ -3,45 +3,44 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; + +public readonly struct PixelDifference { - public readonly struct PixelDifference + public PixelDifference( + Point position, + float redDifference, + float greenDifference, + float blueDifference, + float alphaDifference) { - public PixelDifference( - Point position, - float redDifference, - float greenDifference, - float blueDifference, - float alphaDifference) - { - this.Position = position; - this.RedDifference = redDifference; - this.GreenDifference = greenDifference; - this.BlueDifference = blueDifference; - this.AlphaDifference = alphaDifference; - } - - public PixelDifference(Point position, Vector4 expected, Vector4 actual) - : this( - position, - actual.X - expected.X, - actual.Y - expected.Y, - actual.Z - expected.Z, - actual.W - expected.W) - { - } - - public Point Position { get; } - - public float RedDifference { get; } - - public float GreenDifference { get; } - - public float BlueDifference { get; } - - public float AlphaDifference { get; } - - public override string ToString() => - $"[Δ({this.RedDifference},{this.GreenDifference},{this.BlueDifference},{this.AlphaDifference}) @ ({this.Position.X},{this.Position.Y})]"; + this.Position = position; + this.RedDifference = redDifference; + this.GreenDifference = greenDifference; + this.BlueDifference = blueDifference; + this.AlphaDifference = alphaDifference; } + + public PixelDifference(Point position, Vector4 expected, Vector4 actual) + : this( + position, + actual.X - expected.X, + actual.Y - expected.Y, + actual.Z - expected.Z, + actual.W - expected.W) + { + } + + public Point Position { get; } + + public float RedDifference { get; } + + public float GreenDifference { get; } + + public float BlueDifference { get; } + + public float AlphaDifference { get; } + + public override string ToString() => + $"[Δ({this.RedDifference},{this.GreenDifference},{this.BlueDifference},{this.AlphaDifference}) @ ({this.Position.X},{this.Position.Y})]"; } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index acfd577e..bdf4830f 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -1,127 +1,121 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; + +public class TolerantImageComparer : ImageComparer { - public class TolerantImageComparer : ImageComparer + // 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit + // 257 = (1 / 255) * 65535. + public const float DefaultImageThreshold = 257F / (100 * 100 * 65535); + + /// + /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. + /// + /// The maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. + /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. + public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshold = 0) { - // 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit - // 257 = (1 / 255) * 65535. - public const float DefaultImageThreshold = 257F / (100 * 100 * 65535); - - /// - /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. - /// - /// The maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. - /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. - public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshold = 0) - { - Guard.MustBeGreaterThanOrEqualTo(imageThreshold, 0, nameof(imageThreshold)); + Guard.MustBeGreaterThanOrEqualTo(imageThreshold, 0, nameof(imageThreshold)); - this.ImageThreshold = imageThreshold; - this.PerPixelManhattanThreshold = perPixelManhattanThreshold; - } + this.ImageThreshold = imageThreshold; + this.PerPixelManhattanThreshold = perPixelManhattanThreshold; + } - /// - /// - /// Gets the maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. - /// Examples of percentage differences on a single pixel: - /// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel - /// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel - /// 3. PixelA = (65535,65535,65535,0) PixelB =(32767,32767,32767,32767) leads to 50% difference on a single pixel - /// - /// - /// The total differences is the sum of all pixel differences normalized by image dimensions! - /// The individual distances are calculated using the Manhattan function: - /// - /// https://en.wikipedia.org/wiki/Taxicab_geometry - /// - /// ImageThresholdInPercents = 1/255 = 257/65535 means that we allow one unit difference per channel on a 1x1 image - /// ImageThresholdInPercents = 1/(100*100*255) = 257/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image - /// - /// - public float ImageThreshold { get; } - - /// - /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. - /// For an individual pixel pair the value is the Manhattan distance of pixels: - /// - /// https://en.wikipedia.org/wiki/Taxicab_geometry - /// - /// - public int PerPixelManhattanThreshold { get; } - - public override ImageSimilarityReport CompareImages(Image expected, Image actual) + /// + /// + /// Gets the maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. + /// Examples of percentage differences on a single pixel: + /// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel + /// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel + /// 3. PixelA = (65535,65535,65535,0) PixelB =(32767,32767,32767,32767) leads to 50% difference on a single pixel + /// + /// + /// The total differences is the sum of all pixel differences normalized by image dimensions! + /// The individual distances are calculated using the Manhattan function: + /// + /// https://en.wikipedia.org/wiki/Taxicab_geometry + /// + /// ImageThresholdInPercents = 1/255 = 257/65535 means that we allow one unit difference per channel on a 1x1 image + /// ImageThresholdInPercents = 1/(100*100*255) = 257/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image + /// + /// + public float ImageThreshold { get; } + + /// + /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. + /// For an individual pixel pair the value is the Manhattan distance of pixels: + /// + /// https://en.wikipedia.org/wiki/Taxicab_geometry + /// + /// + public int PerPixelManhattanThreshold { get; } + + public override ImageSimilarityReport CompareImages(Image expected, Image actual) + { + if (expected.Size != actual.Size) { - if (expected.Size != actual.Size) - { - throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); - } + throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); + } - int width = actual.Width; + int width = actual.Width; - // TODO: Comparing through Rgba64 may not robust enough because of the existence of super high precision pixel types. - var aBuffer = new Vector4[width]; - var bBuffer = new Vector4[width]; + // TODO: Comparing through Rgba64 may not robust enough because of the existence of super high precision pixel types. + Vector4[] aBuffer = new Vector4[width]; + Vector4[] bBuffer = new Vector4[width]; - float totalDifference = 0F; + float totalDifference = 0F; - var differences = new List(); - var configuration = new ImageSharp.Configuration(); + List differences = new(); + ImageSharp.Configuration configuration = new(); - Buffer2D expectedBuffer = expected.Frames.RootFrame.PixelBuffer; - Buffer2D actualBuffer = actual.Frames.RootFrame.PixelBuffer; + Buffer2D expectedBuffer = expected.Frames.RootFrame.PixelBuffer; + Buffer2D actualBuffer = actual.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < actual.Height; y++) - { - Span aSpan = expectedBuffer.DangerousGetRowSpan(y); - Span bSpan = actualBuffer.DangerousGetRowSpan(y); + for (int y = 0; y < actual.Height; y++) + { + Span aSpan = expectedBuffer.DangerousGetRowSpan(y); + Span bSpan = actualBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, aSpan, aBuffer); - PixelOperations.Instance.ToVector4(configuration, bSpan, bBuffer); + PixelOperations.Instance.ToVector4(configuration, aSpan, aBuffer); + PixelOperations.Instance.ToVector4(configuration, bSpan, bBuffer); - for (int x = 0; x < width; x++) - { - float d = GetManhattanDistanceInRgbaSpace(ref aBuffer[x], ref bBuffer[x]); + for (int x = 0; x < width; x++) + { + float d = GetManhattanDistanceInRgbaSpace(ref aBuffer[x], ref bBuffer[x]); - if (d > this.PerPixelManhattanThreshold) - { - var diff = new PixelDifference(new Point(x, y), aBuffer[x], bBuffer[x]); - differences.Add(diff); + if (d > this.PerPixelManhattanThreshold) + { + PixelDifference diff = new(new Point(x, y), aBuffer[x], bBuffer[x]); + differences.Add(diff); - totalDifference += d; - } + totalDifference += d; } } + } - float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height); - normalizedDifference /= 4F * 65535F; + float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height); + normalizedDifference /= 4F * 65535F; - if (normalizedDifference > this.ImageThreshold) - { - return new ImageSimilarityReport(expected, actual, differences, normalizedDifference); - } - else - { - // Construct an empty generic report explicitly. - return new ImageSimilarityReport(expected, actual, [], 0f); - } + if (normalizedDifference > this.ImageThreshold) + { + return new ImageSimilarityReport(expected, actual, differences, normalizedDifference); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetManhattanDistanceInRgbaSpace(ref Vector4 a, ref Vector4 b) + else { - return Diff(a.X, b.X) + Diff(a.Y, b.Y) + Diff(a.Z, b.Z) + Diff(a.W, b.W); + // Construct an empty generic report explicitly. + return new ImageSimilarityReport(expected, actual, [], 0f); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float Diff(float a, float b) => MathF.Abs((a * 65535F) - (b * 65535F)); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float GetManhattanDistanceInRgbaSpace(ref Vector4 a, ref Vector4 b) => Diff(a.X, b.X) + Diff(a.Y, b.Y) + Diff(a.Z, b.Z) + Diff(a.W, b.W); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Diff(float a, float b) => MathF.Abs((a * 65535F) - (b * 65535F)); } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/FileProvider.cs index 1a163be6..5a43a22e 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -9,210 +9,209 @@ using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageProviders +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageProviders; + +public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { - public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel + internal sealed class FileProvider : TestImageProvider, IXunitSerializable { - internal sealed class FileProvider : TestImageProvider, IXunitSerializable + // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider + // are shared between PixelTypes.Color & PixelTypes.Rgba32 + private sealed class Key : IEquatable { - // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider - // are shared between PixelTypes.Color & PixelTypes.Rgba32 - private sealed class Key : IEquatable + private readonly Tuple commonValues; + + private readonly Dictionary decoderParameters; + + public Key(PixelTypes pixelType, string filePath, int allocatorBufferCapacity, IImageDecoder customDecoder) + { + Type customType = customDecoder?.GetType(); + this.commonValues = new Tuple( + pixelType, + filePath, + customType, + allocatorBufferCapacity); + this.decoderParameters = GetDecoderParameters(customDecoder); + } + + private static Dictionary GetDecoderParameters(IImageDecoder customDecoder) { - private readonly Tuple commonValues; + Type type = customDecoder.GetType(); - private readonly Dictionary decoderParameters; + Dictionary data = new(); - public Key(PixelTypes pixelType, string filePath, int allocatorBufferCapacity, IImageDecoder customDecoder) + while (type != null && type != typeof(object)) { - Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple( - pixelType, - filePath, - customType, - allocatorBufferCapacity); - this.decoderParameters = GetDecoderParameters(customDecoder); + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (PropertyInfo p in properties) + { + string key = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", type.FullName, p.Name); + object value = p.GetValue(customDecoder); + data[key] = value; + } + + type = type.GetTypeInfo().BaseType; } - private static Dictionary GetDecoderParameters(IImageDecoder customDecoder) + return data; + } + + public bool Equals(Key other) + { + if (other is null) { - Type type = customDecoder.GetType(); + return false; + } - var data = new Dictionary(); + if (ReferenceEquals(this, other)) + { + return true; + } - while (type != null && type != typeof(object)) - { - PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); - foreach (PropertyInfo p in properties) - { - string key = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", type.FullName, p.Name); - object value = p.GetValue(customDecoder); - data[key] = value; - } - - type = type.GetTypeInfo().BaseType; - } + if (!this.commonValues.Equals(other.commonValues)) + { + return false; + } - return data; + if (this.decoderParameters.Count != other.decoderParameters.Count) + { + return false; } - public bool Equals(Key other) + foreach (KeyValuePair kv in this.decoderParameters) { - if (other is null) + if (!other.decoderParameters.TryGetValue(kv.Key, out object otherVal)) { return false; } - if (ReferenceEquals(this, other)) - { - return true; - } - - if (!this.commonValues.Equals(other.commonValues)) + if (!object.Equals(kv.Value, otherVal)) { return false; } + } - if (this.decoderParameters.Count != other.decoderParameters.Count) - { - return false; - } + return true; + } - foreach (KeyValuePair kv in this.decoderParameters) - { - if (!other.decoderParameters.TryGetValue(kv.Key, out object otherVal)) - { - return false; - } - - if (!object.Equals(kv.Value, otherVal)) - { - return false; - } - } + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + if (ReferenceEquals(this, obj)) + { return true; } - public override bool Equals(object obj) + if (obj.GetType() != this.GetType()) { - if (obj is null) - { - return false; - } + return false; + } - if (ReferenceEquals(this, obj)) - { - return true; - } + return this.Equals((Key)obj); + } - if (obj.GetType() != this.GetType()) - { - return false; - } + public override int GetHashCode() => this.commonValues.GetHashCode(); - return this.Equals((Key)obj); - } + public static bool operator ==(Key left, Key right) => Equals(left, right); - public override int GetHashCode() => this.commonValues.GetHashCode(); + public static bool operator !=(Key left, Key right) => !Equals(left, right); + } - public static bool operator ==(Key left, Key right) => Equals(left, right); + private static readonly ConcurrentDictionary> Cache = new(); - public static bool operator !=(Key left, Key right) => !Equals(left, right); - } + // Needed for deserialization! + // ReSharper disable once UnusedMember.Local + public FileProvider() + { + } - private static readonly ConcurrentDictionary> Cache = new ConcurrentDictionary>(); + public FileProvider(string filePath) => this.FilePath = filePath; - // Needed for deserialization! - // ReSharper disable once UnusedMember.Local - public FileProvider() - { - } + /// + /// Gets the file path relative to the "~/tests/images" folder + /// + public string FilePath { get; private set; } - public FileProvider(string filePath) => this.FilePath = filePath; + public override string SourceFileOrDescription => this.FilePath; - /// - /// Gets the file path relative to the "~/tests/images" folder - /// - public string FilePath { get; private set; } + public override Image GetImage() + { + IImageFormat format = TestEnvironment.GetImageFormat(this.FilePath); + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath); + return this.GetImage(format, decoder); + } - public override string SourceFileOrDescription => this.FilePath; + public override Image GetImage(IImageFormat format, IImageDecoder decoder) + { + Guard.NotNull(format, nameof(format)); + Guard.NotNull(decoder, nameof(decoder)); - public override Image GetImage() + if (!TestEnvironment.Is64BitProcess) { - IImageFormat format = TestEnvironment.GetImageFormat(this.FilePath); - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath); - return this.GetImage(format, decoder); + return this.LoadImage(format, decoder); } - public override Image GetImage(IImageFormat format, IImageDecoder decoder) - { - Guard.NotNull(format, nameof(format)); - Guard.NotNull(decoder, nameof(decoder)); - - if (!TestEnvironment.Is64BitProcess) - { - return this.LoadImage(format, decoder); - } + // int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); + int bufferCapacity = 500; + Key key = new(this.PixelType, this.FilePath, bufferCapacity, decoder); - // int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); - int bufferCapacity = 500; - var key = new Key(this.PixelType, this.FilePath, bufferCapacity, decoder); + Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(format, decoder)); - Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(format, decoder)); - - return cachedImage.Clone(this.Configuration); - } + return cachedImage.Clone(this.Configuration); + } - public override Task> GetImageAsync(IImageFormat format, IImageDecoder decoder) + public override Task> GetImageAsync(IImageFormat format, IImageDecoder decoder) + { + Guard.NotNull(format, nameof(format)); + Guard.NotNull(decoder, nameof(decoder)); + + // Used in small subset of decoder tests, no caching. + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); + ImageSharp.Configuration configuration = this.Configuration.Clone(); + configuration.ImageFormatsManager.SetDecoder(format, decoder); + DecoderOptions options = new() { - Guard.NotNull(format, nameof(format)); - Guard.NotNull(decoder, nameof(decoder)); - - // Used in small subset of decoder tests, no caching. - string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); - ImageSharp.Configuration configuration = this.Configuration.Clone(); - configuration.ImageFormatsManager.SetDecoder(format, decoder); - DecoderOptions options = new() - { - Configuration = configuration - }; + Configuration = configuration + }; - return Image.LoadAsync(options, path); - } + return Image.LoadAsync(options, path); + } - public override void Deserialize(IXunitSerializationInfo info) - { - this.FilePath = info.GetValue("path"); + public override void Deserialize(IXunitSerializationInfo info) + { + this.FilePath = info.GetValue("path"); - base.Deserialize(info); // must be called last - } + base.Deserialize(info); // must be called last + } - public override void Serialize(IXunitSerializationInfo info) - { - base.Serialize(info); - info.AddValue("path", this.FilePath); - } + public override void Serialize(IXunitSerializationInfo info) + { + base.Serialize(info); + info.AddValue("path", this.FilePath); + } - private Image LoadImage(IImageFormat format, IImageDecoder decoder) + private Image LoadImage(IImageFormat format, IImageDecoder decoder) + { + TestFile testFile = TestFile.Create(this.FilePath); + ImageSharp.Configuration configuration = this.Configuration.Clone(); + configuration.ImageFormatsManager.SetDecoder(format, decoder); + DecoderOptions options = new() { - TestFile testFile = TestFile.Create(this.FilePath); - ImageSharp.Configuration configuration = this.Configuration.Clone(); - configuration.ImageFormatsManager.SetDecoder(format, decoder); - DecoderOptions options = new() - { - Configuration = configuration - }; + Configuration = configuration + }; - return Image.Load(options, testFile.Bytes); - } + return Image.Load(options, testFile.Bytes); } + } - public static string GetFilePathOrNull(ITestImageProvider provider) - { - var fileProvider = provider as FileProvider; - return fileProvider?.FilePath; - } + public static string GetFilePathOrNull(ITestImageProvider provider) + { + FileProvider fileProvider = provider as FileProvider; + return fileProvider?.FilePath; } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs index 5ff70bdf..0b5cba1c 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageProviders +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageProviders; + +public interface ITestImageProvider { - public interface ITestImageProvider - { - PixelTypes PixelType { get; } + PixelTypes PixelType { get; } - ImagingTestCaseUtility Utility { get; } + ImagingTestCaseUtility Utility { get; } - string SourceFileOrDescription { get; } + string SourceFileOrDescription { get; } - ImageSharp.Configuration Configuration { get; set; } - } + ImageSharp.Configuration Configuration { get; set; } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 490cc22b..e579794b 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -1,10 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using System.Reflection; -using System.Threading.Tasks; using Castle.Core.Internal; using SixLabors.ImageSharp.Formats; @@ -13,117 +11,116 @@ using SixLabors.ImageSharp.Textures.Tests.TestUtilities.Attributes; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageProviders +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageProviders; + +/// +/// Provides instances for parametric unit tests. +/// +/// The pixel format of the image. +public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable + where TPixel : unmanaged, IPixel { + public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); + + public virtual string SourceFileOrDescription => string.Empty; + + public ImageSharp.Configuration Configuration { get; set; } = ImageSharp.Configuration.Default; + /// - /// Provides instances for parametric unit tests. + /// Gets the utility instance to provide information about the test image & manage input/output. /// - /// The pixel format of the image. - public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable - where TPixel : unmanaged, IPixel - { - public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); + public ImagingTestCaseUtility Utility { get; private set; } + + public string TypeName { get; private set; } - public virtual string SourceFileOrDescription => string.Empty; + public string MethodName { get; private set; } - public ImageSharp.Configuration Configuration { get; set; } = ImageSharp.Configuration.Default; + public string OutputSubfolderName { get; private set; } - /// - /// Gets the utility instance to provide information about the test image & manage input/output. - /// - public ImagingTestCaseUtility Utility { get; private set; } + /// + /// Returns a file backed provider. + /// + public static TestImageProvider File( + string filePath, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) => new FileProvider(filePath).Init(testMethod, pixelTypeOverride); - public string TypeName { get; private set; } + /// + /// Returns an instance to the test case with the necessary traits. + /// + /// A test image. + public abstract Image GetImage(); - public string MethodName { get; private set; } + public virtual Image GetImage(IImageFormat format, IImageDecoder decoder) + => throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Decoder specific GetImage() is not supported with {0}!", this.GetType().Name)); - public string OutputSubfolderName { get; private set; } + public virtual Task> GetImageAsync(IImageFormat format, IImageDecoder decoder) + => throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Decoder specific GetImageAsync() is not supported with {0}!", this.GetType().Name)); - /// - /// Returns a file backed provider. - /// - public static TestImageProvider File( - string filePath, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) => new FileProvider(filePath).Init(testMethod, pixelTypeOverride); + /// + /// Returns an instance to the test case with the necessary traits. + /// + /// A test image. + public Image GetImage(Action operationsToApply) + { + Image img = this.GetImage(); + img.Mutate(operationsToApply); + return img; + } - /// - /// Returns an instance to the test case with the necessary traits. - /// - /// A test image. - public abstract Image GetImage(); + public virtual void Deserialize(IXunitSerializationInfo info) + { + PixelTypes pixelType = info.GetValue("PixelType"); + string typeName = info.GetValue("TypeName"); + string methodName = info.GetValue("MethodName"); + string outputSubfolderName = info.GetValue("OutputSubfolderName"); - public virtual Image GetImage(IImageFormat format, IImageDecoder decoder) - => throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Decoder specific GetImage() is not supported with {0}!", this.GetType().Name)); + this.Init(typeName, methodName, outputSubfolderName, pixelType); + } - public virtual Task> GetImageAsync(IImageFormat format, IImageDecoder decoder) - => throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Decoder specific GetImageAsync() is not supported with {0}!", this.GetType().Name)); + public virtual void Serialize(IXunitSerializationInfo info) + { + info.AddValue("PixelType", this.PixelType); + info.AddValue("TypeName", this.TypeName); + info.AddValue("MethodName", this.MethodName); + info.AddValue("OutputSubfolderName", this.OutputSubfolderName); + } - /// - /// Returns an instance to the test case with the necessary traits. - /// - /// A test image. - public Image GetImage(Action operationsToApply) + protected TestImageProvider Init( + string typeName, + string methodName, + string outputSubfolderName, + PixelTypes pixelTypeOverride) + { + if (pixelTypeOverride != PixelTypes.Undefined) { - Image img = this.GetImage(); - img.Mutate(operationsToApply); - return img; + this.PixelType = pixelTypeOverride; } - public virtual void Deserialize(IXunitSerializationInfo info) - { - PixelTypes pixelType = info.GetValue("PixelType"); - string typeName = info.GetValue("TypeName"); - string methodName = info.GetValue("MethodName"); - string outputSubfolderName = info.GetValue("OutputSubfolderName"); - - this.Init(typeName, methodName, outputSubfolderName, pixelType); - } + this.TypeName = typeName; + this.MethodName = methodName; + this.OutputSubfolderName = outputSubfolderName; - public virtual void Serialize(IXunitSerializationInfo info) + this.Utility = new ImagingTestCaseUtility { - info.AddValue("PixelType", this.PixelType); - info.AddValue("TypeName", this.TypeName); - info.AddValue("MethodName", this.MethodName); - info.AddValue("OutputSubfolderName", this.OutputSubfolderName); - } + SourceFileOrDescription = this.SourceFileOrDescription, + PixelTypeName = this.PixelType.ToString() + }; - protected TestImageProvider Init( - string typeName, - string methodName, - string outputSubfolderName, - PixelTypes pixelTypeOverride) + if (methodName != null) { - if (pixelTypeOverride != PixelTypes.Undefined) - { - this.PixelType = pixelTypeOverride; - } - - this.TypeName = typeName; - this.MethodName = methodName; - this.OutputSubfolderName = outputSubfolderName; - - this.Utility = new ImagingTestCaseUtility - { - SourceFileOrDescription = this.SourceFileOrDescription, - PixelTypeName = this.PixelType.ToString() - }; - - if (methodName != null) - { - this.Utility.Init(typeName, methodName, outputSubfolderName); - } - - return this; + this.Utility.Init(typeName, methodName, outputSubfolderName); } - protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) - { - string subfolder = testMethod?.DeclaringType.GetAttribute()?.Subfolder - ?? string.Empty; - return this.Init(testMethod?.DeclaringType.Name, testMethod?.Name, subfolder, pixelTypeOverride); - } + return this; + } - public override string ToString() => string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", this.SourceFileOrDescription, this.PixelType); + protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) + { + string subfolder = testMethod?.DeclaringType.GetAttribute()?.Subfolder + ?? string.Empty; + return this.Init(testMethod?.DeclaringType.Name, testMethod?.Name, subfolder, pixelTypeOverride); } + + public override string ToString() => string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", this.SourceFileOrDescription, this.PixelType); } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/ImagingTestCaseUtility.cs index e143076e..2ee4a638 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -1,322 +1,317 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Reflection; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities; + +/// +/// Utility class to provide information about the test image & the test case for the test code, +/// and help managing IO. +/// +public class ImagingTestCaseUtility { /// - /// Utility class to provide information about the test image & the test case for the test code, - /// and help managing IO. + /// Gets or sets the name of the TPixel in the owner. /// - public class ImagingTestCaseUtility - { - /// - /// Gets or sets the name of the TPixel in the owner. - /// - public string PixelTypeName { get; set; } = string.Empty; - - /// - /// Gets or sets the name of the file which is provided by. - /// Or a short string describing the image in the case of a non-file based image provider. - /// - public string SourceFileOrDescription { get; set; } = string.Empty; - - /// - /// Gets or sets the test group name. - /// By default this is the name of the test class, but it's possible to change it. - /// - public string TestGroupName { get; set; } = string.Empty; - - public string OutputSubfolderName { get; set; } = string.Empty; - - /// - /// Gets or sets the name of the test case (by default). - /// - public string TestName { get; set; } = string.Empty; - - private string GetTestOutputFileNameImpl( - string extension, - string details, - bool appendPixelTypeToFileName, - bool appendSourceFileOrDescription) - { - if (string.IsNullOrWhiteSpace(extension)) - { - extension = null; - } + public string PixelTypeName { get; set; } = string.Empty; - string fn = appendSourceFileOrDescription - ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) - : string.Empty; + /// + /// Gets or sets the name of the file which is provided by. + /// Or a short string describing the image in the case of a non-file based image provider. + /// + public string SourceFileOrDescription { get; set; } = string.Empty; - if (string.IsNullOrWhiteSpace(extension)) - { - extension = Path.GetExtension(this.SourceFileOrDescription); - } + /// + /// Gets or sets the test group name. + /// By default this is the name of the test class, but it's possible to change it. + /// + public string TestGroupName { get; set; } = string.Empty; - if (string.IsNullOrWhiteSpace(extension)) - { - extension = ".bmp"; - } + public string OutputSubfolderName { get; set; } = string.Empty; - extension = extension.ToLowerInvariant(); + /// + /// Gets or sets the name of the test case (by default). + /// + public string TestName { get; set; } = string.Empty; - if (extension[0] != '.') - { - extension = '.' + extension; - } + private string GetTestOutputFileNameImpl( + string extension, + string details, + bool appendPixelTypeToFileName, + bool appendSourceFileOrDescription) + { + if (string.IsNullOrWhiteSpace(extension)) + { + extension = null; + } - if (fn != string.Empty) - { - fn = '_' + fn; - } + string fn = appendSourceFileOrDescription + ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) + : string.Empty; - string pixName = string.Empty; + if (string.IsNullOrWhiteSpace(extension)) + { + extension = Path.GetExtension(this.SourceFileOrDescription); + } - if (appendPixelTypeToFileName) - { - pixName = this.PixelTypeName; + if (string.IsNullOrWhiteSpace(extension)) + { + extension = ".bmp"; + } - if (pixName != string.Empty) - { - pixName = '_' + pixName; - } - } + extension = extension.ToLowerInvariant(); - details ??= string.Empty; - if (details != string.Empty) - { - details = '_' + details; - } + if (extension[0] != '.') + { + extension = '.' + extension; + } - return string.Format( - CultureInfo.InvariantCulture, - "{0}{1}{2}{3}{4}{5}{6}", - this.GetTestOutputDir(), - Path.DirectorySeparatorChar, - this.TestName, - pixName, - fn, - details, - extension); + if (fn != string.Empty) + { + fn = '_' + fn; } - /// - /// Gets the recommended file name for the output of the test - /// - /// The required extension - /// The settings modifying the output path - /// A boolean indicating whether to append the pixel type to output file name. - /// A boolean indicating whether to append SourceFileOrDescription to the test output file name. - /// The file test name - public string GetTestOutputFileName( - string extension = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) + string pixName = string.Empty; + + if (appendPixelTypeToFileName) { - string detailsString = null; + pixName = this.PixelTypeName; - if (testOutputDetails is FormattableString fs) - { - detailsString = FormattableString.Invariant(fs); - } - else if (testOutputDetails is string s) - { - detailsString = s; - } - else if (testOutputDetails != null) + if (pixName != string.Empty) { - Type type = testOutputDetails.GetType(); - TypeInfo info = type.GetTypeInfo(); - if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) - { - detailsString = string.Format(CultureInfo.InvariantCulture, "{0}", testOutputDetails); - } - else - { - IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); - - detailsString = string.Join( - "_", - properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) - .Select(x => string.Format(CultureInfo.InvariantCulture, "{0}-{1}", x.Key, x.Value))); - } + pixName = '_' + pixName; } - - return this.GetTestOutputFileNameImpl( - extension, - detailsString, - appendPixelTypeToFileName, - appendSourceFileOrDescription); } - /// - /// Encodes image by the format matching the required extension, than saves it to the recommended output file. - /// - /// The image instance. - /// The requested extension. - /// Optional encoder. - /// Additional information to append to the test output file name. - /// A value indicating whether to append the pixel type to the test output file name. - /// A boolean indicating whether to append SourceFileOrDescription to the test output file name. - /// The path to the saved image file. - public string SaveTestOutputFile( - Image image, - string extension = null, - IImageEncoder encoder = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) + details ??= string.Empty; + if (details != string.Empty) { - string path = this.GetTestOutputFileName( - extension, - testOutputDetails, - appendPixelTypeToFileName, - appendSourceFileOrDescription); + details = '_' + details; + } - encoder ??= TestEnvironment.GetReferenceEncoder(path); + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}{2}{3}{4}{5}{6}", + this.GetTestOutputDir(), + Path.DirectorySeparatorChar, + this.TestName, + pixName, + fn, + details, + extension); + } - using (FileStream stream = File.OpenWrite(path)) - { - image.Save(stream, encoder); - } + /// + /// Gets the recommended file name for the output of the test + /// + /// The required extension + /// The settings modifying the output path + /// A boolean indicating whether to append the pixel type to output file name. + /// A boolean indicating whether to append SourceFileOrDescription to the test output file name. + /// The file test name + public string GetTestOutputFileName( + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + { + string detailsString = null; - return path; + if (testOutputDetails is FormattableString fs) + { + detailsString = FormattableString.Invariant(fs); } - - public IEnumerable GetTestOutputFileNamesMultiFrame( - int frameCount, - string extension = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) + else if (testOutputDetails is string s) { - string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); - - if (!Directory.Exists(baseDir)) + detailsString = s; + } + else if (testOutputDetails != null) + { + Type type = testOutputDetails.GetType(); + TypeInfo info = type.GetTypeInfo(); + if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) { - Directory.CreateDirectory(baseDir); + detailsString = string.Format(CultureInfo.InvariantCulture, "{0}", testOutputDetails); } - - for (int i = 0; i < frameCount; i++) + else { - string filePath = string.Format(CultureInfo.InvariantCulture, "{0}/{1:D2}.{2}", baseDir, i, extension); - yield return filePath; + IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); + + detailsString = string.Join( + "_", + properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) + .Select(x => string.Format(CultureInfo.InvariantCulture, "{0}-{1}", x.Key, x.Value))); } } - public string[] SaveTestOutputFileMultiFrame( - Image image, - string extension = "png", - IImageEncoder encoder = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel - { - encoder ??= TestEnvironment.GetReferenceEncoder(string.Format(CultureInfo.InvariantCulture, "foo.{0}", extension)); + return this.GetTestOutputFileNameImpl( + extension, + detailsString, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + } - string[] files = this.GetTestOutputFileNamesMultiFrame( - image.Frames.Count, - extension, - testOutputDetails, - appendPixelTypeToFileName).ToArray(); + /// + /// Encodes image by the format matching the required extension, than saves it to the recommended output file. + /// + /// The image instance. + /// The requested extension. + /// Optional encoder. + /// Additional information to append to the test output file name. + /// A value indicating whether to append the pixel type to the test output file name. + /// A boolean indicating whether to append SourceFileOrDescription to the test output file name. + /// The path to the saved image file. + public string SaveTestOutputFile( + Image image, + string extension = null, + IImageEncoder encoder = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + { + string path = this.GetTestOutputFileName( + extension, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); - for (int i = 0; i < image.Frames.Count; i++) - { - using Image frameImage = image.Frames.CloneFrame(i); - string filePath = files[i]; - using FileStream stream = File.OpenWrite(filePath); - frameImage.Save(stream, encoder); - } + encoder ??= TestEnvironment.GetReferenceEncoder(path); - return files; + using (FileStream stream = File.OpenWrite(path)) + { + image.Save(stream, encoder); } - internal string GetReferenceOutputFileName( - string extension, - object testOutputDetails, - bool appendPixelTypeToFileName, - bool appendSourceFileOrDescription) => - TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); + return path; + } + + public IEnumerable GetTestOutputFileNamesMultiFrame( + int frameCount, + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + { + string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); - internal void Init(string typeName, string methodName, string outputSubfolderName) + if (!Directory.Exists(baseDir)) { - this.TestGroupName = typeName; - this.TestName = methodName; - this.OutputSubfolderName = outputSubfolderName; + Directory.CreateDirectory(baseDir); } - internal string GetTestOutputDir() + for (int i = 0; i < frameCount; i++) { - string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); + string filePath = string.Format(CultureInfo.InvariantCulture, "{0}/{1:D2}.{2}", baseDir, i, extension); + yield return filePath; + } + } - if (!string.IsNullOrEmpty(this.OutputSubfolderName)) - { - testGroupName = Path.Combine(this.OutputSubfolderName, testGroupName); - } + public string[] SaveTestOutputFileMultiFrame( + Image image, + string extension = "png", + IImageEncoder encoder = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel + { + encoder ??= TestEnvironment.GetReferenceEncoder(string.Format(CultureInfo.InvariantCulture, "foo.{0}", extension)); - return TestEnvironment.CreateOutputDirectory(testGroupName); + string[] files = [.. this.GetTestOutputFileNamesMultiFrame( + image.Frames.Count, + extension, + testOutputDetails, + appendPixelTypeToFileName)]; + + for (int i = 0; i < image.Frames.Count; i++) + { + using Image frameImage = image.Frames.CloneFrame(i); + string filePath = files[i]; + using FileStream stream = File.OpenWrite(filePath); + frameImage.Save(stream, encoder); } - public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) - where TPixel : unmanaged, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); + return files; + } - public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) - where TPixel : unmanaged, IPixel + internal string GetReferenceOutputFileName( + string extension, + object testOutputDetails, + bool appendPixelTypeToFileName, + bool appendSourceFileOrDescription) => + TestEnvironment.GetReferenceOutputFileName( + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); + + internal void Init(string typeName, string methodName, string outputSubfolderName) + { + this.TestGroupName = typeName; + this.TestName = methodName; + this.OutputSubfolderName = outputSubfolderName; + } + + internal string GetTestOutputDir() + { + string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); + + if (!string.IsNullOrEmpty(this.OutputSubfolderName)) { - TPixel pixel = img[x, y]; - Rgba64 rgbaPixel = default; - rgbaPixel.FromScaledVector4(pixel.ToScaledVector4()); - ushort change = (ushort)Math.Round(perChannelChange / 255F * 65535F); + testGroupName = Path.Combine(this.OutputSubfolderName, testGroupName); + } - if (rgbaPixel.R + perChannelChange <= 255) - { - rgbaPixel.R += change; - } - else - { - rgbaPixel.R -= change; - } + return TestEnvironment.CreateOutputDirectory(testGroupName); + } - if (rgbaPixel.G + perChannelChange <= 255) - { - rgbaPixel.G += change; - } - else - { - rgbaPixel.G -= change; - } + public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) + where TPixel : unmanaged, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); - if (rgbaPixel.B + perChannelChange <= 255) - { - rgbaPixel.B += perChannelChange; - } - else - { - rgbaPixel.B -= perChannelChange; - } + public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) + where TPixel : unmanaged, IPixel + { + TPixel pixel = img[x, y]; + Rgba64 rgbaPixel = default; + rgbaPixel.FromScaledVector4(pixel.ToScaledVector4()); + ushort change = (ushort)Math.Round(perChannelChange / 255F * 65535F); - if (rgbaPixel.A + perChannelChange <= 255) - { - rgbaPixel.A += perChannelChange; - } - else - { - rgbaPixel.A -= perChannelChange; - } + if (rgbaPixel.R + perChannelChange <= 255) + { + rgbaPixel.R += change; + } + else + { + rgbaPixel.R -= change; + } + + if (rgbaPixel.G + perChannelChange <= 255) + { + rgbaPixel.G += change; + } + else + { + rgbaPixel.G -= change; + } - pixel.FromRgba64(rgbaPixel); - img[x, y] = pixel; + if (rgbaPixel.B + perChannelChange <= 255) + { + rgbaPixel.B += perChannelChange; } + else + { + rgbaPixel.B -= perChannelChange; + } + + if (rgbaPixel.A + perChannelChange <= 255) + { + rgbaPixel.A += perChannelChange; + } + else + { + rgbaPixel.A -= perChannelChange; + } + + pixel.FromRgba64(rgbaPixel); + img[x, y] = pixel; } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/PixelTypes.cs index 6499cf5f..3e9b9fc1 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/PixelTypes.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities; + +/// +/// Flags that are mapped to PackedPixel types. +/// They trigger the desired parametrization for . +/// +[Flags] +public enum PixelTypes { - /// - /// Flags that are mapped to PackedPixel types. - /// They trigger the desired parametrization for . - /// - [Flags] - public enum PixelTypes - { #pragma warning disable SA1602 // Enumeration items should be documented - Undefined = 0, + Undefined = 0, - A8 = 1 << 0, + A8 = 1 << 0, - Argb32 = 1 << 1, + Argb32 = 1 << 1, - Bgr565 = 1 << 2, + Bgr565 = 1 << 2, - Bgra4444 = 1 << 3, + Bgra4444 = 1 << 3, - Byte4 = 1 << 4, + Byte4 = 1 << 4, - HalfSingle = 1 << 5, + HalfSingle = 1 << 5, - HalfVector2 = 1 << 6, + HalfVector2 = 1 << 6, - HalfVector4 = 1 << 7, + HalfVector4 = 1 << 7, - NormalizedByte2 = 1 << 8, + NormalizedByte2 = 1 << 8, - NormalizedByte4 = 1 << 9, + NormalizedByte4 = 1 << 9, - NormalizedShort4 = 1 << 10, + NormalizedShort4 = 1 << 10, - Rg32 = 1 << 11, + Rg32 = 1 << 11, - Rgba1010102 = 1 << 12, + Rgba1010102 = 1 << 12, - Rgba32 = 1 << 13, + Rgba32 = 1 << 13, - Rgba64 = 1 << 14, + Rgba64 = 1 << 14, - RgbaVector = 1 << 15, + RgbaVector = 1 << 15, - Short2 = 1 << 16, + Short2 = 1 << 16, - Short4 = 1 << 17, + Short4 = 1 << 17, - Rgb24 = 1 << 18, + Rgb24 = 1 << 18, - Bgr24 = 1 << 19, + Bgr24 = 1 << 19, - Bgra32 = 1 << 20, + Bgra32 = 1 << 20, - Rgb48 = 1 << 21, + Rgb48 = 1 << 21, - Bgra5551 = 1 << 22, + Bgra5551 = 1 << 22, - L8 = 1 << 23, + L8 = 1 << 23, - L16 = 1 << 24, + L16 = 1 << 24, - La16 = 1 << 25, + La16 = 1 << 25, - La32 = 1 << 26, + La32 = 1 << 26, - // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper - // "All" is handled as a separate, individual case instead of using bitwise OR - All = 30 + // "All" is handled as a separate, individual case instead of using bitwise OR + All = 30 #pragma warning restore SA1602 // Enumeration items should be documented - } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/TestEnvironment.cs index bc01a8be..5fc33ebb 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/TestEnvironment.cs @@ -1,333 +1,329 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities; + +public static class TestEnvironment { - public static class TestEnvironment - { - private static readonly Lazy ConfigurationLazy = new Lazy(CreateDefaultConfiguration); + private static readonly Lazy ConfigurationLazy = new(CreateDefaultConfiguration); - internal static ImageSharp.Configuration Configuration => ConfigurationLazy.Value; + internal static ImageSharp.Configuration Configuration => ConfigurationLazy.Value; - private const string ImageSharpTexturesSolutionFileName = "ImageSharp.Textures.sln"; + private const string ImageSharpTexturesSolutionFileName = "ImageSharp.Textures.sln"; - private const string InputImagesRelativePath = @"tests\Images\Input"; + private const string InputImagesRelativePath = @"tests\Images\Input"; - private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; + private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; - private const string ReferenceOutputDirectoryRelativePath = @"tests\Images\ReferenceOutput"; + private const string ReferenceOutputDirectoryRelativePath = @"tests\Images\ReferenceOutput"; - private const string ToolsDirectoryRelativePath = @"tests\Images\External\tools"; + private const string ToolsDirectoryRelativePath = @"tests\Images\External\tools"; - private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); + private static readonly Lazy SolutionDirectoryFullPathLazy = new(GetSolutionDirectoryFullPathImpl); - private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); + private static readonly Lazy NetCoreVersionLazy = new(GetNetCoreVersion); - static TestEnvironment() => PrepareRemoteExecutor(); + static TestEnvironment() => PrepareRemoteExecutor(); - /// - /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. - /// - internal static string NetCoreVersion => NetCoreVersionLazy.Value; + /// + /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. + /// + internal static string NetCoreVersion => NetCoreVersionLazy.Value; - // ReSharper disable once InconsistentNaming + // ReSharper disable once InconsistentNaming - /// - /// Gets a value indicating whether test execution runs on CI. - /// + /// + /// Gets a value indicating whether test execution runs on CI. + /// #if ENV_CI - internal static bool RunsOnCI => true; + internal static bool RunsOnCI => true; #else - internal static bool RunsOnCI => false; + internal static bool RunsOnCI => false; #endif - /// - /// Gets a value indicating whether test execution is running with code coverage testing enabled. - /// + /// + /// Gets a value indicating whether test execution is running with code coverage testing enabled. + /// #if ENV_CODECOV - internal static bool RunsWithCodeCoverage => true; + internal static bool RunsWithCodeCoverage => true; #else - internal static bool RunsWithCodeCoverage => false; + internal static bool RunsWithCodeCoverage => false; #endif - internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; + internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; - private static readonly FileInfo TestAssemblyFile = - new FileInfo(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); + private static readonly FileInfo TestAssemblyFile = + new(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); - private static string GetSolutionDirectoryFullPathImpl() - { - DirectoryInfo directory = TestAssemblyFile.Directory; + private static string GetSolutionDirectoryFullPathImpl() + { + DirectoryInfo directory = TestAssemblyFile.Directory; - while (!directory.EnumerateFiles(ImageSharpTexturesSolutionFileName).Any()) + while (!directory.EnumerateFiles(ImageSharpTexturesSolutionFileName).Any()) + { + try { - try - { - directory = directory.Parent; - } - catch (Exception ex) - { - throw new DirectoryNotFoundException( - $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", - ex); - } - - if (directory == null) - { - throw new DirectoryNotFoundException($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); - } + directory = directory.Parent; + } + catch (Exception ex) + { + throw new DirectoryNotFoundException( + $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", + ex); } - return directory.FullName; + if (directory == null) + { + throw new DirectoryNotFoundException($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); + } } - private static string GetFullPath(string relativePath) => - Path.Combine(SolutionDirectoryFullPath, relativePath) - .Replace('\\', Path.DirectorySeparatorChar); + return directory.FullName; + } + + private static string GetFullPath(string relativePath) => + Path.Combine(SolutionDirectoryFullPath, relativePath) + .Replace('\\', Path.DirectorySeparatorChar); - /// - /// Gets the correct full path to the Input Images directory. - /// - internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); + /// + /// Gets the correct full path to the Input Images directory. + /// + internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); - /// - /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) - /// - internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); + /// + /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) + /// + internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); - /// - /// Gets the correct full path to the Expected Output directory. (To compare the test results to.) - /// - internal static string ReferenceOutputDirectoryFullPath => GetFullPath(ReferenceOutputDirectoryRelativePath); + /// + /// Gets the correct full path to the Expected Output directory. (To compare the test results to.) + /// + internal static string ReferenceOutputDirectoryFullPath => GetFullPath(ReferenceOutputDirectoryRelativePath); - internal static string ToolsDirectoryFullPath => GetFullPath(ToolsDirectoryRelativePath); + internal static string ToolsDirectoryFullPath => GetFullPath(ToolsDirectoryRelativePath); - internal static string GetReferenceOutputFileName(string actualOutputFileName) => - actualOutputFileName.Replace("ActualOutput", @"ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); + internal static string GetReferenceOutputFileName(string actualOutputFileName) => + actualOutputFileName.Replace("ActualOutput", @"ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); - internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - internal static bool IsOSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + internal static bool IsOSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 + internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 - internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - internal static bool Is64BitProcess => IntPtr.Size == 8; + internal static bool Is64BitProcess => IntPtr.Size == 8; - internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); + internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); - /// - /// A dummy operation to enforce the execution of the static constructor. - /// - internal static void EnsureSharedInitializersDone() + /// + /// A dummy operation to enforce the execution of the static constructor. + /// + internal static void EnsureSharedInitializersDone() + { + } + + /// + /// Creates the image output directory. + /// + /// The path. + /// The path parts. + /// + /// The . + /// + internal static string CreateOutputDirectory(string path, params string[] pathParts) + { + path = Path.Combine(ActualOutputDirectoryFullPath, path); + + if (pathParts != null && pathParts.Length > 0) { + path = Path.Combine(path, Path.Combine(pathParts)); } - /// - /// Creates the image output directory. - /// - /// The path. - /// The path parts. - /// - /// The . - /// - internal static string CreateOutputDirectory(string path, params string[] pathParts) + if (!Directory.Exists(path)) { - path = Path.Combine(ActualOutputDirectoryFullPath, path); - - if (pathParts != null && pathParts.Length > 0) - { - path = Path.Combine(path, Path.Combine(pathParts)); - } + Directory.CreateDirectory(path); + } - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } + return path; + } - return path; + /// + /// Creates Microsoft.DotNet.RemoteExecutor.exe.config for .NET framework, + /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe + /// with the help of CorFlags.exe found in Windows SDK. + /// + private static void PrepareRemoteExecutor() + { + if (!IsFramework) + { + return; } - /// - /// Creates Microsoft.DotNet.RemoteExecutor.exe.config for .NET framework, - /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe - /// with the help of CorFlags.exe found in Windows SDK. - /// - private static void PrepareRemoteExecutor() + string remoteExecutorConfigPath = + Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config"); + + if (File.Exists(remoteExecutorConfigPath)) { - if (!IsFramework) - { - return; - } + // Already initialized + return; + } - string remoteExecutorConfigPath = - Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config"); + string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; + if (File.Exists(testProjectConfigPath)) + { + File.Copy(testProjectConfigPath, remoteExecutorConfigPath); + } - if (File.Exists(remoteExecutorConfigPath)) - { - // Already initialized - return; - } + if (Is64BitProcess) + { + return; + } - string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; - if (File.Exists(testProjectConfigPath)) - { - File.Copy(testProjectConfigPath, remoteExecutorConfigPath); - } + EnsureRemoteExecutorIs32Bit(); + } - if (Is64BitProcess) - { - return; - } + /// + /// Locate and run CorFlags.exe /32Bit+ + /// https://docs.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool + /// + private static void EnsureRemoteExecutorIs32Bit() + { + string windowsSdksDir = Path.Combine( + Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), + "Microsoft SDKs", + "Windows"); - EnsureRemoteExecutorIs32Bit(); - } + FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); - /// - /// Locate and run CorFlags.exe /32Bit+ - /// https://docs.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool - /// - private static void EnsureRemoteExecutorIs32Bit() - { - string windowsSdksDir = Path.Combine( - Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), - "Microsoft SDKs", - "Windows"); + string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); - FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); + string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; - string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); + if (File.Exists(remoteExecutorTmpPath)) + { + // Already initialized + return; + } - string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; + File.Copy(remoteExecutorPath, remoteExecutorTmpPath); - if (File.Exists(remoteExecutorTmpPath)) - { - // Already initialized - return; - } + string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; - File.Copy(remoteExecutorPath, remoteExecutorTmpPath); + ProcessStartInfo si = new() + { + FileName = corFlagsFile.FullName, + Arguments = args, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using Process proc = Process.Start(si); + proc.WaitForExit(); + string standardOutput = proc.StandardOutput.ReadToEnd(); + string standardError = proc.StandardError.ReadToEnd(); + + if (proc.ExitCode != 0) + { + throw new Exception( + $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); + } - string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; + File.Delete(remoteExecutorPath); + File.Copy(remoteExecutorTmpPath, remoteExecutorPath); - var si = new ProcessStartInfo() - { - FileName = corFlagsFile.FullName, - Arguments = args, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - - using var proc = Process.Start(si); - proc.WaitForExit(); - string standardOutput = proc.StandardOutput.ReadToEnd(); - string standardError = proc.StandardError.ReadToEnd(); - - if (proc.ExitCode != 0) + static FileInfo Find(DirectoryInfo root, string name) + { + FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); + if (fi != null) { - throw new Exception( - $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); + return fi; } - File.Delete(remoteExecutorPath); - File.Copy(remoteExecutorTmpPath, remoteExecutorPath); - - static FileInfo Find(DirectoryInfo root, string name) + foreach (DirectoryInfo dir in root.EnumerateDirectories()) { - FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); + fi = Find(dir, name); if (fi != null) { return fi; } - - foreach (DirectoryInfo dir in root.EnumerateDirectories()) - { - fi = Find(dir, name); - if (fi != null) - { - return fi; - } - } - - return null; } - } - internal static IImageDecoder GetReferenceDecoder(string filePath) - { - IImageFormat format = GetImageFormat(filePath); - return Configuration.ImageFormatsManager.GetDecoder(format); - } - - internal static IImageEncoder GetReferenceEncoder(string filePath) - { - IImageFormat format = GetImageFormat(filePath); - return Configuration.ImageFormatsManager.GetEncoder(format); + return null; } + } - internal static IImageFormat GetImageFormat(string filePath) - { - string extension = Path.GetExtension(filePath); + internal static IImageDecoder GetReferenceDecoder(string filePath) + { + IImageFormat format = GetImageFormat(filePath); + return Configuration.ImageFormatsManager.GetDecoder(format); + } - if (!Configuration.ImageFormatsManager.TryFindFormatByFileExtension(extension, out IImageFormat format)) - { - throw new NotSupportedException($"No image format found for extension '{extension}'!"); - } + internal static IImageEncoder GetReferenceEncoder(string filePath) + { + IImageFormat format = GetImageFormat(filePath); + return Configuration.ImageFormatsManager.GetEncoder(format); + } - return format; - } + internal static IImageFormat GetImageFormat(string filePath) + { + string extension = Path.GetExtension(filePath); - private static void ConfigureCodecs( - this ImageSharp.Configuration cfg, - IImageFormat imageFormat, - IImageDecoder decoder, - IImageEncoder encoder, - IImageFormatDetector detector) + if (!Configuration.ImageFormatsManager.TryFindFormatByFileExtension(extension, out IImageFormat format)) { - cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder); - cfg.ImageFormatsManager.SetEncoder(imageFormat, encoder); - cfg.ImageFormatsManager.AddImageFormatDetector(detector); + throw new NotSupportedException($"No image format found for extension '{extension}'!"); } - private static ImageSharp.Configuration CreateDefaultConfiguration() - { - var cfg = new ImageSharp.Configuration(); + return format; + } - cfg.ConfigureCodecs( - PngFormat.Instance, - PngDecoder.Instance, - new PngEncoder(), - new PngImageFormatDetector()); + private static void ConfigureCodecs( + this ImageSharp.Configuration cfg, + IImageFormat imageFormat, + IImageDecoder decoder, + IImageEncoder encoder, + IImageFormatDetector detector) + { + cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder); + cfg.ImageFormatsManager.SetEncoder(imageFormat, encoder); + cfg.ImageFormatsManager.AddImageFormatDetector(detector); + } - return cfg; - } + private static ImageSharp.Configuration CreateDefaultConfiguration() + { + ImageSharp.Configuration cfg = new(); - /// - /// Solution borrowed from: - /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 - /// - private static string GetNetCoreVersion() - { - Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; - string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); - int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); - if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) - { - return assemblyPath[netCoreAppIndex + 1]; - } + cfg.ConfigureCodecs( + PngFormat.Instance, + PngDecoder.Instance, + new PngEncoder(), + new PngImageFormatDetector()); - return string.Empty; + return cfg; + } + + /// + /// Solution borrowed from: + /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 + /// + private static string GetNetCoreVersion() + { + Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + string[] assemblyPath = assembly.Location.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries); + int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + { + return assemblyPath[netCoreAppIndex + 1]; } + + return string.Empty; } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/TestImageExtensions.cs index 5218853d..cb20b941 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/TestImageExtensions.cs @@ -1,229 +1,226 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities; + +public static class TestImageExtensions { - public static class TestImageExtensions + public static void DebugSave( + this Image image, + ITestTextureProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool appendPixelTypeToFileName = false, + bool appendSourceFileOrDescription = false, + IImageEncoder encoder = null) => image.DebugSave( + provider, + (object)testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription, + encoder); + + /// + /// Saves the image only when not running in the CI server. + /// + /// The image. + /// The image provider. + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// The extension. + /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append SourceFileOrDescription to the test output file name. + /// Custom encoder to use. + /// The input image. + public static Image DebugSave( + this Image image, + ITestTextureProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = false, + bool appendSourceFileOrDescription = false, + IImageEncoder encoder = null) { - public static void DebugSave( - this Image image, - ITestTextureProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool appendPixelTypeToFileName = false, - bool appendSourceFileOrDescription = false, - IImageEncoder encoder = null) => image.DebugSave( - provider, - (object)testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription, - encoder); - - /// - /// Saves the image only when not running in the CI server. - /// - /// The image. - /// The image provider. - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension. - /// A boolean indicating whether to append the pixel type to the output file name. - /// A boolean indicating whether to append SourceFileOrDescription to the test output file name. - /// Custom encoder to use. - /// The input image. - public static Image DebugSave( - this Image image, - ITestTextureProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = false, - bool appendSourceFileOrDescription = false, - IImageEncoder encoder = null) + if (TestEnvironment.RunsOnCI) { - if (TestEnvironment.RunsOnCI) - { - return image; - } - - // We are running locally then we want to save it out - provider.Utility.SaveTestOutputFile( - image, - extension, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription, - encoder: encoder); return image; } - public static void DebugSave( - this Image image, - ITestTextureProvider provider, - IImageEncoder encoder, - FormattableString testOutputDetails, - bool appendPixelTypeToFileName = false) => image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); - - /// - /// Saves the image only when not running in the CI server. - /// - /// The image - /// The image provider - /// The image encoder - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// A boolean indicating whether to append the pixel type to the output file name. - public static void DebugSave( - this Image image, - ITestTextureProvider provider, - IImageEncoder encoder, - object testOutputDetails = null, - bool appendPixelTypeToFileName = false) + // We are running locally then we want to save it out + provider.Utility.SaveTestOutputFile( + image, + extension, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription, + encoder: encoder); + return image; + } + + public static void DebugSave( + this Image image, + ITestTextureProvider provider, + IImageEncoder encoder, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = false) => image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); + + /// + /// Saves the image only when not running in the CI server. + /// + /// The image + /// The image provider + /// The image encoder + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// A boolean indicating whether to append the pixel type to the output file name. + public static void DebugSave( + this Image image, + ITestTextureProvider provider, + IImageEncoder encoder, + object testOutputDetails = null, + bool appendPixelTypeToFileName = false) + { + if (TestEnvironment.RunsOnCI) { - if (TestEnvironment.RunsOnCI) - { - return; - } - - // We are running locally then we want to save it out - provider.Utility.SaveTestOutputFile( - image, - encoder: encoder, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName); + return; } - public static Image CompareToReferenceOutput( - this Image image, - ITestTextureProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool appendPixelTypeToFileName = false, - bool appendSourceFileOrDescription = false) - where TPixel : unmanaged, IPixel => image.CompareToReferenceOutput( - provider, - (object)testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - /// - /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. - /// The output file should be named identically to the output produced by . - /// - /// The pixel format. - /// The image which should be compared to the reference image. - /// The image provider. - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension - /// A boolean indicating whether to append the pixel type to the output file name. - /// A boolean indicating whether to append to the test output file name. - /// The image. - public static Image CompareToReferenceOutput( - this Image image, - ITestTextureProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = false, - bool appendSourceFileOrDescription = false) - where TPixel : unmanaged, IPixel => CompareToReferenceOutput( - image, - ImageComparer.Tolerant(), - provider, - testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - public static Image CompareToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestTextureProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool appendPixelTypeToFileName = false) - where TPixel : unmanaged, IPixel => image.CompareToReferenceOutput( - comparer, - provider, - (object)testOutputDetails, - extension, - appendPixelTypeToFileName); - - /// - /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. - /// The output file should be named identically to the output produced by . - /// - /// The pixel format. - /// The image which should be compared to the reference output. - /// The to use. - /// The image provider. - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension - /// A boolean indicating whether to append the pixel type to the output file name. - /// A boolean indicating whether to append SourceFileOrDescription to the test output file name. - /// A custom decoder. - /// The image. - public static Image CompareToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestTextureProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = false, - bool appendSourceFileOrDescription = false, - IImageDecoder decoder = null) - where TPixel : unmanaged, IPixel - { - using (Image referenceImage = GetReferenceOutputImage( - provider, - testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription, - decoder)) - { - comparer.VerifySimilarity(referenceImage, image); - } + // We are running locally then we want to save it out + provider.Utility.SaveTestOutputFile( + image, + encoder: encoder, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName); + } - return image; + public static Image CompareToReferenceOutput( + this Image image, + ITestTextureProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool appendPixelTypeToFileName = false, + bool appendSourceFileOrDescription = false) + where TPixel : unmanaged, IPixel => image.CompareToReferenceOutput( + provider, + (object)testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + + /// + /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. + /// The output file should be named identically to the output produced by . + /// + /// The pixel format. + /// The image which should be compared to the reference image. + /// The image provider. + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// The extension + /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append to the test output file name. + /// The image. + public static Image CompareToReferenceOutput( + this Image image, + ITestTextureProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = false, + bool appendSourceFileOrDescription = false) + where TPixel : unmanaged, IPixel => CompareToReferenceOutput( + image, + ImageComparer.Tolerant(), + provider, + testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + + public static Image CompareToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestTextureProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool appendPixelTypeToFileName = false) + where TPixel : unmanaged, IPixel => image.CompareToReferenceOutput( + comparer, + provider, + (object)testOutputDetails, + extension, + appendPixelTypeToFileName); + + /// + /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. + /// The output file should be named identically to the output produced by . + /// + /// The pixel format. + /// The image which should be compared to the reference output. + /// The to use. + /// The image provider. + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// The extension + /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append SourceFileOrDescription to the test output file name. + /// A custom decoder. + /// The image. + public static Image CompareToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestTextureProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = false, + bool appendSourceFileOrDescription = false, + IImageDecoder decoder = null) + where TPixel : unmanaged, IPixel + { + using (Image referenceImage = GetReferenceOutputImage( + provider, + testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription, + decoder)) + { + comparer.VerifySimilarity(referenceImage, image); } - public static Image GetReferenceOutputImage( - this ITestTextureProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = false, - bool appendSourceFileOrDescription = false, - IImageDecoder decoder = null) - where TPixel : unmanaged, IPixel + return image; + } + + public static Image GetReferenceOutputImage( + this ITestTextureProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = false, + bool appendSourceFileOrDescription = false, + IImageDecoder decoder = null) + where TPixel : unmanaged, IPixel + { + string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( + extension, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + + if (!File.Exists(referenceOutputFile)) { - string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( - extension, - testOutputDetails, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - if (!File.Exists(referenceOutputFile)) - { - throw new FileNotFoundException($"Reference output file {referenceOutputFile} is missing", referenceOutputFile); - } - - IImageFormat format = TestEnvironment.GetImageFormat(referenceOutputFile); - decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); - - ImageSharp.Configuration configuration = ImageSharp.Configuration.Default.Clone(); - configuration.ImageFormatsManager.SetDecoder(format, decoder); - DecoderOptions options = new() - { - Configuration = configuration - }; - - return Image.Load(options, referenceOutputFile); + throw new FileNotFoundException($"Reference output file {referenceOutputFile} is missing", referenceOutputFile); } + + IImageFormat format = TestEnvironment.GetImageFormat(referenceOutputFile); + decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); + + ImageSharp.Configuration configuration = ImageSharp.Configuration.Default.Clone(); + configuration.ImageFormatsManager.SetDecoder(format, decoder); + DecoderOptions options = new() + { + Configuration = configuration + }; + + return Image.Load(options, referenceOutputFile); } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/TestUtils.cs index cd3e4c62..710631b9 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/TestUtils.cs @@ -1,104 +1,98 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Reflection; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities; + +/// +/// Various utility and extension methods. +/// +public static class TestUtils { - /// - /// Various utility and extension methods. - /// - public static class TestUtils - { - private static readonly Dictionary ClrTypes2PixelTypes = new Dictionary(); + private static readonly Dictionary ClrTypes2PixelTypes = []; - private static readonly Assembly ImageSharpAssembly = typeof(Rgba32).GetTypeInfo().Assembly; + private static readonly Assembly ImageSharpAssembly = typeof(Rgba32).GetTypeInfo().Assembly; - private static readonly Dictionary PixelTypes2ClrTypes = new Dictionary(); + private static readonly Dictionary PixelTypes2ClrTypes = []; - private static readonly PixelTypes[] AllConcretePixelTypes = GetAllPixelTypes() - .Except(new[] { PixelTypes.Undefined, PixelTypes.All }) - .ToArray(); + private static readonly PixelTypes[] AllConcretePixelTypes = [.. GetAllPixelTypes().Except(new[] { PixelTypes.Undefined, PixelTypes.All })]; - static TestUtils() + static TestUtils() + { + // Add Rgba32 Our default. + Type defaultPixelFormatType = typeof(Rgba32); + PixelTypes2ClrTypes[PixelTypes.Rgba32] = defaultPixelFormatType; + ClrTypes2PixelTypes[defaultPixelFormatType] = PixelTypes.Rgba32; + + // Add PixelFormat types + string nameSpace = typeof(A8).FullName; + nameSpace = nameSpace[..(nameSpace.Length - typeof(A8).Name.Length - 1)]; + foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.Rgba32)) { - // Add Rgba32 Our default. - Type defaultPixelFormatType = typeof(Rgba32); - PixelTypes2ClrTypes[PixelTypes.Rgba32] = defaultPixelFormatType; - ClrTypes2PixelTypes[defaultPixelFormatType] = PixelTypes.Rgba32; - - // Add PixelFormat types - string nameSpace = typeof(A8).FullName; - nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(A8).Name.Length - 1); - foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.Rgba32)) - { - string typeName = $"{nameSpace}.{pt}"; - Type t = ImageSharpAssembly.GetType(typeName); - PixelTypes2ClrTypes[pt] = t ?? throw new InvalidOperationException($"Could not find: {typeName}"); - ClrTypes2PixelTypes[t] = pt; - } + string typeName = $"{nameSpace}.{pt}"; + Type t = ImageSharpAssembly.GetType(typeName); + PixelTypes2ClrTypes[pt] = t ?? throw new InvalidOperationException($"Could not find: {typeName}"); + ClrTypes2PixelTypes[t] = pt; } + } - public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; + public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; - public static string ToCsv(this IEnumerable items, string separator = ",") => string.Join(separator, items.Select(o => string.Format(CultureInfo.InvariantCulture, "{0}", o))); + public static string ToCsv(this IEnumerable items, string separator = ",") => string.Join(separator, items.Select(o => string.Format(CultureInfo.InvariantCulture, "{0}", o))); - public static Type GetClrType(this PixelTypes pixelType) => PixelTypes2ClrTypes[pixelType]; + public static Type GetClrType(this PixelTypes pixelType) => PixelTypes2ClrTypes[pixelType]; - /// - /// Returns the enumerations for the given type. - /// - /// The pixel type. - public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; + /// + /// Returns the enumerations for the given type. + /// + /// The pixel type. + public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; - public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) + public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) + { + if (pixelTypes == PixelTypes.Undefined) { - if (pixelTypes == PixelTypes.Undefined) - { - return Enumerable.Empty>(); - } - else if (pixelTypes == PixelTypes.All) - { - // TODO: Need to return unknown types here without forcing CLR to load all types in ImageSharp assembly - return PixelTypes2ClrTypes; - } + return Enumerable.Empty>(); + } + else if (pixelTypes == PixelTypes.All) + { + // TODO: Need to return unknown types here without forcing CLR to load all types in ImageSharp assembly + return PixelTypes2ClrTypes; + } - var result = new Dictionary(); - foreach (PixelTypes pt in AllConcretePixelTypes) + Dictionary result = new(); + foreach (PixelTypes pt in AllConcretePixelTypes) + { + if (pixelTypes.HasAll(pt)) { - if (pixelTypes.HasAll(pt)) - { - result[pt] = pt.GetClrType(); - } + result[pt] = pt.GetClrType(); } - - return result; } - internal static bool HasAll(this PixelTypes pixelTypes, PixelTypes flagsToCheck) => - (pixelTypes & flagsToCheck) == flagsToCheck; - - /// - /// Enumerate all available -s - /// - /// The pixel types - internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes)); + return result; + } - internal static Color GetColorByName(string colorName) - { - var f = (FieldInfo)typeof(Color).GetMember(colorName)[0]; - return (Color)f.GetValue(null); - } + internal static bool HasAll(this PixelTypes pixelTypes, PixelTypes flagsToCheck) => + (pixelTypes & flagsToCheck) == flagsToCheck; - internal static TPixel GetPixelOfNamedColor(string colorName) - where TPixel : unmanaged, IPixel => - GetColorByName(colorName).ToPixel(); + /// + /// Enumerate all available -s + /// + /// The pixel types + internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes)); - public static string AsInvariantString(this FormattableString formattable) => FormattableString.Invariant(formattable); + internal static Color GetColorByName(string colorName) + { + FieldInfo f = (FieldInfo)typeof(Color).GetMember(colorName)[0]; + return (Color)f.GetValue(null); } + + internal static TPixel GetPixelOfNamedColor(string colorName) + where TPixel : unmanaged, IPixel => + GetColorByName(colorName).ToPixel(); + + public static string AsInvariantString(this FormattableString formattable) => FormattableString.Invariant(formattable); } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/ITestTextureProvider.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/ITestTextureProvider.cs index 757107c3..919110f7 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/ITestTextureProvider.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/ITestTextureProvider.cs @@ -3,31 +3,30 @@ using SixLabors.ImageSharp.Textures.Tests.Enums; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; + +public interface ITestTextureProvider { - public interface ITestTextureProvider - { - string MethodName { get; } + string MethodName { get; } - /// - /// Gets the utility instance to provide information about the test image & manage input/output. - /// - ImagingTestCaseUtility Utility { get; } + /// + /// Gets the utility instance to provide information about the test image & manage input/output. + /// + ImagingTestCaseUtility Utility { get; } - /// - /// Gets the texture container format. - /// - TestTextureFormat TextureFormat { get; } + /// + /// Gets the texture container format. + /// + TestTextureFormat TextureFormat { get; } - /// - /// Gets the type of the texture, e.g. flat, volume or cubemap. - /// - TestTextureType TextureType { get; } + /// + /// Gets the type of the texture, e.g. flat, volume or cubemap. + /// + TestTextureType TextureType { get; } - TestTextureTool TextureTool { get; } + TestTextureTool TextureTool { get; } - string InputFile { get; } + string InputFile { get; } - bool IsRegex { get; } - } + bool IsRegex { get; } } diff --git a/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/TestTextureProvider.cs b/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/TestTextureProvider.cs index 1bcdd600..296da8db 100644 --- a/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/TestTextureProvider.cs +++ b/tests/ImageSharp.Textures.Tests/TestUtilities/TextureProviders/TestTextureProvider.cs @@ -2,128 +2,125 @@ // Licensed under the Six Labors Split License. using System.Globalization; -using System.IO; using System.Text; using SixLabors.ImageSharp.Textures.Formats; using SixLabors.ImageSharp.Textures.Tests.Enums; using SixLabors.ImageSharp.Textures.TextureFormats; -using Xunit; -namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders +namespace SixLabors.ImageSharp.Textures.Tests.TestUtilities.TextureProviders; + +public class TestTextureProvider : ITestTextureProvider { - public class TestTextureProvider : ITestTextureProvider - { - public string MethodName { get; } + public string MethodName { get; } - /// - public ImagingTestCaseUtility Utility { get; private set; } + /// + public ImagingTestCaseUtility Utility { get; private set; } - /// - public TestTextureFormat TextureFormat { get; } + /// + public TestTextureFormat TextureFormat { get; } - /// - public TestTextureType TextureType { get; } + /// + public TestTextureType TextureType { get; } - /// - public TestTextureTool TextureTool { get; } + /// + public TestTextureTool TextureTool { get; } - public string InputFile { get; } + public string InputFile { get; } - public bool IsRegex { get; } + public bool IsRegex { get; } - public virtual Texture GetTexture(ITextureDecoder decoder) - { - using FileStream fileStream = File.OpenRead(this.InputFile); + public virtual Texture GetTexture(ITextureDecoder decoder) + { + using FileStream fileStream = File.OpenRead(this.InputFile); - Texture result = decoder.DecodeTexture(Configuration.Default, fileStream); + Texture result = decoder.DecodeTexture(Configuration.Default, fileStream); - Assert.True(fileStream.Length == fileStream.Position, "The texture file stream was not read to the end"); + Assert.True(fileStream.Length == fileStream.Position, "The texture file stream was not read to the end"); - return result; - } + return result; + } - public TestTextureProvider( - string methodName, - TestTextureFormat textureFormat, - TestTextureType textureType, - TestTextureTool textureTool, - string inputFile, - bool isRegex) + public TestTextureProvider( + string methodName, + TestTextureFormat textureFormat, + TestTextureType textureType, + TestTextureTool textureTool, + string inputFile, + bool isRegex) + { + this.MethodName = methodName; + this.TextureFormat = textureFormat; + this.TextureType = textureType; + this.TextureTool = textureTool; + this.InputFile = inputFile; + this.IsRegex = isRegex; + this.Utility = new ImagingTestCaseUtility { - this.MethodName = methodName; - this.TextureFormat = textureFormat; - this.TextureType = textureType; - this.TextureTool = textureTool; - this.InputFile = inputFile; - this.IsRegex = isRegex; - this.Utility = new ImagingTestCaseUtility - { - SourceFileOrDescription = inputFile, - TestName = methodName - }; - } + SourceFileOrDescription = inputFile, + TestName = methodName + }; + } - private void SaveMipMaps(MipMap[] mipMaps, string name) - { - string path = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, this.TextureFormat.ToString(), this.TextureType.ToString(), this.TextureTool.ToString(), this.MethodName, Path.GetFileNameWithoutExtension(this.InputFile)); + private void SaveMipMaps(MipMap[] mipMaps, string name) + { + string path = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, this.TextureFormat.ToString(), this.TextureType.ToString(), this.TextureTool.ToString(), this.MethodName, Path.GetFileNameWithoutExtension(this.InputFile)); - Directory.CreateDirectory(path); + Directory.CreateDirectory(path); - for (int i = 0; i < mipMaps.Length; i++) + for (int i = 0; i < mipMaps.Length; i++) + { + string filename = string.Format(CultureInfo.InvariantCulture, "mipmap-{0}", i + 1); + if (!string.IsNullOrEmpty(name)) { - string filename = string.Format(CultureInfo.InvariantCulture, "mipmap-{0}", i + 1); - if (!string.IsNullOrEmpty(name)) - { - filename = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", filename, name); - } - - using Image image = mipMaps[i].GetImage(); - image.Save(Path.Combine(path, string.Format(CultureInfo.InvariantCulture, "{0}.png", filename))); + filename = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", filename, name); } + + using Image image = mipMaps[i].GetImage(); + image.Save(Path.Combine(path, string.Format(CultureInfo.InvariantCulture, "{0}.png", filename))); } + } - public void SaveTextures(Texture texture) + public void SaveTextures(Texture texture) + { + if (TestEnvironment.RunsOnCI) { - if (TestEnvironment.RunsOnCI) - { - return; - } + return; + } - if (texture is CubemapTexture cubemapTexture) - { - this.SaveMipMaps(cubemapTexture.PositiveX.MipMaps.ToArray(), "positive-x"); - this.SaveMipMaps(cubemapTexture.NegativeX.MipMaps.ToArray(), "negative-x"); - this.SaveMipMaps(cubemapTexture.PositiveY.MipMaps.ToArray(), "positive-y"); - this.SaveMipMaps(cubemapTexture.NegativeY.MipMaps.ToArray(), "negative-y"); - this.SaveMipMaps(cubemapTexture.PositiveZ.MipMaps.ToArray(), "positive-z"); - this.SaveMipMaps(cubemapTexture.NegativeZ.MipMaps.ToArray(), "negative-z"); - } + if (texture is CubemapTexture cubemapTexture) + { + this.SaveMipMaps([.. cubemapTexture.PositiveX.MipMaps], "positive-x"); + this.SaveMipMaps([.. cubemapTexture.NegativeX.MipMaps], "negative-x"); + this.SaveMipMaps([.. cubemapTexture.PositiveY.MipMaps], "positive-y"); + this.SaveMipMaps([.. cubemapTexture.NegativeY.MipMaps], "negative-y"); + this.SaveMipMaps([.. cubemapTexture.PositiveZ.MipMaps], "positive-z"); + this.SaveMipMaps([.. cubemapTexture.NegativeZ.MipMaps], "negative-z"); + } - if (texture is FlatTexture flatTexture) - { - this.SaveMipMaps(flatTexture.MipMaps.ToArray(), null); - } + if (texture is FlatTexture flatTexture) + { + this.SaveMipMaps([.. flatTexture.MipMaps], null); + } - if (texture is VolumeTexture volumeTexture) + if (texture is VolumeTexture volumeTexture) + { + for (int i = 0; i < volumeTexture.Slices.Count; i++) { - for (int i = 0; i < volumeTexture.Slices.Count; i++) - { - this.SaveMipMaps(volumeTexture.Slices[i].MipMaps.ToArray(), string.Format(CultureInfo.InvariantCulture, "slice{0}", i + 1)); - } + this.SaveMipMaps([.. volumeTexture.Slices[i].MipMaps], string.Format(CultureInfo.InvariantCulture, "slice{0}", i + 1)); } } + } - public override string ToString() - { - var stringBuilder = new StringBuilder(); - stringBuilder.AppendLine(); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Method Name: {0}", this.MethodName)); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Texture Format: {0}", this.TextureFormat)); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Texture Type: {0}", this.TextureType)); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Texture Tool: {0}", this.TextureTool)); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Input File: {0}", this.InputFile)); - stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Is Regex: {0}", this.IsRegex)); - return stringBuilder.ToString(); - } + public override string ToString() + { + StringBuilder stringBuilder = new(); + stringBuilder.AppendLine(); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Method Name: {0}", this.MethodName)); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Texture Format: {0}", this.TextureFormat)); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Texture Type: {0}", this.TextureType)); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Texture Tool: {0}", this.TextureTool)); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Input File: {0}", this.InputFile)); + stringBuilder.AppendLine(string.Format(CultureInfo.InvariantCulture, "Is Regex: {0}", this.IsRegex)); + return stringBuilder.ToString(); } } diff --git a/tests/Images/TestEnvironment.cs b/tests/Images/TestEnvironment.cs index 79a36a4c..ab418d34 100644 --- a/tests/Images/TestEnvironment.cs +++ b/tests/Images/TestEnvironment.cs @@ -1,88 +1,84 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Linq; using System.Reflection; -namespace SixLabors.ImageSharp.Textures +namespace SixLabors.ImageSharp.Textures; + +public static class TestEnvironment { - public static class TestEnvironment - { - private const string ImageSharpTexturesSolutionFileName = "ImageSharp.Textures.sln"; + private const string ImageSharpTexturesSolutionFileName = "ImageSharp.Textures.sln"; - private const string ToolsRelativePath = @"tests\Tools"; + private const string ToolsRelativePath = @"tests\Tools"; - private const string InputImagesRelativePath = @"tests\Images\Input"; + private const string InputImagesRelativePath = @"tests\Images\Input"; - private const string BaselineDirectoryRelativePath = @"tests\Images\Baseline"; + private const string BaselineDirectoryRelativePath = @"tests\Images\Baseline"; - private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; + private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; - private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); + private static readonly Lazy SolutionDirectoryFullPathLazy = new(GetSolutionDirectoryFullPathImpl); - private static readonly Lazy RunsOnCiLazy = new Lazy(() => bool.TryParse(Environment.GetEnvironmentVariable("CI"), out bool isCi) && isCi); + private static readonly Lazy RunsOnCiLazy = new(() => bool.TryParse(Environment.GetEnvironmentVariable("CI"), out bool isCi) && isCi); - /// - /// Gets a value indicating whether test execution runs on CI. - /// - internal static bool RunsOnCi => RunsOnCiLazy.Value; + /// + /// Gets a value indicating whether test execution runs on CI. + /// + internal static bool RunsOnCi => RunsOnCiLazy.Value; - internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; + internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; - private static string GetSolutionDirectoryFullPathImpl() - { - string assemblyLocation = typeof(TestEnvironment).GetTypeInfo().Assembly.Location; + private static string GetSolutionDirectoryFullPathImpl() + { + string assemblyLocation = typeof(TestEnvironment).GetTypeInfo().Assembly.Location; - var assemblyFile = new FileInfo(assemblyLocation); + FileInfo assemblyFile = new(assemblyLocation); - DirectoryInfo directory = assemblyFile.Directory; + DirectoryInfo directory = assemblyFile.Directory; - while (!directory.EnumerateFiles(ImageSharpTexturesSolutionFileName).Any()) + while (!directory.EnumerateFiles(ImageSharpTexturesSolutionFileName).Any()) + { + try { - try - { - directory = directory.Parent; - } - catch (Exception ex) - { - throw new DirectoryNotFoundException( - $"Unable to find ImageSharp solution directory from {assemblyLocation} because of {ex.GetType().Name}!", - ex); - } - - if (directory == null) - { - throw new DirectoryNotFoundException($"Unable to find ImageSharp solution directory from {assemblyLocation}!"); - } + directory = directory.Parent; + } + catch (Exception ex) + { + throw new DirectoryNotFoundException( + $"Unable to find ImageSharp solution directory from {assemblyLocation} because of {ex.GetType().Name}!", + ex); } - return directory.FullName; + if (directory == null) + { + throw new DirectoryNotFoundException($"Unable to find ImageSharp solution directory from {assemblyLocation}!"); + } } - private static string GetFullPath(string relativePath) => - Path.Combine(SolutionDirectoryFullPath, relativePath) - .Replace('\\', Path.DirectorySeparatorChar); - - /// - /// Gets the correct full path to the Tools directory. - /// - internal static string ToolsDirectoryFullPath => GetFullPath(ToolsRelativePath); - - /// - /// Gets the correct full path to the Input Images directory. - /// - internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); - - /// - /// Gets the correct full path to the Baseline directory. - /// - internal static string BaselineDirectoryFullPath => GetFullPath(BaselineDirectoryRelativePath); - - /// - /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) - /// - internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); + return directory.FullName; } + + private static string GetFullPath(string relativePath) => + Path.Combine(SolutionDirectoryFullPath, relativePath) + .Replace('\\', Path.DirectorySeparatorChar); + + /// + /// Gets the correct full path to the Tools directory. + /// + internal static string ToolsDirectoryFullPath => GetFullPath(ToolsRelativePath); + + /// + /// Gets the correct full path to the Input Images directory. + /// + internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); + + /// + /// Gets the correct full path to the Baseline directory. + /// + internal static string BaselineDirectoryFullPath => GetFullPath(BaselineDirectoryRelativePath); + + /// + /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) + /// + internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); } diff --git a/tests/Images/TestTextures.cs b/tests/Images/TestTextures.cs index 6f57d93d..91e23a2d 100644 --- a/tests/Images/TestTextures.cs +++ b/tests/Images/TestTextures.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Textures.Tests +namespace SixLabors.ImageSharp.Textures.Tests; + +public static class TestTextures { - public static class TestTextures + public static class Dds { - public static class Dds - { - } } } From 78ddda862f6a4dec479758698791fa8851b0aec8 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:33:52 +0100 Subject: [PATCH 37/37] Update test dependencies --- .../ImageSharp.Textures.Astc.Reference.Tests.csproj | 12 ++++-------- .../ImageSharp.Textures.Benchmarks.csproj | 6 +++--- .../ImageSharp.Textures.Tests.csproj | 6 +++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj b/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj index b88ef10f..5873dfab 100644 --- a/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj +++ b/tests/ImageSharp.Textures.Astc.Reference.Tests/ImageSharp.Textures.Astc.Reference.Tests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -11,7 +11,7 @@ - + @@ -27,9 +27,7 @@ <_TestData Include="..\Images\Input\Astc\**\*.*" /> - + @@ -37,9 +35,7 @@ <_AstcEncNativeFiles Include="$(NuGetPackageRoot)astcencodercsharp\5.3.0\runtimes\**\*.*" /> - + diff --git a/tests/ImageSharp.Textures.Benchmarks/ImageSharp.Textures.Benchmarks.csproj b/tests/ImageSharp.Textures.Benchmarks/ImageSharp.Textures.Benchmarks.csproj index 810de4bd..5afc6e6b 100644 --- a/tests/ImageSharp.Textures.Benchmarks/ImageSharp.Textures.Benchmarks.csproj +++ b/tests/ImageSharp.Textures.Benchmarks/ImageSharp.Textures.Benchmarks.csproj @@ -1,4 +1,4 @@ - + Exe @@ -12,9 +12,9 @@ - + - + diff --git a/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj b/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj index 2d3473c2..0818c145 100644 --- a/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj +++ b/tests/ImageSharp.Textures.Tests/ImageSharp.Textures.Tests.csproj @@ -10,9 +10,9 @@ - - - + + +