From f6e1285d272f2e0d63531c2d4a41a1794db73df7 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:30:00 +0200 Subject: [PATCH 1/3] Fix zero level mipmaps in KTX2 --- .../Formats/Ktx2/Ktx2DecoderCore.cs | 3 +- .../Formats/Ktx2/Ktx2DecoderTests.cs | 34 +++++++++++++++++++ .../Input/Ktx2/Flat/rgba32-srgb-mips.ktx2 | 3 ++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs create mode 100644 tests/Images/Input/Ktx2/Flat/rgba32-srgb-mips.ktx2 diff --git a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs index 6decbef..4623904 100644 --- a/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs +++ b/src/ImageSharp.Textures/Formats/Ktx2/Ktx2DecoderCore.cs @@ -69,7 +69,8 @@ public Texture DecodeTexture(Stream stream) int width = (int)this.ktxHeader.PixelWidth; int height = (int)this.ktxHeader.PixelHeight; - var levelIndices = new LevelIndex[this.ktxHeader.LevelCount]; + // Zero levels means only the base level is present, so we must read at least 1. + LevelIndex[] levelIndices = new LevelIndex[Math.Max(1, this.ktxHeader.LevelCount)]; for (int i = 0; i < levelIndices.Length; i++) { stream.Read(this.buffer, 0, 24); diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs new file mode 100644 index 0000000..4500c59 --- /dev/null +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.ComponentModel; +using SixLabors.ImageSharp.Textures.Formats.Ktx2; +using SixLabors.ImageSharp.Textures.TextureFormats; + +namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx2; + +[Trait("Format", "Ktx2")] +public class Ktx2DecoderTests +{ + private static readonly Ktx2Decoder Ktx2Decoder = new(); + + [Fact] + [Description("Ensure that a single mipmap level does not result in an empty mipmap collection")] + public void Ktx2Decoder_LevelCountZero_DecodesBaseLevelMipMap() + { + string path = Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + "Ktx2", + "Flat", + "rgba32-srgb-mips.ktx2"); + + using FileStream fileStream = File.OpenRead(path); + using Texture texture = Ktx2Decoder.DecodeTexture(Configuration.Default, fileStream); + + FlatTexture flatTexture = texture as FlatTexture; + Assert.NotNull(flatTexture); + Assert.Single(flatTexture.MipMaps); + Assert.Equal(256, flatTexture.MipMaps[0].GetImage().Width); + Assert.Equal(256, flatTexture.MipMaps[0].GetImage().Height); + } +} diff --git a/tests/Images/Input/Ktx2/Flat/rgba32-srgb-mips.ktx2 b/tests/Images/Input/Ktx2/Flat/rgba32-srgb-mips.ktx2 new file mode 100644 index 0000000..d5de31e --- /dev/null +++ b/tests/Images/Input/Ktx2/Flat/rgba32-srgb-mips.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90846eff319fdfd03121b3e0300fc4106ddcc2a4df881598cca9978e04710004 +size 393468 From 633e20516faeaea2ab40f7661e8b2268f31fd5ec Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:42:27 +0200 Subject: [PATCH 2/3] Use TestTextureProvider in regression test --- .../Formats/Ktx2/Ktx2DecoderTests.cs | 18 ++++++++---------- tests/ImageSharp.Textures.Tests/TestImages.cs | 5 +++++ .../Ktx2/{Flat => }/rgba32-srgb-mips.ktx2 | 0 3 files changed, 13 insertions(+), 10 deletions(-) rename tests/Images/Input/Ktx2/{Flat => }/rgba32-srgb-mips.ktx2 (100%) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs index 4500c59..5715408 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs @@ -3,6 +3,9 @@ using System.ComponentModel; 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.Ktx2; @@ -12,18 +15,13 @@ public class Ktx2DecoderTests { private static readonly Ktx2Decoder Ktx2Decoder = new(); - [Fact] + [Theory] [Description("Ensure that a single mipmap level does not result in an empty mipmap collection")] - public void Ktx2Decoder_LevelCountZero_DecodesBaseLevelMipMap() + [WithFile(TestTextureFormat.Ktx2, TestTextureType.Flat, TestTextureTool.ToKtx, TestImages.Ktx2.Rgba32SrgbMips)] + public void Ktx2Decoder_LevelCountZero_DecodesBaseLevelMipMap(TestTextureProvider provider) { - string path = Path.Combine( - TestEnvironment.InputImagesDirectoryFullPath, - "Ktx2", - "Flat", - "rgba32-srgb-mips.ktx2"); - - using FileStream fileStream = File.OpenRead(path); - using Texture texture = Ktx2Decoder.DecodeTexture(Configuration.Default, fileStream); + using Texture texture = provider.GetTexture(Ktx2Decoder); + provider.SaveTextures(texture); FlatTexture flatTexture = texture as FlatTexture; Assert.NotNull(flatTexture); diff --git a/tests/ImageSharp.Textures.Tests/TestImages.cs b/tests/ImageSharp.Textures.Tests/TestImages.cs index d3df36c..4280d5f 100644 --- a/tests/ImageSharp.Textures.Tests/TestImages.cs +++ b/tests/ImageSharp.Textures.Tests/TestImages.cs @@ -12,5 +12,10 @@ public static class Ktx { public const string Rgba = "rgba8888.ktx"; } + + public static class Ktx2 + { + public const string Rgba32SrgbMips = "rgba32-srgb-mips.ktx2"; + } } } diff --git a/tests/Images/Input/Ktx2/Flat/rgba32-srgb-mips.ktx2 b/tests/Images/Input/Ktx2/rgba32-srgb-mips.ktx2 similarity index 100% rename from tests/Images/Input/Ktx2/Flat/rgba32-srgb-mips.ktx2 rename to tests/Images/Input/Ktx2/rgba32-srgb-mips.ktx2 From 05e5e20c0fcbffd248cf5e48c62c60f58e75d231 Mon Sep 17 00:00:00 2001 From: Erik White <26148654+Erik-White@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:50:58 +0200 Subject: [PATCH 3/3] Rename test file --- .../Ktx2/{Ktx2DecoderTests.cs => Ktx2DecoderFlatTests.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/ImageSharp.Textures.Tests/Formats/Ktx2/{Ktx2DecoderTests.cs => Ktx2DecoderFlatTests.cs} (97%) diff --git a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderFlatTests.cs similarity index 97% rename from tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs rename to tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderFlatTests.cs index 5715408..05952bb 100644 --- a/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderTests.cs +++ b/tests/ImageSharp.Textures.Tests/Formats/Ktx2/Ktx2DecoderFlatTests.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Textures.Tests.Formats.Ktx2; [Trait("Format", "Ktx2")] -public class Ktx2DecoderTests +public class Ktx2DecoderFlatTests { private static readonly Ktx2Decoder Ktx2Decoder = new();