From 0aebe5d80f876842618d92c96d145e531140d3dd Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Fri, 7 Mar 2025 16:17:41 +0200 Subject: [PATCH 01/30] Update dependencies --- BCnEnc.Net/BCnEncoder.csproj | 16 +++++++--------- BCnEnc.Net/Decoder/BcDecoder.cs | 4 ++-- BCnEnc.Net/Encoder/BcEncoder.cs | 4 ++-- BCnEnc.Net/Shared/Colors.cs | 2 +- BCnEnc.Net/Shared/HdrImage.cs | 2 +- BCnEnc.Net/Shared/ImageToBlocks.cs | 2 +- BCnEnc.Net/Shared/MipMapper.cs | 2 +- BCnEncTests/BCnEncTests.csproj | 18 ++++++++++++------ BCnEncTests/Bc6EncoderTests.cs | 2 +- BCnEncTests/Bc6HDecoderTests.cs | 5 +---- BCnEncTests/BlockTests.cs | 6 ++---- BCnEncTests/Examples.cs | 2 +- BCnEncTests/SingleBlockTests.cs | 2 +- BCnEncTests/Support/TestHelper.cs | 2 +- .../BCnDecoderExtensions.cs | 6 +----- .../BCnEncoder.NET.ImageSharp.csproj | 12 ++++++------ .../BCnEncoderExtensions.cs | 4 +--- 17 files changed, 42 insertions(+), 49 deletions(-) diff --git a/BCnEnc.Net/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj index d7cd509..46f0811 100644 --- a/BCnEnc.Net/BCnEncoder.csproj +++ b/BCnEnc.Net/BCnEncoder.csproj @@ -1,8 +1,7 @@ - + - netstandard2.1 - + net6.0 true true snupkg @@ -12,7 +11,7 @@ MIT OR Unlicense false - 2.1.0 + 2.2.0 Nominom BCnEncoder.Net @@ -32,9 +31,8 @@ Supported formats are: git BCn BC BC1 BC2 BC3 BC4 BC5 BC6 BC6H BC7 BPTC RGTC S3TC DXT1 DXT3 DXT5 ktx dds texture compression encoding decoding decompression image gpu https://github.com/Nominom/BCnEncoder.NET - 2.0.0 - Removed ImageSharp dependency, added async api methods, added ATC and BGRA support, added new raw api methods, improved dds file handling and fixed some bugs. See the github page for more information about 2.0. - -2.1.0 - BC6H Support + 2.2.0 - Updated dependencies. Updated to .NET 6.0 + @@ -47,8 +45,8 @@ Supported formats are: - - + + diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index 6c37ad9..a72c1a5 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -1,4 +1,4 @@ -using BCnEncoder.Decoder.Options; +using BCnEncoder.Decoder.Options; using BCnEncoder.Shared; using System; using System.IO; @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Decoder { diff --git a/BCnEnc.Net/Encoder/BcEncoder.cs b/BCnEnc.Net/Encoder/BcEncoder.cs index 5e17c35..d7b3f9b 100644 --- a/BCnEnc.Net/Encoder/BcEncoder.cs +++ b/BCnEnc.Net/Encoder/BcEncoder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Linq; @@ -8,7 +8,7 @@ using BCnEncoder.Encoder.Options; using BCnEncoder.Shared; using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Encoder { diff --git a/BCnEnc.Net/Shared/Colors.cs b/BCnEnc.Net/Shared/Colors.cs index 565301d..d6f7ece 100644 --- a/BCnEnc.Net/Shared/Colors.cs +++ b/BCnEnc.Net/Shared/Colors.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Numerics; namespace BCnEncoder.Shared diff --git a/BCnEnc.Net/Shared/HdrImage.cs b/BCnEnc.Net/Shared/HdrImage.cs index cb899e2..711e0a5 100644 --- a/BCnEnc.Net/Shared/HdrImage.cs +++ b/BCnEnc.Net/Shared/HdrImage.cs @@ -3,7 +3,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Text; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Shared { diff --git a/BCnEnc.Net/Shared/ImageToBlocks.cs b/BCnEnc.Net/Shared/ImageToBlocks.cs index f3ba021..7af2e7d 100644 --- a/BCnEnc.Net/Shared/ImageToBlocks.cs +++ b/BCnEnc.Net/Shared/ImageToBlocks.cs @@ -1,4 +1,4 @@ -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Shared { diff --git a/BCnEnc.Net/Shared/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs index 765bc48..4c82202 100644 --- a/BCnEnc.Net/Shared/MipMapper.cs +++ b/BCnEnc.Net/Shared/MipMapper.cs @@ -1,5 +1,5 @@ using System; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; namespace BCnEncoder.Shared { diff --git a/BCnEncTests/BCnEncTests.csproj b/BCnEncTests/BCnEncTests.csproj index 22b08b5..7dab56d 100644 --- a/BCnEncTests/BCnEncTests.csproj +++ b/BCnEncTests/BCnEncTests.csproj @@ -1,16 +1,22 @@ - + - netcoreapp3.1 + net9.0 false - - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/BCnEncTests/Bc6EncoderTests.cs b/BCnEncTests/Bc6EncoderTests.cs index ae389b2..bd532cb 100644 --- a/BCnEncTests/Bc6EncoderTests.cs +++ b/BCnEncTests/Bc6EncoderTests.cs @@ -7,7 +7,7 @@ using BCnEncoder.Encoder.Bptc; using BCnEncoder.Shared; using BCnEncTests.Support; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; using Xunit; using Xunit.Abstractions; diff --git a/BCnEncTests/Bc6HDecoderTests.cs b/BCnEncTests/Bc6HDecoderTests.cs index b30ca72..6c05289 100644 --- a/BCnEncTests/Bc6HDecoderTests.cs +++ b/BCnEncTests/Bc6HDecoderTests.cs @@ -1,14 +1,11 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; using BCnEncoder.Decoder; using BCnEncoder.Encoder; using BCnEncoder.Shared; using BCnEncTests.Support; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Xunit; diff --git a/BCnEncTests/BlockTests.cs b/BCnEncTests/BlockTests.cs index b413563..fa1eaa8 100644 --- a/BCnEncTests/BlockTests.cs +++ b/BCnEncTests/BlockTests.cs @@ -1,8 +1,6 @@ -using System; +using System; using BCnEncoder.Shared; -using Microsoft.Toolkit.HighPerformance; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; +using CommunityToolkit.HighPerformance; using Xunit; namespace BCnEncTests diff --git a/BCnEncTests/Examples.cs b/BCnEncTests/Examples.cs index b3103bd..f4784e5 100644 --- a/BCnEncTests/Examples.cs +++ b/BCnEncTests/Examples.cs @@ -6,7 +6,7 @@ using BCnEncoder.Encoder; using BCnEncoder.ImageSharp; using BCnEncoder.Shared; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Xunit; diff --git a/BCnEncTests/SingleBlockTests.cs b/BCnEncTests/SingleBlockTests.cs index 29eab5f..f3bd9a7 100644 --- a/BCnEncTests/SingleBlockTests.cs +++ b/BCnEncTests/SingleBlockTests.cs @@ -7,7 +7,7 @@ using BCnEncoder.Encoder; using BCnEncoder.Shared; using BCnEncTests.Support; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using Xunit; diff --git a/BCnEncTests/Support/TestHelper.cs b/BCnEncTests/Support/TestHelper.cs index 5c92096..38940c3 100644 --- a/BCnEncTests/Support/TestHelper.cs +++ b/BCnEncTests/Support/TestHelper.cs @@ -8,7 +8,7 @@ using BCnEncoder.ImageSharp; using BCnEncoder.Shared; using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Xunit; diff --git a/BCnEncoder.NET.ImageSharp/BCnDecoderExtensions.cs b/BCnEncoder.NET.ImageSharp/BCnDecoderExtensions.cs index 7834220..ea88bed 100644 --- a/BCnEncoder.NET.ImageSharp/BCnDecoderExtensions.cs +++ b/BCnEncoder.NET.ImageSharp/BCnDecoderExtensions.cs @@ -1,16 +1,12 @@ -using System; -using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; using BCnEncoder.Decoder; using BCnEncoder.Shared; using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.ImageSharp diff --git a/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj b/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj index 59e9a51..4d7c077 100644 --- a/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj +++ b/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net6.0 true true @@ -17,17 +17,17 @@ https://github.com/Nominom/BCnEncoder.NET https://github.com/Nominom/BCnEncoder.NET git - Fix issues with ImageSharp 2.0.0 - 1.1.1 + Update to ImageSharp 3.1.7 and CommunityToolkit.HighPerformance 8.4.0 + 1.1.2 - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + diff --git a/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs b/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs index 39d215e..31062c8 100644 --- a/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs +++ b/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using BCnEncoder.Encoder; using BCnEncoder.Shared; using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; From 24d60b82838512f123cdf8ee0b0752e4d5385fec Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Fri, 7 Mar 2025 16:19:20 +0200 Subject: [PATCH 02/30] Formatting --- BCnEnc.Net/Decoder/BcDecoder.cs | 2 +- BCnEnc.Net/Encoder/BcEncoder.cs | 6 +- BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs | 2 +- BCnEnc.Net/Shared/Bc6Block.cs | 84 +++++++++---------- BCnEnc.Net/Shared/Colors.cs | 2 +- BCnEnc.Net/Shared/HdrImage.cs | 14 ++-- BCnEnc.Net/Shared/ImageToBlocks.cs | 2 +- BCnEnc.Net/Shared/MipMapper.cs | 8 +- BCnEncTests/Bc6EncoderTests.cs | 14 ++-- BCnEncTests/Bc6HDecoderTests.cs | 14 ++-- BCnEncTests/BlockTests.cs | 4 +- BCnEncTests/Examples.cs | 8 +- BCnEncTests/SingleBlockTests.cs | 8 +- BCnEncTests/Support/TestHelper.cs | 4 +- .../BCnEncoderExtensions.cs | 2 +- 15 files changed, 87 insertions(+), 87 deletions(-) diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index a72c1a5..0d1c10d 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -1773,7 +1773,7 @@ public int GetBlockSize(CompressionFormat format) case CompressionFormat.Unknown: return 0; - + default: throw new ArgumentOutOfRangeException(nameof(format), format, null); } diff --git a/BCnEnc.Net/Encoder/BcEncoder.cs b/BCnEnc.Net/Encoder/BcEncoder.cs index d7b3f9b..bcd6ba2 100644 --- a/BCnEnc.Net/Encoder/BcEncoder.cs +++ b/BCnEnc.Net/Encoder/BcEncoder.cs @@ -610,7 +610,7 @@ public int GetBlockSize() if (compressedEncoder == null) { var hdrEncoder = GetFloatBlockEncoder(OutputOptions.Format); - + if (hdrEncoder == null) { throw new NotSupportedException($"This format is either not supported or does not use block compression: {OutputOptions.Format}"); @@ -1319,7 +1319,7 @@ private DdsFile EncodeCubeMapToDdsInternalHdr(ReadOnlyMemory2D ri { DdsFile output; IBcBlockEncoder compressedEncoder = null; - + var faces = new[] { right, left, top, down, back, front }; var width = right.Width; @@ -1373,7 +1373,7 @@ private DdsFile EncodeCubeMapToDdsInternalHdr(ReadOnlyMemory2D ri for (var mip = 0; mip < numMipMaps; mip++) { byte[] encoded; - + var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, out var blocksHeight); encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); diff --git a/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs index a972ca2..6919f15 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs @@ -321,7 +321,7 @@ public static void SwapIndicesIfNecessary2Sub(RawBlock4X4RgbFloat block, ref (in InternalUtils.Swap(ref unQuantizedEp0, ref unQuantizedEp1); FindOptimalIndicesInt2Sub(block, unQuantizedEp0, unQuantizedEp1, indices, partitionSetId, subsetIndex, signed); } - + public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4RgbFloat block, out ColorRgbFloat ep0, out ColorRgbFloat ep1, int partitionSetId, int subsetIndex) { diff --git a/BCnEnc.Net/Shared/Bc6Block.cs b/BCnEnc.Net/Shared/Bc6Block.cs index ab99421..ad47b8f 100644 --- a/BCnEnc.Net/Shared/Bc6Block.cs +++ b/BCnEnc.Net/Shared/Bc6Block.cs @@ -209,36 +209,36 @@ internal void StoreEp0((int, int, int) endpoint) var r0 = (ulong)endpoint.Item1; var g0 = (ulong)endpoint.Item2; var b0 = (ulong)endpoint.Item3; - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 5, Math.Min(10, EndpointBits), r0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 15, Math.Min(10, EndpointBits), g0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 25, Math.Min(10, EndpointBits), b0); - + switch (Type) { case Bc6BlockType.Type2: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, r0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 49, 1, g0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 59, 1, b0 >> 10); - + break; case Bc6BlockType.Type6: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 39, 1, r0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, g0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 59, 1, b0 >> 10); - + break; case Bc6BlockType.Type10: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 39, 1, r0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 49, 1, g0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b0 >> 10); - + break; case Bc6BlockType.Type7: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 44, 1, r0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 54, 1, g0 >> 10); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 64, 1, b0 >> 10); - + break; case Bc6BlockType.Type11: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 44, 1, r0 >> 10); @@ -248,7 +248,7 @@ internal void StoreEp0((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 43, 1, r0 >> 11); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 53, 1, g0 >> 11); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 63, 1, b0 >> 11); - + break; case Bc6BlockType.Type15: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 44, 1, r0 >> 10); @@ -266,15 +266,15 @@ internal void StoreEp0((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 41, 1, r0 >> 13); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 51, 1, g0 >> 13); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 61, 1, b0 >> 13); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, r0 >> 14); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, g0 >> 14); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b0 >> 14); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 39, 1, r0 >> 15); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 49, 1, g0 >> 15); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 59, 1, b0 >> 15); - + break; } } @@ -376,7 +376,7 @@ internal void StoreEp1((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 35, Math.Min(5, DeltaBits.Item1), r1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 45, Math.Min(5, DeltaBits.Item2), g1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 55, Math.Min(5, DeltaBits.Item3), b1); - + } switch (Type) @@ -385,12 +385,12 @@ internal void StoreEp1((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, r1 >> 5); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, g1 >> 5); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b1 >> 5); - + break; case Bc6BlockType.Type18: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, r1 >> 5); - + break; case Bc6BlockType.Type22: @@ -505,17 +505,17 @@ internal void StoreEp2((int, int, int) endpoint) case Bc6BlockType.Type0: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 2, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 3, 1, b2 >> 4); - + break; case Bc6BlockType.Type1: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, r2 >> 5); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 2, 1, g2 >> 5); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 22, 1, b2 >> 5); - + break; case Bc6BlockType.Type6: @@ -530,14 +530,14 @@ internal void StoreEp2((int, int, int) endpoint) case Bc6BlockType.Type14: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); - + break; case Bc6BlockType.Type18: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, r2 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); - + break; case Bc6BlockType.Type22: @@ -545,17 +545,17 @@ internal void StoreEp2((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 23, 1, g2 >> 5); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); - + break; case Bc6BlockType.Type26: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 23, 1, b2 >> 5); - + break; case Bc6BlockType.Type30: - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 65, 6, r2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 24, 1, g2 >> 4); @@ -563,7 +563,7 @@ internal void StoreEp2((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 14, 1, b2 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 22, 1, b2 >> 5); - + break; } } @@ -581,7 +581,7 @@ internal readonly (int, int, int) ExtractEp2() switch (Type) { case Bc6BlockType.Type0: - + g2 |= ByteHelper.ExtractFrom128(lowBits, highBits, 2, 1) << 4; b2 |= ByteHelper.ExtractFrom128(lowBits, highBits, 3, 1) << 4; break; @@ -658,7 +658,7 @@ internal void StoreEp3((int, int, int) endpoint) switch (Type) { case Bc6BlockType.Type0: - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, g3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); @@ -666,7 +666,7 @@ internal void StoreEp3((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 4, 1, b3 >> 4); - + break; case Bc6BlockType.Type1: @@ -682,7 +682,7 @@ internal void StoreEp3((int, int, int) endpoint) break; case Bc6BlockType.Type2: - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); @@ -706,69 +706,69 @@ internal void StoreEp3((int, int, int) endpoint) (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 75, 1, b3 >> 4); - + break; case Bc6BlockType.Type14: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, g3 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); - + break; case Bc6BlockType.Type18: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, r3 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 13, 1, g3 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 23, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 33, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); - + break; case Bc6BlockType.Type22: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, g3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 33, 1, g3 >> 5); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 13, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 60, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); - + break; case Bc6BlockType.Type26: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 40, 1, g3 >> 4); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 50, 1, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 13, 1, b3 >> 1); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 70, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 76, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 33, 1, b3 >> 5); - + break; case Bc6BlockType.Type30: (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 71, 6, r3); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 11, 1, g3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 31, 1, g3 >> 5); - + (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 12, 2, b3 >> 0); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 23, 1, b3 >> 2); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 32, 1, b3 >> 3); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 34, 1, b3 >> 4); (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, 33, 1, b3 >> 5); - + break; } } @@ -1156,7 +1156,7 @@ public readonly RawBlock4X4RgbFloat Decode(bool signed) numSubsets = 2; partitionIndex = PartitionSetId; } - + for (var i = 0; i < NumEndpoints; i++) { endpoints[i] = UnQuantize(endpoints[i], EndpointBits, signed); @@ -1203,7 +1203,7 @@ private void StorePartitionSetId(int partitionSetId) { highBits = ByteHelper.Store5(highBits, 13, (byte) partitionSetId); } - + public static Bc6Block PackType0((int, int, int) endpoint0, (int, int, int) endpoint1, (int, int, int) endpoint2, (int, int, int) endpoint3, int partitionSetId, Span indices) { diff --git a/BCnEnc.Net/Shared/Colors.cs b/BCnEnc.Net/Shared/Colors.cs index d6f7ece..b44645b 100644 --- a/BCnEnc.Net/Shared/Colors.cs +++ b/BCnEnc.Net/Shared/Colors.cs @@ -311,7 +311,7 @@ public ColorRgba32 ToRgba32() } } - + public struct ColorRgbFloat : IEquatable { public float r, g, b; diff --git a/BCnEnc.Net/Shared/HdrImage.cs b/BCnEnc.Net/Shared/HdrImage.cs index 711e0a5..0dd33ec 100644 --- a/BCnEnc.Net/Shared/HdrImage.cs +++ b/BCnEnc.Net/Shared/HdrImage.cs @@ -75,7 +75,7 @@ private static string ReadFromStream(Stream stream) } while (c != (char)10); return new string(buffer.AsSpan().Slice(0, i)).Trim(); } - + private static void WriteLineToStream(BinaryWriter br, string s) { foreach (var c in s) @@ -115,7 +115,7 @@ public static HdrImage Read(Stream stream) } var colorSpace = ColorSpace.Rgbe; - + do { line = ReadFromStream(stream); @@ -252,7 +252,7 @@ private static void ReadPixels(HdrImage destImage, Stream stream) var isRle = header[0] == 2 && header[1] == 2 && (header[2] << 8) + header[3] == width; // whether the scanline is rle or not - + if (isRle) { // for each channel @@ -312,10 +312,10 @@ public void Write(Stream stream) { WriteLineToStream(br, "GAMMA=" + gamma.ToString(CultureInfo.InvariantCulture)); } - + WriteLineToStream(br, ""); // Start data with empty row WriteLineToStream(br, $"-Y {height} +X {width}"); - + WritePixels(br); } @@ -323,14 +323,14 @@ private void WritePixels(BinaryWriter br) { var buffer = new byte[4]; var span = PixelSpan; - + for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var pixel = span[y, x]; var rgbe = new ColorRgbe(pixel); - + buffer[0] = rgbe.r; buffer[1] = rgbe.g; buffer[2] = rgbe.b; diff --git a/BCnEnc.Net/Shared/ImageToBlocks.cs b/BCnEnc.Net/Shared/ImageToBlocks.cs index 7af2e7d..49acb21 100644 --- a/BCnEnc.Net/Shared/ImageToBlocks.cs +++ b/BCnEnc.Net/Shared/ImageToBlocks.cs @@ -80,7 +80,7 @@ internal static RawBlock4X4Rgba32[] ImageTo4X4(ReadOnlyMemory2D ima { blocksWidth = ((image.Width + 3) & ~3) >> 2; blocksHeight = ((image.Height + 3) & ~3) >> 2; - + var output = new RawBlock4X4Rgba32[blocksWidth * blocksHeight]; var span = image.Span; diff --git a/BCnEnc.Net/Shared/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs index 4c82202..e44dbf0 100644 --- a/BCnEnc.Net/Shared/MipMapper.cs +++ b/BCnEnc.Net/Shared/MipMapper.cs @@ -50,7 +50,7 @@ public static ReadOnlyMemory2D[] GenerateMipChain(ReadOnlyMemory2D< { var mipWidth = Math.Max(1, width >> mipLevel); var mipHeight = Math.Max(1, height >> mipLevel); - + var newMip = ResizeToHalf(result[mipLevel - 1].Span); result[mipLevel] = newMip; @@ -171,12 +171,12 @@ public static void CalculateMipLevelSize(int width, int height, int mipIdx, out var oldHeight = pixelsRgba.Height; var newWidth = Math.Max(1, oldWidth >> 1); var newHeight = Math.Max(1, oldHeight >> 1); - + var result = new ColorRgba32[newHeight, newWidth]; - + int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x)); int ClampH(int y) => Math.Max(0, Math.Min(oldHeight - 1, y)); - + for (var y2 = 0; y2 < newHeight; y2++) { for (var x2 = 0; x2 < newWidth; x2++) diff --git a/BCnEncTests/Bc6EncoderTests.cs b/BCnEncTests/Bc6EncoderTests.cs index bd532cb..7352f86 100644 --- a/BCnEncTests/Bc6EncoderTests.cs +++ b/BCnEncTests/Bc6EncoderTests.cs @@ -16,9 +16,9 @@ namespace BCnEncTests public class Bc6EncoderTests { private ITestOutputHelper output; - + public Bc6EncoderTests(ITestOutputHelper output) => this.output = output; - + [Theory] [InlineData(0x7F, 8, false)] [InlineData(0xFF, 8, false)] @@ -102,7 +102,7 @@ public void PackMode3() const int endpointPrecision = 10; var random = new Random(); var rand = random.Next(); - + var ep0 = (random.Next() & (1 << endpointPrecision) - 1, random.Next() & (1 << endpointPrecision) - 1, random.Next() & (1 << endpointPrecision) - 1); @@ -117,7 +117,7 @@ public void PackMode3() } var block = Bc6Block.PackType3(ep0, ep1, indices); - + Assert.Equal(Bc6BlockType.Type3, block.Type); var extracted0 = block.ExtractEp0(); @@ -133,7 +133,7 @@ public void PackMode3() } } - + [Theory] [InlineData(Bc6BlockType.Type0)] [InlineData(Bc6BlockType.Type1)] @@ -172,7 +172,7 @@ internal void EncodeAllModesUnsigned(Bc6BlockType type) }; Bc6Block encoded; var badTransform = false; - + if(type.HasSubsets()) { var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2); @@ -392,7 +392,7 @@ public void EncodeToRaw() Assert.True(allMips.Length > 1); Assert.True(allMips.Length == ktx.MipMaps.Count); - + for (var i = 0; i < allMips.Length; i++) { var single = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory, i, out var mW, out var mH); diff --git a/BCnEncTests/Bc6HDecoderTests.cs b/BCnEncTests/Bc6HDecoderTests.cs index 6c05289..e472b2f 100644 --- a/BCnEncTests/Bc6HDecoderTests.cs +++ b/BCnEncTests/Bc6HDecoderTests.cs @@ -16,7 +16,7 @@ namespace BCnEncTests public class Bc6HDecoderTests { private ITestOutputHelper output; - + public Bc6HDecoderTests(ITestOutputHelper output) => this.output = output; [Fact] @@ -27,7 +27,7 @@ public void DecodeDds() var img = new Image((int)HdrLoader.TestHdrKiaraDds.header.dwWidth, (int)HdrLoader.TestHdrKiaraDds.header.dwHeight); - + var pixels = TestHelper.GetSinglePixelArray(img); for (var i = 0; i < decoded.Length; i++) @@ -39,13 +39,13 @@ public void DecodeDds() var hdr = new HdrImage((int) HdrLoader.TestHdrKiaraDds.header.dwWidth, (int) HdrLoader.TestHdrKiaraDds.header.dwHeight); - + Assert.Equal(hdr.pixels.Length, decoded.Length); - + hdr.pixels = decoded; using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr"); hdr.Write(sfs); - + img.SaveAsPng("decoding_test_dds_bc6h.png"); TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output); @@ -59,7 +59,7 @@ public void DecodeKtx() var img = new Image((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth, (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight); - + var pixels = TestHelper.GetSinglePixelArray(img); for (var i = 0; i < decoded.Length; i++) @@ -123,7 +123,7 @@ public void DecodeModes() var bytes = hdr.Faces[0].MipMaps[0].Data; var blocks = MemoryMarshal.Cast(bytes); - + for (var i = 0; i < blocks.Length; i++) { var block = blocks[i]; diff --git a/BCnEncTests/BlockTests.cs b/BCnEncTests/BlockTests.cs index fa1eaa8..98ffc59 100644 --- a/BCnEncTests/BlockTests.cs +++ b/BCnEncTests/BlockTests.cs @@ -37,7 +37,7 @@ public void PaddingColor() var testImage = new ColorRgba32[15, 11]; var pixels = testImage.AsSpan(); - + for (var i = 0; i < pixels.Length; i++) { pixels[i] = new ColorRgba32(0, 125, 125); } @@ -64,7 +64,7 @@ public void BlocksToImage() var testImage = new ColorRgba32[16, 16]; var pixels = testImage.AsSpan(); - + for (var i = 0; i < pixels.Length; i++) { pixels[i] = new ColorRgba32( (byte)r.Next(255), diff --git a/BCnEncTests/Examples.cs b/BCnEncTests/Examples.cs index f4784e5..210d8a9 100644 --- a/BCnEncTests/Examples.cs +++ b/BCnEncTests/Examples.cs @@ -15,7 +15,7 @@ namespace BCnEncTests { public class Examples { - + public void EncodeImageSharp() { using Image image = Image.Load("example.png"); @@ -30,14 +30,14 @@ public void EncodeImageSharp() using FileStream fs = File.OpenWrite("example.ktx"); encoder.EncodeToStream(image, fs); } - + public void DecodeImageSharp() { using FileStream fs = File.OpenRead("compressed_bc1.ktx"); BcDecoder decoder = new BcDecoder(); using Image image = decoder.DecodeToImageRgba32(fs); - + using FileStream outFs = File.OpenWrite("decoding_test_bc1.png"); image.SaveAsPng(outFs); } @@ -45,7 +45,7 @@ public void DecodeImageSharp() public void EncodeHdr() { HdrImage image = HdrImage.Read("example.hdr"); - + BcEncoder encoder = new BcEncoder(); diff --git a/BCnEncTests/SingleBlockTests.cs b/BCnEncTests/SingleBlockTests.cs index f3bd9a7..89b0778 100644 --- a/BCnEncTests/SingleBlockTests.cs +++ b/BCnEncTests/SingleBlockTests.cs @@ -40,8 +40,8 @@ public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQ var colors = MemoryMarshal.Cast(pixels) .AsSpan2D(testImage.Height, testImage.Width); - var ms = new MemoryStream(); - + var ms = new MemoryStream(); + for (var y = 0; y < testImage.Height; y+=4) { for (var x = 0; x < testImage.Width; x+=4) @@ -110,7 +110,7 @@ public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality blockIndex++; } } - + var decoder = new BcDecoder(); var decoded = new ColorRgba32[testImage.Height, testImage.Width]; @@ -120,7 +120,7 @@ public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality { for (var x = 0; x < testImage.Width; x += 4) { - + decoder.DecodeBlock( buffer.Slice( blockIndex * decoder.GetBlockSize(format), diff --git a/BCnEncTests/Support/TestHelper.cs b/BCnEncTests/Support/TestHelper.cs index 38940c3..40bc50c 100644 --- a/BCnEncTests/Support/TestHelper.cs +++ b/BCnEncTests/Support/TestHelper.cs @@ -151,7 +151,7 @@ public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original) var decoder = new BcDecoder() { }; - + var decoded = decoder.DecodeHdr(ktx); return ImageQuality.CalculateLogRMSE(original.pixels, decoded); @@ -194,7 +194,7 @@ private static float CalculatePSNR(Image original, Image decoded { var pixels = GetSinglePixelArrayAsColors(original); var pixels2 = GetSinglePixelArrayAsColors(decoded); - + return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, countAlpha); } diff --git a/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs b/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs index 31062c8..fc8a0f9 100644 --- a/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs +++ b/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs @@ -300,7 +300,7 @@ public static void CalculateMipMapSize(this BcEncoder encoder, Image inp mipLevel, out mipWidth, out mipHeight); } - + private static Memory2D ImageToMemory2D(Image inputImage) { var pixels = inputImage.GetPixelMemoryGroup()[0]; From 479de0ba2ffd8d7fb0aedad3fb975ea78ec37a53 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Fri, 7 Mar 2025 16:54:53 +0200 Subject: [PATCH 03/30] Don't update to net6.0 afterall --- BCnEnc.Net/BCnEncoder.csproj | 5 ++--- BCnEncTests/Bc6HDecoderTests.cs | 1 + README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BCnEnc.Net/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj index 46f0811..f6c12ef 100644 --- a/BCnEnc.Net/BCnEncoder.csproj +++ b/BCnEnc.Net/BCnEncoder.csproj @@ -1,7 +1,7 @@  - net6.0 + netstandard2.1 true true snupkg @@ -31,8 +31,7 @@ Supported formats are: git BCn BC BC1 BC2 BC3 BC4 BC5 BC6 BC6H BC7 BPTC RGTC S3TC DXT1 DXT3 DXT5 ktx dds texture compression encoding decoding decompression image gpu https://github.com/Nominom/BCnEncoder.NET - 2.2.0 - Updated dependencies. Updated to .NET 6.0 - + 2.2.0 - Updated dependencies. diff --git a/BCnEncTests/Bc6HDecoderTests.cs b/BCnEncTests/Bc6HDecoderTests.cs index e472b2f..60a99fa 100644 --- a/BCnEncTests/Bc6HDecoderTests.cs +++ b/BCnEncTests/Bc6HDecoderTests.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Abstractions; +using Half = BCnEncoder.Shared.Half; namespace BCnEncTests { diff --git a/README.md b/README.md index e3f3583..8ca5074 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ different algorithms. The current version is capable of encoding and decoding BC Please note, that the API might change between versions. # Dependencies Current dependencies are: -* [Microsoft.Toolkit.HighPerformance](https://github.com/windows-toolkit/WindowsCommunityToolkit) licensed under the [MIT](https://opensource.org/licenses/MIT) license for Span2D and Memory2D types. +* [CommunityToolkit.HighPerformance](https://www.nuget.org/packages/CommunityToolkit.HighPerformance/) licensed under the [MIT](https://opensource.org/licenses/MIT) license for Span2D and Memory2D types. # Image library extensions This library has extension packages available for the following image libraries: From b125c5521080dd20650faf1cb867c399db9d79c8 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:03:32 +0200 Subject: [PATCH 04/30] Update workflow --- .github/workflows/dotnetcore.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 7308a9d..a3d6c23 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -11,15 +11,15 @@ jobs: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.101 + steps: + - uses: actions/checkout@v4 + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' - name: Install dependencies - run: dotnet restore + run: dotnet restore - name: Build - run: dotnet build --configuration Release --no-restore - - name: Test - run: dotnet test --no-restore --verbosity normal + run: dotnet build --no-restore --configuration Release + - name: Test with the dotnet CLI + run: dotnet test --no-build From aeff731a3bbd233fc3403d51ddd2515a365977e7 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:08:28 +0200 Subject: [PATCH 05/30] Update workflow again --- .github/workflows/dotnetcore.yml | 37 ++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index a3d6c23..9195bfb 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -1,25 +1,34 @@ -name: Tests +name: build and test on: push: - branches: [ master ] pull_request: - branches: [ master ] + branches: [ main ] + paths: + - '**.cs' + - '**.csproj' + +env: + DOTNET_VERSION: '9.0.x' # The .NET SDK version to use jobs: - build: + build-and-test: + name: build-and-test runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup dotnet - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '9.0.x' + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Install dependencies - run: dotnet restore + run: dotnet restore + - name: Build - run: dotnet build --no-restore --configuration Release - - name: Test with the dotnet CLI - run: dotnet test --no-build + run: dotnet build --configuration Release --no-restore + + - name: Test + run: dotnet test --no-restore --verbosity normal \ No newline at end of file From 924e267a9fa70bd4fc08e4a9b9c8bba96e95933c Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:28:59 +0200 Subject: [PATCH 06/30] Fix warnings --- BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs | 2 +- BCnEncTests/CancellationTests.cs | 5 +-- BCnEncTests/DecodingAsyncTests.cs | 11 ++++--- BCnEncTests/EncodingAsyncTest.cs | 11 ++++--- BCnEncTests/ProgressTests.cs | 32 +++++++++---------- .../BCnEncoderExtensions.cs | 2 +- 6 files changed, 33 insertions(+), 30 deletions(-) diff --git a/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs index 6919f15..fc321e1 100644 --- a/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs +++ b/BCnEnc.Net/Encoder/Bptc/Bc6EncodingHelpers.cs @@ -39,7 +39,7 @@ internal static int PreQuantize(float value, bool signed) } /// - /// Opposite of + /// Opposite of /// internal static int Quantize(int component, int endpointBits, bool signed) { diff --git a/BCnEncTests/CancellationTests.cs b/BCnEncTests/CancellationTests.cs index 07689a7..ca85a9a 100644 --- a/BCnEncTests/CancellationTests.cs +++ b/BCnEncTests/CancellationTests.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using BCnEncoder.Decoder; using BCnEncTests.Support; using Xunit; @@ -9,13 +10,13 @@ namespace BCnEncTests public class CancellationTests { [Fact] - public async void EncodeParallelCancellation() + public async Task EncodeParallelCancellation() { await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, true); } [Fact] - public async void EncodeNonParallelCancellation() + public async Task EncodeNonParallelCancellation() { await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, false); } diff --git a/BCnEncTests/DecodingAsyncTests.cs b/BCnEncTests/DecodingAsyncTests.cs index d4993bc..04cfa29 100644 --- a/BCnEncTests/DecodingAsyncTests.cs +++ b/BCnEncTests/DecodingAsyncTests.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.IO; +using System.Threading.Tasks; using BCnEncoder.Decoder; using BCnEncoder.Encoder; using BCnEncoder.ImageSharp; @@ -12,7 +13,7 @@ namespace BCnEncTests public class DecodingAsyncTests { [Fact] - public async void DecodeAsync() + public async Task DecodeAsync() { var decoder = new BcDecoder(); var encoder = new BcEncoder(); @@ -26,7 +27,7 @@ public async void DecodeAsync() } [Fact] - public async void DecodeAllMipMapsAsync() + public async Task DecodeAllMipMapsAsync() { var decoder = new BcDecoder(); var encoder = new BcEncoder(); @@ -41,16 +42,16 @@ public async void DecodeAllMipMapsAsync() } [Fact] - public async void DecodeRawAsync() + public async Task DecodeRawAsync() { var decoder = new BcDecoder(); var encoder = new BcEncoder(); var original = ImageLoader.TestGradient1; var file = encoder.EncodeToKtx(original); - + var ms = new MemoryStream(file.MipMaps[0].Faces[0].Data); - + var image = await decoder.DecodeRawToImageRgba32Async(ms, (int) file.MipMaps[0].Width, (int) file.MipMaps[0].Height, CompressionFormat.Bc1); diff --git a/BCnEncTests/EncodingAsyncTest.cs b/BCnEncTests/EncodingAsyncTest.cs index 9542e46..6d3e9ff 100644 --- a/BCnEncTests/EncodingAsyncTest.cs +++ b/BCnEncTests/EncodingAsyncTest.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using BCnEncoder.Decoder; using BCnEncoder.Encoder; using BCnEncoder.ImageSharp; @@ -25,7 +26,7 @@ public EncodingAsyncTest() } [Fact] - public async void EncodeToDdsAsync() + public async Task EncodeToDdsAsync() { var file = await encoder.EncodeToDdsAsync(originalImage); var image = decoder.DecodeToImageRgba32(file); @@ -35,7 +36,7 @@ public async void EncodeToDdsAsync() } [Fact] - public async void EncodeToKtxAsync() + public async Task EncodeToKtxAsync() { var file = await encoder.EncodeToKtxAsync(originalImage); var image = decoder.DecodeToImageRgba32(file); @@ -45,7 +46,7 @@ public async void EncodeToKtxAsync() } [Fact] - public async void EncodeCubemapToDdsAsync() + public async Task EncodeCubemapToDdsAsync() { var file = await encoder.EncodeCubeMapToDdsAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2], originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]); @@ -60,7 +61,7 @@ public async void EncodeCubemapToDdsAsync() } [Fact] - public async void EncodeCubemapToKtxAsync() + public async Task EncodeCubemapToKtxAsync() { var file = await encoder.EncodeCubeMapToKtxAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2], originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]); @@ -75,7 +76,7 @@ public async void EncodeCubemapToKtxAsync() } [Fact] - public async void EncodeToRawBytesAsync() + public async Task EncodeToRawBytesAsync() { var data = await encoder.EncodeToRawBytesAsync(originalImage); var image = decoder.DecodeRawToImageRgba32(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1); diff --git a/BCnEncTests/ProgressTests.cs b/BCnEncTests/ProgressTests.cs index 5eae2e9..1a58c31 100644 --- a/BCnEncTests/ProgressTests.cs +++ b/BCnEncTests/ProgressTests.cs @@ -99,7 +99,7 @@ private async Task ExecuteDecodeProgressReport(BcDecoder decoder, DdsFile image, } [Fact] - public async void DecodeProgressReportParallel() + public async Task DecodeProgressReportParallel() { var testImage = KtxLoader.TestDecompressBc1; var decoder = new BcDecoder @@ -118,7 +118,7 @@ public async void DecodeProgressReportParallel() } [Fact] - public async void DecodeProgressReportNonParallel() + public async Task DecodeProgressReportNonParallel() { var testImage = KtxLoader.TestDecompressBc1; var decoder = new BcDecoder @@ -137,7 +137,7 @@ public async void DecodeProgressReportNonParallel() } [Fact] - public async void DecodeProgressReportParallelOneMip() + public async Task DecodeProgressReportParallelOneMip() { var testImage = KtxLoader.TestDecompressBc1; var decoder = new BcDecoder @@ -151,7 +151,7 @@ public async void DecodeProgressReportParallelOneMip() } [Fact] - public async void DecodeProgressReportNonParallelOneMip() + public async Task DecodeProgressReportNonParallelOneMip() { var testImage = KtxLoader.TestDecompressBc1; var decoder = new BcDecoder @@ -165,7 +165,7 @@ public async void DecodeProgressReportNonParallelOneMip() } [Fact] - public async void DecodeProgressReportParallelDds() + public async Task DecodeProgressReportParallelDds() { var testImage = DdsLoader.TestDecompressBc1; var decoder = new BcDecoder @@ -184,7 +184,7 @@ public async void DecodeProgressReportParallelDds() } [Fact] - public async void DecodeProgressReportNonParallelDds() + public async Task DecodeProgressReportNonParallelDds() { var testImage = DdsLoader.TestDecompressBc1; var decoder = new BcDecoder @@ -203,7 +203,7 @@ public async void DecodeProgressReportNonParallelDds() } [Fact] - public async void DecodeProgressReportParallelOneMipDds() + public async Task DecodeProgressReportParallelOneMipDds() { var testImage = DdsLoader.TestDecompressBc1; var decoder = new BcDecoder @@ -217,7 +217,7 @@ public async void DecodeProgressReportParallelOneMipDds() } [Fact] - public async void DecodeProgressReportNonParallelOneMipDds() + public async Task DecodeProgressReportNonParallelOneMipDds() { var testImage = DdsLoader.TestDecompressBc1; var decoder = new BcDecoder @@ -231,7 +231,7 @@ public async void DecodeProgressReportNonParallelOneMipDds() } [Fact] - public async void EncodeProgressReportParallelKtx() + public async Task EncodeProgressReportParallelKtx() { var testImage = ImageLoader.TestBlur1; var encoder = new BcEncoder @@ -252,7 +252,7 @@ public async void EncodeProgressReportParallelKtx() } [Fact] - public async void EncodeProgressReportNonParallelKtx() + public async Task EncodeProgressReportNonParallelKtx() { var testImage = ImageLoader.TestBlur1; var encoder = new BcEncoder @@ -273,7 +273,7 @@ public async void EncodeProgressReportNonParallelKtx() } [Fact] - public async void EncodeProgressReportParallelOneMipKtx() + public async Task EncodeProgressReportParallelOneMipKtx() { var testImage = ImageLoader.TestBlur1; var encoder = new BcEncoder @@ -288,7 +288,7 @@ public async void EncodeProgressReportParallelOneMipKtx() } [Fact] - public async void EncodeProgressReportNonParallelOneMipKtx() + public async Task EncodeProgressReportNonParallelOneMipKtx() { var testImage = ImageLoader.TestBlur1; var encoder = new BcEncoder @@ -303,7 +303,7 @@ public async void EncodeProgressReportNonParallelOneMipKtx() } [Fact] - public async void EncodeProgressReportParallelDds() + public async Task EncodeProgressReportParallelDds() { var testImage = ImageLoader.TestBlur1; var encoder = new BcEncoder @@ -324,7 +324,7 @@ public async void EncodeProgressReportParallelDds() } [Fact] - public async void EncodeProgressReportNonParallelDds() + public async Task EncodeProgressReportNonParallelDds() { var testImage = ImageLoader.TestBlur1; var encoder = new BcEncoder @@ -345,7 +345,7 @@ public async void EncodeProgressReportNonParallelDds() } [Fact] - public async void EncodeProgressReportParallelOneMipDds() + public async Task EncodeProgressReportParallelOneMipDds() { var testImage = ImageLoader.TestBlur1; var encoder = new BcEncoder @@ -360,7 +360,7 @@ public async void EncodeProgressReportParallelOneMipDds() } [Fact] - public async void EncodeProgressReportNonParallelOneMipDds() + public async Task EncodeProgressReportNonParallelOneMipDds() { var testImage = ImageLoader.TestBlur1; var encoder = new BcEncoder diff --git a/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs b/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs index fc8a0f9..15c4640 100644 --- a/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs +++ b/BCnEncoder.NET.ImageSharp/BCnEncoderExtensions.cs @@ -192,7 +192,7 @@ public static Task EncodeToRawBytesAsync(this BcEncoder encoder, Image /// The mipmap to encode. /// The cancellation token for this operation. Can be default, if the operation is not asynchronous. /// The raw encoded data. - /// To get the width and height of the encoded mipLevel, see . + /// To get the width and height of the encoded mipLevel, see . public static Task EncodeToRawBytesAsync(this BcEncoder encoder, Image inputImage, int mipLevel, CancellationToken token = default) { return encoder.EncodeToRawBytesAsync(ImageToMemory2D(inputImage), mipLevel, token); From eb305479ffc15f70e9fe810af1aa0cbb5e381a8f Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Sun, 4 May 2025 23:03:30 +0300 Subject: [PATCH 07/30] Assume at least 1 mipmap in dds files --- BCnEnc.Net/Decoder/BcDecoder.cs | 4 ++++ BCnEnc.Net/Shared/ImageFiles/DdsFile.cs | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index 0d1c10d..2189d36 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -1161,6 +1161,10 @@ private ColorRgba32[][] DecodeInternal(KtxFile file, bool allMipMaps, Cancellati private ColorRgba32[][] DecodeInternal(DdsFile file, bool allMipMaps, CancellationToken token) { var mipMaps = allMipMaps ? file.header.dwMipMapCount : 1; + + // Assume at least 1 mip map + mipMaps = Math.Max(1, mipMaps); + var colors = new ColorRgba32[mipMaps][]; var context = new OperationContext diff --git a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs index a1b3172..4134a37 100644 --- a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs +++ b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs @@ -55,6 +55,10 @@ public static DdsFile Load(Stream s) } var mipMapCount = (header.dwCaps & HeaderCaps.DdscapsMipmap) != 0 ? header.dwMipMapCount : 1; + + // Assume at least 1 mip level + mipMapCount = Math.Max(mipMapCount, 1); + var faceCount = (header.dwCaps2 & HeaderCaps2.Ddscaps2Cubemap) != 0 ? 6u : 1u; var width = header.dwWidth; var height = header.dwHeight; @@ -416,7 +420,7 @@ public static DdsHeader InitializeUncompressed(int width, int height, DxgiFormat public struct DdsPixelFormat { public static readonly uint Dx10 = MakeFourCc('D', 'X', '1', '0'); - + public static readonly uint Dxt1 = MakeFourCc('D', 'X', 'T', '1'); public static readonly uint Dxt2 = MakeFourCc('D', 'X', 'T', '2'); public static readonly uint Dxt3 = MakeFourCc('D', 'X', 'T', '3'); From 91ee406ac02cbcc17e04284846bde59ff3c9bfd9 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Sun, 4 May 2025 23:04:07 +0300 Subject: [PATCH 08/30] Check for supported formats when decoding --- BCnEnc.Net/Decoder/BcDecoder.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index 2189d36..9c9b84c 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -1092,6 +1092,15 @@ private Memory2D[] DecodeFromStreamInternal2D(Stream stream, bool a /// An array of decoded Rgba32 images. private ColorRgba32[][] DecodeInternal(KtxFile file, bool allMipMaps, CancellationToken token) { + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (GetCompressionFormat(file.header.GlInternalFormat) == CompressionFormat.Unknown) + { + throw new ArgumentException($"Unsupported compression format: {file.header.GlInternalFormat}"); + } + var mipMaps = allMipMaps ? file.MipMaps.Count : 1; var colors = new ColorRgba32[mipMaps][]; @@ -1160,6 +1169,18 @@ private ColorRgba32[][] DecodeInternal(KtxFile file, bool allMipMaps, Cancellati /// An array of decoded Rgba32 images. private ColorRgba32[][] DecodeInternal(DdsFile file, bool allMipMaps, CancellationToken token) { + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (GetCompressionFormat(file) == CompressionFormat.Unknown) + { + var format = file.header.ddsPixelFormat.IsDxt10Format ? + file.dx10Header.dxgiFormat : + file.header.ddsPixelFormat.DxgiFormat; + throw new ArgumentException($"Unsupported compression format: {format}"); + } + var mipMaps = allMipMaps ? file.header.dwMipMapCount : 1; // Assume at least 1 mip map From 37ab6c7d52717d822bc8a8bdab95537e0c089f76 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Sun, 4 May 2025 23:07:29 +0300 Subject: [PATCH 09/30] Update workflow --- .github/workflows/dotnetcore.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 9195bfb..b834778 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -2,6 +2,7 @@ name: build and test on: push: + branches: [ main ] pull_request: branches: [ main ] paths: From 97524c9a74475ec97ad5285fa506d173413d7f82 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Sun, 4 May 2025 23:12:29 +0300 Subject: [PATCH 10/30] Version 2.2.1 --- BCnEnc.Net/BCnEncoder.csproj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BCnEnc.Net/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj index f6c12ef..7b60090 100644 --- a/BCnEnc.Net/BCnEncoder.csproj +++ b/BCnEnc.Net/BCnEncoder.csproj @@ -11,7 +11,7 @@ MIT OR Unlicense false - 2.2.0 + 2.2.1 Nominom BCnEncoder.Net @@ -31,7 +31,9 @@ Supported formats are: git BCn BC BC1 BC2 BC3 BC4 BC5 BC6 BC6H BC7 BPTC RGTC S3TC DXT1 DXT3 DXT5 ktx dds texture compression encoding decoding decompression image gpu https://github.com/Nominom/BCnEncoder.NET - 2.2.0 - Updated dependencies. + 2.2.1 +- Fixed divide by zero error on unsupported format. +- Assume dds files always have at least 1 mipmap. From c4714039eef2a727b41cb9244a8c0aa9b0fcddef Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Sun, 4 May 2025 23:26:44 +0300 Subject: [PATCH 11/30] Update workflow branch --- .github/workflows/dotnetcore.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index b834778..6ac8ac1 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -2,9 +2,9 @@ name: build and test on: push: - branches: [ main ] + branches: [ master ] pull_request: - branches: [ main ] + branches: [ master ] paths: - '**.cs' - '**.csproj' From 353ea1c3ae6f168c18fdb840810ec3a41421c34a Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Sun, 4 May 2025 23:43:08 +0300 Subject: [PATCH 12/30] Add more format checks --- BCnEnc.Net/Decoder/BcDecoder.cs | 35 ++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index 9c9b84c..446c83a 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -1261,6 +1261,10 @@ private ColorRgba32[][] DecodeInternal(DdsFile file, bool allMipMaps, Cancellati /// The decoded Rgba32 image. private ColorRgba32[] DecodeRawInternal(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token) { + if (format == CompressionFormat.Unknown) + { + throw new ArgumentException($"Unsupported compression format: {format}"); + } if (input.Length % GetBlockSize(format) != 0) { throw new ArgumentException("The size of the input buffer does not align with the compression format."); @@ -1387,6 +1391,11 @@ private Memory2D[] DecodeFromStreamInternalHdr2D(Stream stream, b /// An array of decoded Rgba32 images. private ColorRgbFloat[][] DecodeInternalHdr(KtxFile file, bool allMipMaps, CancellationToken token) { + if (GetCompressionFormat(file.header.GlInternalFormat) == CompressionFormat.Unknown) + { + throw new ArgumentException($"Unsupported compression format: {file.header.GlInternalFormat}"); + } + var mipMaps = allMipMaps ? file.MipMaps.Count : 1; var colors = new ColorRgbFloat[mipMaps][]; @@ -1439,7 +1448,25 @@ private ColorRgbFloat[][] DecodeInternalHdr(KtxFile file, bool allMipMaps, Cance /// An array of decoded Rgba32 images. private ColorRgbFloat[][] DecodeInternalHdr(DdsFile file, bool allMipMaps, CancellationToken token) { + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + + var dxtFormat = file.header.ddsPixelFormat.IsDxt10Format + ? file.dx10Header.dxgiFormat + : file.header.ddsPixelFormat.DxgiFormat; + + if (GetCompressionFormat(file) == CompressionFormat.Unknown) + { + throw new ArgumentException($"Unsupported compression format: {dxtFormat}"); + } + var mipMaps = allMipMaps ? file.header.dwMipMapCount : 1; + + // Assume at least 1 mip map + mipMaps = Math.Max(1, mipMaps); + var colors = new ColorRgbFloat[mipMaps][]; var context = new OperationContext @@ -1455,9 +1482,7 @@ private ColorRgbFloat[][] DecodeInternalHdr(DdsFile file, bool allMipMaps, Cance context.Progress = new OperationProgress(Options.Progress, totalBlocks); - var dxtFormat = file.header.ddsPixelFormat.IsDxt10Format - ? file.dx10Header.dxgiFormat - : file.header.ddsPixelFormat.DxgiFormat; + var format = GetCompressionFormat(file); var decoder = GetRgbFloatDecoder(format); @@ -1499,6 +1524,10 @@ private ColorRgbFloat[][] DecodeInternalHdr(DdsFile file, bool allMipMaps, Cance /// The decoded Rgba32 image. private ColorRgbFloat[] DecodeRawInternalHdr(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token) { + if (format == CompressionFormat.Unknown) + { + throw new ArgumentException($"Unsupported compression format: {format}"); + } if (input.Length % GetBlockSize(format) != 0) { throw new ArgumentException("The size of the input buffer does not align with the compression format."); From 7173400b43f18e646ba6dc924a070296638b2d9e Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Sun, 4 May 2025 23:58:50 +0300 Subject: [PATCH 13/30] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ca5074..367c927 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Nuget](https://img.shields.io/nuget/v/BCnEncoder.Net)](https://www.nuget.org/packages/BCnEncoder.Net/) -![Tests](https://github.com/Nominom/BCnEncoder.NET/workflows/Tests/badge.svg) +[![Tests](https://github.com/Nominom/BCnEncoder.NET/actions/workflows/dotnetcore.yml/badge.svg)](https://github.com/Nominom/BCnEncoder.NET/actions/workflows/dotnetcore.yml) # BCnEncoder.NET A Cross-platform BCn / DXT encoding libary for .NET From 0215b91a3cc179369067e34509a3dda8829240e3 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:50:08 +0200 Subject: [PATCH 14/30] Bump ImageSharp version --- BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj b/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj index 4d7c077..ef8673c 100644 --- a/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj +++ b/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj @@ -17,8 +17,8 @@ https://github.com/Nominom/BCnEncoder.NET https://github.com/Nominom/BCnEncoder.NET git - Update to ImageSharp 3.1.7 and CommunityToolkit.HighPerformance 8.4.0 - 1.1.2 + Update to ImageSharp 3.1.12 + 1.1.3 @@ -27,7 +27,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 9ed649f2b912f79f9488ba78b0871b238b3485b9 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:28:22 +0200 Subject: [PATCH 15/30] Add back netstandard 2.0 support Co-authored-by: Crsky --- BCnEnc.Net/BCnEncoder.csproj | 10 +- .../Encoder/Bptc/BptcEncodingHelpers.cs | 12 +- BCnEnc.Net/Encoder/LeastSquares.cs | 4 + BCnEnc.Net/Shared/Colors.cs | 2 +- BCnEnc.Net/Shared/HdrImage.cs | 2 +- BCnEnc.Net/Shared/LinearClustering.cs | 8 +- BCnEnc.Net/Shared/NetStdPolyfills.cs | 155 ++++++++++++++++++ BCnEnc.Net/Shared/RawBlocks.cs | 12 +- 8 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 BCnEnc.Net/Shared/NetStdPolyfills.cs diff --git a/BCnEnc.Net/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj index 7b60090..84f5a2b 100644 --- a/BCnEnc.Net/BCnEncoder.csproj +++ b/BCnEnc.Net/BCnEncoder.csproj @@ -1,7 +1,8 @@  - netstandard2.1 + netstandard2.1;netstandard2.0 + 8.0 true true snupkg @@ -49,5 +50,12 @@ Supported formats are: + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs index 3f19504..df19f58 100644 --- a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs +++ b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs @@ -5,6 +5,10 @@ using System.Text; using BCnEncoder.Shared; +#if NETSTANDARD2_0 +using MemoryMarshal = BCnEncoder.Shared.MemoryMarshalPolyfills; +#endif + namespace BCnEncoder.Encoder.Bptc { internal static class BptcEncodingHelpers @@ -20,7 +24,7 @@ public static int InterpolateInt(int e0, int e1, int index, int indexPrecision) var aWeights2 = ColorInterpolationWeights2; var aWeights3 = ColorInterpolationWeights3; var aWeights4 = ColorInterpolationWeights4; - + if (indexPrecision == 2) return (((64 - aWeights2[index]) * e0 + aWeights2[index] * e1 + 32) >> 6); if (indexPrecision == 3) @@ -52,7 +56,7 @@ public static int[] Rank2SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock, int CalculatePartitionError(int partitionIndex) - { + { var error = 0; ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[partitionIndex]; Span subset0 = stackalloc int[numDistinctClusters]; @@ -60,7 +64,7 @@ int CalculatePartitionError(int partitionIndex) var max0Idx = 0; var max1Idx = 0; - //Calculate largest cluster index for each subset + //Calculate largest cluster index for each subset for (var i = 0; i < 16; i++) { if (partitionTable[i] == 0) @@ -122,7 +126,7 @@ int CalculatePartitionError(int partitionIndex) var max1Idx = 0; var max2Idx = 0; - //Calculate largest cluster index for each subset + //Calculate largest cluster index for each subset for (var i = 0; i < 16; i++) { if (partitionTable[i] == 0) diff --git a/BCnEnc.Net/Encoder/LeastSquares.cs b/BCnEnc.Net/Encoder/LeastSquares.cs index 1c9bf68..bafdb61 100644 --- a/BCnEnc.Net/Encoder/LeastSquares.cs +++ b/BCnEnc.Net/Encoder/LeastSquares.cs @@ -5,6 +5,10 @@ using BCnEncoder.Encoder.Bptc; using BCnEncoder.Shared; +#if NETSTANDARD2_0 +using Math = BCnEncoder.Shared.MathPolyfills; +#endif + namespace BCnEncoder.Encoder { /// diff --git a/BCnEnc.Net/Shared/Colors.cs b/BCnEnc.Net/Shared/Colors.cs index b44645b..488d0f6 100644 --- a/BCnEnc.Net/Shared/Colors.cs +++ b/BCnEnc.Net/Shared/Colors.cs @@ -1241,7 +1241,7 @@ public static ColorLab XyzToLab(ColorXyz xyz) private static float PivotXyz(float n) { - var i = MathF.Cbrt(n); + var i = MathCbrt.Cbrt(n); return n > 0.008856f ? i : 7.787f * n + 16 / 116f; } } diff --git a/BCnEnc.Net/Shared/HdrImage.cs b/BCnEnc.Net/Shared/HdrImage.cs index 0dd33ec..a4143de 100644 --- a/BCnEnc.Net/Shared/HdrImage.cs +++ b/BCnEnc.Net/Shared/HdrImage.cs @@ -73,7 +73,7 @@ private static string ReadFromStream(Stream stream) c = (char)b; buffer[i++] = c; } while (c != (char)10); - return new string(buffer.AsSpan().Slice(0, i)).Trim(); + return new string(buffer, 0, i).Trim(); } private static void WriteLineToStream(BinaryWriter br, string s) diff --git a/BCnEnc.Net/Shared/LinearClustering.cs b/BCnEnc.Net/Shared/LinearClustering.cs index baf10aa..7e23f5b 100644 --- a/BCnEnc.Net/Shared/LinearClustering.cs +++ b/BCnEnc.Net/Shared/LinearClustering.cs @@ -1,6 +1,10 @@ using System; using System.Collections.Generic; +#if NETSTANDARD2_0 +using Array = BCnEncoder.Shared.ArrayPolyfills; +#endif + namespace BCnEncoder.Shared { /// @@ -194,7 +198,7 @@ public static int[] ClusterPixels(ReadOnlySpan pixels, int width, i if (enforceConnectivity) { clusterIndices = EnforceConnectivity(clusterIndices, width, height, clusters); } - + return clusterIndices; } @@ -427,7 +431,7 @@ private static int[] EnforceConnectivity(int[] oldLabels, int width, int height, { ReadOnlySpan neighborX = new[] { -1, 0, 1, 0 }; ReadOnlySpan neighborY = new[] { 0, -1, 0, 1 }; - + var sSquared = width * height / clusters; var clusterX = new List(sSquared); diff --git a/BCnEnc.Net/Shared/NetStdPolyfills.cs b/BCnEnc.Net/Shared/NetStdPolyfills.cs new file mode 100644 index 0000000..3936f96 --- /dev/null +++ b/BCnEnc.Net/Shared/NetStdPolyfills.cs @@ -0,0 +1,155 @@ + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using CommunityToolkit.HighPerformance; + + +namespace BCnEncoder.Shared +{ +#if NETSTANDARD2_0 + internal static class MemoryPolyfills + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory2D AsMemory2D(this Memory memory, int height, int width) + { + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + T[] array = segment.Array; + ref T value = ref array.DangerousGetReference(); + return Memory2D.DangerousCreate(array, ref value, height, width, 0); + } + else + { + throw new NotSupportedException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory2D AsMemory2D(this ReadOnlyMemory memory, int height, int width) + { + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) + { + T[] array = segment.Array; + ref T value = ref array.DangerousGetReference(); + return ReadOnlyMemory2D.DangerousCreate(array, ref value, height, width, 0); + } + else + { + throw new NotSupportedException(); + } + } + } + + internal static class SpanPolyfills + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ReadOnlySpan2D AsSpan2D(this ReadOnlySpan span, int height, int width) + { + ref T value = ref span.DangerousGetReference(); + void* pointer = Unsafe.AsPointer(ref value); + return new ReadOnlySpan2D(pointer, height, width, 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe Span GetRowSpan(this Span2D span, int row) + { + ref T value = ref span.DangerousGetReferenceAt(row, 0); + void* pointer = Unsafe.AsPointer(ref value); + return new Span(pointer, span.Width); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ReadOnlySpan GetRowSpan(this ReadOnlySpan2D span, int row) + { + ref T value = ref span.DangerousGetReferenceAt(row, 0); + void* pointer = Unsafe.AsPointer(ref value); + return new Span(pointer, span.Width); + } + } + + internal static class BinaryWriterPolyfills + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this BinaryWriter writer, ReadOnlySpan buffer) + { + writer.BaseStream.Write(buffer); + } + } + + internal static class BinaryReaderPolyfills + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Read(this BinaryReader reader, Span buffer) + { + return reader.BaseStream.Read(buffer); + } + } + + internal static class EncodingPolyfills + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe string GetString(this Encoding encoding, ReadOnlySpan buffer) + { + ref byte value = ref buffer.DangerousGetReference(); + byte* pointer = (byte*)Unsafe.AsPointer(ref value); + return encoding.GetString(pointer, buffer.Length); + } + } + + internal static class MemoryMarshalPolyfills + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe Span CreateSpan(ref T reference, int length) + { + return new Span(Unsafe.AsPointer(ref reference), length); + } + } + + internal static class ArrayPolyfills + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Fill(T[] array, T value) + { + array.AsSpan().Fill(value); + } + } + + internal static class MathPolyfills + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Clamp(float value, float min, float max) + { + if (min > max) + { + throw new ArgumentException(); + } + + if (value < min) + { + return min; + } + else if (value > max) + { + return max; + } + + return value; + } + } +#endif + + internal static class MathCbrt + { + public static float Cbrt(float f) + { + #if NETSTANDARD2_0 + return MathF.Pow(f, 1 / 3.0f); + #else + return MathF.Cbrt(f); + #endif + } + } +} diff --git a/BCnEnc.Net/Shared/RawBlocks.cs b/BCnEnc.Net/Shared/RawBlocks.cs index c78e5d8..dadb4cc 100644 --- a/BCnEnc.Net/Shared/RawBlocks.cs +++ b/BCnEnc.Net/Shared/RawBlocks.cs @@ -2,6 +2,10 @@ using System.Runtime.InteropServices; using BCnEncoder.Encoder.Bptc; +#if NETSTANDARD2_0 +using MemoryMarshal = BCnEncoder.Shared.MemoryMarshalPolyfills; +#endif + namespace BCnEncoder.Shared { @@ -19,7 +23,7 @@ public RawBlock4X4Rgba32(ColorRgba32 fillColor) p20 = p21 = p22 = p23 = p30 = p31 = p32 = p33 = fillColor; } - + public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); public ColorRgba32 this[int x, int y] @@ -156,7 +160,7 @@ public RawBlock4X4RgbFloat(ColorRgbFloat fillColor) p20 = p21 = p22 = p23 = p30 = p31 = p32 = p33 = fillColor; } - + public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); public ColorRgbFloat this[int x, int y] @@ -184,7 +188,7 @@ internal float CalculateError(RawBlock4X4RgbFloat other) var re = Math.Sign(col1.r) * MathF.Log( 1 + MathF.Abs(col1.r)) - Math.Sign(col2.r) * MathF.Log( 1 + MathF.Abs(col2.r)); var ge = Math.Sign(col1.g) * MathF.Log( 1 + MathF.Abs(col1.g)) - Math.Sign(col2.g) * MathF.Log( 1 + MathF.Abs(col2.g)); var be = Math.Sign(col1.b) * MathF.Log( 1 + MathF.Abs(col1.b)) - Math.Sign(col2.b) * MathF.Log( 1 + MathF.Abs(col2.b)); - + error += re * re; error += ge * ge; error += be * be; @@ -222,7 +226,7 @@ internal float CalculateYCbCrError(RawBlock4X4RgbFloat other) return error; } - + internal RawBlock4X4Ycbcr ToRawBlockYcbcr() { From 0eee44ebcb672603c2f74632577e46dfa8102b5d Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:29:10 +0200 Subject: [PATCH 16/30] Add dotnet framework tests --- BCnEnc.Net/AssemblyInfo.cs | 1 + .../Encoder/Bptc/BptcEncodingHelpers.cs | 31 +- BCnEnc.Net/Shared/MipMapper.cs | 20 +- BCnEncNet.sln | 8 + BCnEncTests.Framework/AtcTests.cs | 90 ++++ BCnEncTests.Framework/BC1Tests.cs | 164 ++++++++ .../BCnEncTests.Framework.csproj | 34 ++ BCnEncTests.Framework/Bc5Tests.cs | 83 ++++ BCnEncTests.Framework/Bc6EncoderTests.cs | 398 ++++++++++++++++++ BCnEncTests.Framework/Bc6HDecoderTests.cs | 147 +++++++ BCnEncTests.Framework/Bc7BlockTests.cs | 348 +++++++++++++++ BCnEncTests.Framework/BgraTests.cs | 63 +++ BCnEncTests.Framework/CancellationTests.cs | 21 + BCnEncTests.Framework/ClusterTests.cs | 52 +++ BCnEncTests.Framework/DdsReadTests.cs | 51 +++ BCnEncTests.Framework/DdsWritingTests.cs | 57 +++ BCnEncTests.Framework/DecodingAsyncTests.cs | 62 +++ BCnEncTests.Framework/DecodingTests.cs | 62 +++ BCnEncTests.Framework/EncoderOptionsTests.cs | 66 +++ BCnEncTests.Framework/EncodingAsyncTest.cs | 88 ++++ BCnEncTests.Framework/EncodingTest.cs | 315 ++++++++++++++ BCnEncTests.Framework/HdrImageTests.cs | 26 ++ BCnEncTests.Framework/ProgressTests.cs | 390 +++++++++++++++++ BCnEncTests.Framework/RawTests.cs | 153 +++++++ BCnEncTests.Framework/SingleBlockTests.cs | 143 +++++++ BCnEncTests.Framework/Support/HdrLoader.cs | 15 + BCnEncTests.Framework/Support/ImageLoader.cs | 98 +++++ BCnEncTests.Framework/Support/TestHelper.cs | 258 ++++++++++++ .../BCnEncTests.Shared.projitems | 15 + BCnEncTests.Shared/BCnEncTests.Shared.shproj | 10 + .../BlockTests.cs | 34 +- .../ColorTest.cs | 0 .../IntHelperTests.cs | 0 .../MathHelperTests.cs | 0 .../PcaTests.cs | 0 BCnEncTests/BCnEncTests.csproj | 2 + 36 files changed, 3269 insertions(+), 36 deletions(-) create mode 100644 BCnEncTests.Framework/AtcTests.cs create mode 100644 BCnEncTests.Framework/BC1Tests.cs create mode 100644 BCnEncTests.Framework/BCnEncTests.Framework.csproj create mode 100644 BCnEncTests.Framework/Bc5Tests.cs create mode 100644 BCnEncTests.Framework/Bc6EncoderTests.cs create mode 100644 BCnEncTests.Framework/Bc6HDecoderTests.cs create mode 100644 BCnEncTests.Framework/Bc7BlockTests.cs create mode 100644 BCnEncTests.Framework/BgraTests.cs create mode 100644 BCnEncTests.Framework/CancellationTests.cs create mode 100644 BCnEncTests.Framework/ClusterTests.cs create mode 100644 BCnEncTests.Framework/DdsReadTests.cs create mode 100644 BCnEncTests.Framework/DdsWritingTests.cs create mode 100644 BCnEncTests.Framework/DecodingAsyncTests.cs create mode 100644 BCnEncTests.Framework/DecodingTests.cs create mode 100644 BCnEncTests.Framework/EncoderOptionsTests.cs create mode 100644 BCnEncTests.Framework/EncodingAsyncTest.cs create mode 100644 BCnEncTests.Framework/EncodingTest.cs create mode 100644 BCnEncTests.Framework/HdrImageTests.cs create mode 100644 BCnEncTests.Framework/ProgressTests.cs create mode 100644 BCnEncTests.Framework/RawTests.cs create mode 100644 BCnEncTests.Framework/SingleBlockTests.cs create mode 100644 BCnEncTests.Framework/Support/HdrLoader.cs create mode 100644 BCnEncTests.Framework/Support/ImageLoader.cs create mode 100644 BCnEncTests.Framework/Support/TestHelper.cs create mode 100644 BCnEncTests.Shared/BCnEncTests.Shared.projitems create mode 100644 BCnEncTests.Shared/BCnEncTests.Shared.shproj rename {BCnEncTests => BCnEncTests.Shared}/BlockTests.cs (79%) rename {BCnEncTests => BCnEncTests.Shared}/ColorTest.cs (100%) rename {BCnEncTests => BCnEncTests.Shared}/IntHelperTests.cs (100%) rename {BCnEncTests => BCnEncTests.Shared}/MathHelperTests.cs (100%) rename {BCnEncTests => BCnEncTests.Shared}/PcaTests.cs (100%) diff --git a/BCnEnc.Net/AssemblyInfo.cs b/BCnEnc.Net/AssemblyInfo.cs index 6d23fdb..0bce899 100644 --- a/BCnEnc.Net/AssemblyInfo.cs +++ b/BCnEnc.Net/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("BCnEncTests")] +[assembly: InternalsVisibleTo("BCnEncTests.Framework")] diff --git a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs index df19f58..7c9c911 100644 --- a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs +++ b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs @@ -54,6 +54,11 @@ public static int[] Rank2SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock, { var output = Enumerable.Range(0, smallIndex ? 32 : 64).ToArray(); + // Copy struct to array before the closure so that reducedIndicesBlock is not + // heap-captured. On .NET Framework, MemoryMarshalPolyfills.CreateSpan uses + // Unsafe.AsPointer, which is only GC-safe for stack-allocated structs. + var indices = new int[16]; + for (var k = 0; k < 16; k++) indices[k] = reducedIndicesBlock[k]; int CalculatePartitionError(int partitionIndex) { @@ -69,7 +74,7 @@ int CalculatePartitionError(int partitionIndex) { if (partitionTable[i] == 0) { - var r = reducedIndicesBlock[i]; + var r = indices[i]; subset0[r]++; var count = subset0[r]; if (count > subset0[max0Idx]) @@ -79,7 +84,7 @@ int CalculatePartitionError(int partitionIndex) } else { - var r = reducedIndicesBlock[i]; + var r = indices[i]; subset1[r]++; var count = subset1[r]; if (count > subset1[max1Idx]) @@ -94,11 +99,11 @@ int CalculatePartitionError(int partitionIndex) { if (partitionTable[i] == 0) { - if (reducedIndicesBlock[i] != max0Idx) error++; + if (indices[i] != max0Idx) error++; } else { - if (reducedIndicesBlock[i] != max1Idx) error++; + if (indices[i] != max1Idx) error++; } } @@ -114,6 +119,12 @@ public static int[] Rank3SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock, { var output = Enumerable.Range(0, 64).ToArray(); + // Copy struct to array before the closure so that reducedIndicesBlock is not + // heap-captured. On .NET Framework, MemoryMarshalPolyfills.CreateSpan uses + // Unsafe.AsPointer, which is only GC-safe for stack-allocated structs. + var indices = new int[16]; + for (var k = 0; k < 16; k++) indices[k] = reducedIndicesBlock[k]; + int CalculatePartitionError(int partitionIndex) { var error = 0; @@ -131,7 +142,7 @@ int CalculatePartitionError(int partitionIndex) { if (partitionTable[i] == 0) { - var r = reducedIndicesBlock[i]; + var r = indices[i]; subset0[r]++; var count = subset0[r]; if (count > subset0[max0Idx]) @@ -141,7 +152,7 @@ int CalculatePartitionError(int partitionIndex) } else if (partitionTable[i] == 1) { - var r = reducedIndicesBlock[i]; + var r = indices[i]; subset1[r]++; var count = subset1[r]; if (count > subset1[max1Idx]) @@ -151,7 +162,7 @@ int CalculatePartitionError(int partitionIndex) } else { - var r = reducedIndicesBlock[i]; + var r = indices[i]; subset2[r]++; var count = subset2[r]; if (count > subset2[max2Idx]) @@ -166,15 +177,15 @@ int CalculatePartitionError(int partitionIndex) { if (partitionTable[i] == 0) { - if (reducedIndicesBlock[i] != max0Idx) error++; + if (indices[i] != max0Idx) error++; } else if (partitionTable[i] == 1) { - if (reducedIndicesBlock[i] != max1Idx) error++; + if (indices[i] != max1Idx) error++; } else { - if (reducedIndicesBlock[i] != max2Idx) error++; + if (indices[i] != max2Idx) error++; } } diff --git a/BCnEnc.Net/Shared/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs index e44dbf0..746ac29 100644 --- a/BCnEnc.Net/Shared/MipMapper.cs +++ b/BCnEnc.Net/Shared/MipMapper.cs @@ -164,15 +164,15 @@ public static void CalculateMipLevelSize(int width, int height, int mipIdx, out mipHeight = Math.Max(1, height >> mipIdx); } - private static ColorRgba32[,] ResizeToHalf(ReadOnlySpan2D pixelsRgba) + private static Memory2D ResizeToHalf(ReadOnlySpan2D pixelsRgba) { - var oldWidth = pixelsRgba.Width; var oldHeight = pixelsRgba.Height; var newWidth = Math.Max(1, oldWidth >> 1); var newHeight = Math.Max(1, oldHeight >> 1); - var result = new ColorRgba32[newHeight, newWidth]; + // Use a 1D backing array so TryGetMemory() succeeds on all target frameworks + var result = new ColorRgba32[newHeight * newWidth]; int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x)); int ClampH(int y) => Math.Max(0, Math.Min(oldHeight - 1, y)); @@ -186,22 +186,22 @@ public static void CalculateMipLevelSize(int width, int height, int mipIdx, out var ll = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2)].ToFloat(); var lr = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2 + 1)].ToFloat(); - result[y2, x2] = ((ul + ur + ll + lr) / 4).ToRgba32(); + result[y2 * newWidth + x2] = ((ul + ur + ll + lr) / 4).ToRgba32(); } } - return result; + return ((Memory)result).AsMemory2D(newHeight, newWidth); } - private static ColorRgbFloat[,] ResizeToHalf(ReadOnlySpan2D pixelsRgba) + private static Memory2D ResizeToHalf(ReadOnlySpan2D pixelsRgba) { - var oldWidth = pixelsRgba.Width; var oldHeight = pixelsRgba.Height; var newWidth = Math.Max(1, oldWidth >> 1); var newHeight = Math.Max(1, oldHeight >> 1); - var result = new ColorRgbFloat[newHeight, newWidth]; + // Use a 1D backing array so TryGetMemory() succeeds on all target frameworks + var result = new ColorRgbFloat[newHeight * newWidth]; int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x)); int ClampH(int y) => Math.Max(0, Math.Min(oldHeight - 1, y)); @@ -215,11 +215,11 @@ public static void CalculateMipLevelSize(int width, int height, int mipIdx, out var ll = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2)]; var lr = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2 + 1)]; - result[y2, x2] = ((ul + ur + ll + lr) / 4); + result[y2 * newWidth + x2] = ((ul + ur + ll + lr) / 4); } } - return result; + return ((Memory)result).AsMemory2D(newHeight, newWidth); } } } diff --git a/BCnEncNet.sln b/BCnEncNet.sln index 6e3c4d8..dca5cf7 100644 --- a/BCnEncNet.sln +++ b/BCnEncNet.sln @@ -14,6 +14,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCnEncoder.NET.ImageSharp", "BCnEncoder.NET.ImageSharp\BCnEncoder.NET.ImageSharp.csproj", "{7D884C56-B982-4B8C-907E-68F231CF3D89}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "BCnEncTests.Shared", "BCnEncTests.Shared\BCnEncTests.Shared.shproj", "{D954291E-2A0B-460D-934E-DC6B0785DB48}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCnEncTests.Framework", "BCnEncTests.Framework\BCnEncTests.Framework.csproj", "{CA12ED85-B4E8-4D62-9B03-0D02F742B00C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,6 +36,10 @@ Global {7D884C56-B982-4B8C-907E-68F231CF3D89}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D884C56-B982-4B8C-907E-68F231CF3D89}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D884C56-B982-4B8C-907E-68F231CF3D89}.Release|Any CPU.Build.0 = Release|Any CPU + {CA12ED85-B4E8-4D62-9B03-0D02F742B00C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA12ED85-B4E8-4D62-9B03-0D02F742B00C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA12ED85-B4E8-4D62-9B03-0D02F742B00C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA12ED85-B4E8-4D62-9B03-0D02F742B00C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/BCnEncTests.Framework/AtcTests.cs b/BCnEncTests.Framework/AtcTests.cs new file mode 100644 index 0000000..eff342c --- /dev/null +++ b/BCnEncTests.Framework/AtcTests.cs @@ -0,0 +1,90 @@ +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; + +namespace BCnEncTests +{ + public class AtcTests + { + [Fact] + public void AtcKtxDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.Atc); + var original = ImageLoader.TestLenna; + + var ktx = encoder.EncodeToKtx(original); + var image = decoder.Decode2D(ktx); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void AtcDdsDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.Atc); + var original = ImageLoader.TestLenna; + + var dds = encoder.EncodeToDds(original); + var image = decoder.Decode2D(dds); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void AtcExplicitKtxDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha); + var original = ImageLoader.TestAlphaGradient1; + + var ktx = encoder.EncodeToKtx(original); + var image = decoder.Decode2D(ktx); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void AtcExplicitDdsDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha); + var original = ImageLoader.TestAlphaGradient1; + + var dds = encoder.EncodeToDds(original); + var image = decoder.Decode2D(dds); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void AtcInterpolatedKtxDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha); + var original = ImageLoader.TestAlphaGradient1; + + var ktx = encoder.EncodeToKtx(original); + var image = decoder.Decode2D(ktx); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void AtcInterpolatedDdsDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha); + var original = ImageLoader.TestAlphaGradient1; + + var dds = encoder.EncodeToDds(original); + var image = decoder.Decode2D(dds); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + } +} diff --git a/BCnEncTests.Framework/BC1Tests.cs b/BCnEncTests.Framework/BC1Tests.cs new file mode 100644 index 0000000..314c284 --- /dev/null +++ b/BCnEncTests.Framework/BC1Tests.cs @@ -0,0 +1,164 @@ +using BCnEncoder.Shared; +using Xunit; + +namespace BCnEncTests +{ + public class Bc1Tests + { + [Fact] + public void Decode() + { + var block = new Bc1Block + { + color0 = new ColorRgb565(255, 255, 255), + color1 = new ColorRgb565(0, 0, 0) + }; + + Assert.False(block.HasAlphaOrBlack); + block[0] = 1; + block[1] = 1; + block[2] = 1; + block[3] = 1; + + block[4] = 3; + block[5] = 3; + block[6] = 3; + block[7] = 3; + + block[8] = 2; + block[9] = 2; + block[10] = 2; + block[11] = 2; + + block[12] = 0; + block[13] = 0; + block[14] = 0; + block[15] = 0; + + var raw = block.Decode(false); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p00); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p10); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p20); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p30); + + Assert.Equal(new ColorRgba32(85, 85, 85), raw.p01); + Assert.Equal(new ColorRgba32(85, 85, 85), raw.p11); + Assert.Equal(new ColorRgba32(85, 85, 85), raw.p21); + Assert.Equal(new ColorRgba32(85, 85, 85), raw.p31); + + Assert.Equal(new ColorRgba32(170, 170, 170), raw.p02); + Assert.Equal(new ColorRgba32(170, 170, 170), raw.p12); + Assert.Equal(new ColorRgba32(170, 170, 170), raw.p22); + Assert.Equal(new ColorRgba32(170, 170, 170), raw.p32); + + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); + } + + [Fact] + public void DecodeBlack() + { + var block = new Bc1Block + { + color0 = new ColorRgb565(200, 200, 200), + color1 = new ColorRgb565(255, 255, 255) + }; + + Assert.True(block.HasAlphaOrBlack); + block[0] = 0; + block[1] = 0; + block[2] = 0; + block[3] = 0; + + block[4] = 3; + block[5] = 3; + block[6] = 3; + block[7] = 3; + + block[8] = 2; + block[9] = 2; + block[10] = 2; + block[11] = 2; + + block[12] = 1; + block[13] = 1; + block[14] = 1; + block[15] = 1; + + var raw = block.Decode(false); + Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00); + Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10); + Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20); + Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30); + + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p01); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p11); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p21); + Assert.Equal(new ColorRgba32(0, 0, 0), raw.p31); + + Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02); + Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12); + Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22); + Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32); + + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); + } + + [Fact] + public void DecodeAlpha() + { + var block = new Bc1Block + { + color0 = new ColorRgb565(200, 200, 200), + color1 = new ColorRgb565(255, 255, 255) + }; + + Assert.True(block.HasAlphaOrBlack); + block[0] = 0; + block[1] = 0; + block[2] = 0; + block[3] = 0; + + block[4] = 3; + block[5] = 3; + block[6] = 3; + block[7] = 3; + + block[8] = 2; + block[9] = 2; + block[10] = 2; + block[11] = 2; + + block[12] = 1; + block[13] = 1; + block[14] = 1; + block[15] = 1; + + var raw = block.Decode(true); + Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00); + Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10); + Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20); + Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30); + + Assert.Equal(new ColorRgba32(0, 0, 0, 0), raw.p01); + Assert.Equal(new ColorRgba32(0, 0, 0, 0), raw.p11); + Assert.Equal(new ColorRgba32(0, 0, 0, 0), raw.p21); + Assert.Equal(new ColorRgba32(0, 0, 0, 0), raw.p31); + + Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02); + Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12); + Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22); + Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32); + + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); + Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); + } + } +} diff --git a/BCnEncTests.Framework/BCnEncTests.Framework.csproj b/BCnEncTests.Framework/BCnEncTests.Framework.csproj new file mode 100644 index 0000000..e14d3cc --- /dev/null +++ b/BCnEncTests.Framework/BCnEncTests.Framework.csproj @@ -0,0 +1,34 @@ + + + + net481 + 8.0 + false + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/BCnEncTests.Framework/Bc5Tests.cs b/BCnEncTests.Framework/Bc5Tests.cs new file mode 100644 index 0000000..4e361ef --- /dev/null +++ b/BCnEncTests.Framework/Bc5Tests.cs @@ -0,0 +1,83 @@ +using BCnEncoder.Decoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class Bc5Tests + { + [Fact] + public void Bc5Indices() + { + var block = new Bc5Block(); + + for (var i = 0; i < 16; i++) + { + block.SetRedIndex(i, (byte)(i % 8)); + block.SetGreenIndex(i, (byte)((i + 3) % 8)); + } + + for (var i = 0; i < 16; i++) + { + int rI = block.GetRedIndex(i); + int gI = block.GetGreenIndex(i); + + Assert.Equal((byte)(i % 8), rI); + Assert.Equal((byte)((i + 3) % 8), gI); + } + } + + [Fact] + public void Bc5DdsDecode() + { + var reference = ImageLoader.TestDecodingBc5Reference; + var decoded = new BcDecoder().Decode2D(DdsLoader.TestDecompressBc5); + + var refSpan = TestHelper.GetSinglePixelArrayAsColors(reference); + var decSpan = TestHelper.GetSinglePixelArrayAsColors(decoded); + + Assert.Equal(reference.Width, decoded.Width); + Assert.Equal(reference.Height, decoded.Height); + + // Exactly equal + for (var i = 0; i < reference.Width * reference.Height; i++) + Assert.Equal(refSpan[i], decSpan[i]); + } + + [Fact] + public void Bc5BlockDecode() + { + var block = new Bc5Block() + { + greenBlock = new Bc4ComponentBlock() { componentBlock = 0x91824260008935ee }, + redBlock = new Bc4ComponentBlock() { componentBlock = 0x6d900f66d3c0a70d } + }; + + var referenceBlock = new RawBlock4X4Rgba32 + { + p00 = new ColorRgba32(13, 53, 0, 255), + p01 = new ColorRgba32(136, 238, 0, 255), + p02 = new ColorRgba32(255, 212, 0, 255), + p03 = new ColorRgba32(167, 238, 0, 255), + p10 = new ColorRgba32(13, 53, 0, 255), + p11 = new ColorRgba32(136, 238, 0, 255), + p12 = new ColorRgba32(167, 238, 0, 255), + p13 = new ColorRgba32(75, 185, 0, 255), + p20 = new ColorRgba32(255, 212, 0, 255), + p21 = new ColorRgba32(167, 238, 0, 255), + p22 = new ColorRgba32(13, 53, 0, 255), + p23 = new ColorRgba32(75, 159, 0, 255), + p30 = new ColorRgba32(167, 238, 0, 255), + p31 = new ColorRgba32(75, 185, 0, 255), + p32 = new ColorRgba32(13, 53, 0, 255), + p33 = new ColorRgba32(75, 159, 0, 255) + }; + + var decodedBlock = block.Decode(); + + for (var i = 0; i < 16; i++) + Assert.Equal(referenceBlock[i], decodedBlock[i]); + } + } +} diff --git a/BCnEncTests.Framework/Bc6EncoderTests.cs b/BCnEncTests.Framework/Bc6EncoderTests.cs new file mode 100644 index 0000000..5fb1f5d --- /dev/null +++ b/BCnEncTests.Framework/Bc6EncoderTests.cs @@ -0,0 +1,398 @@ +using System; +using System.IO; +using BCnEncoder.Encoder; +using BCnEncoder.Encoder.Bptc; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests +{ + public class Bc6EncoderTests + { + private ITestOutputHelper output; + + public Bc6EncoderTests(ITestOutputHelper output) => this.output = output; + + [Theory] + [InlineData(0x7F, 8, false)] + [InlineData(0xFF, 8, false)] + [InlineData(0x7F, 8, true)] + [InlineData(0xFF, 8, true)] + [InlineData(0b111_1100_1101, 11, true)] + [InlineData(0b111_1100_1101, 11, false)] + [InlineData(0b011_1100_1101, 11, true)] + [InlineData(0b011_1100_1101, 11, false)] + [InlineData(0b0_1001, 5, true)] + [InlineData(0b0_1001, 5, false)] + [InlineData(0b1_1100, 5, true)] + [InlineData(0b1_1100, 5, false)] + public void Quantize(int initialQuantizedValue, int endpointBits, bool signed) + { + if (signed) + { + initialQuantizedValue = IntHelper.SignExtend(initialQuantizedValue, endpointBits); + } + var unquantized = Bc6Block.UnQuantize(initialQuantizedValue, endpointBits, signed); + var finishedUnquantized = Bc6Block.FinishUnQuantize(unquantized, signed); + + var prequantized = Bc6EncodingHelpers.PreQuantize(finishedUnquantized, signed); + var quantized = Bc6EncodingHelpers.Quantize(prequantized, endpointBits, signed); + + Assert.InRange(prequantized, unquantized - 2, unquantized + 2); + Assert.InRange(quantized, initialQuantizedValue - 1, initialQuantizedValue + 1); + } + + [Fact] + public void PreQuantizeRawEndpoint() + { + var ep0 = new ColorRgbFloat(1, 0.001f, 0.2f); + + var preQuantized = Bc6EncodingHelpers.PreQuantizeRawEndpoint(ep0, false); + var unQu = Bc6Block.FinishUnQuantize(preQuantized, false); + + Assert.InRange(unQu.Item1, ep0.r - 0.001f, ep0.r + 0.001f); + Assert.InRange(unQu.Item2, ep0.g - 0.001f, ep0.g + 0.001f); + Assert.InRange(unQu.Item3, ep0.b - 0.001f, ep0.b + 0.001f); + } + + [Fact] + public void RgbBoundingBox() + { + var testBlock = new RawBlock4X4RgbFloat() + { + p00 = new ColorRgbFloat(0.01f, -0.05f, 0.02f), + p01 = new ColorRgbFloat(0.02f, 0.02f, 0.03f), + p02 = new ColorRgbFloat(0.02f, 0.02f, 0.02f), + p03 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), + p10 = new ColorRgbFloat(0.02f, 0.02f, 0.02f), + p11 = new ColorRgbFloat(0.045f, 0.02f, 0.02f), + p12 = new ColorRgbFloat(0.04f, 0.02f, 0.02f), + p13 = new ColorRgbFloat(0.04f, 0.02f, 0.02f), + p20 = new ColorRgbFloat(0.21f, 0.02f, 0.02f), + p21 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), + p22 = new ColorRgbFloat(0.01f, 0.32f, 0.02f), + p23 = new ColorRgbFloat(1.01f, 0.22f, 0.02f), + p30 = new ColorRgbFloat(0.01f, 0.12f, 0.02f), + p31 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), + p32 = new ColorRgbFloat(0.01f, 0.62f, 0.7f), + p33 = new ColorRgbFloat(0.01f, 0.02f, 0.02f) + }; + + BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); + + Assert.InRange(min.r, 0.01, 0.02); + Assert.InRange(min.g, -0.05, -0.03); + Assert.InRange(min.b, 0.02, 0.03); + + Assert.InRange(max.r, 0.9, 1.01); + Assert.InRange(max.g, 0.59, 0.62); + Assert.InRange(max.b, 0.60, 0.7); + } + + [Fact] + public void PackMode3() + { + const int endpointPrecision = 10; + var random = new Random(); + + var ep0 = (random.Next() & (1 << endpointPrecision) - 1, + random.Next() & (1 << endpointPrecision) - 1, + random.Next() & (1 << endpointPrecision) - 1); + var ep1 = (random.Next() & (1 << endpointPrecision) - 1, + random.Next() & (1 << endpointPrecision) - 1, + random.Next() & (1 << endpointPrecision) - 1); + + var indices = new byte[16]; + for (var i = 1; i < indices.Length; i++) + { + indices[i] = (byte)random.Next(1 << 4); + } + + var block = Bc6Block.PackType3(ep0, ep1, indices); + + Assert.Equal(Bc6BlockType.Type3, block.Type); + + var extracted0 = block.ExtractEp0(); + var extracted1 = block.ExtractEp1(); + + Assert.Equal(ep0, extracted0); + Assert.Equal(ep1, extracted1); + + for (var i = 0; i < indices.Length; i++) + { + var idx = block.GetColorIndex(1, 0, 4, i); + Assert.Equal(indices[i], idx); + } + } + + [Theory] + [InlineData(Bc6BlockType.Type0)] + [InlineData(Bc6BlockType.Type1)] + [InlineData(Bc6BlockType.Type2)] + [InlineData(Bc6BlockType.Type6)] + [InlineData(Bc6BlockType.Type10)] + [InlineData(Bc6BlockType.Type14)] + [InlineData(Bc6BlockType.Type18)] + [InlineData(Bc6BlockType.Type22)] + [InlineData(Bc6BlockType.Type26)] + [InlineData(Bc6BlockType.Type30)] + [InlineData(Bc6BlockType.Type3)] + [InlineData(Bc6BlockType.Type7)] + [InlineData(Bc6BlockType.Type11)] + //[InlineData(Bc6BlockType.Type15)] //deltabits too small to encode the testblock + internal void EncodeAllModesUnsigned(Bc6BlockType type) + { + var testBlock = new RawBlock4X4RgbFloat() + { + p00 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), + p01 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), + p02 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), + p03 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), + p10 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), + p11 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), + p12 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), + p13 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), + p20 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), + p21 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), + p22 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), + p23 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), + p30 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), + p31 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), + p32 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), + p33 = new ColorRgbFloat(1.01f, 10.013f, 2.023f) + }; + Bc6Block encoded; + var badTransform = false; + + if (type.HasSubsets()) + { + var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2); + var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true); + + var bestPartition = best2SubsetPartitions[0]; + + Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0); + Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1); + + encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition, + false, out badTransform); + } + else + { + BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); + encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, false, + out badTransform); + } + + Assert.False(badTransform); + Assert.Equal(type, encoded.Type); + var decoded = encoded.Decode(false); + var error = testBlock.CalculateError(decoded); + Assert.InRange(error, 0, 0.3); + } + + [Theory] + [InlineData(Bc6BlockType.Type0)] + [InlineData(Bc6BlockType.Type1)] + [InlineData(Bc6BlockType.Type2)] + [InlineData(Bc6BlockType.Type6)] + [InlineData(Bc6BlockType.Type10)] + [InlineData(Bc6BlockType.Type14)] + [InlineData(Bc6BlockType.Type18)] + [InlineData(Bc6BlockType.Type22)] + [InlineData(Bc6BlockType.Type26)] + [InlineData(Bc6BlockType.Type30)] + [InlineData(Bc6BlockType.Type3)] + [InlineData(Bc6BlockType.Type7)] + [InlineData(Bc6BlockType.Type11)] + [InlineData(Bc6BlockType.Type15)] + internal void EncodeAllModesSigned(Bc6BlockType type) + { + var testBlock = new RawBlock4X4RgbFloat() + { + p00 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), + p01 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), + p02 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), + p03 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), + p10 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), + p11 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), + p12 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), + p13 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), + p20 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), + p21 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), + p22 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), + p23 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), + p30 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), + p31 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), + p32 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), + p33 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f) + }; + Bc6Block encoded; + var badTransform = false; + + if (type.HasSubsets()) + { + var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2); + var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true); + + var bestPartition = best2SubsetPartitions[0]; + + Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0); + Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1); + + encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition, + true, out badTransform); + } + else + { + BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); + encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, true, + out badTransform); + } + + Assert.False(badTransform); + Assert.Equal(type, encoded.Type); + var decoded = encoded.Decode(true); + var error = testBlock.CalculateError(decoded); + Assert.InRange(error, 0, 0.5); + } + + [Fact] + public void Encode() + { + var signed = true; + var image = HdrLoader.TestHdrKiara; + var blocks = ImageToBlocks.ImageTo4X4(image.pixels.AsMemory().AsMemory2D(image.height, image.width), out var bW, out var bH); + + for (var i = 0; i < blocks.Length; i++) + { + var encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed); + var decoded = encoded.Decode(signed); + + var error = decoded.CalculateError(blocks[i]); + if (error > 0.06 || i == 14749) + { + encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed); + } + } + } + + [Fact] + public void EncodeFast() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Fast, + "encoding_bc6_kiara_fast.ktx", output); + } + + [Fact] + public void EncodeBalanced() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Balanced, + "encoding_bc6_kiara_balanced.ktx", output); + } + + [Fact] + public void EncodeBestQuality() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.BestQuality, + "encoding_bc6_kiara_bestquality.ktx", output); + } + + [Fact] + public void EncodeProbeFast() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Fast, + "encoding_bc6_probe_fast.ktx", output); + } + + [Fact] + public void EncodeProbeBalanced() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Balanced, + "encoding_bc6_probe_balanced.ktx", output); + } + + [Fact] + public void EncodeProbeBestQuality() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.BestQuality, + "encoding_bc6_probe_bestquality.ktx", output); + } + + [Fact] + public void EncodeProbeSignedFast() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Fast, + "encoding_bc6_probe_signed_fast.ktx", output); + } + + [Fact] + public void EncodeProbeSignedBalanced() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Balanced, + "encoding_bc6_probe_signed_balanced.ktx", output); + } + + [Fact] + public void EncodeProbeSignedBestQuality() + { + TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.BestQuality, + "encoding_bc6_probe_signed_bestquality.ktx", output); + } + + [Fact] + public void EncodeToKtx() + { + var encoder = new BcEncoder(); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.Format = CompressionFormat.Bc6U; + + var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory); + + using var fs = File.OpenWrite("encoding_bc6_ktx.ktx"); + ktx.Write(fs); + } + + [Fact] + public void EncodeToDds() + { + var encoder = new BcEncoder(); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.Format = CompressionFormat.Bc6U; + + var dds = encoder.EncodeToDdsHdr(HdrLoader.TestHdrKiara.PixelMemory); + + using var fs = File.OpenWrite("encoding_bc6_dds.dds"); + dds.Write(fs); + } + + [Fact] + public void EncodeToRaw() + { + var encoder = new BcEncoder(); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.Format = CompressionFormat.Bc6U; + + var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory); + + var allMips = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory); + + Assert.True(allMips.Length > 1); + Assert.True(allMips.Length == ktx.MipMaps.Count); + + for (var i = 0; i < allMips.Length; i++) + { + var single = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory, i, out var mW, out var mH); + + Assert.Equal(ktx.MipMaps[i].Faces[0].Data, single); + Assert.Equal(allMips[i], single); + } + } + } +} diff --git a/BCnEncTests.Framework/Bc6HDecoderTests.cs b/BCnEncTests.Framework/Bc6HDecoderTests.cs new file mode 100644 index 0000000..0453379 --- /dev/null +++ b/BCnEncTests.Framework/Bc6HDecoderTests.cs @@ -0,0 +1,147 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; +using Xunit.Abstractions; +using Half = BCnEncoder.Shared.Half; + +namespace BCnEncTests +{ + public class Bc6HDecoderTests + { + private ITestOutputHelper output; + + public Bc6HDecoderTests(ITestOutputHelper output) => this.output = output; + + [Fact] + public void DecodeDds() + { + var decoder = new BcDecoder(); + var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); + + var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraDds.header.dwWidth, + (int)HdrLoader.TestHdrKiaraDds.header.dwHeight); + + Assert.Equal(hdr.pixels.Length, decoded.Length); + + hdr.pixels = decoded; + using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr"); + hdr.Write(sfs); + + TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output); + } + + [Fact] + public void DecodeKtx() + { + var decoder = new BcDecoder(); + var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraKtx); + + var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth, + (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight); + + Assert.Equal(hdr.pixels.Length, decoded.Length); + + hdr.pixels = decoded; + using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr"); + hdr.Write(sfs); + + TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.BestQuality, output); + } + + // TestHdrKiaraDds includes some blocks with all modes. + // The test_hdr_kiara_dds_float16_data.bin file contains Half-float values decoded with a reference decoder. + // This test ensures that decoding is bit-exact. + [Fact] + public void AllBlocksDecodesExact() + { + var decoder = new BcDecoder(); + var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); + + using var fs = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin"); + using var ms = new MemoryStream(); + fs.CopyTo(ms); + var length = (int)ms.Position; + + var bytes = ms.GetBuffer().AsSpan(0, length); + var halfs = MemoryMarshal.Cast(bytes); + Assert.Equal(halfs.Length / 4, decoded.Length); + + for (var i = 0; i < decoded.Length; i++) + { + float r = halfs[i * 4 + 0]; + float g = halfs[i * 4 + 1]; + float b = halfs[i * 4 + 2]; + + Assert.Equal(r, decoded[i].r); + Assert.Equal(g, decoded[i].g); + Assert.Equal(b, decoded[i].b); + } + } + + // Test that above statement holds true. And that modes are recognized correctly + [Fact] + public void DecodeModes() + { + var modes = new int[31]; + + var hdr = HdrLoader.TestHdrKiaraDds; + + var bytes = hdr.Faces[0].MipMaps[0].Data; + var blocks = MemoryMarshal.Cast(bytes); + + for (var i = 0; i < blocks.Length; i++) + { + var block = blocks[i]; + var mode = block.Type; + modes[(int)mode]++; + } + + for (var i = 0; i < modes.Length; i++) + { + output.WriteLine($"Mode {i}: {modes[i]}"); + } + + Assert.True(modes[0] > 0); + Assert.True(modes[1] > 0); + Assert.True(modes[2] > 0); + Assert.True(modes[6] > 0); + Assert.True(modes[10] > 0); + Assert.True(modes[14] > 0); + Assert.True(modes[18] > 0); + Assert.True(modes[22] > 0); + Assert.True(modes[26] > 0); + Assert.True(modes[30] > 0); + Assert.True(modes[3] > 0); + Assert.True(modes[7] > 0); + Assert.True(modes[11] > 0); + Assert.True(modes[15] > 0); + } + + [Fact] + public void DecodeErrorBlock() + { + var decoder = new BcDecoder(); + + var width = 16; + var height = 16; + var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc6U) * width * height; + + var buffer = new byte[bufferSize]; + var r = new System.Random(44); + r.NextBytes(buffer); + + var decoded = decoder.DecodeRawHdr(buffer, width * 4, height * 4, CompressionFormat.Bc6U); + Assert.Contains(new ColorRgbFloat(1, 0, 1), decoded); + + HdrImage image = new HdrImage(new Span2D(decoded, height * 4, width * 4)); + using var fs = File.OpenWrite("test_decode_bc6h_error.hdr"); + image.Write(fs); + } + } +} diff --git a/BCnEncTests.Framework/Bc7BlockTests.cs b/BCnEncTests.Framework/Bc7BlockTests.cs new file mode 100644 index 0000000..c356fb7 --- /dev/null +++ b/BCnEncTests.Framework/Bc7BlockTests.cs @@ -0,0 +1,348 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using BCnEncoder.Decoder; +using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; + +namespace BCnEncTests +{ + public class Bc7BlockTests + { + [Fact] + public void PackTypes() + { + var numWidthBlocks = 4; + var numHeightBlocks = 2; + var outputBlocks = new Bc7Block[64 * numWidthBlocks * numHeightBlocks]; + var encoded = new byte[64 * numWidthBlocks * numHeightBlocks * Unsafe.SizeOf()]; + + var output = new KtxFile( + KtxHeader.InitializeCompressed(numWidthBlocks * 8 * 4, numHeightBlocks * 8 * 4, + GlInternalFormat.GlCompressedRgbaBptcUnormArb, + GlFormat.GlRgba)); + + var type0 = new Span(outputBlocks, 0, 64); + Type0Pack(type0); + PlaceBlock(0, 0, type0, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); + + var type1 = new Span(outputBlocks, 64 * 1, 64); + Type1Pack(type1); + PlaceBlock(1, 0, type1, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); + + var type2 = new Span(outputBlocks, 64 * 2, 64); + Type2Pack(type2); + PlaceBlock(2, 0, type2, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); + + var type3 = new Span(outputBlocks, 64 * 3, 64); + Type3Pack(type3); + PlaceBlock(3, 0, type3, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); + + var type4 = new Span(outputBlocks, 64 * 4, 64); + Type4Pack(type4); + PlaceBlock(0, 1, type4, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); + + var type5 = new Span(outputBlocks, 64 * 5, 64); + Type5Pack(type5); + PlaceBlock(1, 1, type5, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); + + var type6 = new Span(outputBlocks, 64 * 6, 64); + Type6Pack(type6); + PlaceBlock(2, 1, type6, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); + + var type7 = new Span(outputBlocks, 64 * 7, 64); + Type7Pack(type7); + PlaceBlock(3, 1, type7, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); + + output.MipMaps.Add(new KtxMipmap((uint)encoded.Length, + (uint)(8 * 4 * numWidthBlocks), + (uint)(8 * 4 * numHeightBlocks), 1)); + output.MipMaps[0].Faces[0] = new KtxMipFace(encoded, + (uint)(8 * 4 * numWidthBlocks), + (uint)(8 * 4 * numHeightBlocks)); + + var fs = File.OpenWrite("bc7_blocktests.ktx"); + output.Write(fs); + } + + [Fact] + public void DecodeErrorBlock() + { + var decoder = new BcDecoder(); + + var width = 32; + var height = 32; + var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc7) * width * height; + + var buffer = new byte[bufferSize]; + var r = new Random(50); + r.NextBytes(buffer); + + var pixels = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc7); + Assert.Contains(new ColorRgba32(255, 0, 255), pixels); + + var decoded = pixels.AsMemory().AsMemory2D(height * 4, width * 4); + using var fs = File.OpenWrite("test_decode_bc7_error.png"); + TestHelper.SaveAsPng(decoded, fs); + } + + #region Type Packs + + private void Type0Pack(Span output) + { + var subsetEndpoints = new[] { + new byte[]{0b1111, 0, 0}, + new byte[]{0b1000, 0, 0}, + new byte[]{0, 0b1111, 0}, + new byte[]{0, 0b1000, 0}, + new byte[]{0, 0, 0b1111}, + new byte[]{0, 0, 0b1000} + }; + var pBits = new byte[] { 1, 0, 1, 0, 1, 1 }; + var indices = new byte[] { + 0, 1, 2, 3, + 0, 1, 2, 3, + 3, 2, 1, 0, + 3, 2, 1, 0 + }; + for (var i = 0; i < 16; i++) + { + output[i].PackType0(i, subsetEndpoints, pBits, indices); + Assert.Equal(Bc7BlockType.Type0, output[i].Type); + Assert.Equal(i, output[i].PartitionSetId); + } + + pBits = new byte[] { 0, 0, 0, 0, 0, 0 }; + indices = new byte[] { + 0, 1, 0, 3, + 1, 1, 1, 3, + 2, 2, 2, 0, + 3, 2, 3, 0 + }; + for (var i = 0; i < 16; i++) + { + output[i + 16].PackType0(i, subsetEndpoints, pBits, indices); + Assert.Equal(Bc7BlockType.Type0, output[i].Type); + Assert.Equal(i, output[i].PartitionSetId); + } + + pBits = new byte[] { 1, 1, 1, 1, 1, 1 }; + indices = new byte[] { + 1, 0, 3, 3, + 2, 1, 2, 3, + 3, 2, 1, 0, + 0, 3, 0, 0 + }; + for (var i = 0; i < 16; i++) + { + output[i + 32].PackType0(i, subsetEndpoints, pBits, indices); + Assert.Equal(Bc7BlockType.Type0, output[i].Type); + Assert.Equal(i, output[i].PartitionSetId); + } + + pBits = new byte[] { 0, 1, 0, 1, 0, 0 }; + indices = new byte[] { + 3, 2, 1, 0, + 0, 1, 2, 3, + 3, 2, 1, 0, + 0, 1, 2, 3 + }; + for (var i = 0; i < 16; i++) + { + output[i + 48].PackType0(i, subsetEndpoints, pBits, indices); + Assert.Equal(Bc7BlockType.Type0, output[i].Type); + Assert.Equal(i, output[i].PartitionSetId); + } + } + + private void Type1Pack(Span output) + { + var subsetEndpoints = new[] { + new byte[]{0b111111, 0b0100, 0}, + new byte[]{0b0100, 0b111111, 0}, + new byte[]{0, 0, 0b111111}, + new byte[]{0, 0, 0b1010} + }; + var pBits = new byte[] { 1, 1 }; + var indices = new byte[] { + 0, 1, 2, 3, + 0, 1, 2, 3, + 3, 2, 1, 0, + 3, 2, 1, 0 + }; + for (var i = 0; i < 64; i++) + { + output[i].PackType1(i, subsetEndpoints, pBits, indices); + Assert.Equal(Bc7BlockType.Type1, output[i].Type); + Assert.Equal(i, output[i].PartitionSetId); + } + } + + private void Type2Pack(Span output) + { + var subsetEndpoints = new[] { + new byte[]{0b11111, 0, 0}, + new byte[]{0b01000, 0, 0}, + new byte[]{0, 0b11111, 0}, + new byte[]{0, 0b01000, 0}, + new byte[]{0, 0, 0b11111}, + new byte[]{0, 0, 0b01000} + }; + var indices = new byte[] { + 0, 1, 2, 3, + 0, 1, 2, 3, + 3, 2, 1, 0, + 3, 2, 1, 0 + }; + for (var i = 0; i < 64; i++) + { + output[i].PackType2(i, subsetEndpoints, indices); + Assert.Equal(Bc7BlockType.Type2, output[i].Type); + Assert.Equal(i, output[i].PartitionSetId); + } + } + + private void Type3Pack(Span output) + { + var subsetEndpoints = new[] { + new byte[]{0b1111111, 0b10100, 0}, + new byte[]{0b10100, 0b1111111, 0}, + new byte[]{0, 0, 0b1111111}, + new byte[]{0, 0, 0b11010} + }; + var pBits = new byte[] { 1, 0, 0, 1 }; + var indices = new byte[] { + 0, 1, 2, 3, + 0, 1, 2, 3, + 3, 2, 1, 0, + 3, 2, 1, 0 + }; + for (var i = 0; i < 64; i++) + { + output[i].PackType3(i, subsetEndpoints, pBits, indices); + Assert.Equal(Bc7BlockType.Type3, output[i].Type); + Assert.Equal(i, output[i].PartitionSetId); + } + } + + private void Type4Pack(Span output) + { + var colorEndpoints = new[] { + new byte[]{0b11111, 0, 0b0100}, + new byte[]{0, 0b11111, 0b0100} + }; + var alphaEndPoints = new byte[] { 0b111111, 0 }; + var indices2Bit = new byte[] { + 0, 1, 2, 3, + 0, 1, 2, 3, + 3, 2, 1, 0, + 3, 2, 1, 0 + }; + var indices3Bit = new byte[] { + 0, 1, 2, 3, + 7, 6, 5, 4, + 7, 6, 5, 4, + 3, 2, 1, 0 + }; + var rotation = 0; + byte idxMode = 0; + for (var i = 0; i < 64; i++) + { + rotation = (rotation + 1) % 4; + idxMode = (byte)((idxMode + 1) % 2); + output[i].PackType4(rotation, idxMode, colorEndpoints, alphaEndPoints, indices2Bit, indices3Bit); + Assert.Equal(Bc7BlockType.Type4, output[i].Type); + Assert.Equal(rotation, output[i].RotationBits); + Assert.Equal(idxMode, output[i].Type4IndexMode); + } + } + + private void Type5Pack(Span output) + { + var colorEndpoints = new[] { + new byte[]{0b1111111, 0b0100, 0}, + new byte[]{0, 0, 0b1111111} + }; + var alphaEndPoints = new byte[] { 255, 100 }; + var colorIndices = new byte[] { + 0, 1, 2, 3, + 0, 1, 2, 3, + 3, 2, 1, 0, + 3, 2, 1, 0 + }; + var alphaIndices = new byte[] { + 1, 2, 3, 0, + 0, 1, 2, 3, + 2, 3, 0, 1, + 3, 2, 1, 0 + }; + var rotation = 0; + for (var i = 0; i < 64; i++) + { + rotation = (rotation + 1) % 4; + output[i].PackType5(rotation, colorEndpoints, alphaEndPoints, colorIndices, alphaIndices); + Assert.Equal(Bc7BlockType.Type5, output[i].Type); + Assert.Equal(rotation, output[i].RotationBits); + } + } + + private void Type6Pack(Span output) + { + var colorEndpoints = new[] { + new byte[]{0b1111111, 0b0100, 0, 0b1111111}, + new byte[]{0, 0, 0b1111111, 0b1111} + }; + var pBits = new byte[] { 1, 0 }; + var colorIndices = new byte[] { + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15 + }; + for (var i = 0; i < 64; i++) + { + output[i].PackType6(colorEndpoints, pBits, colorIndices); + Assert.Equal(Bc7BlockType.Type6, output[i].Type); + } + } + + private void Type7Pack(Span output) + { + var colorEndpoints = new[] { + new byte[]{0b11111, 0b0100, 0, 0b11111}, + new byte[]{0, 0b11111, 0, 0b111}, + new byte[]{0b11111, 0b0100, 0, 0b1111}, + new byte[]{0b10100, 0, 0b11111, 0b11111} + }; + var pBits = new byte[] { 1, 0, 1, 0 }; + var indices = new byte[] { + 0, 1, 2, 3, + 0, 1, 2, 3, + 3, 2, 1, 0, + 3, 2, 1, 0 + }; + for (var i = 0; i < 64; i++) + { + output[i].PackType7(i, colorEndpoints, pBits, indices); + Assert.Equal(Bc7BlockType.Type7, output[i].Type); + Assert.Equal(i, output[i].PartitionSetId); + } + } + + private void PlaceBlock(int x, int y, Span data, Span destination, int destBlockWidth) + { + for (var i = 0; i < 8; i++) + { + var xStart = x * 8 + (y * 8 + i) * 8 * destBlockWidth; + var row = data.Slice(i * 8, 8); + row.CopyTo(destination.Slice(xStart, 8)); + } + } + + #endregion + } +} diff --git a/BCnEncTests.Framework/BgraTests.cs b/BCnEncTests.Framework/BgraTests.cs new file mode 100644 index 0000000..6f0e8bf --- /dev/null +++ b/BCnEncTests.Framework/BgraTests.cs @@ -0,0 +1,63 @@ +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class BgraTests + { + [Fact] + public void BgraDdsDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.Bgra); + var original = ImageLoader.TestLenna; + + var dds = encoder.EncodeToDds(original); + var image = decoder.Decode2D(dds); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void BgraAlphaDdsDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.Bgra); + var original = ImageLoader.TestAlphaGradient1; + + var dds = encoder.EncodeToDds(original); + var image = decoder.Decode2D(dds); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void BgraKtxDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.Bgra); + var original = ImageLoader.TestLenna; + + var ktx = encoder.EncodeToKtx(original); + var image = decoder.Decode2D(ktx); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void BgraAlphaKtxDecode() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.Bgra); + var original = ImageLoader.TestAlphaGradient1; + + var ktx = encoder.EncodeToKtx(original); + var image = decoder.Decode2D(ktx); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + } +} diff --git a/BCnEncTests.Framework/CancellationTests.cs b/BCnEncTests.Framework/CancellationTests.cs new file mode 100644 index 0000000..9932c6c --- /dev/null +++ b/BCnEncTests.Framework/CancellationTests.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class CancellationTests + { + [Fact] + public async Task EncodeParallelCancellation() + { + await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, true); + } + + [Fact] + public async Task EncodeNonParallelCancellation() + { + await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, false); + } + } +} diff --git a/BCnEncTests.Framework/ClusterTests.cs b/BCnEncTests.Framework/ClusterTests.cs new file mode 100644 index 0000000..c141928 --- /dev/null +++ b/BCnEncTests.Framework/ClusterTests.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class ClusterTests + { + [Fact] + public void Clusterize() + { + var original = ImageLoader.TestBlur1; + int height = original.Height; + int width = original.Width; + + // Copy pixels from the source image + var pixels = new ColorRgba32[height * width]; + var srcSpan = original.Span; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + pixels[y * width + x] = srcSpan[y, x]; + + var numClusters = (width / 32) * (height / 32); + var clusters = LinearClustering.ClusterPixels(pixels, width, height, numClusters, 10, 10); + + var pixC = new ColorYCbCr[numClusters]; + var counts = new int[numClusters]; + + for (var i = 0; i < pixels.Length; i++) + { + pixC[clusters[i]] += new ColorYCbCr(pixels[i]); + counts[clusters[i]]++; + } + + for (var i = 0; i < numClusters; i++) + { + pixC[i] /= counts[i]; + } + + for (var i = 0; i < pixels.Length; i++) + { + pixels[i] = pixC[clusters[i]].ToColorRgba32(); + } + + var result = pixels.AsMemory().AsMemory2D(height, width); + using var fs = File.OpenWrite("test_cluster.png"); + TestHelper.SaveAsPng(result, fs); + } + } +} diff --git a/BCnEncTests.Framework/DdsReadTests.cs b/BCnEncTests.Framework/DdsReadTests.cs new file mode 100644 index 0000000..90c9279 --- /dev/null +++ b/BCnEncTests.Framework/DdsReadTests.cs @@ -0,0 +1,51 @@ +using System.IO; +using BCnEncoder.Decoder; +using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class DdsReadTests + { + [Fact] + public void ReadRgba() + { + TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressRgba, DxgiFormat.DxgiFormatR8G8B8A8Unorm, "decoding_test_dds_rgba_mip{0}.png"); + } + + [Fact] + public void ReadBc1() + { + TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1_mip{0}.png"); + } + + [Fact] + public void ReadBc1A() + { + TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1A, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1a_mip{0}.png"); + } + + [Fact] + public void ReadBc7() + { + TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc7, DxgiFormat.DxgiFormatUnknown, "decoding_test_dds_bc7_mip{0}.png"); + } + + [Fact] + public void ReadFromStream() + { + using var fs = File.OpenRead(DdsLoader.TestDecompressBc1Name); + + var decoder = new BcDecoder(); + var images = decoder.DecodeAllMipMaps2D(fs); + + for (var i = 0; i < images.Length; i++) + { + using var outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png"); + TestHelper.SaveAsPng(images[i], outFs); + } + } + } +} diff --git a/BCnEncTests.Framework/DdsWritingTests.cs b/BCnEncTests.Framework/DdsWritingTests.cs new file mode 100644 index 0000000..44a0616 --- /dev/null +++ b/BCnEncTests.Framework/DdsWritingTests.cs @@ -0,0 +1,57 @@ +using BCnEncoder.Shared; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class DdsWritingTests + { + [Fact] + public void DdsWriteRgba() + { + TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Rgba, "encoding_dds_rgba.dds"); + } + + [Fact] + public void DdsWriteBc1() + { + TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc1, "encoding_dds_bc1.dds"); + } + + [Fact] + public void DdsWriteBc2() + { + TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc2, "encoding_dds_bc2.dds"); + } + + [Fact] + public void DdsWriteBc3() + { + TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc3, "encoding_dds_bc3.dds"); + } + + [Fact] + public void DdsWriteBc4() + { + TestHelper.ExecuteDdsWritingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, "encoding_dds_bc4.dds"); + } + + [Fact] + public void DdsWriteBc5() + { + TestHelper.ExecuteDdsWritingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, "encoding_dds_bc5.dds"); + } + + [Fact] + public void DdsWriteBc7() + { + TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc7, "encoding_dds_bc7.dds"); + } + + [Fact] + public void DdsWriteCubemap() + { + TestHelper.ExecuteDdsWritingTest(ImageLoader.TestCubemap, CompressionFormat.Bc1, "encoding_dds_cubemap_bc1.dds"); + } + } +} diff --git a/BCnEncTests.Framework/DecodingAsyncTests.cs b/BCnEncTests.Framework/DecodingAsyncTests.cs new file mode 100644 index 0000000..da61e5e --- /dev/null +++ b/BCnEncTests.Framework/DecodingAsyncTests.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class DecodingAsyncTests + { + [Fact] + public async Task DecodeAsync() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(); + var original = ImageLoader.TestGradient1; + + var file = encoder.EncodeToKtx(original); + var image = await decoder.Decode2DAsync(file); + + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task DecodeAllMipMapsAsync() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(); + var original = ImageLoader.TestGradient1; + + var file = encoder.EncodeToKtx(original); + var images = await decoder.DecodeAllMipMaps2DAsync(file); + + TestHelper.AssertImagesEqual(original, images[0], encoder.OutputOptions.Quality); + } + + [Fact] + public async Task DecodeRawAsync() + { + var decoder = new BcDecoder(); + var encoder = new BcEncoder(); + var original = ImageLoader.TestGradient1; + + var file = encoder.EncodeToKtx(original); + + var rawBytes = file.MipMaps[0].Faces[0].Data; + var mipWidth = (int)file.MipMaps[0].Width; + var mipHeight = (int)file.MipMaps[0].Height; + + var decoded = await Task.Run(() => + { + var pixels = decoder.DecodeRaw(rawBytes, mipWidth, mipHeight, CompressionFormat.Bc1); + return pixels.AsMemory().AsMemory2D(mipHeight, mipWidth); + }); + + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + } +} diff --git a/BCnEncTests.Framework/DecodingTests.cs b/BCnEncTests.Framework/DecodingTests.cs new file mode 100644 index 0000000..c2fc08c --- /dev/null +++ b/BCnEncTests.Framework/DecodingTests.cs @@ -0,0 +1,62 @@ +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class DecodingTests + { + [Fact] + public void Bc1Decode() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1, "decoding_test_bc1.png"); + } + + [Fact] + public void Bc1AlphaDecode() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1A, "decoding_test_bc1a.png"); + } + + [Fact] + public void Bc2Decode() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc2, "decoding_test_bc2.png"); + } + + [Fact] + public void Bc3Decode() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc3, "decoding_test_bc3.png"); + } + + [Fact] + public void Bc4Decode() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc4Unorm, "decoding_test_bc4.png"); + } + + [Fact] + public void Bc5Decode() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc5Unorm, "decoding_test_bc5.png"); + } + + [Fact] + public void Bc7DecodeRgb() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Rgb, "decoding_test_bc7_rgb.png"); + } + + [Fact] + public void Bc7DecodeUnorm() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Unorm, "decoding_test_bc7_unorm.png"); + } + + [Fact] + public void Bc7DecodeEveryBlockType() + { + TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Types, "decoding_test_bc7_types.png"); + } + } +} diff --git a/BCnEncTests.Framework/EncoderOptionsTests.cs b/BCnEncTests.Framework/EncoderOptionsTests.cs new file mode 100644 index 0000000..f73ecd3 --- /dev/null +++ b/BCnEncTests.Framework/EncoderOptionsTests.cs @@ -0,0 +1,66 @@ +using BCnEncoder.Encoder; +using BCnEncTests.Support; +using Xunit; + +namespace BCnEncTests +{ + public class EncoderOptionsTests + { + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + [InlineData(10)] + public void MaxMipMaps(int requestedMipMaps) + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder() + { + OutputOptions = + { + GenerateMipMaps = true, + MaxMipMapLevel = requestedMipMaps + } + }; + + Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height)); + + var ktx = encoder.EncodeToKtx(testImage); + + Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels); + Assert.Equal(requestedMipMaps, ktx.MipMaps.Count); + + var dds = encoder.EncodeToDds(testImage); + + Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount); + Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length); + } + + [Fact] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.", Justification = "")] + public void GenerateMipMaps() + { + var testImage = ImageLoader.TestBlur1; + const int requestedMipMaps = 1; + var encoder = new BcEncoder() + { + OutputOptions = + { + GenerateMipMaps = false + } + }; + + Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height)); + + var ktx = encoder.EncodeToKtx(testImage); + + Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels); + Assert.Equal(requestedMipMaps, (int)ktx.MipMaps.Count); + + var dds = encoder.EncodeToDds(testImage); + + Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount); + Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length); + } + } +} diff --git a/BCnEncTests.Framework/EncodingAsyncTest.cs b/BCnEncTests.Framework/EncodingAsyncTest.cs new file mode 100644 index 0000000..a936b7d --- /dev/null +++ b/BCnEncTests.Framework/EncodingAsyncTest.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; + +namespace BCnEncTests +{ + public class EncodingAsyncTest + { + private readonly BcEncoder encoder; + private readonly BcDecoder decoder; + private readonly Memory2D originalImage; + private readonly Memory2D[] originalCubeMap; + + public EncodingAsyncTest() + { + encoder = new BcEncoder(); + decoder = new BcDecoder(); + originalImage = ImageLoader.TestGradient1; + originalCubeMap = ImageLoader.TestCubemap; + } + + [Fact] + public async Task EncodeToDdsAsync() + { + var file = await encoder.EncodeToDdsAsync(originalImage); + var image = decoder.Decode2D(file); + + TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task EncodeToKtxAsync() + { + var file = await encoder.EncodeToKtxAsync(originalImage); + var image = decoder.Decode2D(file); + + TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task EncodeCubemapToDdsAsync() + { + var file = await encoder.EncodeCubeMapToDdsAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2], + originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]); + + for (var i = 0; i < 6; i++) + { + var rawPixels = decoder.DecodeRaw(file.Faces[i].MipMaps[0].Data, + (int)file.Faces[i].Width, (int)file.Faces[i].Height, CompressionFormat.Bc1); + var decodedImage = rawPixels.AsMemory().AsMemory2D((int)file.Faces[i].Height, (int)file.Faces[i].Width); + + TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality); + } + } + + [Fact] + public async Task EncodeCubemapToKtxAsync() + { + var file = await encoder.EncodeCubeMapToKtxAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2], + originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]); + + for (var i = 0; i < 6; i++) + { + var rawPixels = decoder.DecodeRaw(file.MipMaps[0].Faces[i].Data, + (int)file.MipMaps[0].Faces[i].Width, (int)file.MipMaps[0].Faces[i].Height, CompressionFormat.Bc1); + var decodedImage = rawPixels.AsMemory().AsMemory2D( + (int)file.MipMaps[0].Faces[i].Height, (int)file.MipMaps[0].Faces[i].Width); + + TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality); + } + } + + [Fact] + public async Task EncodeToRawBytesAsync() + { + var data = await encoder.EncodeToRawBytesAsync(originalImage); + var rawPixels = decoder.DecodeRaw(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1); + var image = rawPixels.AsMemory().AsMemory2D(originalImage.Height, originalImage.Width); + + TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); + } + } +} diff --git a/BCnEncTests.Framework/EncodingTest.cs b/BCnEncTests.Framework/EncodingTest.cs new file mode 100644 index 0000000..055de03 --- /dev/null +++ b/BCnEncTests.Framework/EncodingTest.cs @@ -0,0 +1,315 @@ +using System.IO; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests +{ + public class Bc1GradientTest + { + private readonly ITestOutputHelper output; + + public Bc1GradientTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc1GradientBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestGradient1, CompressionFormat.Bc1, CompressionQuality.BestQuality, "encoding_bc1_gradient_bestQuality.ktx", output); + } + + [Fact] + public void Bc1GradientBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestGradient1, CompressionFormat.Bc1, CompressionQuality.Balanced, "encoding_bc1_gradient_balanced.ktx", output); + } + + [Fact] + public void Bc1GradientFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestGradient1, CompressionFormat.Bc1, CompressionQuality.Fast, "encoding_bc1_gradient_fast.ktx", output); + } + } + + public class Bc1DiffuseTest + { + private readonly ITestOutputHelper output; + + public Bc1DiffuseTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc1DiffuseBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestDiffuse1, CompressionFormat.Bc1, CompressionQuality.BestQuality, "encoding_bc1_diffuse_bestQuality.ktx", output); + } + + [Fact] + public void Bc1DiffuseBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestDiffuse1, CompressionFormat.Bc1, CompressionQuality.Balanced, "encoding_bc1_diffuse_balanced.ktx", output); + } + + [Fact] + public void Bc1DiffuseFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestDiffuse1, CompressionFormat.Bc1, CompressionQuality.Fast, "encoding_bc1_diffuse_fast.ktx", output); + } + } + + public class Bc1BlurryTest + { + private readonly ITestOutputHelper output; + + public Bc1BlurryTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc1BlurBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestBlur1, CompressionFormat.Bc1, CompressionQuality.BestQuality, "encoding_bc1_blur_bestQuality.ktx", output); + } + + [Fact] + public void Bc1BlurBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestBlur1, CompressionFormat.Bc1, CompressionQuality.Balanced, "encoding_bc1_blur_balanced.ktx", output); + } + + [Fact] + public void Bc1BlurFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestBlur1, CompressionFormat.Bc1, CompressionQuality.Fast, "encoding_bc1_blur_fast.ktx", output); + } + } + + public class Bc1ASpriteTest + { + private readonly ITestOutputHelper output; + + public Bc1ASpriteTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc1ASpriteBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestTransparentSprite1, CompressionFormat.Bc1WithAlpha, CompressionQuality.BestQuality, "encoding_bc1a_sprite_bestQuality.ktx", output); + } + + [Fact] + public void Bc1ASpriteBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestTransparentSprite1, CompressionFormat.Bc1WithAlpha, CompressionQuality.Balanced, "encoding_bc1a_sprite_balanced.ktx", output); + } + + [Fact] + public void Bc1ASpriteFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestTransparentSprite1, CompressionFormat.Bc1WithAlpha, CompressionQuality.Fast, "encoding_bc1a_sprite_fast.ktx", output); + } + } + + public class Bc2GradientTest + { + private readonly ITestOutputHelper output; + + public Bc2GradientTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc2GradientBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc2, CompressionQuality.BestQuality, "encoding_bc2_gradient_bestQuality.ktx", output); + } + + [Fact] + public void Bc2GradientBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc2, CompressionQuality.Balanced, "encoding_bc2_gradient_balanced.ktx", output); + } + + [Fact] + public void Bc2GradientFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc2, CompressionQuality.Fast, "encoding_bc2_gradient_fast.ktx", output); + } + } + + public class Bc3GradientTest + { + private readonly ITestOutputHelper output; + + public Bc3GradientTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc3GradientBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc3, CompressionQuality.BestQuality, "encoding_bc3_gradient_bestQuality.ktx", output); + } + + [Fact] + public void Bc3GradientBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc3, CompressionQuality.Balanced, "encoding_bc3_gradient_balanced.ktx", output); + } + + [Fact] + public void Bc3GradientFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc3, CompressionQuality.Fast, "encoding_bc3_gradient_fast.ktx", output); + } + } + + public class Bc4RedTest + { + private readonly ITestOutputHelper output; + + public Bc4RedTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc4RedBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, CompressionQuality.BestQuality, "encoding_bc4_red_bestQuality.ktx", output); + } + + [Fact] + public void Bc4RedBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, CompressionQuality.Balanced, "encoding_bc4_red_balanced.ktx", output); + } + + [Fact] + public void Bc4RedFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, CompressionQuality.Fast, "encoding_bc4_red_fast.ktx", output); + } + } + + public class Bc5RedGreenTest + { + private readonly ITestOutputHelper output; + + public Bc5RedGreenTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc5RedGreenBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, CompressionQuality.BestQuality, "encoding_bc5_red_green_bestQuality.ktx", output); + } + + [Fact] + public void Bc5RedGreenBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, CompressionQuality.Balanced, "encoding_bc5_red_green_balanced.ktx", output); + } + + [Fact] + public void Bc5RedGreenFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, CompressionQuality.Fast, "encoding_bc5_red_green_fast.ktx", output); + } + } + + public class Bc7RgbTest + { + private readonly ITestOutputHelper output; + + public Bc7RgbTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc7RgbBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestRgbHard1, CompressionFormat.Bc7, CompressionQuality.BestQuality, "encoding_bc7_rgb_bestQuality.ktx", output); + } + + [Fact] + public void Bc7RgbBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestRgbHard1, CompressionFormat.Bc7, CompressionQuality.Balanced, "encoding_bc7_rgb_balanced.ktx", output); + } + + [Fact] + public void Bc7LennaBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestLenna, CompressionFormat.Bc7, CompressionQuality.Balanced, "encoding_bc7_lenna_balanced.ktx", output); + } + + [Fact] + public void Bc7RgbFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestRgbHard1, CompressionFormat.Bc7, CompressionQuality.Fast, "encoding_bc7_rgb_fast.ktx", output); + } + } + + public class Bc7RgbaTest + { + private readonly ITestOutputHelper output; + + public Bc7RgbaTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Bc7RgbaBestQuality() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc7, CompressionQuality.BestQuality, "encoding_bc7_rgba_bestQuality.ktx", output); + } + + [Fact] + public void Bc7RgbaBalanced() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc7, CompressionQuality.Balanced, "encoding_bc7_rgba_balanced.ktx", output); + } + + [Fact] + public void Bc7RgbaFast() + { + TestHelper.ExecuteEncodingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc7, CompressionQuality.Fast, "encoding_bc7_rgba_fast.ktx", output); + } + } + + public class CubemapTest + { + [Fact] + public void WriteCubeMapFile() + { + var images = ImageLoader.TestCubemap; + + var filename = "encoding_bc1_cubemap.ktx"; + + var encoder = new BcEncoder(); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.Format = CompressionFormat.Bc1; + + using var fs = File.OpenWrite(filename); + encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); + } + } +} diff --git a/BCnEncTests.Framework/HdrImageTests.cs b/BCnEncTests.Framework/HdrImageTests.cs new file mode 100644 index 0000000..eda8e56 --- /dev/null +++ b/BCnEncTests.Framework/HdrImageTests.cs @@ -0,0 +1,26 @@ +using System.IO; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; + +namespace BCnEncTests +{ + public class HdrImageTests + { + [Fact] + public void LoadHdr() + { + using var stream = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara.hdr"); + var hdrImg = HdrImage.Read(stream); + Assert.True(hdrImg.width > 0); + Assert.True(hdrImg.height > 0); + Assert.True(hdrImg.pixels.Length == hdrImg.width * hdrImg.height); + + // Save as HDR to verify round-trip + var hdrOut = new HdrImage(new Span2D(hdrImg.pixels, hdrImg.height, hdrImg.width)); + using var outStream = File.OpenWrite("test_hdr_load.hdr"); + hdrOut.Write(outStream); + } + } +} diff --git a/BCnEncTests.Framework/ProgressTests.cs b/BCnEncTests.Framework/ProgressTests.cs new file mode 100644 index 0000000..c82777b --- /dev/null +++ b/BCnEncTests.Framework/ProgressTests.cs @@ -0,0 +1,390 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests +{ + public class ProgressTests + { + private ITestOutputHelper output; + + public ProgressTests(ITestOutputHelper output) => this.output = output; + + private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Memory2D testImage, int expectedTotalBlocks) + { + var lastProgress = new ProgressElement(0, 1); + + var processedBlocks = 0; + encoder.Options.Progress = new SynchronousProgress(element => + { + output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); + + Assert.Equal(++processedBlocks, element.CurrentBlock); + lastProgress = element; + }); + + using var ms = new MemoryStream(); + await encoder.EncodeToStreamAsync(testImage, ms); + + output.WriteLine("LastProgress = " + lastProgress); + + Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); + Assert.Equal(1, lastProgress.Percentage); + } + + private async Task ExecuteDecodeProgressReport(BcDecoder decoder, KtxFile image, int expectedTotalBlocks, bool singleMip) + { + var lastProgress = new ProgressElement(0, 1); + + var processedBlocks = 0; + decoder.Options.Progress = new SynchronousProgress(element => + { + output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); + + Assert.Equal(++processedBlocks, element.CurrentBlock); + lastProgress = element; + }); + + if (singleMip) + { + await decoder.DecodeAsync(image); + } + else + { + await decoder.DecodeAllMipMapsAsync(image); + } + + output.WriteLine("LastProgress = " + lastProgress); + + Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); + Assert.Equal(1, lastProgress.Percentage); + } + + private async Task ExecuteDecodeProgressReport(BcDecoder decoder, DdsFile image, int expectedTotalBlocks, bool singleMip) + { + var lastProgress = new ProgressElement(0, 1); + + var processedBlocks = 0; + decoder.Options.Progress = new SynchronousProgress(element => + { + output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); + + Assert.Equal(++processedBlocks, element.CurrentBlock); + lastProgress = element; + }); + + if (singleMip) + { + await decoder.DecodeAsync(image); + } + else + { + await decoder.DecodeAllMipMapsAsync(image); + } + + output.WriteLine("LastProgress = " + lastProgress); + + Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); + Assert.Equal(1, lastProgress.Percentage); + } + + [Fact] + public async Task DecodeProgressReportParallel() + { + var testImage = KtxLoader.TestDecompressBc1; + var decoder = new BcDecoder + { + Options = { IsParallel = true } + }; + + var expectedTotal = 0; + + for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++) + { + expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height); + } + + await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); + } + + [Fact] + public async Task DecodeProgressReportNonParallel() + { + var testImage = KtxLoader.TestDecompressBc1; + var decoder = new BcDecoder + { + Options = { IsParallel = false } + }; + + var expectedTotal = 0; + + for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++) + { + expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height); + } + + await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); + } + + [Fact] + public async Task DecodeProgressReportParallelOneMip() + { + var testImage = KtxLoader.TestDecompressBc1; + var decoder = new BcDecoder + { + Options = { IsParallel = true } + }; + + var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height); + + await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); + } + + [Fact] + public async Task DecodeProgressReportNonParallelOneMip() + { + var testImage = KtxLoader.TestDecompressBc1; + var decoder = new BcDecoder + { + Options = { IsParallel = false } + }; + + var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height); + + await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); + } + + [Fact] + public async Task DecodeProgressReportParallelDds() + { + var testImage = DdsLoader.TestDecompressBc1; + var decoder = new BcDecoder + { + Options = { IsParallel = true } + }; + + var expectedTotal = 0; + + for (var i = 0; i < testImage.header.dwMipMapCount; i++) + { + expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height); + } + + await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); + } + + [Fact] + public async Task DecodeProgressReportNonParallelDds() + { + var testImage = DdsLoader.TestDecompressBc1; + var decoder = new BcDecoder + { + Options = { IsParallel = false } + }; + + var expectedTotal = 0; + + for (var i = 0; i < testImage.header.dwMipMapCount; i++) + { + expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height); + } + + await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); + } + + [Fact] + public async Task DecodeProgressReportParallelOneMipDds() + { + var testImage = DdsLoader.TestDecompressBc1; + var decoder = new BcDecoder + { + Options = { IsParallel = true } + }; + + var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height); + + await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); + } + + [Fact] + public async Task DecodeProgressReportNonParallelOneMipDds() + { + var testImage = DdsLoader.TestDecompressBc1; + var decoder = new BcDecoder + { + Options = { IsParallel = false } + }; + + var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height); + + await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); + } + + [Fact] + public async Task EncodeProgressReportParallelKtx() + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder + { + Options = { IsParallel = true }, + OutputOptions = { FileFormat = OutputFileFormat.Ktx } + }; + + var expectedTotal = 0; + + for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++) + { + encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH); + expectedTotal += encoder.GetBlockCount(mW, mH); + } + + await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); + } + + [Fact] + public async Task EncodeProgressReportNonParallelKtx() + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder + { + Options = { IsParallel = false }, + OutputOptions = { FileFormat = OutputFileFormat.Ktx } + }; + + var expectedTotal = 0; + + for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++) + { + encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH); + expectedTotal += encoder.GetBlockCount(mW, mH); + } + + await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); + } + + [Fact] + public async Task EncodeProgressReportParallelOneMipKtx() + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder + { + Options = { IsParallel = true }, + OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx } + }; + + var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); + + await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); + } + + [Fact] + public async Task EncodeProgressReportNonParallelOneMipKtx() + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder + { + Options = { IsParallel = false }, + OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx } + }; + + var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); + + await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); + } + + [Fact] + public async Task EncodeProgressReportParallelDds() + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder + { + Options = { IsParallel = true }, + OutputOptions = { FileFormat = OutputFileFormat.Dds } + }; + + var expectedTotal = 0; + + for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++) + { + encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH); + expectedTotal += encoder.GetBlockCount(mW, mH); + } + + await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); + } + + [Fact] + public async Task EncodeProgressReportNonParallelDds() + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder + { + Options = { IsParallel = false }, + OutputOptions = { FileFormat = OutputFileFormat.Dds } + }; + + var expectedTotal = 0; + + for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++) + { + encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH); + expectedTotal += encoder.GetBlockCount(mW, mH); + } + + await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); + } + + [Fact] + public async Task EncodeProgressReportParallelOneMipDds() + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder + { + Options = { IsParallel = true }, + OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds } + }; + + var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); + + await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); + } + + [Fact] + public async Task EncodeProgressReportNonParallelOneMipDds() + { + var testImage = ImageLoader.TestBlur1; + var encoder = new BcEncoder + { + Options = { IsParallel = false }, + OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds } + }; + + var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); + + await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); + } + } + + internal class SynchronousProgress : IProgress + { + private readonly Action handler; + + public SynchronousProgress(Action handler) + { + this.handler = handler; + } + + public void Report(T value) + { + handler(value); + } + } +} diff --git a/BCnEncTests.Framework/RawTests.cs b/BCnEncTests.Framework/RawTests.cs new file mode 100644 index 0000000..d2a572a --- /dev/null +++ b/BCnEncTests.Framework/RawTests.cs @@ -0,0 +1,153 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; + +namespace BCnEncTests +{ + public class RawTests + { + [Fact] + public void EncodeDecode() + { + var inputImage = ImageLoader.TestGradient1; + var decoder = new BcDecoder(); + var encoder = new BcEncoder + { + OutputOptions = { Quality = CompressionQuality.BestQuality } + }; + + var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); + var decodedPixels = decoder.DecodeRaw(encodedRawBytes[0], inputImage.Width, inputImage.Height, CompressionFormat.Bc1); + var decodedImage = decodedPixels.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width); + + var originalColors = TestHelper.GetSinglePixelArrayAsColors(inputImage); + var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage); + + TestHelper.AssertPixelsEqual(originalColors, decodedColors, encoder.OutputOptions.Quality); + } + + [Fact] + public void EncodeDecodeStream() + { + var inputImage = ImageLoader.TestGradient1; + var decoder = new BcDecoder(); + var encoder = new BcEncoder + { + OutputOptions = { Quality = CompressionQuality.BestQuality } + }; + + var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); + + using var ms = new MemoryStream(encodedRawBytes[0]); + + Assert.Equal(0, ms.Position); + + var rawPixels = decoder.DecodeRaw(ms, inputImage.Width, inputImage.Height, CompressionFormat.Bc1); + var decodedImage = rawPixels.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width); + + var originalColors = TestHelper.GetSinglePixelArrayAsColors(inputImage); + var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage); + + TestHelper.AssertPixelsEqual(originalColors, decodedColors, encoder.OutputOptions.Quality); + } + + [Fact] + public void EncodeDecodeAllMipMapsStream() + { + var inputImage = ImageLoader.TestGradient1; + var decoder = new BcDecoder(); + var encoder = new BcEncoder + { + OutputOptions = + { + Quality = CompressionQuality.BestQuality, + GenerateMipMaps = true, + MaxMipMapLevel = 0 + } + }; + + using var ms = new MemoryStream(); + + var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); + + var mipLevels = encoder.CalculateNumberOfMipLevels(inputImage.Width, inputImage.Height); + Assert.True(mipLevels > 1); + + for (var i = 0; i < mipLevels; i++) + { + ms.Write(encodedRawBytes[i], 0, encodedRawBytes[i].Length); + } + + ms.Position = 0; + Assert.Equal(0, ms.Position); + + for (var i = 0; i < mipLevels; i++) + { + encoder.CalculateMipMapSize(inputImage.Width, inputImage.Height, i, out var mipWidth, out var mipHeight); + var resized = ResizeImage(inputImage, mipWidth, mipHeight); + + var blockSize = decoder.GetBlockSize(CompressionFormat.Bc1); + var blockCount = decoder.GetBlockCount(mipWidth, mipHeight); + var buffer = new byte[blockSize * blockCount]; + ms.Read(buffer, 0, buffer.Length); + + var rawPixels = decoder.DecodeRaw(buffer, mipWidth, mipHeight, CompressionFormat.Bc1); + var decodedImage = rawPixels.AsMemory().AsMemory2D(mipHeight, mipWidth); + + var originalColors = TestHelper.GetSinglePixelArrayAsColors(resized); + var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage); + + TestHelper.AssertPixelsEqual(originalColors, decodedColors, encoder.OutputOptions.Quality); + } + + encoder.CalculateMipMapSize(inputImage.Width, inputImage.Height, mipLevels - 1, out var lastMWidth, out var lastMHeight); + Assert.Equal(1, lastMWidth); + Assert.Equal(1, lastMHeight); + } + + private static ReadOnlyMemory2D ResizeImage(Memory2D image, int newWidth, int newHeight) + { + int numMips = 0; + var chain = MipMapper.GenerateMipChain(image, ref numMips); + + foreach (var memory2D in chain) + { + if (memory2D.Width == newWidth && memory2D.Height == newHeight) + { + return memory2D; + } + } + throw new InvalidOperationException("Cannot resize image to non-mip dimensions"); + } + + private static unsafe Bitmap ToBitmap(Memory2D image) + { + var bmp = new Bitmap(image.Width, image.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + var data = bmp.LockBits(new Rectangle(0, 0, image.Width, image.Height), + ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + byte* ptr = (byte*)data.Scan0; + var span = image.Span; + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + var c = span[y, x]; + ptr[0] = c.b; + ptr[1] = c.g; + ptr[2] = c.r; + ptr[3] = c.a; + ptr += 4; + } + } + bmp.UnlockBits(data); + return bmp; + } + } +} diff --git a/BCnEncTests.Framework/SingleBlockTests.cs b/BCnEncTests.Framework/SingleBlockTests.cs new file mode 100644 index 0000000..74691ed --- /dev/null +++ b/BCnEncTests.Framework/SingleBlockTests.cs @@ -0,0 +1,143 @@ +using System; +using System.IO; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; + +namespace BCnEncTests +{ + public class SingleBlockTests + { + [Theory] + [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)] + [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)] + [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)] + [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)] + [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)] + public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQuality quality) + { + var testImage = ImageLoader.TestAlpha1; + int height = testImage.Height; + int width = testImage.Width; + + var encoder = new BcEncoder() + { + OutputOptions = + { + Format = format, + Quality = quality + } + }; + + var colors = testImage.Span; + + var ms = new MemoryStream(); + + for (var y = 0; y < height; y += 4) + { + for (var x = 0; x < width; x += 4) + { + encoder.EncodeBlock(colors.Slice(y, x, 4, 4), ms); + } + } + + Assert.Equal(ms.Position, encoder.GetBlockSize() * encoder.GetBlockCount(width, height)); + + ms.Position = 0; + + var decoder = new BcDecoder(); + + var decoded = new ColorRgba32[height, width]; + + for (var y = 0; y < height; y += 4) + { + for (var x = 0; x < width; x += 4) + { + decoder.DecodeBlock(ms, format, + decoded.AsSpan2D().Slice(y, x, 4, 4)); + } + } + + var oPixels = TestHelper.GetSinglePixelArrayAsColors(testImage); + var dPixels = FlattenDecoded(decoded, height, width); + var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels, + format != CompressionFormat.Bc1); + + TestHelper.AssertPSNR(psnr, quality); + } + + [Theory] + [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)] + [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)] + [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)] + [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)] + [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)] + public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality quality) + { + var testImage = ImageLoader.TestAlpha1; + int height = testImage.Height; + int width = testImage.Width; + + var encoder = new BcEncoder() + { + OutputOptions = + { + Format = format, + Quality = quality + } + }; + + var colors = testImage.Span; + + var encMs = new MemoryStream(); + + for (var y = 0; y < height; y += 4) + { + for (var x = 0; x < width; x += 4) + { + encoder.EncodeBlock(colors.Slice(y, x, 4, 4), encMs); + } + } + + var buffer = encMs.ToArray(); + + var decoder = new BcDecoder(); + + var decoded = new ColorRgba32[height, width]; + + var blockIndex = 0; + for (var y = 0; y < height; y += 4) + { + for (var x = 0; x < width; x += 4) + { + decoder.DecodeBlock( + new Span(buffer, + blockIndex * decoder.GetBlockSize(format), + decoder.GetBlockSize(format)), + format, + decoded.AsSpan2D().Slice(y, x, 4, 4)); + blockIndex++; + } + } + + var oPixels = TestHelper.GetSinglePixelArrayAsColors(testImage); + var dPixels = FlattenDecoded(decoded, height, width); + var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels, + format != CompressionFormat.Bc1); + + TestHelper.AssertPSNR(psnr, quality); + } + + private static ColorRgba32[] FlattenDecoded(ColorRgba32[,] decoded, int height, int width) + { + var result = new ColorRgba32[height * width]; + for (var y = 0; y < height; y++) + for (var x = 0; x < width; x++) + result[y * width + x] = decoded[y, x]; + return result; + } + } +} diff --git a/BCnEncTests.Framework/Support/HdrLoader.cs b/BCnEncTests.Framework/Support/HdrLoader.cs new file mode 100644 index 0000000..51f7d5b --- /dev/null +++ b/BCnEncTests.Framework/Support/HdrLoader.cs @@ -0,0 +1,15 @@ +using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; + +namespace BCnEncTests.Support +{ + public static class HdrLoader + { + public static HdrImage TestHdrKiara { get; } = HdrImage.Read("../../../../BCnEncTests/testImages/test_hdr_kiara.hdr"); + public static HdrImage TestHdrProbe { get; } = HdrImage.Read("../../../../BCnEncTests/testImages/test_hdr_probe.hdr"); + public static DdsFile TestHdrKiaraDds { get; } = + DdsLoader.LoadDdsFile("../../../../BCnEncTests/testImages/test_hdr_kiara_bc6h.dds"); + public static KtxFile TestHdrKiaraKtx { get; } = + KtxLoader.LoadKtxFile("../../../../BCnEncTests/testImages/test_hdr_kiara_bc6h_ktx.ktx"); + } +} diff --git a/BCnEncTests.Framework/Support/ImageLoader.cs b/BCnEncTests.Framework/Support/ImageLoader.cs new file mode 100644 index 0000000..2dc9958 --- /dev/null +++ b/BCnEncTests.Framework/Support/ImageLoader.cs @@ -0,0 +1,98 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; +using CommunityToolkit.HighPerformance; + +namespace BCnEncTests.Support +{ + public static class ImageLoader + { + public static Memory2D TestDiffuse1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_diffuse_1_512.jpg"); + public static Memory2D TestBlur1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_blur_1_512.jpg"); + public static Memory2D TestNormal1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_normal_1_512.jpg"); + public static Memory2D TestHeight1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_height_1_512.jpg"); + public static Memory2D TestGradient1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_gradient_1_512.jpg"); + + public static Memory2D TestTransparentSprite1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_transparent.png"); + public static Memory2D TestAlphaGradient1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_alphagradient_1_512.png"); + public static Memory2D TestAlpha1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_alpha_1_512.png"); + public static Memory2D TestRedGreen1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_red_green_1_64.png"); + public static Memory2D TestRgbHard1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_rgb_hard_1.png"); + public static Memory2D TestLenna { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_lenna_512.png"); + public static Memory2D TestDecodingBc5Reference { get; } = LoadTestImage("../../../../BCnEncTests/testImages/decoding_dds_bc5_reference.png"); + + public static Memory2D[] TestCubemap { get; } = { + LoadTestImage("../../../../BCnEncTests/testImages/cubemap/right.png"), + LoadTestImage("../../../../BCnEncTests/testImages/cubemap/left.png"), + LoadTestImage("../../../../BCnEncTests/testImages/cubemap/top.png"), + LoadTestImage("../../../../BCnEncTests/testImages/cubemap/bottom.png"), + LoadTestImage("../../../../BCnEncTests/testImages/cubemap/back.png"), + LoadTestImage("../../../../BCnEncTests/testImages/cubemap/forward.png") + }; + + internal static Memory2D LoadTestImage(string filename) + { + using var bmp = new Bitmap(filename); + return FromBitmap(bmp); + } + + internal static unsafe Memory2D FromBitmap(Bitmap bmp) + { + var pixels = new ColorRgba32[bmp.Width * bmp.Height]; + var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + byte* ptr = (byte*)data.Scan0; + for (int i = 0; i < pixels.Length; i++) + { + // GDI+ Format32bppArgb memory order: B,G,R,A + pixels[i] = new ColorRgba32(ptr[2], ptr[1], ptr[0], ptr[3]); + ptr += 4; + } + bmp.UnlockBits(data); + return pixels.AsMemory().AsMemory2D(bmp.Height, bmp.Width); + } + } + + public static class DdsLoader + { + public const string TestDecompressBc1Name = "../../../../BCnEncTests/testImages/test_decompress_bc1.dds"; + public const string TestDecompressBc1AName = "../../../../BCnEncTests/testImages/test_decompress_bc1a.dds"; + public const string TestDecompressBc7Name = "../../../../BCnEncTests/testImages/test_decompress_bc7.dds"; + public const string TestDecompressBc5Name = "../../../../BCnEncTests/testImages/decoding_dds_bc5.dds"; + public const string TestDecompressRgbaName = "../../../../BCnEncTests/testImages/test_decompress_rgba.dds"; + + public static DdsFile TestDecompressBc1 { get; } = LoadDdsFile(TestDecompressBc1Name); + public static DdsFile TestDecompressBc1A { get; } = LoadDdsFile(TestDecompressBc1AName); + public static DdsFile TestDecompressBc5 { get; } = LoadDdsFile(TestDecompressBc5Name); + public static DdsFile TestDecompressBc7 { get; } = LoadDdsFile(TestDecompressBc7Name); + public static DdsFile TestDecompressRgba { get; } = LoadDdsFile(TestDecompressRgbaName); + + internal static DdsFile LoadDdsFile(string filename) + { + using var fs = File.OpenRead(filename); + return DdsFile.Load(fs); + } + } + + public static class KtxLoader + { + public static KtxFile TestDecompressBc1 { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc1.ktx"); + public static KtxFile TestDecompressBc1A { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc1a.ktx"); + public static KtxFile TestDecompressBc2 { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc2.ktx"); + public static KtxFile TestDecompressBc3 { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc3.ktx"); + public static KtxFile TestDecompressBc4Unorm { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc4_unorm.ktx"); + public static KtxFile TestDecompressBc5Unorm { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc5_unorm.ktx"); + public static KtxFile TestDecompressBc7Rgb { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc7_rgb.ktx"); + public static KtxFile TestDecompressBc7Types { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc7_types.ktx"); + public static KtxFile TestDecompressBc7Unorm { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc7_unorm.ktx"); + + internal static KtxFile LoadKtxFile(string filename) + { + using var fs = File.OpenRead(filename); + return KtxFile.Load(fs); + } + } +} diff --git a/BCnEncTests.Framework/Support/TestHelper.cs b/BCnEncTests.Framework/Support/TestHelper.cs new file mode 100644 index 0000000..0a9bcd1 --- /dev/null +++ b/BCnEncTests.Framework/Support/TestHelper.cs @@ -0,0 +1,258 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; +using CommunityToolkit.HighPerformance; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests.Support +{ + public static class TestHelper + { + #region Assertions + + public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null) + { + var psnr = ImageQuality.PeakSignalToNoiseRatio(originalPixels, pixels); + AssertPSNR(psnr, quality, output); + } + + public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null) + { + var rmse = ImageQuality.CalculateLogRMSE(originalPixels, pixels); + AssertRMSE(rmse, quality, output); + } + + public static void AssertImagesEqual(Memory2D original, Memory2D image, CompressionQuality quality, bool countAlpha = true) + { + var psnr = CalculatePSNR(original, image, countAlpha); + AssertPSNR(psnr, quality); + } + + #endregion + + #region Execute methods + + public static void ExecuteDecodingTest(KtxFile file, string outputFile) + { + Assert.True(file.header.VerifyHeader()); + Assert.Equal((uint)1, file.header.NumberOfFaces); + + var decoder = new BcDecoder(); + var pixels = decoder.Decode2D(file); + + Assert.Equal((uint)pixels.Width, file.header.PixelWidth); + Assert.Equal((uint)pixels.Height, file.header.PixelHeight); + + using var outFs = File.OpenWrite(outputFile); + SaveAsPng(pixels, outFs); + } + + #region Dds + + public static void ExecuteDdsWritingTest(Memory2D image, CompressionFormat format, string outputFile) + { + ExecuteDdsWritingTest(new[] { image }, format, outputFile); + } + + public static void ExecuteDdsWritingTest(Memory2D[] images, CompressionFormat format, string outputFile) + { + var encoder = new BcEncoder(); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.Format = format; + encoder.OutputOptions.FileFormat = OutputFileFormat.Dds; + + using var fs = File.OpenWrite(outputFile); + + if (images.Length == 1) + { + encoder.EncodeToStream(images[0], fs); + } + else + { + encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); + } + } + + public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string outputFile, bool assertAlpha = false) + { + Assert.Equal(format, file.header.ddsPixelFormat.DxgiFormat); + Assert.Equal(file.header.dwMipMapCount, (uint)file.Faces[0].MipMaps.Length); + + var decoder = new BcDecoder(); + decoder.InputOptions.DdsBc1ExpectAlpha = assertAlpha; + var images = decoder.DecodeAllMipMaps2D(file); + + Assert.Equal((uint)images[0].Width, file.header.dwWidth); + Assert.Equal((uint)images[0].Height, file.header.dwHeight); + + for (var i = 0; i < images.Length; i++) + { + if (assertAlpha) + { + var pixels = GetSinglePixelArrayAsColors(images[0]); + Assert.Contains(pixels, x => x.a == 0); + } + + using var outFs = File.OpenWrite(string.Format(outputFile, i)); + SaveAsPng(images[i], outFs); + } + } + + #endregion + + #region Cancellation + + public static async Task ExecuteCancellationTest(Memory2D image, bool isParallel) + { + var encoder = new BcEncoder(CompressionFormat.Bc7); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.Options.IsParallel = isParallel; + + var source = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); + await Assert.ThrowsAnyAsync(() => + encoder.EncodeToRawBytesAsync(image, source.Token)); + } + + #endregion + + #endregion + + public static float DecodeKtxCheckPSNR(string filename, Memory2D original) + { + using var fs = File.OpenRead(filename); + var ktx = KtxFile.Load(fs); + var decoder = new BcDecoder() + { + OutputOptions = { Bc4Component = ColorComponent.Luminance } + }; + var decoded = decoder.Decode2D(ktx); + + return CalculatePSNR(original, decoded); + } + + public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original) + { + using var fs = File.OpenRead(filename); + var ktx = KtxFile.Load(fs); + var decoder = new BcDecoder(); + + var decoded = decoder.DecodeHdr(ktx); + + return ImageQuality.CalculateLogRMSE(original.pixels, decoded); + } + + public static void ExecuteEncodingTest(Memory2D image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output) + { + var encoder = new BcEncoder(); + encoder.OutputOptions.Quality = quality; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.Format = format; + + var fs = File.OpenWrite(filename); + encoder.EncodeToStream(image, fs); + fs.Close(); + + var psnr = DecodeKtxCheckPSNR(filename, image); + output.WriteLine("RGBA PSNR: " + psnr + "db"); + AssertPSNR(psnr, encoder.OutputOptions.Quality); + } + + public static void ExecuteHdrEncodingTest(HdrImage image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output) + { + var encoder = new BcEncoder(); + encoder.OutputOptions.Quality = quality; + encoder.OutputOptions.GenerateMipMaps = true; + encoder.OutputOptions.Format = format; + + var fs = File.OpenWrite(filename); + encoder.EncodeToStreamHdr(image.pixels.AsMemory().AsMemory2D(image.height, image.width), fs); + fs.Close(); + + var rmse = DecodeKtxCheckRMSEHdr(filename, image); + output.WriteLine("RGBFloat RMSE: " + rmse); + AssertRMSE(rmse, encoder.OutputOptions.Quality); + } + + private static float CalculatePSNR(Memory2D original, Memory2D decoded, bool countAlpha = true) + { + var pixels = GetSinglePixelArrayAsColors(original); + var pixels2 = GetSinglePixelArrayAsColors(decoded); + + return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, countAlpha); + } + + public static void AssertPSNR(float psnr, CompressionQuality quality, ITestOutputHelper output = null) + { + output?.WriteLine($"PSNR: {psnr} , quality: {quality}"); + if (quality == CompressionQuality.Fast) + { + Assert.True(psnr > 25, $"PSNR was less than 25: {psnr} , quality: {quality}"); + } + else + { + Assert.True(psnr > 30, $"PSNR was less than 30: {psnr} , quality: {quality}"); + } + } + + public static void AssertRMSE(float rmse, CompressionQuality quality, ITestOutputHelper output = null) + { + output?.WriteLine($"RMSE: {rmse} , quality: {quality}"); + if (quality == CompressionQuality.Fast) + { + Assert.True(rmse < 0.1); + } + else + { + Assert.True(rmse < 0.04); + } + } + + public static ColorRgba32[] GetSinglePixelArrayAsColors(ReadOnlyMemory2D image) + { + var pixels = new ColorRgba32[image.Width * image.Height]; + var span = image.Span; + for (var y = 0; y < image.Height; y++) + { + for (var x = 0; x < image.Width; x++) + { + pixels[y * image.Width + x] = span[y, x]; + } + } + return pixels; + } + + public static unsafe void SaveAsPng(Memory2D image, Stream stream) + { + int width = image.Width; + int height = image.Height; + using var bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + var data = bmp.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + byte* ptr = (byte*)data.Scan0; + var span = image.Span; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + var c = span[y, x]; + ptr[0] = c.b; + ptr[1] = c.g; + ptr[2] = c.r; + ptr[3] = c.a; + ptr += 4; + } + } + bmp.UnlockBits(data); + bmp.Save(stream, ImageFormat.Png); + } + } +} diff --git a/BCnEncTests.Shared/BCnEncTests.Shared.projitems b/BCnEncTests.Shared/BCnEncTests.Shared.projitems new file mode 100644 index 0000000..f25f88b --- /dev/null +++ b/BCnEncTests.Shared/BCnEncTests.Shared.projitems @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + D954291E-2A0B-460D-934E-DC6B0785DB48 + + + + + + + + + diff --git a/BCnEncTests.Shared/BCnEncTests.Shared.shproj b/BCnEncTests.Shared/BCnEncTests.Shared.shproj new file mode 100644 index 0000000..be4f3fb --- /dev/null +++ b/BCnEncTests.Shared/BCnEncTests.Shared.shproj @@ -0,0 +1,10 @@ + + + + {D954291E-2A0B-460D-934E-DC6B0785DB48} + 14.0 + + + + + diff --git a/BCnEncTests/BlockTests.cs b/BCnEncTests.Shared/BlockTests.cs similarity index 79% rename from BCnEncTests/BlockTests.cs rename to BCnEncTests.Shared/BlockTests.cs index 98ffc59..14d6621 100644 --- a/BCnEncTests/BlockTests.cs +++ b/BCnEncTests.Shared/BlockTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using BCnEncoder.Shared; using CommunityToolkit.HighPerformance; using Xunit; @@ -36,10 +36,10 @@ public void PaddingColor() { var testImage = new ColorRgba32[15, 11]; - var pixels = testImage.AsSpan(); - - for (var i = 0; i < pixels.Length; i++) { - pixels[i] = new ColorRgba32(0, 125, 125); + for (var y = 0; y < testImage.GetLength(0); y++) { + for (var x = 0; x < testImage.GetLength(1); x++) { + testImage[y, x] = new ColorRgba32(0, 125, 125); + } } var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight); @@ -63,14 +63,14 @@ public void BlocksToImage() var r = new Random(0); var testImage = new ColorRgba32[16, 16]; - var pixels = testImage.AsSpan(); - - for (var i = 0; i < pixels.Length; i++) { - pixels[i] = new ColorRgba32( - (byte)r.Next(255), - (byte)r.Next(255), - (byte)r.Next(255), - (byte)r.Next(255)); + for (var y = 0; y < testImage.GetLength(0); y++) { + for (var x = 0; x < testImage.GetLength(1); x++) { + testImage[y, x] = new ColorRgba32( + (byte)r.Next(255), + (byte)r.Next(255), + (byte)r.Next(255), + (byte)r.Next(255)); + } } var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight); @@ -83,9 +83,11 @@ public void BlocksToImage() var pixels2 = output.AsSpan(); - Assert.Equal(pixels.Length, pixels2.Length); - for (var i = 0; i < pixels.Length; i++) { - Assert.Equal(pixels[i], pixels2[i]); + Assert.Equal(testImage.Length, pixels2.Length); + for (var y = 0; y < 16; y++) { + for (var x = 0; x < 16; x++) { + Assert.Equal(testImage[y, x], pixels2[y * 16 + x]); + } } } diff --git a/BCnEncTests/ColorTest.cs b/BCnEncTests.Shared/ColorTest.cs similarity index 100% rename from BCnEncTests/ColorTest.cs rename to BCnEncTests.Shared/ColorTest.cs diff --git a/BCnEncTests/IntHelperTests.cs b/BCnEncTests.Shared/IntHelperTests.cs similarity index 100% rename from BCnEncTests/IntHelperTests.cs rename to BCnEncTests.Shared/IntHelperTests.cs diff --git a/BCnEncTests/MathHelperTests.cs b/BCnEncTests.Shared/MathHelperTests.cs similarity index 100% rename from BCnEncTests/MathHelperTests.cs rename to BCnEncTests.Shared/MathHelperTests.cs diff --git a/BCnEncTests/PcaTests.cs b/BCnEncTests.Shared/PcaTests.cs similarity index 100% rename from BCnEncTests/PcaTests.cs rename to BCnEncTests.Shared/PcaTests.cs diff --git a/BCnEncTests/BCnEncTests.csproj b/BCnEncTests/BCnEncTests.csproj index 7dab56d..fabc4d1 100644 --- a/BCnEncTests/BCnEncTests.csproj +++ b/BCnEncTests/BCnEncTests.csproj @@ -24,4 +24,6 @@ + + From 339582ac14db22468bee2ad3d5447e614185a262 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:49:35 +0200 Subject: [PATCH 17/30] Change test runner to windows --- .github/workflows/dotnetcore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 6ac8ac1..96d29b4 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -16,7 +16,7 @@ jobs: build-and-test: name: build-and-test - runs-on: ubuntu-latest + runs-on: windows-latest steps: - uses: actions/checkout@v3 From 476d4267de19c7e311b6007206bdc017f99487d0 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:43:18 +0200 Subject: [PATCH 18/30] Fix bug with decoder not decoding all mipmaps on some methods --- BCnEnc.Net/Decoder/BcDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index 446c83a..c11ec62 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -166,7 +166,7 @@ public Task> Decode2DAsync(Stream inputStream, Cancellatio /// The awaitable operation to retrieve the decoded image. public Task[]> DecodeAllMipMaps2DAsync(Stream inputStream, CancellationToken token = default) { - return Task.Run(() => DecodeFromStreamInternal2D(inputStream, false, token), token); + return Task.Run(() => DecodeFromStreamInternal2D(inputStream, true, token), token); } /// @@ -675,7 +675,7 @@ public Task> DecodeHdr2DAsync(Stream inputStream, Cancel /// The awaitable operation to retrieve the decoded image. public Task[]> DecodeAllMipMapsHdr2DAsync(Stream inputStream, CancellationToken token = default) { - return Task.Run(() => DecodeFromStreamInternalHdr2D(inputStream, false, token), token); + return Task.Run(() => DecodeFromStreamInternalHdr2D(inputStream, true, token), token); } /// From ae862fbf17bdadafd0665a8312590de77b7f07ca Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:43:27 +0200 Subject: [PATCH 19/30] Remove extra comment --- BCnEnc.Net/Shared/MipMapper.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/BCnEnc.Net/Shared/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs index 746ac29..ddbcea7 100644 --- a/BCnEnc.Net/Shared/MipMapper.cs +++ b/BCnEnc.Net/Shared/MipMapper.cs @@ -171,7 +171,6 @@ private static Memory2D ResizeToHalf(ReadOnlySpan2D pi var newWidth = Math.Max(1, oldWidth >> 1); var newHeight = Math.Max(1, oldHeight >> 1); - // Use a 1D backing array so TryGetMemory() succeeds on all target frameworks var result = new ColorRgba32[newHeight * newWidth]; int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x)); @@ -200,7 +199,6 @@ private static Memory2D ResizeToHalf(ReadOnlySpan2D> 1); var newHeight = Math.Max(1, oldHeight >> 1); - // Use a 1D backing array so TryGetMemory() succeeds on all target frameworks var result = new ColorRgbFloat[newHeight * newWidth]; int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x)); From 6eb5f4b0b9611cb8dc935a5aefb4d0e36bf59cc0 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:10:50 +0200 Subject: [PATCH 20/30] Move most tests to shared test project --- BCnEncTests.Framework/MipMapperTests.cs | 83 +++ BCnEncTests.Framework/Support/TestHelper.cs | 15 + .../AtcTests.cs | 0 .../BC1Tests.cs | 0 .../BCnEncTests.Shared.projitems | 19 + .../Bc5Tests.cs | 0 .../Bc6EncoderTests.cs | 2 +- .../Bc6HDecoderTests.cs | 20 +- .../Bc7BlockTests.cs | 5 +- .../BgraTests.cs | 0 .../CancellationTests.cs | 0 .../ClusterTests.cs | 3 +- .../DdsReadTests.cs | 0 .../DdsWritingTests.cs | 0 .../DecodingAsyncTests.cs | 3 +- .../DecodingTests.cs | 0 .../EncoderOptionsTests.cs | 0 .../EncodingAsyncTest.cs | 6 +- .../EncodingTest.cs | 0 .../ProgressTests.cs | 0 .../RawTests.cs | 48 +- .../SingleBlockTests.cs | 0 BCnEncTests/AtcTests.cs | 108 ---- BCnEncTests/BC1Tests.cs | 164 ------ BCnEncTests/BCnEncTests.csproj | 1 + BCnEncTests/Bc5Tests.cs | 84 --- BCnEncTests/Bc6EncoderTests.cs | 405 -------------- BCnEncTests/Bc6HDecoderTests.cs | 178 ------- BCnEncTests/Bc7BlockTests.cs | 389 -------------- BCnEncTests/BgraTests.cs | 78 --- BCnEncTests/CancellationTests.cs | 28 - BCnEncTests/ClusterTests.cs | 52 -- BCnEncTests/DdsReadTests.cs | 54 -- BCnEncTests/DdsWritingTests.cs | 57 -- BCnEncTests/DecodingAsyncTests.cs | 62 --- BCnEncTests/DecodingTests.cs | 62 --- BCnEncTests/EncodeByteOrderTests.cs | 2 +- BCnEncTests/EncoderOptionsTests.cs | 71 --- BCnEncTests/EncodingAsyncTest.cs | 88 --- BCnEncTests/EncodingTest.cs | 501 ------------------ BCnEncTests/ProgressTests.cs | 392 -------------- BCnEncTests/RawTests.cs | 106 ---- BCnEncTests/SingleBlockTests.cs | 143 ----- BCnEncTests/Support/HdrLoader.cs | 2 +- BCnEncTests/Support/ImageLoader.cs | 47 +- BCnEncTests/Support/TestHelper.cs | 107 +++- 46 files changed, 269 insertions(+), 3116 deletions(-) create mode 100644 BCnEncTests.Framework/MipMapperTests.cs rename {BCnEncTests.Framework => BCnEncTests.Shared}/AtcTests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/BC1Tests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/Bc5Tests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/Bc6EncoderTests.cs (98%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/Bc6HDecoderTests.cs (84%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/Bc7BlockTests.cs (99%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/BgraTests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/CancellationTests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/ClusterTests.cs (91%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/DdsReadTests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/DdsWritingTests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/DecodingAsyncTests.cs (93%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/DecodingTests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/EncoderOptionsTests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/EncodingAsyncTest.cs (90%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/EncodingTest.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/ProgressTests.cs (100%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/RawTests.cs (70%) rename {BCnEncTests.Framework => BCnEncTests.Shared}/SingleBlockTests.cs (100%) delete mode 100644 BCnEncTests/AtcTests.cs delete mode 100644 BCnEncTests/BC1Tests.cs delete mode 100644 BCnEncTests/Bc5Tests.cs delete mode 100644 BCnEncTests/Bc6EncoderTests.cs delete mode 100644 BCnEncTests/Bc6HDecoderTests.cs delete mode 100644 BCnEncTests/Bc7BlockTests.cs delete mode 100644 BCnEncTests/BgraTests.cs delete mode 100644 BCnEncTests/CancellationTests.cs delete mode 100644 BCnEncTests/ClusterTests.cs delete mode 100644 BCnEncTests/DdsReadTests.cs delete mode 100644 BCnEncTests/DdsWritingTests.cs delete mode 100644 BCnEncTests/DecodingAsyncTests.cs delete mode 100644 BCnEncTests/DecodingTests.cs delete mode 100644 BCnEncTests/EncoderOptionsTests.cs delete mode 100644 BCnEncTests/EncodingAsyncTest.cs delete mode 100644 BCnEncTests/EncodingTest.cs delete mode 100644 BCnEncTests/ProgressTests.cs delete mode 100644 BCnEncTests/RawTests.cs delete mode 100644 BCnEncTests/SingleBlockTests.cs diff --git a/BCnEncTests.Framework/MipMapperTests.cs b/BCnEncTests.Framework/MipMapperTests.cs new file mode 100644 index 0000000..8b9e292 --- /dev/null +++ b/BCnEncTests.Framework/MipMapperTests.cs @@ -0,0 +1,83 @@ +using System; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using Xunit; + +namespace BCnEncTests +{ + public class MipMapperTests + { + [Fact] + public void MipChainHasCorrectDimensions() + { + var image = ImageLoader.TestGradient1; // 512x416 + var numMips = 0; + var chain = MipMapper.GenerateMipChain(image, ref numMips); + + Assert.Equal(chain.Length, numMips); + Assert.True(numMips > 1); + + for (var i = 0; i < numMips; i++) + { + Assert.Equal(Math.Max(1, image.Width >> i), chain[i].Width); + Assert.Equal(Math.Max(1, image.Height >> i), chain[i].Height); + } + + // Last level must be 1x1 + Assert.Equal(1, chain[numMips - 1].Width); + Assert.Equal(1, chain[numMips - 1].Height); + } + + /// + /// A solid-color image must downsample to exactly the same color at + /// every mip level, regardless of how many times the box filter is applied. + /// + [Fact] + public void SolidColorMipChainIsExact() + { + var color = new ColorRgba32(200, 100, 50, 255); + var pixels = new ColorRgba32[16 * 16]; + for (var i = 0; i < pixels.Length; i++) pixels[i] = color; + + var image = new Memory2D(pixels, 16, 16); + var numMips = 0; + var chain = MipMapper.GenerateMipChain(image, ref numMips); + + // 16x16 -> 8x8 -> 4x4 -> 2x2 -> 1x1 = 5 levels + Assert.Equal(5, numMips); + + for (var level = 0; level < numMips; level++) + { + var span = chain[level].Span; + for (var y = 0; y < chain[level].Height; y++) + for (var x = 0; x < chain[level].Width; x++) + Assert.Equal(color, span[y, x]); + } + } + + /// + /// Verifies the exact 2x2 box-filter arithmetic with a known input. + /// A 2x2 image of two values repeated both rows should average exactly. + /// + [Fact] + public void KnownPatternDownsamplesCorrectly() + { + // 2-wide, 2-tall: left column = 100 red, right column = 200 red. + // Expected 1x1 average: r = (100+200+100+200)/4 = 150, g=b=0, a=255. + var pixels = new[] + { + new ColorRgba32(100, 0, 0, 255), new ColorRgba32(200, 0, 0, 255), + new ColorRgba32(100, 0, 0, 255), new ColorRgba32(200, 0, 0, 255), + }; + var image = new Memory2D(pixels, 2, 2); + var numMips = 0; + var chain = MipMapper.GenerateMipChain(image, ref numMips); + + Assert.Equal(2, numMips); // 2x2 -> 1x1 + Assert.Equal(1, chain[1].Width); + Assert.Equal(1, chain[1].Height); + Assert.Equal(new ColorRgba32(150, 0, 0, 255), chain[1].Span[0, 0]); + } + } +} diff --git a/BCnEncTests.Framework/Support/TestHelper.cs b/BCnEncTests.Framework/Support/TestHelper.cs index 0a9bcd1..e5e2280 100644 --- a/BCnEncTests.Framework/Support/TestHelper.cs +++ b/BCnEncTests.Framework/Support/TestHelper.cs @@ -254,5 +254,20 @@ public static unsafe void SaveAsPng(Memory2D image, Stream stream) bmp.UnlockBits(data); bmp.Save(stream, ImageFormat.Png); } + + public static void SaveAsPng(ColorRgbFloat[] pixels, int width, int height, Stream stream) + { + var rgba = new ColorRgba32[pixels.Length]; + for (var i = 0; i < pixels.Length; i++) + { + var p = pixels[i]; + byte r = (byte)(Math.Max(0, Math.Min(1, p.r)) * 255 + 0.5f); + byte g = (byte)(Math.Max(0, Math.Min(1, p.g)) * 255 + 0.5f); + byte b = (byte)(Math.Max(0, Math.Min(1, p.b)) * 255 + 0.5f); + rgba[i] = new ColorRgba32(r, g, b, 255); + } + var mem = new Memory2D(rgba, height, width); + SaveAsPng(mem, stream); + } } } diff --git a/BCnEncTests.Framework/AtcTests.cs b/BCnEncTests.Shared/AtcTests.cs similarity index 100% rename from BCnEncTests.Framework/AtcTests.cs rename to BCnEncTests.Shared/AtcTests.cs diff --git a/BCnEncTests.Framework/BC1Tests.cs b/BCnEncTests.Shared/BC1Tests.cs similarity index 100% rename from BCnEncTests.Framework/BC1Tests.cs rename to BCnEncTests.Shared/BC1Tests.cs diff --git a/BCnEncTests.Shared/BCnEncTests.Shared.projitems b/BCnEncTests.Shared/BCnEncTests.Shared.projitems index f25f88b..a221845 100644 --- a/BCnEncTests.Shared/BCnEncTests.Shared.projitems +++ b/BCnEncTests.Shared/BCnEncTests.Shared.projitems @@ -6,10 +6,29 @@ D954291E-2A0B-460D-934E-DC6B0785DB48 + + + + + + + + + + + + + + + + + + + diff --git a/BCnEncTests.Framework/Bc5Tests.cs b/BCnEncTests.Shared/Bc5Tests.cs similarity index 100% rename from BCnEncTests.Framework/Bc5Tests.cs rename to BCnEncTests.Shared/Bc5Tests.cs diff --git a/BCnEncTests.Framework/Bc6EncoderTests.cs b/BCnEncTests.Shared/Bc6EncoderTests.cs similarity index 98% rename from BCnEncTests.Framework/Bc6EncoderTests.cs rename to BCnEncTests.Shared/Bc6EncoderTests.cs index 5fb1f5d..6a5bcad 100644 --- a/BCnEncTests.Framework/Bc6EncoderTests.cs +++ b/BCnEncTests.Shared/Bc6EncoderTests.cs @@ -265,7 +265,7 @@ public void Encode() { var signed = true; var image = HdrLoader.TestHdrKiara; - var blocks = ImageToBlocks.ImageTo4X4(image.pixels.AsMemory().AsMemory2D(image.height, image.width), out var bW, out var bH); + var blocks = ImageToBlocks.ImageTo4X4(new Memory2D(image.pixels, image.height, image.width), out var bW, out var bH); for (var i = 0; i < blocks.Length; i++) { diff --git a/BCnEncTests.Framework/Bc6HDecoderTests.cs b/BCnEncTests.Shared/Bc6HDecoderTests.cs similarity index 84% rename from BCnEncTests.Framework/Bc6HDecoderTests.cs rename to BCnEncTests.Shared/Bc6HDecoderTests.cs index 0453379..75aa7c3 100644 --- a/BCnEncTests.Framework/Bc6HDecoderTests.cs +++ b/BCnEncTests.Shared/Bc6HDecoderTests.cs @@ -24,8 +24,9 @@ public void DecodeDds() var decoder = new BcDecoder(); var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); - var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraDds.header.dwWidth, - (int)HdrLoader.TestHdrKiaraDds.header.dwHeight); + var width = (int)HdrLoader.TestHdrKiaraDds.header.dwWidth; + var height = (int)HdrLoader.TestHdrKiaraDds.header.dwHeight; + var hdr = new HdrImage(width, height); Assert.Equal(hdr.pixels.Length, decoded.Length); @@ -33,6 +34,9 @@ public void DecodeDds() using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr"); hdr.Write(sfs); + using var pngFs = File.OpenWrite("decoding_test_dds_bc6h.png"); + TestHelper.SaveAsPng(decoded, width, height, pngFs); + TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output); } @@ -42,8 +46,9 @@ public void DecodeKtx() var decoder = new BcDecoder(); var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraKtx); - var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth, - (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight); + var width = (int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth; + var height = (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight; + var hdr = new HdrImage(width, height); Assert.Equal(hdr.pixels.Length, decoded.Length); @@ -51,6 +56,9 @@ public void DecodeKtx() using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr"); hdr.Write(sfs); + using var pngFs = File.OpenWrite("decoding_test_ktx_bc6h.png"); + TestHelper.SaveAsPng(decoded, width, height, pngFs); + TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.BestQuality, output); } @@ -63,7 +71,11 @@ public void AllBlocksDecodesExact() var decoder = new BcDecoder(); var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); +#if NETCOREAPP + using var fs = File.OpenRead("../../../testImages/test_hdr_kiara_dds_float16_data.bin"); +#else using var fs = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin"); +#endif using var ms = new MemoryStream(); fs.CopyTo(ms); var length = (int)ms.Position; diff --git a/BCnEncTests.Framework/Bc7BlockTests.cs b/BCnEncTests.Shared/Bc7BlockTests.cs similarity index 99% rename from BCnEncTests.Framework/Bc7BlockTests.cs rename to BCnEncTests.Shared/Bc7BlockTests.cs index c356fb7..a2904da 100644 --- a/BCnEncTests.Framework/Bc7BlockTests.cs +++ b/BCnEncTests.Shared/Bc7BlockTests.cs @@ -6,9 +6,10 @@ using BCnEncoder.Shared; using BCnEncoder.Shared.ImageFiles; using BCnEncTests.Support; -using CommunityToolkit.HighPerformance; using Xunit; +using CommunityToolkit.HighPerformance; + namespace BCnEncTests { public class Bc7BlockTests @@ -85,7 +86,7 @@ public void DecodeErrorBlock() var pixels = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc7); Assert.Contains(new ColorRgba32(255, 0, 255), pixels); - var decoded = pixels.AsMemory().AsMemory2D(height * 4, width * 4); + var decoded = new Memory2D(pixels, height * 4, width * 4); using var fs = File.OpenWrite("test_decode_bc7_error.png"); TestHelper.SaveAsPng(decoded, fs); } diff --git a/BCnEncTests.Framework/BgraTests.cs b/BCnEncTests.Shared/BgraTests.cs similarity index 100% rename from BCnEncTests.Framework/BgraTests.cs rename to BCnEncTests.Shared/BgraTests.cs diff --git a/BCnEncTests.Framework/CancellationTests.cs b/BCnEncTests.Shared/CancellationTests.cs similarity index 100% rename from BCnEncTests.Framework/CancellationTests.cs rename to BCnEncTests.Shared/CancellationTests.cs diff --git a/BCnEncTests.Framework/ClusterTests.cs b/BCnEncTests.Shared/ClusterTests.cs similarity index 91% rename from BCnEncTests.Framework/ClusterTests.cs rename to BCnEncTests.Shared/ClusterTests.cs index c141928..d8c20b8 100644 --- a/BCnEncTests.Framework/ClusterTests.cs +++ b/BCnEncTests.Shared/ClusterTests.cs @@ -2,6 +2,7 @@ using System.IO; using BCnEncoder.Shared; using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; using Xunit; namespace BCnEncTests @@ -44,7 +45,7 @@ public void Clusterize() pixels[i] = pixC[clusters[i]].ToColorRgba32(); } - var result = pixels.AsMemory().AsMemory2D(height, width); + var result = new Memory2D(pixels, height, width); using var fs = File.OpenWrite("test_cluster.png"); TestHelper.SaveAsPng(result, fs); } diff --git a/BCnEncTests.Framework/DdsReadTests.cs b/BCnEncTests.Shared/DdsReadTests.cs similarity index 100% rename from BCnEncTests.Framework/DdsReadTests.cs rename to BCnEncTests.Shared/DdsReadTests.cs diff --git a/BCnEncTests.Framework/DdsWritingTests.cs b/BCnEncTests.Shared/DdsWritingTests.cs similarity index 100% rename from BCnEncTests.Framework/DdsWritingTests.cs rename to BCnEncTests.Shared/DdsWritingTests.cs diff --git a/BCnEncTests.Framework/DecodingAsyncTests.cs b/BCnEncTests.Shared/DecodingAsyncTests.cs similarity index 93% rename from BCnEncTests.Framework/DecodingAsyncTests.cs rename to BCnEncTests.Shared/DecodingAsyncTests.cs index da61e5e..cba775b 100644 --- a/BCnEncTests.Framework/DecodingAsyncTests.cs +++ b/BCnEncTests.Shared/DecodingAsyncTests.cs @@ -5,6 +5,7 @@ using BCnEncoder.Encoder; using BCnEncoder.Shared; using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; using Xunit; namespace BCnEncTests @@ -53,7 +54,7 @@ public async Task DecodeRawAsync() var decoded = await Task.Run(() => { var pixels = decoder.DecodeRaw(rawBytes, mipWidth, mipHeight, CompressionFormat.Bc1); - return pixels.AsMemory().AsMemory2D(mipHeight, mipWidth); + return new Memory2D(pixels, mipHeight, mipWidth); }); TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); diff --git a/BCnEncTests.Framework/DecodingTests.cs b/BCnEncTests.Shared/DecodingTests.cs similarity index 100% rename from BCnEncTests.Framework/DecodingTests.cs rename to BCnEncTests.Shared/DecodingTests.cs diff --git a/BCnEncTests.Framework/EncoderOptionsTests.cs b/BCnEncTests.Shared/EncoderOptionsTests.cs similarity index 100% rename from BCnEncTests.Framework/EncoderOptionsTests.cs rename to BCnEncTests.Shared/EncoderOptionsTests.cs diff --git a/BCnEncTests.Framework/EncodingAsyncTest.cs b/BCnEncTests.Shared/EncodingAsyncTest.cs similarity index 90% rename from BCnEncTests.Framework/EncodingAsyncTest.cs rename to BCnEncTests.Shared/EncodingAsyncTest.cs index a936b7d..88cfb3f 100644 --- a/BCnEncTests.Framework/EncodingAsyncTest.cs +++ b/BCnEncTests.Shared/EncodingAsyncTest.cs @@ -52,7 +52,7 @@ public async Task EncodeCubemapToDdsAsync() { var rawPixels = decoder.DecodeRaw(file.Faces[i].MipMaps[0].Data, (int)file.Faces[i].Width, (int)file.Faces[i].Height, CompressionFormat.Bc1); - var decodedImage = rawPixels.AsMemory().AsMemory2D((int)file.Faces[i].Height, (int)file.Faces[i].Width); + var decodedImage = new Memory2D(rawPixels, (int)file.Faces[i].Height, (int)file.Faces[i].Width); TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality); } @@ -68,7 +68,7 @@ public async Task EncodeCubemapToKtxAsync() { var rawPixels = decoder.DecodeRaw(file.MipMaps[0].Faces[i].Data, (int)file.MipMaps[0].Faces[i].Width, (int)file.MipMaps[0].Faces[i].Height, CompressionFormat.Bc1); - var decodedImage = rawPixels.AsMemory().AsMemory2D( + var decodedImage = new Memory2D(rawPixels, (int)file.MipMaps[0].Faces[i].Height, (int)file.MipMaps[0].Faces[i].Width); TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality); @@ -80,7 +80,7 @@ public async Task EncodeToRawBytesAsync() { var data = await encoder.EncodeToRawBytesAsync(originalImage); var rawPixels = decoder.DecodeRaw(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1); - var image = rawPixels.AsMemory().AsMemory2D(originalImage.Height, originalImage.Width); + var image = new Memory2D(rawPixels, originalImage.Height, originalImage.Width); TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); } diff --git a/BCnEncTests.Framework/EncodingTest.cs b/BCnEncTests.Shared/EncodingTest.cs similarity index 100% rename from BCnEncTests.Framework/EncodingTest.cs rename to BCnEncTests.Shared/EncodingTest.cs diff --git a/BCnEncTests.Framework/ProgressTests.cs b/BCnEncTests.Shared/ProgressTests.cs similarity index 100% rename from BCnEncTests.Framework/ProgressTests.cs rename to BCnEncTests.Shared/ProgressTests.cs diff --git a/BCnEncTests.Framework/RawTests.cs b/BCnEncTests.Shared/RawTests.cs similarity index 70% rename from BCnEncTests.Framework/RawTests.cs rename to BCnEncTests.Shared/RawTests.cs index d2a572a..e19d3f9 100644 --- a/BCnEncTests.Framework/RawTests.cs +++ b/BCnEncTests.Shared/RawTests.cs @@ -1,6 +1,4 @@ using System; -using System.Drawing; -using System.Drawing.Imaging; using System.IO; using BCnEncoder.Decoder; using BCnEncoder.Encoder; @@ -25,7 +23,7 @@ public void EncodeDecode() var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); var decodedPixels = decoder.DecodeRaw(encodedRawBytes[0], inputImage.Width, inputImage.Height, CompressionFormat.Bc1); - var decodedImage = decodedPixels.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width); + var decodedImage = new Memory2D(decodedPixels, inputImage.Height, inputImage.Width); var originalColors = TestHelper.GetSinglePixelArrayAsColors(inputImage); var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage); @@ -50,7 +48,7 @@ public void EncodeDecodeStream() Assert.Equal(0, ms.Position); var rawPixels = decoder.DecodeRaw(ms, inputImage.Width, inputImage.Height, CompressionFormat.Bc1); - var decodedImage = rawPixels.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width); + var decodedImage = new Memory2D(rawPixels, inputImage.Height, inputImage.Width); var originalColors = TestHelper.GetSinglePixelArrayAsColors(inputImage); var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage); @@ -87,11 +85,12 @@ public void EncodeDecodeAllMipMapsStream() ms.Position = 0; Assert.Equal(0, ms.Position); + var resized = ResizeImageToMips(inputImage); + for (var i = 0; i < mipLevels; i++) { encoder.CalculateMipMapSize(inputImage.Width, inputImage.Height, i, out var mipWidth, out var mipHeight); - var resized = ResizeImage(inputImage, mipWidth, mipHeight); var blockSize = decoder.GetBlockSize(CompressionFormat.Bc1); var blockCount = decoder.GetBlockCount(mipWidth, mipHeight); @@ -99,9 +98,9 @@ public void EncodeDecodeAllMipMapsStream() ms.Read(buffer, 0, buffer.Length); var rawPixels = decoder.DecodeRaw(buffer, mipWidth, mipHeight, CompressionFormat.Bc1); - var decodedImage = rawPixels.AsMemory().AsMemory2D(mipHeight, mipWidth); + var decodedImage = new Memory2D(rawPixels, mipHeight, mipWidth); - var originalColors = TestHelper.GetSinglePixelArrayAsColors(resized); + var originalColors = TestHelper.GetSinglePixelArrayAsColors(resized[i]); var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage); TestHelper.AssertPixelsEqual(originalColors, decodedColors, encoder.OutputOptions.Quality); @@ -112,42 +111,11 @@ public void EncodeDecodeAllMipMapsStream() Assert.Equal(1, lastMHeight); } - private static ReadOnlyMemory2D ResizeImage(Memory2D image, int newWidth, int newHeight) + private static ReadOnlyMemory2D[] ResizeImageToMips(Memory2D image) { int numMips = 0; var chain = MipMapper.GenerateMipChain(image, ref numMips); - - foreach (var memory2D in chain) - { - if (memory2D.Width == newWidth && memory2D.Height == newHeight) - { - return memory2D; - } - } - throw new InvalidOperationException("Cannot resize image to non-mip dimensions"); - } - - private static unsafe Bitmap ToBitmap(Memory2D image) - { - var bmp = new Bitmap(image.Width, image.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - var data = bmp.LockBits(new Rectangle(0, 0, image.Width, image.Height), - ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - byte* ptr = (byte*)data.Scan0; - var span = image.Span; - for (int y = 0; y < image.Height; y++) - { - for (int x = 0; x < image.Width; x++) - { - var c = span[y, x]; - ptr[0] = c.b; - ptr[1] = c.g; - ptr[2] = c.r; - ptr[3] = c.a; - ptr += 4; - } - } - bmp.UnlockBits(data); - return bmp; + return chain; } } } diff --git a/BCnEncTests.Framework/SingleBlockTests.cs b/BCnEncTests.Shared/SingleBlockTests.cs similarity index 100% rename from BCnEncTests.Framework/SingleBlockTests.cs rename to BCnEncTests.Shared/SingleBlockTests.cs diff --git a/BCnEncTests/AtcTests.cs b/BCnEncTests/AtcTests.cs deleted file mode 100644 index d02da3e..0000000 --- a/BCnEncTests/AtcTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; -using BCnEncoder.ImageSharp; - -namespace BCnEncTests -{ - public class AtcTests - { - [Fact] - public void AtcKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Atc); - var original = ImageLoader.TestLenna; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Atc); - var original = ImageLoader.TestLenna; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcExplicitKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcExplicitDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcInterpolatedKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void AtcInterpolatedDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - } -} diff --git a/BCnEncTests/BC1Tests.cs b/BCnEncTests/BC1Tests.cs deleted file mode 100644 index bfc7320..0000000 --- a/BCnEncTests/BC1Tests.cs +++ /dev/null @@ -1,164 +0,0 @@ -using BCnEncoder.Shared; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using BCnEncoder.ImageSharp; - -namespace BCnEncTests -{ - public class Bc1Tests - { - - [Fact] - public void Decode() { - var block = new Bc1Block - { - color0 = new ColorRgb565(255, 255, 255), - color1 = new ColorRgb565(0, 0, 0) - }; - - Assert.False(block.HasAlphaOrBlack); - block[0] = 1; - block[1] = 1; - block[2] = 1; - block[3] = 1; - - block[4] = 3; - block[5] = 3; - block[6] = 3; - block[7] = 3; - - block[8] = 2; - block[9] = 2; - block[10] = 2; - block[11] = 2; - - block[12] = 0; - block[13] = 0; - block[14] = 0; - block[15] = 0; - - var raw = block.Decode(false); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p00); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p10); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p20); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p30); - - Assert.Equal(new ColorRgba32(85, 85, 85), raw.p01); - Assert.Equal(new ColorRgba32(85, 85, 85), raw.p11); - Assert.Equal(new ColorRgba32(85, 85, 85), raw.p21); - Assert.Equal(new ColorRgba32(85, 85, 85), raw.p31); - - Assert.Equal(new ColorRgba32(170, 170, 170), raw.p02); - Assert.Equal(new ColorRgba32(170, 170, 170), raw.p12); - Assert.Equal(new ColorRgba32(170, 170, 170), raw.p22); - Assert.Equal(new ColorRgba32(170, 170, 170), raw.p32); - - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); - } - - [Fact] - public void DecodeBlack() { - var block = new Bc1Block - { - color0 = new ColorRgb565(200, 200, 200), - color1 = new ColorRgb565(255, 255, 255) - }; - - Assert.True(block.HasAlphaOrBlack); - block[0] = 0; - block[1] = 0; - block[2] = 0; - block[3] = 0; - - block[4] = 3; - block[5] = 3; - block[6] = 3; - block[7] = 3; - - block[8] = 2; - block[9] = 2; - block[10] = 2; - block[11] = 2; - - block[12] = 1; - block[13] = 1; - block[14] = 1; - block[15] = 1; - - var raw = block.Decode(false); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30); - - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p01); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p11); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p21); - Assert.Equal(new ColorRgba32(0, 0, 0), raw.p31); - - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32); - - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); - } - - [Fact] - public void DecodeAlpha() { - var block = new Bc1Block - { - color0 = new ColorRgb565(200, 200, 200), - color1 = new ColorRgb565(255, 255, 255) - }; - - Assert.True(block.HasAlphaOrBlack); - block[0] = 0; - block[1] = 0; - block[2] = 0; - block[3] = 0; - - block[4] = 3; - block[5] = 3; - block[6] = 3; - block[7] = 3; - - block[8] = 2; - block[9] = 2; - block[10] = 2; - block[11] = 2; - - block[12] = 1; - block[13] = 1; - block[14] = 1; - block[15] = 1; - - var raw = block.Decode(true); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20); - Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30); - - Assert.Equal(new ColorRgba32(0,0,0,0), raw.p01); - Assert.Equal(new ColorRgba32(0,0,0,0), raw.p11); - Assert.Equal(new ColorRgba32(0,0,0,0), raw.p21); - Assert.Equal(new ColorRgba32(0,0,0,0), raw.p31); - - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22); - Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32); - - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23); - Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33); - } - } -} diff --git a/BCnEncTests/BCnEncTests.csproj b/BCnEncTests/BCnEncTests.csproj index fabc4d1..74aa454 100644 --- a/BCnEncTests/BCnEncTests.csproj +++ b/BCnEncTests/BCnEncTests.csproj @@ -7,6 +7,7 @@ + diff --git a/BCnEncTests/Bc5Tests.cs b/BCnEncTests/Bc5Tests.cs deleted file mode 100644 index 7eb37ff..0000000 --- a/BCnEncTests/Bc5Tests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using BCnEncoder.Decoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class Bc5Tests - { - [Fact] - public void Bc5Indices() - { - var block = new Bc5Block(); - - for (var i = 0; i < 16; i++) - { - block.SetRedIndex(i, (byte)(i % 8)); - block.SetGreenIndex(i, (byte)((i + 3) % 8)); - } - - for (var i = 0; i < 16; i++) - { - int rI = block.GetRedIndex(i); - int gI = block.GetGreenIndex(i); - - Assert.Equal((byte)(i % 8), rI); - Assert.Equal((byte)((i + 3) % 8), gI); - } - } - - [Fact] - public void Bc5DdsDecode() - { - var reference = ImageLoader.TestDecodingBc5Reference; - var decoded = new BcDecoder().DecodeToImageRgba32(DdsLoader.TestDecompressBc5); - - var refSpan = TestHelper.GetSinglePixelArrayAsColors(reference); - var decSpan = TestHelper.GetSinglePixelArrayAsColors(decoded); - - Assert.Equal(reference.Width, decoded.Width); - Assert.Equal(reference.Height, decoded.Height); - - // Exactly equal - for (var i = 0; i < reference.Width * reference.Height; i++) - Assert.Equal(refSpan[i], decSpan[i]); - } - - [Fact] - public void Bc5BlockDecode() - { - var block = new Bc5Block() - { - greenBlock = new Bc4ComponentBlock() { componentBlock = 0x91824260008935ee }, - redBlock = new Bc4ComponentBlock() { componentBlock = 0x6d900f66d3c0a70d } - }; - - var referenceBlock = new RawBlock4X4Rgba32 - { - p00 = new ColorRgba32(13, 53, 0, 255), - p01 = new ColorRgba32(136, 238, 0, 255), - p02 = new ColorRgba32(255, 212, 0, 255), - p03 = new ColorRgba32(167, 238, 0, 255), - p10 = new ColorRgba32(13, 53, 0, 255), - p11 = new ColorRgba32(136, 238, 0, 255), - p12 = new ColorRgba32(167, 238, 0, 255), - p13 = new ColorRgba32(75, 185, 0, 255), - p20 = new ColorRgba32(255, 212, 0, 255), - p21 = new ColorRgba32(167, 238, 0, 255), - p22 = new ColorRgba32(13, 53, 0, 255), - p23 = new ColorRgba32(75, 159, 0, 255), - p30 = new ColorRgba32(167, 238, 0, 255), - p31 = new ColorRgba32(75, 185, 0, 255), - p32 = new ColorRgba32(13, 53, 0, 255), - p33 = new ColorRgba32(75, 159, 0, 255) - }; - - var decodedBlock = block.Decode(); - - for (var i = 0; i < 16; i++) - Assert.Equal(referenceBlock[i], decodedBlock[i]); - } - } -} diff --git a/BCnEncTests/Bc6EncoderTests.cs b/BCnEncTests/Bc6EncoderTests.cs deleted file mode 100644 index 7352f86..0000000 --- a/BCnEncTests/Bc6EncoderTests.cs +++ /dev/null @@ -1,405 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using BCnEncoder.Encoder; -using BCnEncoder.Encoder.Bptc; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using CommunityToolkit.HighPerformance; -using Xunit; -using Xunit.Abstractions; - -namespace BCnEncTests -{ - public class Bc6EncoderTests - { - private ITestOutputHelper output; - - public Bc6EncoderTests(ITestOutputHelper output) => this.output = output; - - [Theory] - [InlineData(0x7F, 8, false)] - [InlineData(0xFF, 8, false)] - [InlineData(0x7F, 8, true)] - [InlineData(0xFF, 8, true)] - [InlineData(0b111_1100_1101, 11, true)] - [InlineData(0b111_1100_1101, 11, false)] - [InlineData(0b011_1100_1101, 11, true)] - [InlineData(0b011_1100_1101, 11, false)] - [InlineData(0b0_1001, 5, true)] - [InlineData(0b0_1001, 5, false)] - [InlineData(0b1_1100, 5, true)] - [InlineData(0b1_1100, 5, false)] - public void Quantize(int initialQuantizedValue, int endpointBits, bool signed) - { - if (signed) - { - initialQuantizedValue = IntHelper.SignExtend(initialQuantizedValue, endpointBits); - } - var unquantized = Bc6Block.UnQuantize(initialQuantizedValue, endpointBits, signed); - var finishedUnquantized = Bc6Block.FinishUnQuantize(unquantized, signed); - - var prequantized = Bc6EncodingHelpers.PreQuantize(finishedUnquantized, signed); - var quantized = Bc6EncodingHelpers.Quantize(prequantized, endpointBits, signed); - - Assert.InRange(prequantized, unquantized - 2, unquantized + 2); - Assert.InRange(quantized, initialQuantizedValue - 1, initialQuantizedValue + 1); - } - - [Fact] - public void PreQuantizeRawEndpoint() - { - var ep0 = new ColorRgbFloat(1, 0.001f, 0.2f); - - var preQuantized = Bc6EncodingHelpers.PreQuantizeRawEndpoint(ep0, false); - var unQu = Bc6Block.FinishUnQuantize(preQuantized, false); - - Assert.InRange(unQu.Item1, ep0.r - 0.001f, ep0.r + 0.001f); - Assert.InRange(unQu.Item2, ep0.g - 0.001f, ep0.g + 0.001f); - Assert.InRange(unQu.Item3, ep0.b - 0.001f, ep0.b + 0.001f); - } - - [Fact] - public void RgbBoundingBox() - { - var testBlock = new RawBlock4X4RgbFloat() - { - p00 = new ColorRgbFloat(0.01f, -0.05f, 0.02f), - p01 = new ColorRgbFloat(0.02f, 0.02f, 0.03f), - p02 = new ColorRgbFloat(0.02f, 0.02f, 0.02f), - p03 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), - p10 = new ColorRgbFloat(0.02f, 0.02f, 0.02f), - p11 = new ColorRgbFloat(0.045f, 0.02f, 0.02f), - p12 = new ColorRgbFloat(0.04f, 0.02f, 0.02f), - p13 = new ColorRgbFloat(0.04f, 0.02f, 0.02f), - p20 = new ColorRgbFloat(0.21f, 0.02f, 0.02f), - p21 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), - p22 = new ColorRgbFloat(0.01f, 0.32f, 0.02f), - p23 = new ColorRgbFloat(1.01f, 0.22f, 0.02f), - p30 = new ColorRgbFloat(0.01f, 0.12f, 0.02f), - p31 = new ColorRgbFloat(0.01f, 0.02f, 0.02f), - p32 = new ColorRgbFloat(0.01f, 0.62f, 0.7f), - p33 = new ColorRgbFloat(0.01f, 0.02f, 0.02f) - }; - - BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); - - Assert.InRange(min.r, 0.01, 0.02); - Assert.InRange(min.g, -0.05, -0.03); - Assert.InRange(min.b, 0.02, 0.03); - - Assert.InRange(max.r, 0.9, 1.01); - Assert.InRange(max.g, 0.59, 0.62); - Assert.InRange(max.b, 0.60, 0.7); - } - - - [Fact] - public void PackMode3() - { - const int endpointPrecision = 10; - var random = new Random(); - var rand = random.Next(); - - var ep0 = (random.Next() & (1 << endpointPrecision) - 1, - random.Next() & (1 << endpointPrecision) - 1, - random.Next() & (1 << endpointPrecision) - 1); - var ep1 = (random.Next() & (1 << endpointPrecision) - 1, - random.Next() & (1 << endpointPrecision) - 1, - random.Next() & (1 << endpointPrecision) - 1); - - var indices = new byte[16]; - for (var i = 1; i < indices.Length; i++) - { - indices[i] = (byte) random.Next(1 << 4); - } - - var block = Bc6Block.PackType3(ep0, ep1, indices); - - Assert.Equal(Bc6BlockType.Type3, block.Type); - - var extracted0 = block.ExtractEp0(); - var extracted1 = block.ExtractEp1(); - - Assert.Equal(ep0, extracted0); - Assert.Equal(ep1, extracted1); - - for (var i = 0; i < indices.Length; i++) - { - var idx = block.GetColorIndex(1, 0, 4, i); - Assert.Equal(indices[i], idx); - } - } - - - [Theory] - [InlineData(Bc6BlockType.Type0)] - [InlineData(Bc6BlockType.Type1)] - [InlineData(Bc6BlockType.Type2)] - [InlineData(Bc6BlockType.Type6)] - [InlineData(Bc6BlockType.Type10)] - [InlineData(Bc6BlockType.Type14)] - [InlineData(Bc6BlockType.Type18)] - [InlineData(Bc6BlockType.Type22)] - [InlineData(Bc6BlockType.Type26)] - [InlineData(Bc6BlockType.Type30)] - [InlineData(Bc6BlockType.Type3)] - [InlineData(Bc6BlockType.Type7)] - [InlineData(Bc6BlockType.Type11)] - //[InlineData(Bc6BlockType.Type15)] //deltabits too small to encode the testblock - internal void EncodeAllModesUnsigned(Bc6BlockType type) - { - var testBlock = new RawBlock4X4RgbFloat() - { - p00 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), - p01 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), - p02 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), - p03 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), - p10 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), - p11 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), - p12 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), - p13 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), - p20 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), - p21 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), - p22 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), - p23 = new ColorRgbFloat(1.01f, 10.013f, 2.023f), - p30 = new ColorRgbFloat(1.011f, 10.01f, 2.01f), - p31 = new ColorRgbFloat(1.01f, 10.014f, 2.012f), - p32 = new ColorRgbFloat(1.005f, 10.012f, 2.02f), - p33 = new ColorRgbFloat(1.01f, 10.013f, 2.023f) - }; - Bc6Block encoded; - var badTransform = false; - - if(type.HasSubsets()) - { - var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2); - var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true); - - var bestPartition = best2SubsetPartitions[0]; - - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0); - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1); - - encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition, - false, out badTransform); - } - else - { - BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); - encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, false, - out badTransform); - } - - Assert.False(badTransform); - Assert.Equal(type, encoded.Type); - var decoded = encoded.Decode(false); - var error = testBlock.CalculateError(decoded); - Assert.InRange(error, 0, 0.3); - } - - [Theory] - [InlineData(Bc6BlockType.Type0)] - [InlineData(Bc6BlockType.Type1)] - [InlineData(Bc6BlockType.Type2)] - [InlineData(Bc6BlockType.Type6)] - [InlineData(Bc6BlockType.Type10)] - [InlineData(Bc6BlockType.Type14)] - [InlineData(Bc6BlockType.Type18)] - [InlineData(Bc6BlockType.Type22)] - [InlineData(Bc6BlockType.Type26)] - [InlineData(Bc6BlockType.Type30)] - [InlineData(Bc6BlockType.Type3)] - [InlineData(Bc6BlockType.Type7)] - [InlineData(Bc6BlockType.Type11)] - [InlineData(Bc6BlockType.Type15)] - internal void EncodeAllModesSigned(Bc6BlockType type) - { - var testBlock = new RawBlock4X4RgbFloat() - { - p00 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), - p01 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), - p02 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), - p03 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), - p10 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), - p11 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), - p12 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), - p13 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), - p20 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), - p21 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), - p22 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), - p23 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f), - p30 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f), - p31 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f), - p32 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f), - p33 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f) - }; - Bc6Block encoded; - var badTransform = false; - - if (type.HasSubsets()) - { - var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2); - var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true); - - var bestPartition = best2SubsetPartitions[0]; - - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0); - Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1); - - encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition, - true, out badTransform); - } - else - { - BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max); - encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, true, - out badTransform); - } - - Assert.False(badTransform); - Assert.Equal(type, encoded.Type); - var decoded = encoded.Decode(true); - var error = testBlock.CalculateError(decoded); - Assert.InRange(error, 0, 0.5); - } - - [Fact] - public void Encode() - { - var signed = true; - var image = HdrLoader.TestHdrKiara; - var blocks = ImageToBlocks.ImageTo4X4(image.pixels.AsMemory().AsMemory2D(image.height, image.width), out var bW, out var bH); - - for (var i = 0; i < blocks.Length; i++) - { - var encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed); - var decoded = encoded.Decode(signed); - - var error = decoded.CalculateError(blocks[i]); - if (error > 0.06 || i == 14749) - { - Debugger.Break(); - encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed); - } - } - } - - [Fact] - public void EncodeFast() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Fast, - "encoding_bc6_kiara_fast.ktx", output); - } - - [Fact] - public void EncodeBalanced() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Balanced, - "encoding_bc6_kiara_balanced.ktx", output); - } - - [Fact] - public void EncodeBestQuality() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.BestQuality, - "encoding_bc6_kiara_bestquality.ktx", output); - } - - [Fact] - public void EncodeProbeFast() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Fast, - "encoding_bc6_probe_fast.ktx", output); - } - - [Fact] - public void EncodeProbeBalanced() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Balanced, - "encoding_bc6_probe_balanced.ktx", output); - } - - [Fact] - public void EncodeProbeBestQuality() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.BestQuality, - "encoding_bc6_probe_bestquality.ktx", output); - } - - [Fact] - public void EncodeProbeSignedFast() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Fast, - "encoding_bc6_probe_signed_fast.ktx", output); - } - - [Fact] - public void EncodeProbSignedBalanced() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Balanced, - "encoding_bc6_probe_signed_balanced.ktx", output); - } - - [Fact] - public void EncodeProbeSignedBestQuality() - { - TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.BestQuality, - "encoding_bc6_probe_signed_bestquality.ktx", output); - } - - [Fact] - public void EncodeToKtx() - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = CompressionFormat.Bc6U; - - var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory); - - using var fs = File.OpenWrite("encoding_bc6_ktx.ktx"); - ktx.Write(fs); - } - - [Fact] - public void EncodeToDds() - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = CompressionFormat.Bc6U; - - var dds = encoder.EncodeToDdsHdr(HdrLoader.TestHdrKiara.PixelMemory); - - using var fs = File.OpenWrite("encoding_bc6_dds.dds"); - dds.Write(fs); - } - - [Fact] - public void EncodeToRaw() - { - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = CompressionFormat.Bc6U; - - var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory); - - var allMips = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory); - - Assert.True(allMips.Length > 1); - Assert.True(allMips.Length == ktx.MipMaps.Count); - - for (var i = 0; i < allMips.Length; i++) - { - var single = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory, i, out var mW, out var mH); - - Assert.Equal(ktx.MipMaps[i].Faces[0].Data, single); - Assert.Equal(allMips[i], single); - } - } - } -} diff --git a/BCnEncTests/Bc6HDecoderTests.cs b/BCnEncTests/Bc6HDecoderTests.cs deleted file mode 100644 index 60a99fa..0000000 --- a/BCnEncTests/Bc6HDecoderTests.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using CommunityToolkit.HighPerformance; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using Xunit.Abstractions; -using Half = BCnEncoder.Shared.Half; - -namespace BCnEncTests -{ - public class Bc6HDecoderTests - { - private ITestOutputHelper output; - - public Bc6HDecoderTests(ITestOutputHelper output) => this.output = output; - - [Fact] - public void DecodeDds() - { - var decoder = new BcDecoder(); - var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); - - var img = new Image((int)HdrLoader.TestHdrKiaraDds.header.dwWidth, - (int)HdrLoader.TestHdrKiaraDds.header.dwHeight); - - var pixels = TestHelper.GetSinglePixelArray(img); - - for (var i = 0; i < decoded.Length; i++) - { - pixels[i] = new RgbaVector(decoded[i].r, decoded[i].g, decoded[i].b); - } - - TestHelper.SetSinglePixelArray(img, pixels); - - var hdr = new HdrImage((int) HdrLoader.TestHdrKiaraDds.header.dwWidth, - (int) HdrLoader.TestHdrKiaraDds.header.dwHeight); - - Assert.Equal(hdr.pixels.Length, decoded.Length); - - hdr.pixels = decoded; - using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr"); - hdr.Write(sfs); - - img.SaveAsPng("decoding_test_dds_bc6h.png"); - - TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output); - } - - [Fact] - public void DecodeKtx() - { - var decoder = new BcDecoder(); - var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraKtx); - - var img = new Image((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth, - (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight); - - var pixels = TestHelper.GetSinglePixelArray(img); - - for (var i = 0; i < decoded.Length; i++) - { - pixels[i] = new RgbaVector(decoded[i].r, decoded[i].g, decoded[i].b); - } - - TestHelper.SetSinglePixelArray(img, pixels); - - var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth, - (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight); - - Assert.Equal(hdr.pixels.Length, decoded.Length); - - hdr.pixels = decoded; - using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr"); - hdr.Write(sfs); - - img.SaveAsPng("decoding_test_ktx_bc6h.png"); - - TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.BestQuality, output); - } - - // TestHdrKiaraDds includes some blocks with all modes. - // The test_hdr_kiara_dds_float16_data.bin file contains Half-float values decoded with a reference decoder. - // This test ensures that decoding is bit-exact. - [Fact] - public void AllBlocksDecodesExact() - { - var decoder = new BcDecoder(); - var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); - - using var fs = File.OpenRead("../../../testImages/test_hdr_kiara_dds_float16_data.bin"); - using var ms = new MemoryStream(); - fs.CopyTo(ms); - var length = (int)ms.Position; - - var bytes = ms.GetBuffer().AsSpan(0, length); - var halfs = MemoryMarshal.Cast(bytes); - Assert.Equal(halfs.Length / 4, decoded.Length); - - for (var i = 0; i < decoded.Length; i++) - { - float r = halfs[i * 4 + 0]; - float g = halfs[i * 4 + 1]; - float b = halfs[i * 4 + 2]; - - Assert.Equal(r, decoded[i].r); - Assert.Equal(g, decoded[i].g); - Assert.Equal(b, decoded[i].b); - } - } - - // Test that above statement holds true. And that modes are recognized correctly - [Fact] - public void DecodeModes() - { - var modes = new int[31]; - - var hdr = HdrLoader.TestHdrKiaraDds; - - var bytes = hdr.Faces[0].MipMaps[0].Data; - var blocks = MemoryMarshal.Cast(bytes); - - for (var i = 0; i < blocks.Length; i++) - { - var block = blocks[i]; - var mode = block.Type; - modes[(int) mode]++; - } - - for (var i = 0; i < modes.Length; i++) - { - output.WriteLine($"Mode {i}: {modes[i]}"); - } - - Assert.True(modes[0] > 0); - Assert.True(modes[1] > 0); - Assert.True(modes[2] > 0); - Assert.True(modes[6] > 0); - Assert.True(modes[10] > 0); - Assert.True(modes[14] > 0); - Assert.True(modes[18] > 0); - Assert.True(modes[22] > 0); - Assert.True(modes[26] > 0); - Assert.True(modes[30] > 0); - Assert.True(modes[3] > 0); - Assert.True(modes[7] > 0); - Assert.True(modes[11] > 0); - Assert.True(modes[15] > 0); - } - - [Fact] - public void DecodeErrorBlock() - { - var decoder = new BcDecoder(); - - var width = 16; - var height = 16; - var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc6U) * width * height; - - var buffer = new byte[bufferSize]; - Random r = new Random(44); - r.NextBytes(buffer); - - var decoded = decoder.DecodeRawHdr(buffer, width * 4, height * 4, CompressionFormat.Bc6U); - Assert.Contains(new ColorRgbFloat(1, 0, 1), decoded); - - HdrImage image = new HdrImage(new Span2D(decoded, height * 4, width * 4)); - using var fs = File.OpenWrite("test_decode_bc6h_error.hdr"); - image.Write(fs); - } - - } -} diff --git a/BCnEncTests/Bc7BlockTests.cs b/BCnEncTests/Bc7BlockTests.cs deleted file mode 100644 index c05fdc6..0000000 --- a/BCnEncTests/Bc7BlockTests.cs +++ /dev/null @@ -1,389 +0,0 @@ -using System; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using BCnEncoder.Decoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using SixLabors.ImageSharp; -using Xunit; - -namespace BCnEncTests -{ - public class Bc7BlockTests - { - [Fact] - public void PackTypes() - { - var numWidthBlocks = 4; - var numHeightBlocks = 2; - var outputBlocks = new Bc7Block[64 * numWidthBlocks * numHeightBlocks]; - var encoded = new byte[64 * numWidthBlocks * numHeightBlocks * Unsafe.SizeOf()]; - - var output = new KtxFile( - KtxHeader.InitializeCompressed(numWidthBlocks * 8 * 4, numHeightBlocks * 8 * 4, - GlInternalFormat.GlCompressedRgbaBptcUnormArb, - GlFormat.GlRgba)); - - var type0 = new Span(outputBlocks, 0, 64); - Type0Pack(type0); - PlaceBlock(0, 0, type0, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); - - var type1 = new Span(outputBlocks, 64 * 1, 64); - Type1Pack(type1); - PlaceBlock(1, 0, type1, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); - - var type2 = new Span(outputBlocks, 64 * 2, 64); - Type2Pack(type2); - PlaceBlock(2, 0, type2, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); - - var type3 = new Span(outputBlocks, 64 * 3, 64); - Type3Pack(type3); - PlaceBlock(3, 0, type3, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); - - var type4 = new Span(outputBlocks, 64 * 4, 64); - Type4Pack(type4); - PlaceBlock(0, 1, type4, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); - - var type5 = new Span(outputBlocks, 64 * 5, 64); - Type5Pack(type5); - PlaceBlock(1, 1, type5, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); - - var type6 = new Span(outputBlocks, 64 * 6, 64); - Type6Pack(type6); - PlaceBlock(2, 1, type6, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); - - var type7 = new Span(outputBlocks, 64 * 7, 64); - Type7Pack(type7); - PlaceBlock(3, 1, type7, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks); - - output.MipMaps.Add(new KtxMipmap((uint)encoded.Length, - (uint)(8 * 4 * numWidthBlocks), - (uint)(8 * 4 * numHeightBlocks), 1)); - output.MipMaps[0].Faces[0] = new KtxMipFace(encoded, - (uint)(8 * 4 * numWidthBlocks), - (uint)(8 * 4 * numHeightBlocks)); - - var fs = File.OpenWrite("bc7_blocktests.ktx"); - output.Write(fs); - } - - [Fact] - public void DecodeErrorBlock() - { - var decoder = new BcDecoder(); - - var width = 32; - var height = 32; - var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc7) * width * height; - - var buffer = new byte[bufferSize]; - Random r = new Random(50); - r.NextBytes(buffer); - - var pixels = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc7); - var decoded = decoder.DecodeRawToImageRgba32(buffer, width * 4, height * 4, CompressionFormat.Bc7); - Assert.Contains(new ColorRgba32(255, 0, 255), pixels); - - using var fs = File.OpenWrite("test_decode_bc7_error.png"); - decoded.SaveAsPng(fs); - } - - #region Type Packs - - private void Type0Pack(Span output) - { - var subsetEndpoints = new[] { - //subset 1 - new byte[]{0b1111, 0, 0}, - new byte[]{0b1000, 0, 0}, - // subset 2 - new byte[]{0, 0b1111, 0}, - new byte[]{0, 0b1000, 0}, - //subset 3 - new byte[]{0, 0, 0b1111}, - new byte[]{0, 0, 0b1000} - }; - var pBits = new byte[] { - 1, 0, 1, 0, 1, 1 - }; - var indices = new byte[] { - 0, 1, 2, 3, - 0, 1, 2, 3, - 3, 2, 1, 0, - 3, 2, 1, 0 - }; - for (var i = 0; i < 16; i++) - { - output[i].PackType0(i, subsetEndpoints, pBits, indices); - Assert.Equal(Bc7BlockType.Type0, output[i].Type); - Assert.Equal(i, output[i].PartitionSetId); - } - - pBits = new byte[] { - 0, 0, 0, 0, 0, 0 - }; - indices = new byte[] { - 0, 1, 0, 3, - 1, 1, 1, 3, - 2, 2, 2, 0, - 3, 2, 3, 0 - }; - - for (var i = 0; i < 16; i++) - { - output[i + 16].PackType0(i, subsetEndpoints, pBits, indices); - Assert.Equal(Bc7BlockType.Type0, output[i].Type); - Assert.Equal(i, output[i].PartitionSetId); - } - - pBits = new byte[] { - 1, 1, 1, 1, 1, 1 - }; - indices = new byte[] { - 1, 0, 3, 3, - 2, 1, 2, 3, - 3, 2, 1, 0, - 0, 3, 0, 0 - }; - - for (var i = 0; i < 16; i++) - { - output[i + 32].PackType0(i, subsetEndpoints, pBits, indices); - Assert.Equal(Bc7BlockType.Type0, output[i].Type); - Assert.Equal(i, output[i].PartitionSetId); - } - - pBits = new byte[] { - 0, 1, 0, 1, 0, 0 - }; - indices = new byte[] { - 3, 2, 1, 0, - 0, 1, 2, 3, - 3, 2, 1, 0, - 0, 1, 2, 3 - }; - - for (var i = 0; i < 16; i++) - { - output[i + 48].PackType0(i, subsetEndpoints, pBits, indices); - Assert.Equal(Bc7BlockType.Type0, output[i].Type); - Assert.Equal(i, output[i].PartitionSetId); - } - } - - private void Type1Pack(Span output) - { - var subsetEndpoints = new[] { - //subset 1 - new byte[]{0b111111, 0b0100, 0}, - new byte[]{0b0100, 0b111111, 0}, - // subset 2 - new byte[]{0, 0, 0b111111}, - new byte[]{0, 0, 0b1010} - }; - var pBits = new byte[] { - 1, 1 - }; - var indices = new byte[] { - 0, 1, 2, 3, - 0, 1, 2, 3, - 3, 2, 1, 0, - 3, 2, 1, 0 - }; - for (var i = 0; i < 64; i++) - { - output[i].PackType1(i, subsetEndpoints, pBits, indices); - Assert.Equal(Bc7BlockType.Type1, output[i].Type); - Assert.Equal(i, output[i].PartitionSetId); - } - } - - private void Type2Pack(Span output) - { - var subsetEndpoints = new[] { - //subset 1 - new byte[]{0b11111, 0, 0}, - new byte[]{0b01000, 0, 0}, - // subset 2 - new byte[]{0, 0b11111, 0}, - new byte[]{0, 0b01000, 0}, - //subset 3 - new byte[]{0, 0, 0b11111}, - new byte[]{0, 0, 0b01000} - }; - var indices = new byte[] { - 0, 1, 2, 3, - 0, 1, 2, 3, - 3, 2, 1, 0, - 3, 2, 1, 0 - }; - for (var i = 0; i < 64; i++) - { - output[i].PackType2(i, subsetEndpoints, indices); - Assert.Equal(Bc7BlockType.Type2, output[i].Type); - Assert.Equal(i, output[i].PartitionSetId); - } - } - - private void Type3Pack(Span output) - { - var subsetEndpoints = new[] { - //subset 1 - new byte[]{0b1111111, 0b10100, 0}, - new byte[]{0b10100, 0b1111111, 0}, - // subset 2 - new byte[]{0, 0, 0b1111111}, - new byte[]{0, 0, 0b11010} - }; - var pBits = new byte[] { - 1, 0, 0, 1 - }; - var indices = new byte[] { - 0, 1, 2, 3, - 0, 1, 2, 3, - 3, 2, 1, 0, - 3, 2, 1, 0 - }; - for (var i = 0; i < 64; i++) - { - output[i].PackType3(i, subsetEndpoints, pBits, indices); - Assert.Equal(Bc7BlockType.Type3, output[i].Type); - Assert.Equal(i, output[i].PartitionSetId); - } - } - - private void Type4Pack(Span output) - { - var colorEndpoints = new[] { - //subset 1 - new byte[]{0b11111, 0, 0b0100}, - new byte[]{0, 0b11111, 0b0100} - }; - var alphaEndPoints = new byte[]{ - 0b111111, - 0 - }; - var indices2Bit = new byte[] { - 0, 1, 2, 3, - 0, 1, 2, 3, - 3, 2, 1, 0, - 3, 2, 1, 0 - }; - - var indices3Bit = new byte[] { - 0, 1, 2, 3, - 7, 6, 5, 4, - 7, 6, 5, 4, - 3, 2, 1, 0 - }; - var rotation = 0; - byte idxMode = 0; - for (var i = 0; i < 64; i++) - { - rotation = (rotation + 1) % 4; - idxMode = (byte)((idxMode + 1) % 2); - output[i].PackType4(rotation, idxMode, colorEndpoints, alphaEndPoints, indices2Bit, indices3Bit); - Assert.Equal(Bc7BlockType.Type4, output[i].Type); - Assert.Equal(rotation, output[i].RotationBits); - Assert.Equal(idxMode, output[i].Type4IndexMode); - } - } - - private void Type5Pack(Span output) - { - var colorEndpoints = new[] { - //subset 1 - new byte[]{0b1111111, 0b0100, 0}, - new byte[]{0, 0, 0b1111111} - }; - var alphaEndPoints = new byte[]{ - 255, - 100 - }; - var colorIndices = new byte[] { - 0, 1, 2, 3, - 0, 1, 2, 3, - 3, 2, 1, 0, - 3, 2, 1, 0 - }; - var alphaIndices = new byte[] { - 1, 2, 3, 0, - 0, 1, 2, 3, - 2, 3, 0, 1, - 3, 2, 1, 0 - }; - var rotation = 0; - for (var i = 0; i < 64; i++) - { - rotation = (rotation + 1) % 4; - output[i].PackType5(rotation, colorEndpoints, alphaEndPoints, colorIndices, alphaIndices); - Assert.Equal(Bc7BlockType.Type5, output[i].Type); - Assert.Equal(rotation, output[i].RotationBits); - } - } - - private void Type6Pack(Span output) - { - var colorEndpoints = new[] { - //subset 1 - new byte[]{0b1111111, 0b0100, 0, 0b1111111}, - new byte[]{0, 0, 0b1111111, 0b1111} - }; - var pBits = new byte[] { - 1, 0 - }; - var colorIndices = new byte[] { - 0, 1, 2, 3, - 4, 5, 6, 7, - 8, 9, 10, 11, - 12, 13, 14, 15 - }; - for (var i = 0; i < 64; i++) - { - output[i].PackType6(colorEndpoints, pBits, colorIndices); - Assert.Equal(Bc7BlockType.Type6, output[i].Type); - } - } - - private void Type7Pack(Span output) - { - var colorEndpoints = new[] { - //subset 1 - new byte[]{0b11111, 0b0100, 0, 0b11111}, - new byte[]{0, 0b11111, 0, 0b111}, - //subset 2 - new byte[]{0b11111, 0b0100, 0, 0b1111}, - new byte[]{0b10100, 0, 0b11111, 0b11111} - }; - var pBits = new byte[] { - 1, 0, 1, 0 - }; - var indices = new byte[] { - 0, 1, 2, 3, - 0, 1, 2, 3, - 3, 2, 1, 0, - 3, 2, 1, 0 - }; - for (var i = 0; i < 64; i++) - { - output[i].PackType7(i, colorEndpoints, pBits, indices); - Assert.Equal(Bc7BlockType.Type7, output[i].Type); - Assert.Equal(i, output[i].PartitionSetId); - } - } - - private void PlaceBlock(int x, int y, Span data, Span destination, int destBlockWidth) - { - for (var i = 0; i < 8; i++) - { - var xStart = x * 8 + (y * 8 + i) * 8 * destBlockWidth; - var row = data.Slice(i * 8, 8); - row.CopyTo(destination.Slice(xStart, 8)); - } - } - - #endregion - } -} diff --git a/BCnEncTests/BgraTests.cs b/BCnEncTests/BgraTests.cs deleted file mode 100644 index 7ae666e..0000000 --- a/BCnEncTests/BgraTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class BgraTests - { - [Fact] - public void BgraDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Bgra); - var original = ImageLoader.TestLenna; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void BgraAlphaDdsDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Bgra); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var dds = encoder.EncodeToDds(original); - var image = decoder.DecodeToImageRgba32(dds); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void BgraKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Bgra); - var original = ImageLoader.TestLenna; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - - [Fact] - public void BgraAlphaKtxDecode() - { - // Arrange - var decoder = new BcDecoder(); - var encoder = new BcEncoder(CompressionFormat.Bgra); - var original = ImageLoader.TestAlphaGradient1; - - // Act - var ktx = encoder.EncodeToKtx(original); - var image = decoder.DecodeToImageRgba32(ktx); - - // Assert - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - } - } -} diff --git a/BCnEncTests/CancellationTests.cs b/BCnEncTests/CancellationTests.cs deleted file mode 100644 index ca85a9a..0000000 --- a/BCnEncTests/CancellationTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using BCnEncoder.Decoder; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class CancellationTests - { - [Fact] - public async Task EncodeParallelCancellation() - { - await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, true); - } - - [Fact] - public async Task EncodeNonParallelCancellation() - { - await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, false); - } - - // HINT: Decoding in general is too fast to be cancelled. - // HINT: For parallel decoding even with TimeSpan.FromTicks(1) the test never successfully threw an exception when executed in bulk with other tests. - // HINT: For non parallel decoding the test was partially successful, due to fluctuations in how much time the decoding needed and when the cancellation was introduced. - } -} diff --git a/BCnEncTests/ClusterTests.cs b/BCnEncTests/ClusterTests.cs deleted file mode 100644 index cee9ccd..0000000 --- a/BCnEncTests/ClusterTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class ClusterTests - { - [Fact] - public void Clusterize() - { - var testImage = ImageLoader.TestBlur1.Clone(); - - var pix = TestHelper.GetSinglePixelArray(testImage); - - var pixels = MemoryMarshal.Cast(pix).ToArray(); - - var numClusters = (testImage.Width / 32) * (testImage.Height / 32); - var clusters = LinearClustering.ClusterPixels(pixels, testImage.Width, testImage.Height, numClusters, 10, 10); - - var pixC = new ColorYCbCr[numClusters]; - var counts = new int[numClusters]; - - for (var i = 0; i < pixels.Length; i++) - { - pixC[clusters[i]] += new ColorYCbCr(pixels[i]); - counts[clusters[i]]++; - } - - for (var i = 0; i < numClusters; i++) - { - pixC[i] /= counts[i]; - } - - for (var i = 0; i < pixels.Length; i++) - { - pixels[i] = pixC[clusters[i]].ToColorRgba32(); - pix[i] = new Rgba32(pixels[i].r, pixels[i].g, pixels[i].b, pixels[i].a); - } - - TestHelper.SetSinglePixelArray(testImage, pix); - - using var fs = File.OpenWrite("test_cluster.png"); - testImage.SaveAsPng(fs); - } - } -} diff --git a/BCnEncTests/DdsReadTests.cs b/BCnEncTests/DdsReadTests.cs deleted file mode 100644 index 9a5ba2e..0000000 --- a/BCnEncTests/DdsReadTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.IO; -using BCnEncoder.Decoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using Xunit; - -namespace BCnEncTests -{ - public class DdsReadTests - { - [Fact] - public void ReadRgba() - { - TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressRgba, DxgiFormat.DxgiFormatR8G8B8A8Unorm, "decoding_test_dds_rgba_mip{0}.png"); - } - - [Fact] - public void ReadBc1() - { - TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1_mip{0}.png"); - } - - [Fact] - public void ReadBc1A() - { - TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1A, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1a_mip{0}.png"); - } - - [Fact] - public void ReadBc7() - { - TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc7, DxgiFormat.DxgiFormatUnknown, "decoding_test_dds_bc7_mip{0}.png"); - } - - [Fact] - public void ReadFromStream() - { - using var fs = File.OpenRead(DdsLoader.TestDecompressBc1Name); - - var decoder = new BcDecoder(); - var images = decoder.DecodeAllMipMapsToImageRgba32(fs); - - for (var i = 0; i < images.Length; i++) - { - using var outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png"); - images[i].SaveAsPng(outFs); - images[i].Dispose(); - } - } - } -} diff --git a/BCnEncTests/DdsWritingTests.cs b/BCnEncTests/DdsWritingTests.cs deleted file mode 100644 index 44a0616..0000000 --- a/BCnEncTests/DdsWritingTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class DdsWritingTests - { - [Fact] - public void DdsWriteRgba() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Rgba, "encoding_dds_rgba.dds"); - } - - [Fact] - public void DdsWriteBc1() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc1, "encoding_dds_bc1.dds"); - } - - [Fact] - public void DdsWriteBc2() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc2, "encoding_dds_bc2.dds"); - } - - [Fact] - public void DdsWriteBc3() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc3, "encoding_dds_bc3.dds"); - } - - [Fact] - public void DdsWriteBc4() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, "encoding_dds_bc4.dds"); - } - - [Fact] - public void DdsWriteBc5() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, "encoding_dds_bc5.dds"); - } - - [Fact] - public void DdsWriteBc7() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc7, "encoding_dds_bc7.dds"); - } - - [Fact] - public void DdsWriteCubemap() - { - TestHelper.ExecuteDdsWritingTest(ImageLoader.TestCubemap, CompressionFormat.Bc1, "encoding_dds_cubemap_bc1.dds"); - } - } -} diff --git a/BCnEncTests/DecodingAsyncTests.cs b/BCnEncTests/DecodingAsyncTests.cs deleted file mode 100644 index 04cfa29..0000000 --- a/BCnEncTests/DecodingAsyncTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.IO; -using System.Threading.Tasks; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class DecodingAsyncTests - { - [Fact] - public async Task DecodeAsync() - { - var decoder = new BcDecoder(); - var encoder = new BcEncoder(); - var original = ImageLoader.TestGradient1; - - var file = encoder.EncodeToKtx(original); - var image = await decoder.DecodeToImageRgba32Async(file); - - TestHelper.AssertImagesEqual(original, image,encoder.OutputOptions.Quality); - image.Dispose(); - } - - [Fact] - public async Task DecodeAllMipMapsAsync() - { - var decoder = new BcDecoder(); - var encoder = new BcEncoder(); - var original = ImageLoader.TestGradient1; - - var file = encoder.EncodeToKtx(original); - var images = await decoder.DecodeAllMipMapsToImageRgba32Async(file); - - TestHelper.AssertImagesEqual(original, images[0], encoder.OutputOptions.Quality); - foreach(var img in images) - img.Dispose(); - } - - [Fact] - public async Task DecodeRawAsync() - { - var decoder = new BcDecoder(); - var encoder = new BcEncoder(); - var original = ImageLoader.TestGradient1; - - var file = encoder.EncodeToKtx(original); - - var ms = new MemoryStream(file.MipMaps[0].Faces[0].Data); - - var image = await decoder.DecodeRawToImageRgba32Async(ms, - (int) file.MipMaps[0].Width, (int) file.MipMaps[0].Height, CompressionFormat.Bc1); - - TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); - image.Dispose(); - } - } -} diff --git a/BCnEncTests/DecodingTests.cs b/BCnEncTests/DecodingTests.cs deleted file mode 100644 index c2fc08c..0000000 --- a/BCnEncTests/DecodingTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using BCnEncTests.Support; -using Xunit; - -namespace BCnEncTests -{ - public class DecodingTests - { - [Fact] - public void Bc1Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1, "decoding_test_bc1.png"); - } - - [Fact] - public void Bc1AlphaDecode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1A, "decoding_test_bc1a.png"); - } - - [Fact] - public void Bc2Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc2, "decoding_test_bc2.png"); - } - - [Fact] - public void Bc3Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc3, "decoding_test_bc3.png"); - } - - [Fact] - public void Bc4Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc4Unorm, "decoding_test_bc4.png"); - } - - [Fact] - public void Bc5Decode() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc5Unorm, "decoding_test_bc5.png"); - } - - [Fact] - public void Bc7DecodeRgb() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Rgb, "decoding_test_bc7_rgb.png"); - } - - [Fact] - public void Bc7DecodeUnorm() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Unorm, "decoding_test_bc7_unorm.png"); - } - - [Fact] - public void Bc7DecodeEveryBlockType() - { - TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Types, "decoding_test_bc7_types.png"); - } - } -} diff --git a/BCnEncTests/EncodeByteOrderTests.cs b/BCnEncTests/EncodeByteOrderTests.cs index 928fbc9..9b1d042 100644 --- a/BCnEncTests/EncodeByteOrderTests.cs +++ b/BCnEncTests/EncodeByteOrderTests.cs @@ -19,7 +19,7 @@ public class EncodeByteOrderTests private void EncodeByteOrderTest(PixelFormat pixelFormat) where T : unmanaged, IPixel { - var imageOrig = ImageLoader.TestAlpha1; + using var imageOrig = ImageLoader.LoadTestImageSharp("../../../testImages/test_alpha_1_512.png"); var testImage = imageOrig.CloneAs(); var pixels = testImage.GetPixelMemoryGroup()[0]; diff --git a/BCnEncTests/EncoderOptionsTests.cs b/BCnEncTests/EncoderOptionsTests.cs deleted file mode 100644 index 677fce4..0000000 --- a/BCnEncTests/EncoderOptionsTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using BCnEncoder.Encoder; -using BCnEncTests.Support; -using Xunit; -using BCnEncoder.ImageSharp; - -namespace BCnEncTests -{ - public class EncoderOptionsTests - { - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(5)] - [InlineData(10)] - public void MaxMipMaps(int requestedMipMaps) - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder() - { - OutputOptions = - { - GenerateMipMaps = true, - MaxMipMapLevel = requestedMipMaps - } - }; - - Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage)); - - var ktx = encoder.EncodeToKtx(testImage); - - Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels); - Assert.Equal(requestedMipMaps, ktx.MipMaps.Count); - - var dds = encoder.EncodeToDds(testImage); - - Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount); - Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length); - } - - [Fact] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.", Justification = "")] - public void GenerateMipMaps() - { - var testImage = ImageLoader.TestBlur1; - const int requestedMipMaps = 1; - var encoder = new BcEncoder() - { - OutputOptions = - { - GenerateMipMaps = false - } - }; - - Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage)); - - var ktx = encoder.EncodeToKtx(testImage); - - Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels); - Assert.Equal(requestedMipMaps, (int)ktx.MipMaps.Count); - - var dds = encoder.EncodeToDds(testImage); - - Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount); - Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length); - } - } -} diff --git a/BCnEncTests/EncodingAsyncTest.cs b/BCnEncTests/EncodingAsyncTest.cs deleted file mode 100644 index 6d3e9ff..0000000 --- a/BCnEncTests/EncodingAsyncTest.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Threading.Tasks; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class EncodingAsyncTest - { - private readonly BcEncoder encoder; - private readonly BcDecoder decoder; - private readonly Image originalImage; - private readonly Image[] originalCubeMap; - - public EncodingAsyncTest() - { - encoder = new BcEncoder(); - decoder = new BcDecoder(); - originalImage = ImageLoader.TestGradient1; - originalCubeMap = ImageLoader.TestCubemap; - } - - [Fact] - public async Task EncodeToDdsAsync() - { - var file = await encoder.EncodeToDdsAsync(originalImage); - var image = decoder.DecodeToImageRgba32(file); - - TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); - image.Dispose(); - } - - [Fact] - public async Task EncodeToKtxAsync() - { - var file = await encoder.EncodeToKtxAsync(originalImage); - var image = decoder.DecodeToImageRgba32(file); - - TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); - image.Dispose(); - } - - [Fact] - public async Task EncodeCubemapToDdsAsync() - { - var file = await encoder.EncodeCubeMapToDdsAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2], - originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]); - - for (var i = 0; i < 6; i++) - { - var image = decoder.DecodeRawToImageRgba32(file.Faces[i].MipMaps[0].Data, (int)file.Faces[i].Width, (int)file.Faces[i].Height, CompressionFormat.Bc1); - - TestHelper.AssertImagesEqual(originalCubeMap[i], image, encoder.OutputOptions.Quality); - image.Dispose(); - } - } - - [Fact] - public async Task EncodeCubemapToKtxAsync() - { - var file = await encoder.EncodeCubeMapToKtxAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2], - originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]); - - for (var i = 0; i < 6; i++) - { - var image = decoder.DecodeRawToImageRgba32(file.MipMaps[0].Faces[i].Data, (int)file.MipMaps[0].Faces[i].Width, (int)file.MipMaps[0].Faces[i].Height, CompressionFormat.Bc1); - - TestHelper.AssertImagesEqual(originalCubeMap[i], image, encoder.OutputOptions.Quality); - image.Dispose(); - } - } - - [Fact] - public async Task EncodeToRawBytesAsync() - { - var data = await encoder.EncodeToRawBytesAsync(originalImage); - var image = decoder.DecodeRawToImageRgba32(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1); - - TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality); - image.Dispose(); - } - } -} diff --git a/BCnEncTests/EncodingTest.cs b/BCnEncTests/EncodingTest.cs deleted file mode 100644 index 00e46f2..0000000 --- a/BCnEncTests/EncodingTest.cs +++ /dev/null @@ -1,501 +0,0 @@ -using System.IO; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using Xunit; -using Xunit.Abstractions; - -namespace BCnEncTests -{ - public class Bc1GradientTest - { - private readonly ITestOutputHelper output; - - public Bc1GradientTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc1GradientBestQuality() - { - var image = ImageLoader.TestGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.BestQuality, - "encoding_bc1_gradient_bestQuality.ktx", - output); - } - - [Fact] - public void Bc1GradientBalanced() - { - var image = ImageLoader.TestGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Balanced, - "encoding_bc1_gradient_balanced.ktx", - output); - } - - [Fact] - public void Bc1GradientFast() - { - var image = ImageLoader.TestGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Fast, - "encoding_bc1_gradient_fast.ktx", - output); - } - } - - public class Bc1DiffuseTest - { - private readonly ITestOutputHelper output; - - public Bc1DiffuseTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc1DiffuseBestQuality() - { - var image = ImageLoader.TestDiffuse1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.BestQuality, - "encoding_bc1_diffuse_bestQuality.ktx", - output); - } - - [Fact] - public void Bc1DiffuseBalanced() - { - var image = ImageLoader.TestDiffuse1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Balanced, - "encoding_bc1_diffuse_balanced.ktx", - output); - } - - [Fact] - public void Bc1DiffuseFast() - { - var image = ImageLoader.TestDiffuse1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Fast, - "encoding_bc1_diffuse_fast.ktx", - output); - } - } - - public class Bc1BlurryTest - { - private readonly ITestOutputHelper output; - - public Bc1BlurryTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc1BlurBestQuality() - { - var image = ImageLoader.TestBlur1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.BestQuality, - "encoding_bc1_blur_bestQuality.ktx", - output); - } - - [Fact] - public void Bc1BlurBalanced() - { - var image = ImageLoader.TestBlur1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Balanced, - "encoding_bc1_blur_balanced.ktx", - output); - } - - [Fact] - public void Bc1BlurFast() - { - var image = ImageLoader.TestBlur1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1, - CompressionQuality.Fast, - "encoding_bc1_blur_fast.ktx", - output); - } - } - - public class Bc1ASpriteTest - { - private readonly ITestOutputHelper output; - - public Bc1ASpriteTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc1ASpriteBestQuality() - { - var image = ImageLoader.TestTransparentSprite1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1WithAlpha, - CompressionQuality.BestQuality, - "encoding_bc1a_sprite_bestQuality.ktx", - output); - } - - [Fact] - public void Bc1ASpriteBalanced() - { - var image = ImageLoader.TestTransparentSprite1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1WithAlpha, - CompressionQuality.Balanced, - "encoding_bc1a_sprite_balanced.ktx", - output); - } - - [Fact] - public void Bc1ASpriteFast() - { - var image = ImageLoader.TestTransparentSprite1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc1WithAlpha, - CompressionQuality.Fast, - "encoding_bc1a_sprite_fast.ktx", - output); - } - } - - public class Bc2GradientTest - { - private readonly ITestOutputHelper output; - - public Bc2GradientTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc2GradientBestQuality() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc2, - CompressionQuality.BestQuality, - "encoding_bc2_gradient_bestQuality.ktx", - output); - } - - [Fact] - public void Bc2GradientBalanced() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc2, - CompressionQuality.Balanced, - "encoding_bc2_gradient_balanced.ktx", - output); - } - - [Fact] - public void Bc2GradientFast() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc2, - CompressionQuality.Fast, - "encoding_bc2_gradient_fast.ktx", - output); - } - } - - public class Bc3GradientTest - { - private readonly ITestOutputHelper output; - - public Bc3GradientTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc3GradientBestQuality() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc3, - CompressionQuality.BestQuality, - "encoding_bc3_gradient_bestQuality.ktx", - output); - } - - [Fact] - public void Bc3GradientBalanced() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc3, - CompressionQuality.Balanced, - "encoding_bc3_gradient_balanced.ktx", - output); - } - - [Fact] - public void Bc3GradientFast() - { - var image = ImageLoader.TestAlphaGradient1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc3, - CompressionQuality.Fast, - "encoding_bc3_gradient_fast.ktx", - output); - } - } - - public class Bc4RedTest - { - private readonly ITestOutputHelper output; - - public Bc4RedTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc4RedBestQuality() - { - var image = ImageLoader.TestHeight1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc4, - CompressionQuality.BestQuality, - "encoding_bc4_red_bestQuality.ktx", - output); - } - - [Fact] - public void Bc4RedBalanced() - { - var image = ImageLoader.TestHeight1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc4, - CompressionQuality.Balanced, - "encoding_bc4_red_balanced.ktx", - output); - } - - [Fact] - public void Bc4RedFast() - { - var image = ImageLoader.TestHeight1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc4, - CompressionQuality.Fast, - "encoding_bc4_red_fast.ktx", - output); - } - } - - public class Bc5RedGreenTest - { - private readonly ITestOutputHelper output; - - public Bc5RedGreenTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc5RedGreenBestQuality() - { - var image = ImageLoader.TestRedGreen1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc5, - CompressionQuality.BestQuality, - "encoding_bc5_red_green_bestQuality.ktx", - output); - } - - [Fact] - public void Bc5RedGreenBalanced() - { - var image = ImageLoader.TestRedGreen1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc5, - CompressionQuality.Balanced, - "encoding_bc5_red_green_balanced.ktx", - output); - } - - [Fact] - public void Bc5RedGreenFast() - { - var image = ImageLoader.TestRedGreen1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc5, - CompressionQuality.Fast, - "encoding_bc5_red_green_fast.ktx", - output); - } - } - - public class Bc7RgbTest - { - private readonly ITestOutputHelper output; - - public Bc7RgbTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc7RgbBestQuality() - { - var image = ImageLoader.TestRgbHard1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.BestQuality, - "encoding_bc7_rgb_bestQuality.ktx", - output); - } - - [Fact] - public void Bc7RgbBalanced() - { - var image = ImageLoader.TestRgbHard1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Balanced, - "encoding_bc7_rgb_balanced.ktx", - output); - } - - [Fact] - public void Bc7LennaBalanced() - { - var image = ImageLoader.TestLenna; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Balanced, - "encoding_bc7_lenna_balanced.ktx", - output); - } - - [Fact] - public void Bc7RgbFast() - { - var image = ImageLoader.TestRgbHard1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Fast, - "encoding_bc7_rgb_fast.ktx", - output); - } - } - - public class Bc7RgbaTest - { - private readonly ITestOutputHelper output; - - public Bc7RgbaTest(ITestOutputHelper output) - { - this.output = output; - } - - [Fact] - public void Bc7RgbaBestQuality() - { - var image = ImageLoader.TestAlpha1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.BestQuality, - "encoding_bc7_rgba_bestQuality.ktx", - output); - } - - [Fact] - public void Bc7RgbaBalanced() - { - var image = ImageLoader.TestAlpha1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Balanced, - "encoding_bc7_rgba_balanced.ktx", - output); - } - - [Fact] - public void Bc7RgbaFast() - { - var image = ImageLoader.TestAlpha1; - - TestHelper.ExecuteEncodingTest(image, - CompressionFormat.Bc7, - CompressionQuality.Fast, - "encoding_bc7_rgba_fast.ktx", - output); - } - } - - public class CubemapTest - { - [Fact] - public void WriteCubeMapFile() - { - var images = ImageLoader.TestCubemap; - - var filename = "encoding_bc1_cubemap.ktx"; - - var encoder = new BcEncoder(); - encoder.OutputOptions.Quality = CompressionQuality.Fast; - encoder.OutputOptions.GenerateMipMaps = true; - encoder.OutputOptions.Format = CompressionFormat.Bc1; - - using var fs = File.OpenWrite(filename); - encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); - } - } -} diff --git a/BCnEncTests/ProgressTests.cs b/BCnEncTests/ProgressTests.cs deleted file mode 100644 index 1a58c31..0000000 --- a/BCnEncTests/ProgressTests.cs +++ /dev/null @@ -1,392 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using BCnEncTests.Support; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using Xunit.Abstractions; - -namespace BCnEncTests -{ - public class ProgressTests - { - private ITestOutputHelper output; - - public ProgressTests(ITestOutputHelper output) => this.output = output; - - private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Image testImage, int expectedTotalBlocks) - { - var lastProgress = new ProgressElement(0, 1); - - var processedBlocks = 0; - encoder.Options.Progress = new SynchronousProgress(element => - { - output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); - - Assert.Equal(++processedBlocks, element.CurrentBlock); - lastProgress = element; - }); - - await using var ms = new MemoryStream(); - await encoder.EncodeToStreamAsync(testImage, ms); - - output.WriteLine("LastProgress = " + lastProgress); - - Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); - Assert.Equal(1, lastProgress.Percentage); - } - - private async Task ExecuteDecodeProgressReport(BcDecoder decoder, KtxFile image, int expectedTotalBlocks, bool singleMip) - { - var lastProgress = new ProgressElement(0, 1); - - var processedBlocks = 0; - decoder.Options.Progress = new SynchronousProgress(element => - { - output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); - - Assert.Equal(++processedBlocks, element.CurrentBlock); - lastProgress = element; - }); - - if (singleMip) - { - await decoder.DecodeAsync(image); - } - else - { - await decoder.DecodeAllMipMapsAsync(image); - } - - output.WriteLine("LastProgress = " + lastProgress); - - Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); - Assert.Equal(1, lastProgress.Percentage); - } - - private async Task ExecuteDecodeProgressReport(BcDecoder decoder, DdsFile image, int expectedTotalBlocks, bool singleMip) - { - var lastProgress = new ProgressElement(0, 1); - - var processedBlocks = 0; - decoder.Options.Progress = new SynchronousProgress(element => - { - output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}"); - - Assert.Equal(++processedBlocks, element.CurrentBlock); - lastProgress = element; - }); - - if (singleMip) - { - await decoder.DecodeAsync(image); - } - else - { - await decoder.DecodeAllMipMapsAsync(image); - } - - output.WriteLine("LastProgress = " + lastProgress); - - Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks); - Assert.Equal(1, lastProgress.Percentage); - } - - [Fact] - public async Task DecodeProgressReportParallel() - { - var testImage = KtxLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = true } - }; - - var expectedTotal = 0; - - for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++) - { - expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height); - } - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); - } - - [Fact] - public async Task DecodeProgressReportNonParallel() - { - var testImage = KtxLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = false } - }; - - var expectedTotal = 0; - - for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++) - { - expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height); - } - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); - } - - [Fact] - public async Task DecodeProgressReportParallelOneMip() - { - var testImage = KtxLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = true } - }; - - var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height); - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); - } - - [Fact] - public async Task DecodeProgressReportNonParallelOneMip() - { - var testImage = KtxLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = false } - }; - - var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height); - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); - } - - [Fact] - public async Task DecodeProgressReportParallelDds() - { - var testImage = DdsLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = true } - }; - - var expectedTotal = 0; - - for (var i = 0; i < testImage.header.dwMipMapCount; i++) - { - expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height); - } - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); - } - - [Fact] - public async Task DecodeProgressReportNonParallelDds() - { - var testImage = DdsLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = false } - }; - - var expectedTotal = 0; - - for (var i = 0; i < testImage.header.dwMipMapCount; i++) - { - expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height); - } - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false); - } - - [Fact] - public async Task DecodeProgressReportParallelOneMipDds() - { - var testImage = DdsLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = true } - }; - - var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height); - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); - } - - [Fact] - public async Task DecodeProgressReportNonParallelOneMipDds() - { - var testImage = DdsLoader.TestDecompressBc1; - var decoder = new BcDecoder - { - Options = { IsParallel = false } - }; - - var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height); - - await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true); - } - - [Fact] - public async Task EncodeProgressReportParallelKtx() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = true }, - OutputOptions = { FileFormat = OutputFileFormat.Ktx } - }; - - var expectedTotal = 0; - - for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++) - { - encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH); - expectedTotal += encoder.GetBlockCount(mW, mH); - } - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async Task EncodeProgressReportNonParallelKtx() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = false }, - OutputOptions = { FileFormat = OutputFileFormat.Ktx } - }; - - var expectedTotal = 0; - - for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++) - { - encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH); - expectedTotal += encoder.GetBlockCount(mW, mH); - } - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async Task EncodeProgressReportParallelOneMipKtx() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = true }, - OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx } - }; - - var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async Task EncodeProgressReportNonParallelOneMipKtx() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = false }, - OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx } - }; - - var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async Task EncodeProgressReportParallelDds() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = true }, - OutputOptions = { FileFormat = OutputFileFormat.Dds } - }; - - var expectedTotal = 0; - - for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++) - { - encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH); - expectedTotal += encoder.GetBlockCount(mW, mH); - } - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async Task EncodeProgressReportNonParallelDds() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = false }, - OutputOptions = { FileFormat = OutputFileFormat.Dds } - }; - - var expectedTotal = 0; - - for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++) - { - encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH); - expectedTotal += encoder.GetBlockCount(mW, mH); - } - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async Task EncodeProgressReportParallelOneMipDds() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = true }, - OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds } - }; - - var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - - [Fact] - public async Task EncodeProgressReportNonParallelOneMipDds() - { - var testImage = ImageLoader.TestBlur1; - var encoder = new BcEncoder - { - Options = { IsParallel = false }, - OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds } - }; - - var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height); - - await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal); - } - } - - class SynchronousProgress : IProgress - { - private readonly Action handler; - - public SynchronousProgress(Action handler) - { - this.handler = handler; - } - - public void Report(T value) - { - handler(value); - } - } -} diff --git a/BCnEncTests/RawTests.cs b/BCnEncTests/RawTests.cs deleted file mode 100644 index 8bfc6f8..0000000 --- a/BCnEncTests/RawTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.IO; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.ImageSharp; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using SixLabors.ImageSharp.Processing; -using Xunit; - -namespace BCnEncTests -{ - public class RawTests - { - [Fact] - public void EncodeDecode() - { - var inputImage = ImageLoader.TestGradient1; - var decoder = new BcDecoder(); - var encoder = new BcEncoder - { - OutputOptions = { Quality = CompressionQuality.BestQuality } - }; - - var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); - var decodedImage = decoder.DecodeRawToImageRgba32(encodedRawBytes[0], inputImage.Width, inputImage.Height, CompressionFormat.Bc1); - - var originalPixels = TestHelper.GetSinglePixelArray(ImageLoader.TestGradient1); - var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage); - - TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality); - } - - [Fact] - public void EncodeDecodeStream() - { - var inputImage = ImageLoader.TestGradient1; - var decoder = new BcDecoder(); - var encoder = new BcEncoder - { - OutputOptions = { Quality = CompressionQuality.BestQuality } - }; - - var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); - - using var ms = new MemoryStream(encodedRawBytes[0]); - - Assert.Equal(0, ms.Position); - - var decodedImage = decoder.DecodeRawToImageRgba32(ms, inputImage.Width, inputImage.Height, CompressionFormat.Bc1); - - var originalPixels = TestHelper.GetSinglePixelArray(inputImage); - var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage); - - TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality); - } - - - [Fact] - public void EncodeDecodeAllMipMapsStream() - { - var inputImage = ImageLoader.TestGradient1; - var decoder = new BcDecoder(); - var encoder = new BcEncoder - { - OutputOptions = - { - Quality = CompressionQuality.BestQuality, - GenerateMipMaps = true, - MaxMipMapLevel = 0 - } - }; - - using var ms = new MemoryStream(); - - var encodedRawBytes = encoder.EncodeToRawBytes(inputImage); - - var mipLevels = encoder.CalculateNumberOfMipLevels(inputImage); - Assert.True(mipLevels > 1); - - for (var i = 0; i < mipLevels; i++) - { - ms.Write(encodedRawBytes[i]); - } - - ms.Position = 0; - Assert.Equal(0, ms.Position); - - for (var i = 0; i < mipLevels; i++) - { - encoder.CalculateMipMapSize(inputImage, i, out var mipWidth, out var mipHeight); - using var resized = inputImage.Clone(x => x.Resize(mipWidth, mipHeight)); - - var decodedImage = decoder.DecodeRawToImageRgba32(ms, mipWidth, mipHeight, CompressionFormat.Bc1); - - var originalPixels = TestHelper.GetSinglePixelArray(resized); - var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage); - - TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality); - } - - encoder.CalculateMipMapSize(inputImage, mipLevels - 1, out var lastMWidth, out var lastMHeight); - Assert.Equal(1, lastMWidth); - Assert.Equal(1, lastMHeight); - } - } -} diff --git a/BCnEncTests/SingleBlockTests.cs b/BCnEncTests/SingleBlockTests.cs deleted file mode 100644 index 89b0778..0000000 --- a/BCnEncTests/SingleBlockTests.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using BCnEncoder.Decoder; -using BCnEncoder.Encoder; -using BCnEncoder.Shared; -using BCnEncTests.Support; -using CommunityToolkit.HighPerformance; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace BCnEncTests -{ - public class SingleBlockTests - { - - [Theory] - [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)] - [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)] - [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)] - [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)] - [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)] - public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQuality quality) - { - var testImage = ImageLoader.TestAlpha1; - - var encoder = new BcEncoder() - { - OutputOptions = - { - Format = format, - Quality = quality - } - }; - - var pixels = TestHelper.GetSinglePixelArray(testImage); - var colors = MemoryMarshal.Cast(pixels) - .AsSpan2D(testImage.Height, testImage.Width); - - var ms = new MemoryStream(); - - for (var y = 0; y < testImage.Height; y+=4) - { - for (var x = 0; x < testImage.Width; x+=4) - { - encoder.EncodeBlock(colors.Slice(y, x, 4, 4), ms); - } - } - - Assert.Equal(ms.Position, encoder.GetBlockSize() * encoder.GetBlockCount(testImage.Width, testImage.Height)); - - ms.Position = 0; - - var decoder = new BcDecoder(); - - var decoded = new ColorRgba32[testImage.Height, testImage.Width]; - - for (var y = 0; y < testImage.Height; y += 4) - { - for (var x = 0; x < testImage.Width; x += 4) - { - decoder.DecodeBlock(ms, format, - decoded.AsSpan2D().Slice(y, x, 4, 4)); - } - } - - colors.TryGetSpan(out var oPixels); - decoded.AsSpan2D().TryGetSpan(out var dPixels); - var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels, - format != CompressionFormat.Bc1); - - TestHelper.AssertPSNR(psnr, quality); - } - - [Theory] - [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)] - [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)] - [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)] - [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)] - [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)] - public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality quality) - { - var testImage = ImageLoader.TestAlpha1; - - var encoder = new BcEncoder() - { - OutputOptions = - { - Format = format, - Quality = quality - } - }; - - var pixels = TestHelper.GetSinglePixelArray(testImage); - var colors = MemoryMarshal.Cast(pixels) - .AsSpan2D(testImage.Height, testImage.Width); - - Span buffer = new byte[encoder.GetBlockSize() * encoder.GetBlockCount(testImage.Width, testImage.Height)]; - - var blockIndex = 0; - for (var y = 0; y < testImage.Height; y += 4) - { - for (var x = 0; x < testImage.Width; x += 4) - { - var bytes = encoder.EncodeBlock(colors.Slice(y, x, 4, 4)); - bytes.CopyTo(buffer.Slice(blockIndex * encoder.GetBlockSize())); - blockIndex++; - } - } - - var decoder = new BcDecoder(); - - var decoded = new ColorRgba32[testImage.Height, testImage.Width]; - - blockIndex = 0; - for (var y = 0; y < testImage.Height; y += 4) - { - for (var x = 0; x < testImage.Width; x += 4) - { - - decoder.DecodeBlock( - buffer.Slice( - blockIndex * decoder.GetBlockSize(format), - decoder.GetBlockSize(format) - ), - format, - decoded.AsSpan2D().Slice(y, x, 4, 4)); - blockIndex++; - } - } - - colors.TryGetSpan(out var oPixels); - decoded.AsSpan2D().TryGetSpan(out var dPixels); - var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels, - format != CompressionFormat.Bc1); - - TestHelper.AssertPSNR(psnr, quality); - } - } -} diff --git a/BCnEncTests/Support/HdrLoader.cs b/BCnEncTests/Support/HdrLoader.cs index 519f02e..6080cf4 100644 --- a/BCnEncTests/Support/HdrLoader.cs +++ b/BCnEncTests/Support/HdrLoader.cs @@ -12,7 +12,7 @@ public static class HdrLoader { public static HdrImage TestHdrKiara { get; } = HdrImage.Read("../../../testImages/test_hdr_kiara.hdr"); public static HdrImage TestHdrProbe { get; } = HdrImage.Read("../../../testImages/test_hdr_probe.hdr"); - public static Image ReferenceKiara { get; } = ImageLoader.LoadTestImage("../../../testImages/test_hdr_kiara.png"); + public static Image ReferenceKiara { get; } = ImageLoader.LoadTestImageSharp("../../../testImages/test_hdr_kiara.png"); public static DdsFile TestHdrKiaraDds { get; } = DdsLoader.LoadDdsFile("../../../testImages/test_hdr_kiara_bc6h.dds"); public static KtxFile TestHdrKiaraKtx { get; } = diff --git a/BCnEncTests/Support/ImageLoader.cs b/BCnEncTests/Support/ImageLoader.cs index 3a825d2..37d410b 100644 --- a/BCnEncTests/Support/ImageLoader.cs +++ b/BCnEncTests/Support/ImageLoader.cs @@ -1,6 +1,7 @@ using System.IO; using BCnEncoder.Shared; using BCnEncoder.Shared.ImageFiles; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; @@ -8,21 +9,21 @@ namespace BCnEncTests.Support { public static class ImageLoader { - public static Image TestDiffuse1 { get; } = LoadTestImage("../../../testImages/test_diffuse_1_512.jpg"); - public static Image TestBlur1 { get; } = LoadTestImage("../../../testImages/test_blur_1_512.jpg"); - public static Image TestNormal1 { get; } = LoadTestImage("../../../testImages/test_normal_1_512.jpg"); - public static Image TestHeight1 { get; } = LoadTestImage("../../../testImages/test_height_1_512.jpg"); - public static Image TestGradient1 { get; } = LoadTestImage("../../../testImages/test_gradient_1_512.jpg"); + public static Memory2D TestDiffuse1 { get; } = LoadTestImage("../../../testImages/test_diffuse_1_512.jpg"); + public static Memory2D TestBlur1 { get; } = LoadTestImage("../../../testImages/test_blur_1_512.jpg"); + public static Memory2D TestNormal1 { get; } = LoadTestImage("../../../testImages/test_normal_1_512.jpg"); + public static Memory2D TestHeight1 { get; } = LoadTestImage("../../../testImages/test_height_1_512.jpg"); + public static Memory2D TestGradient1 { get; } = LoadTestImage("../../../testImages/test_gradient_1_512.jpg"); - public static Image TestTransparentSprite1 { get; } = LoadTestImage("../../../testImages/test_transparent.png"); - public static Image TestAlphaGradient1 { get; } = LoadTestImage("../../../testImages/test_alphagradient_1_512.png"); - public static Image TestAlpha1 { get; } = LoadTestImage("../../../testImages/test_alpha_1_512.png"); - public static Image TestRedGreen1 { get; } = LoadTestImage("../../../testImages/test_red_green_1_64.png"); - public static Image TestRgbHard1 { get; } = LoadTestImage("../../../testImages/test_rgb_hard_1.png"); - public static Image TestLenna { get; } = LoadTestImage("../../../testImages/test_lenna_512.png"); - public static Image TestDecodingBc5Reference { get; } = LoadTestImage("../../../testImages/decoding_dds_bc5_reference.png"); + public static Memory2D TestTransparentSprite1 { get; } = LoadTestImage("../../../testImages/test_transparent.png"); + public static Memory2D TestAlphaGradient1 { get; } = LoadTestImage("../../../testImages/test_alphagradient_1_512.png"); + public static Memory2D TestAlpha1 { get; } = LoadTestImage("../../../testImages/test_alpha_1_512.png"); + public static Memory2D TestRedGreen1 { get; } = LoadTestImage("../../../testImages/test_red_green_1_64.png"); + public static Memory2D TestRgbHard1 { get; } = LoadTestImage("../../../testImages/test_rgb_hard_1.png"); + public static Memory2D TestLenna { get; } = LoadTestImage("../../../testImages/test_lenna_512.png"); + public static Memory2D TestDecodingBc5Reference { get; } = LoadTestImage("../../../testImages/decoding_dds_bc5_reference.png"); - public static Image[] TestCubemap { get; } = { + public static Memory2D[] TestCubemap { get; } = { LoadTestImage("../../../testImages/cubemap/right.png"), LoadTestImage("../../../testImages/cubemap/left.png"), LoadTestImage("../../../testImages/cubemap/top.png"), @@ -31,10 +32,28 @@ public static class ImageLoader LoadTestImage("../../../testImages/cubemap/forward.png") }; - internal static Image LoadTestImage(string filename) + internal static Memory2D LoadTestImage(string filename) + { + using var image = Image.Load(filename); + return ToMemory2D(image); + } + + internal static Image LoadTestImageSharp(string filename) { return Image.Load(filename); } + + private static Memory2D ToMemory2D(Image image) + { + var pixels = new ColorRgba32[image.Width * image.Height]; + for (var y = 0; y < image.Height; y++) + for (var x = 0; x < image.Width; x++) + { + var p = image[x, y]; + pixels[y * image.Width + x] = new ColorRgba32(p.R, p.G, p.B, p.A); + } + return new Memory2D(pixels, image.Height, image.Width); + } } public static class DdsLoader diff --git a/BCnEncTests/Support/TestHelper.cs b/BCnEncTests/Support/TestHelper.cs index 40bc50c..9d6349a 100644 --- a/BCnEncTests/Support/TestHelper.cs +++ b/BCnEncTests/Support/TestHelper.cs @@ -20,17 +20,15 @@ public static class TestHelper { #region Assertions - public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality) + public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null) { - var psnr = ImageQuality.PeakSignalToNoiseRatio( - MemoryMarshal.Cast(originalPixels), - MemoryMarshal.Cast(pixels)); - AssertPSNR(psnr, quality); + var psnr = ImageQuality.PeakSignalToNoiseRatio(originalPixels, pixels); + AssertPSNR(psnr, quality, output); } public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null) { - var rmse = ImageQuality.CalculateLogRMSE(originalPixels,pixels); + var rmse = ImageQuality.CalculateLogRMSE(originalPixels, pixels); AssertRMSE(rmse, quality, output); } @@ -40,6 +38,12 @@ public static void AssertImagesEqual(Image original, Image image AssertPSNR(psnr, quality); } + public static void AssertImagesEqual(Memory2D original, Memory2D image, CompressionQuality quality, bool countAlpha = true) + { + var psnr = CalculatePSNR(original, image, countAlpha); + AssertPSNR(psnr, quality); + } + #endregion #region Execute methods @@ -50,23 +54,23 @@ public static void ExecuteDecodingTest(KtxFile file, string outputFile) Assert.Equal((uint)1, file.header.NumberOfFaces); var decoder = new BcDecoder(); - using var image = decoder.DecodeToImageRgba32(file); + var pixels = decoder.Decode2D(file); - Assert.Equal((uint)image.Width, file.header.PixelWidth); - Assert.Equal((uint)image.Height, file.header.PixelHeight); + Assert.Equal((uint)pixels.Width, file.header.PixelWidth); + Assert.Equal((uint)pixels.Height, file.header.PixelHeight); using var outFs = File.OpenWrite(outputFile); - image.SaveAsPng(outFs); + SaveAsPng(pixels, outFs); } #region Dds - public static void ExecuteDdsWritingTest(Image image, CompressionFormat format, string outputFile) + public static void ExecuteDdsWritingTest(Memory2D image, CompressionFormat format, string outputFile) { ExecuteDdsWritingTest(new[] { image }, format, outputFile); } - public static void ExecuteDdsWritingTest(Image[] images, CompressionFormat format, string outputFile) + public static void ExecuteDdsWritingTest(Memory2D[] images, CompressionFormat format, string outputFile) { var encoder = new BcEncoder(); encoder.OutputOptions.Quality = CompressionQuality.Fast; @@ -93,7 +97,7 @@ public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string var decoder = new BcDecoder(); decoder.InputOptions.DdsBc1ExpectAlpha = assertAlpha; - var images = decoder.DecodeAllMipMapsToImageRgba32(file); + var images = decoder.DecodeAllMipMaps2D(file); Assert.Equal((uint)images[0].Width, file.header.dwWidth); Assert.Equal((uint)images[0].Height, file.header.dwHeight); @@ -107,8 +111,7 @@ public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string } using var outFs = File.OpenWrite(string.Format(outputFile, i)); - images[i].SaveAsPng(outFs); - images[i].Dispose(); + SaveAsPng(images[i], outFs); } } @@ -116,7 +119,7 @@ public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string #region Cancellation - public static async Task ExecuteCancellationTest(Image image, bool isParallel) + public static async Task ExecuteCancellationTest(Memory2D image, bool isParallel) { var encoder = new BcEncoder(CompressionFormat.Bc7); encoder.OutputOptions.Quality = CompressionQuality.Fast; @@ -124,14 +127,14 @@ public static async Task ExecuteCancellationTest(Image image, bool isPar var source = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); await Assert.ThrowsAnyAsync(() => - encoder.EncodeToRawBytesAsync(image, 0, source.Token)); + encoder.EncodeToRawBytesAsync(image, source.Token)); } #endregion #endregion - public static float DecodeKtxCheckPSNR(string filename, Image original) + public static float DecodeKtxCheckPSNR(string filename, Memory2D original) { using var fs = File.OpenRead(filename); var ktx = KtxFile.Load(fs); @@ -139,9 +142,9 @@ public static float DecodeKtxCheckPSNR(string filename, Image original) { OutputOptions = { Bc4Component = ColorComponent.Luminance } }; - using var img = decoder.DecodeToImageRgba32(ktx); + var decoded = decoder.Decode2D(ktx); - return CalculatePSNR(original, img); + return CalculatePSNR(original, decoded); } public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original) @@ -157,8 +160,7 @@ public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original) return ImageQuality.CalculateLogRMSE(original.pixels, decoded); } - - public static void ExecuteEncodingTest(Image image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output) + public static void ExecuteEncodingTest(Memory2D image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output) { var encoder = new BcEncoder(); encoder.OutputOptions.Quality = quality; @@ -182,7 +184,7 @@ public static void ExecuteHdrEncodingTest(HdrImage image, CompressionFormat form encoder.OutputOptions.Format = format; var fs = File.OpenWrite(filename); - encoder.EncodeToStreamHdr(image.pixels.AsMemory().AsMemory2D(image.height, image.width), fs); + encoder.EncodeToStreamHdr(new Memory2D(image.pixels, image.height, image.width), fs); fs.Close(); var rmse = DecodeKtxCheckRMSEHdr(filename, image); @@ -198,15 +200,24 @@ private static float CalculatePSNR(Image original, Image decoded return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, countAlpha); } - public static void AssertPSNR(float psnr, CompressionQuality quality) + private static float CalculatePSNR(Memory2D original, Memory2D decoded, bool countAlpha = true) { + var pixels = GetSinglePixelArrayAsColors(original); + var pixels2 = GetSinglePixelArrayAsColors(decoded); + + return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, countAlpha); + } + + public static void AssertPSNR(float psnr, CompressionQuality quality, ITestOutputHelper output = null) + { + output?.WriteLine($"PSNR: {psnr} , quality: {quality}"); if (quality == CompressionQuality.Fast) { - Assert.True(psnr > 25); + Assert.True(psnr > 25, $"PSNR was less than 25: {psnr} , quality: {quality}"); } else { - Assert.True(psnr > 30); + Assert.True(psnr > 30, $"PSNR was less than 30: {psnr} , quality: {quality}"); } } @@ -237,6 +248,20 @@ public static ColorRgba32[] GetSinglePixelArrayAsColors(Image original) return pixels; } + public static ColorRgba32[] GetSinglePixelArrayAsColors(ReadOnlyMemory2D image) + { + var pixels = new ColorRgba32[image.Width * image.Height]; + var span = image.Span; + for (var y = 0; y < image.Height; y++) + { + for (var x = 0; x < image.Width; x++) + { + pixels[y * image.Width + x] = span[y, x]; + } + } + return pixels; + } + public static T[] GetSinglePixelArray(Image original) where T : unmanaged, IPixel { T[] pixels = new T[original.Width * original.Height]; @@ -261,5 +286,35 @@ public static void SetSinglePixelArray(Image dest, T[] pixels) where T : u } } } + + public static void SaveAsPng(Memory2D img, Stream fs) + { + var imageRgba = new Image(img.Width, img.Height); + var span = img.Span; + for (var y = 0; y < img.Height; y++) + { + for (var x = 0; x < img.Width; x++) + { + var col = span[y, x]; + imageRgba[x, y] = new Rgba32(col.r, col.g, col.b, col.a); + } + } + imageRgba.SaveAsPng(fs); + } + + public static void SaveAsPng(ColorRgbFloat[] pixels, int width, int height, Stream stream) + { + var rgba = new ColorRgba32[pixels.Length]; + for (var i = 0; i < pixels.Length; i++) + { + var p = pixels[i]; + byte r = (byte)(Math.Max(0, Math.Min(1, p.r)) * 255 + 0.5f); + byte g = (byte)(Math.Max(0, Math.Min(1, p.g)) * 255 + 0.5f); + byte b = (byte)(Math.Max(0, Math.Min(1, p.b)) * 255 + 0.5f); + rgba[i] = new ColorRgba32(r, g, b, 255); + } + var mem = new Memory2D(rgba, height, width); + SaveAsPng(mem, stream); + } } } From b093181611e624f5dd757d15c244a204b759074c Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:11:04 +0200 Subject: [PATCH 21/30] Add separate ImageSharp tests --- BCnEncTests/ImageSharpTests.cs | 388 +++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 BCnEncTests/ImageSharpTests.cs diff --git a/BCnEncTests/ImageSharpTests.cs b/BCnEncTests/ImageSharpTests.cs new file mode 100644 index 0000000..855a051 --- /dev/null +++ b/BCnEncTests/ImageSharpTests.cs @@ -0,0 +1,388 @@ +using System.IO; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.ImageSharp; +using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; +using BCnEncTests.Support; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace BCnEncTests +{ + public class ImageSharpEncodingTests + { + private readonly BcEncoder encoder; + private readonly BcDecoder decoder; + private readonly Image original; + private readonly Image[] cubemap; + + public ImageSharpEncodingTests() + { + encoder = new BcEncoder(); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + decoder = new BcDecoder(); + original = ImageLoader.LoadTestImageSharp("../../../testImages/test_gradient_1_512.jpg"); + cubemap = new[] + { + ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/right.png"), + ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/left.png"), + ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/top.png"), + ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/bottom.png"), + ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/back.png"), + ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/forward.png"), + }; + } + + [Fact] + public void EncodeToKtx() + { + var ktx = encoder.EncodeToKtx(original); + using var decoded = decoder.DecodeToImageRgba32(ktx); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void EncodeToDds() + { + var dds = encoder.EncodeToDds(original); + using var decoded = decoder.DecodeToImageRgba32(dds); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void EncodeToStreamKtx() + { + encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx; + using var ms = new MemoryStream(); + encoder.EncodeToStream(original, ms); + ms.Position = 0; + using var decoded = decoder.DecodeToImageRgba32(ms); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void EncodeToStreamDds() + { + encoder.OutputOptions.FileFormat = OutputFileFormat.Dds; + using var ms = new MemoryStream(); + encoder.EncodeToStream(original, ms); + ms.Position = 0; + using var decoded = decoder.DecodeToImageRgba32(ms); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void EncodeToRawBytes() + { + var rawBytes = encoder.EncodeToRawBytes(original); + using var decoded = decoder.DecodeRawToImageRgba32(rawBytes[0], original.Width, original.Height, CompressionFormat.Bc1); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void EncodeToRawBytesSingleMip() + { + var mip0 = encoder.EncodeToRawBytes(original, 0, out var mipWidth, out var mipHeight); + Assert.Equal(original.Width, mipWidth); + Assert.Equal(original.Height, mipHeight); + Assert.True(mip0.Length > 0); + } + + [Fact] + public void CalculateMipLevelCount() + { + var fromImage = encoder.CalculateNumberOfMipLevels(original); + var fromDims = encoder.CalculateNumberOfMipLevels(original.Width, original.Height); + Assert.Equal(fromDims, fromImage); + } + + [Fact] + public void CalculateMipMapSize() + { + encoder.CalculateMipMapSize(original, 0, out var w0, out var h0); + Assert.Equal(original.Width, w0); + Assert.Equal(original.Height, h0); + + encoder.CalculateMipMapSize(original, 1, out var w1, out var h1); + Assert.Equal(original.Width / 2, w1); + Assert.Equal(original.Height / 2, h1); + } + + [Fact] + public void EncodeCubeMapToKtx() + { + var ktx = encoder.EncodeCubeMapToKtx(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5]); + Assert.Equal(6, ktx.MipMaps[0].Faces.Length); + for (var i = 0; i < 6; i++) + { + var face = ktx.MipMaps[0].Faces[i]; + using var decoded = decoder.DecodeRawToImageRgba32(face.Data, (int)face.Width, (int)face.Height, CompressionFormat.Bc1); + TestHelper.AssertImagesEqual(cubemap[i], decoded, encoder.OutputOptions.Quality); + } + } + + [Fact] + public void EncodeCubeMapToDds() + { + var dds = encoder.EncodeCubeMapToDds(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5]); + Assert.Equal(6, dds.Faces.Count); + for (var i = 0; i < 6; i++) + { + var face = dds.Faces[i].MipMaps[0]; + using var decoded = decoder.DecodeRawToImageRgba32(face.Data, (int)face.Width, (int)face.Height, CompressionFormat.Bc1); + TestHelper.AssertImagesEqual(cubemap[i], decoded, encoder.OutputOptions.Quality); + } + } + + [Fact] + public void EncodeCubeMapToStream() + { + encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx; + using var ms = new MemoryStream(); + encoder.EncodeCubeMapToStream(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5], ms); + ms.Position = 0; + var ktx = KtxFile.Load(ms); + Assert.Equal(6, ktx.MipMaps[0].Faces.Length); + } + + [Fact] + public async Task EncodeToKtxAsync() + { + var ktx = await encoder.EncodeToKtxAsync(original); + using var decoded = decoder.DecodeToImageRgba32(ktx); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task EncodeToDdsAsync() + { + var dds = await encoder.EncodeToDdsAsync(original); + using var decoded = decoder.DecodeToImageRgba32(dds); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task EncodeToStreamAsync() + { + encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx; + using var ms = new MemoryStream(); + await encoder.EncodeToStreamAsync(original, ms); + ms.Position = 0; + using var decoded = decoder.DecodeToImageRgba32(ms); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task EncodeToRawBytesAsync() + { + var rawBytes = await encoder.EncodeToRawBytesAsync(original); + using var decoded = decoder.DecodeRawToImageRgba32(rawBytes[0], original.Width, original.Height, CompressionFormat.Bc1); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task EncodeCubeMapToKtxAsync() + { + var ktx = await encoder.EncodeCubeMapToKtxAsync(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5]); + Assert.Equal(6, ktx.MipMaps[0].Faces.Length); + } + + [Fact] + public async Task EncodeCubeMapToDdsAsync() + { + var dds = await encoder.EncodeCubeMapToDdsAsync(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5]); + Assert.Equal(6, dds.Faces.Count); + } + + [Fact] + public async Task EncodeCubeMapToStreamAsync() + { + encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx; + using var ms = new MemoryStream(); + await encoder.EncodeCubeMapToStreamAsync(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5], ms); + ms.Position = 0; + var ktx = KtxFile.Load(ms); + Assert.Equal(6, ktx.MipMaps[0].Faces.Length); + } + } + + public class ImageSharpDecodingTests + { + private readonly BcEncoder encoder; + private readonly BcDecoder decoder; + private readonly Image original; + private readonly KtxFile encodedKtx; + private readonly DdsFile encodedDds; + private readonly byte[] rawEncoded; + + public ImageSharpDecodingTests() + { + encoder = new BcEncoder(); + encoder.OutputOptions.Quality = CompressionQuality.Fast; + decoder = new BcDecoder(); + original = ImageLoader.LoadTestImageSharp("../../../testImages/test_gradient_1_512.jpg"); + encodedKtx = encoder.EncodeToKtx(ImageLoader.TestGradient1); + encodedDds = encoder.EncodeToDds(ImageLoader.TestGradient1); + rawEncoded = encoder.EncodeToRawBytes(ImageLoader.TestGradient1)[0]; + } + + [Fact] + public void DecodeKtxFile() + { + using var decoded = decoder.DecodeToImageRgba32(encodedKtx); + Assert.Equal(original.Width, decoded.Width); + Assert.Equal(original.Height, decoded.Height); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void DecodeDdsFile() + { + using var decoded = decoder.DecodeToImageRgba32(encodedDds); + Assert.Equal(original.Width, decoded.Width); + Assert.Equal(original.Height, decoded.Height); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void DecodeStream() + { + using var ms = new MemoryStream(); + encodedKtx.Write(ms); + ms.Position = 0; + using var decoded = decoder.DecodeToImageRgba32(ms); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void DecodeAllMipMapsKtx() + { + encoder.OutputOptions.GenerateMipMaps = true; + var ktxWithMips = encoder.EncodeToKtx(ImageLoader.TestGradient1); + var images = decoder.DecodeAllMipMapsToImageRgba32(ktxWithMips); + + Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length); + Assert.True(images.Length > 1); + TestHelper.AssertImagesEqual(original, images[0], encoder.OutputOptions.Quality); + + foreach (var img in images) img.Dispose(); + } + + [Fact] + public void DecodeAllMipMapsDds() + { + encoder.OutputOptions.GenerateMipMaps = true; + var ddsWithMips = encoder.EncodeToDds(ImageLoader.TestGradient1); + var images = decoder.DecodeAllMipMapsToImageRgba32(ddsWithMips); + + Assert.Equal((int)ddsWithMips.header.dwMipMapCount, images.Length); + Assert.True(images.Length > 1); + + foreach (var img in images) img.Dispose(); + } + + [Fact] + public void DecodeAllMipMapsStream() + { + encoder.OutputOptions.GenerateMipMaps = true; + var ktxWithMips = encoder.EncodeToKtx(ImageLoader.TestGradient1); + using var ms = new MemoryStream(); + ktxWithMips.Write(ms); + ms.Position = 0; + + var images = decoder.DecodeAllMipMapsToImageRgba32(ms); + Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length); + + foreach (var img in images) img.Dispose(); + } + + [Fact] + public void DecodeRaw() + { + using var decoded = decoder.DecodeRawToImageRgba32(rawEncoded, original.Width, original.Height, CompressionFormat.Bc1); + Assert.Equal(original.Width, decoded.Width); + Assert.Equal(original.Height, decoded.Height); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public void DecodeRawStream() + { + using var ms = new MemoryStream(rawEncoded); + using var decoded = decoder.DecodeRawToImageRgba32(ms, original.Width, original.Height, CompressionFormat.Bc1); + Assert.Equal(original.Width, decoded.Width); + Assert.Equal(original.Height, decoded.Height); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task DecodeKtxFileAsync() + { + using var decoded = await decoder.DecodeToImageRgba32Async(encodedKtx); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task DecodeDdsFileAsync() + { + using var decoded = await decoder.DecodeToImageRgba32Async(encodedDds); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task DecodeStreamAsync() + { + using var ms = new MemoryStream(); + encodedKtx.Write(ms); + ms.Position = 0; + using var decoded = await decoder.DecodeToImageRgba32Async(ms); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task DecodeAllMipMapsKtxAsync() + { + encoder.OutputOptions.GenerateMipMaps = true; + var ktxWithMips = encoder.EncodeToKtx(ImageLoader.TestGradient1); + var images = await decoder.DecodeAllMipMapsToImageRgba32Async(ktxWithMips); + + Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length); + Assert.True(images.Length > 1); + + foreach (var img in images) img.Dispose(); + } + + [Fact] + public async Task DecodeAllMipMapsStreamAsync() + { + encoder.OutputOptions.GenerateMipMaps = true; + var ktxWithMips = encoder.EncodeToKtx(ImageLoader.TestGradient1); + using var ms = new MemoryStream(); + ktxWithMips.Write(ms); + ms.Position = 0; + + var images = await decoder.DecodeAllMipMapsToImageRgba32Async(ms); + Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length); + + foreach (var img in images) img.Dispose(); + } + + [Fact] + public async Task DecodeRawAsync() + { + using var decoded = await decoder.DecodeRawToImageRgba32Async(rawEncoded, original.Width, original.Height, CompressionFormat.Bc1); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + + [Fact] + public async Task DecodeRawStreamAsync() + { + using var ms = new MemoryStream(rawEncoded); + using var decoded = await decoder.DecodeRawToImageRgba32Async(ms, original.Width, original.Height, CompressionFormat.Bc1); + TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality); + } + } +} From 69fcfaf63f0bd11d06491643333f1e9f39c0f816 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:12:23 +0200 Subject: [PATCH 22/30] Add MipMapper tests --- BCnEncTests/MipMapperTests.cs | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 BCnEncTests/MipMapperTests.cs diff --git a/BCnEncTests/MipMapperTests.cs b/BCnEncTests/MipMapperTests.cs new file mode 100644 index 0000000..5da9c7e --- /dev/null +++ b/BCnEncTests/MipMapperTests.cs @@ -0,0 +1,79 @@ +using System; +using BCnEncoder.Shared; +using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; +using Xunit; +using Xunit.Abstractions; + +namespace BCnEncTests +{ + public class MipMapperTests + { + private readonly ITestOutputHelper output; + + public MipMapperTests(ITestOutputHelper output) => this.output = output; + + [Fact] + public void MipChainHasCorrectDimensions() + { + var image = ImageLoader.TestGradient1; // 512x416 + var numMips = 0; + var chain = MipMapper.GenerateMipChain(image, ref numMips); + + Assert.Equal(chain.Length, numMips); + Assert.True(numMips > 1); + + for (var i = 0; i < numMips; i++) + { + Assert.Equal(Math.Max(1, image.Width >> i), chain[i].Width); + Assert.Equal(Math.Max(1, image.Height >> i), chain[i].Height); + } + + // Last level must be 1x1 + Assert.Equal(1, chain[numMips - 1].Width); + Assert.Equal(1, chain[numMips - 1].Height); + } + + /// + /// Compares each mip level produced by MipMapper against the equivalent + /// ImageSharp Box-filter resize (Compand=false so both operate in sRGB + /// byte space without gamma correction). The PSNR should be very high + /// because both algorithms perform the same 2x2 box average. + /// + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + public void MipLevelMatchesImageSharpBoxFilter(int mipLevel) + { + var image = ImageLoader.TestGradient1; + var numMips = mipLevel + 1; + var chain = MipMapper.GenerateMipChain(image, ref numMips); + + var mipW = chain[mipLevel].Width; + var mipH = chain[mipLevel].Height; + + // ImageSharp Box sampler + Compand=false: no gamma correction, + // averages pixel values in sRGB space — matches MipMapper exactly. + using var imageSharp = ImageLoader.LoadTestImageSharp("../../../testImages/test_gradient_1_512.jpg"); + using var reference = imageSharp.Clone(x => x.Resize(new ResizeOptions + { + Size = new Size(mipW, mipH), + Sampler = KnownResamplers.Box, + Compand = false + })); + + var mipColors = TestHelper.GetSinglePixelArrayAsColors(chain[mipLevel]); + var refColors = TestHelper.GetSinglePixelArrayAsColors(reference); + + var psnr = ImageQuality.PeakSignalToNoiseRatio(mipColors, refColors); + output.WriteLine($"Mip level {mipLevel} ({mipW}x{mipH}): PSNR vs ImageSharp Box = {psnr:F2} dB"); + + Assert.True(psnr > 40, + $"Mip level {mipLevel}: PSNR vs ImageSharp Box was {psnr:F2} dB (expected > 40)"); + } + } +} From e529a89730b5b478d24e74a0f8963429e0d6fdb6 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:50:32 +0200 Subject: [PATCH 23/30] Better HdrImageTests --- BCnEncTests.Framework/HdrImageTests.cs | 41 +++++++++++++++++++++++--- BCnEncTests/HdrImageTests.cs | 23 +++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/BCnEncTests.Framework/HdrImageTests.cs b/BCnEncTests.Framework/HdrImageTests.cs index eda8e56..613b104 100644 --- a/BCnEncTests.Framework/HdrImageTests.cs +++ b/BCnEncTests.Framework/HdrImageTests.cs @@ -1,4 +1,8 @@ +using System; using System.IO; +using System.Threading.Tasks; +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; using BCnEncoder.Shared; using BCnEncTests.Support; using CommunityToolkit.HighPerformance; @@ -17,10 +21,39 @@ public void LoadHdr() Assert.True(hdrImg.height > 0); Assert.True(hdrImg.pixels.Length == hdrImg.width * hdrImg.height); - // Save as HDR to verify round-trip - var hdrOut = new HdrImage(new Span2D(hdrImg.pixels, hdrImg.height, hdrImg.width)); - using var outStream = File.OpenWrite("test_hdr_load.hdr"); - hdrOut.Write(outStream); + var rgba = new ColorRgba32[hdrImg.pixels.Length]; + for (var i = 0; i < hdrImg.pixels.Length; i++) + { + var p = hdrImg.pixels[i]; + rgba[i] = new ColorRgba32( + (byte)(Math.Max(0, Math.Min(1, p.r)) * 255 + 0.5f), + (byte)(Math.Max(0, Math.Min(1, p.g)) * 255 + 0.5f), + (byte)(Math.Max(0, Math.Min(1, p.b)) * 255 + 0.5f), + 255); + } + var converted = new Memory2D(rgba, hdrImg.height, hdrImg.width); + var reference = ImageLoader.LoadTestImage("../../../../BCnEncTests/testImages/test_hdr_kiara.png"); + TestHelper.AssertImagesEqual(reference, converted, CompressionQuality.BestQuality); + } + + [Fact] + public async Task DecodeAllMipMapsHdrStreamAsync() + { + var encoder = new BcEncoder(); + encoder.OutputOptions.Format = CompressionFormat.Bc6U; + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + + var decoder = new BcDecoder(); + var input = HdrLoader.TestHdrKiara; + var ktxWithMips = encoder.EncodeToKtxHdr(new Memory2D(input.pixels, input.height, input.width)); + using var ms = new MemoryStream(); + ktxWithMips.Write(ms); + ms.Position = 0; + + var images = await decoder.DecodeAllMipMapsHdr2DAsync(ms); + Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length); + Assert.True(images.Length > 1); } } } diff --git a/BCnEncTests/HdrImageTests.cs b/BCnEncTests/HdrImageTests.cs index 9e05ab2..14dc3d1 100644 --- a/BCnEncTests/HdrImageTests.cs +++ b/BCnEncTests/HdrImageTests.cs @@ -2,9 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading.Tasks; +using BCnEncoder.Decoder; using BCnEncoder.Encoder; using BCnEncoder.Shared; using BCnEncTests.Support; +using CommunityToolkit.HighPerformance; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -39,5 +42,25 @@ public void LoadHdr() TestHelper.AssertImagesEqual(HdrLoader.ReferenceKiara, img2, CompressionQuality.BestQuality); } + + [Fact] + public async Task DecodeAllMipMapsHdrStreamAsync() + { + var encoder = new BcEncoder(); + encoder.OutputOptions.Format = CompressionFormat.Bc6U; + encoder.OutputOptions.Quality = CompressionQuality.Fast; + encoder.OutputOptions.GenerateMipMaps = true; + + var decoder = new BcDecoder(); + var input = HdrLoader.TestHdrKiara; + var ktxWithMips = encoder.EncodeToKtxHdr(new Memory2D(input.pixels, input.height, input.width)); + using var ms = new MemoryStream(); + ktxWithMips.Write(ms); + ms.Position = 0; + + var images = await decoder.DecodeAllMipMapsHdr2DAsync(ms); + Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length); + Assert.True(images.Length > 1); + } } } From df04fce14c792e1969adc10365e389ca940a1581 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:12:41 +0200 Subject: [PATCH 24/30] Don't heap alloc on indices selection on netstandard 2.1 --- BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs index 7c9c911..0eb201d 100644 --- a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs +++ b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs @@ -57,11 +57,17 @@ public static int[] Rank2SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock, // Copy struct to array before the closure so that reducedIndicesBlock is not // heap-captured. On .NET Framework, MemoryMarshalPolyfills.CreateSpan uses // Unsafe.AsPointer, which is only GC-safe for stack-allocated structs. + #if NETSTANDARD2_0 var indices = new int[16]; - for (var k = 0; k < 16; k++) indices[k] = reducedIndicesBlock[k]; + reducedIndicesBlock.AsSpan.CopyTo(indices); + #endif int CalculatePartitionError(int partitionIndex) { + #if NETSTANDARD2_1 + var indices = reducedIndicesBlock.AsSpan; + #endif + var error = 0; ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[partitionIndex]; Span subset0 = stackalloc int[numDistinctClusters]; @@ -122,11 +128,17 @@ public static int[] Rank3SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock, // Copy struct to array before the closure so that reducedIndicesBlock is not // heap-captured. On .NET Framework, MemoryMarshalPolyfills.CreateSpan uses // Unsafe.AsPointer, which is only GC-safe for stack-allocated structs. + #if NETSTANDARD2_0 var indices = new int[16]; - for (var k = 0; k < 16; k++) indices[k] = reducedIndicesBlock[k]; + reducedIndicesBlock.AsSpan.CopyTo(indices); + #endif int CalculatePartitionError(int partitionIndex) { + #if NETSTANDARD2_1 + var indices = reducedIndicesBlock.AsSpan; + #endif + var error = 0; ReadOnlySpan partitionTable = Bc7Block.Subsets3PartitionTable[partitionIndex]; From dcb9f71ffe2b4ddb23806aa416c819ae5dc8b090 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:03:49 +0200 Subject: [PATCH 25/30] Add back helpful comments --- BCnEncTests.Shared/Bc7BlockTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/BCnEncTests.Shared/Bc7BlockTests.cs b/BCnEncTests.Shared/Bc7BlockTests.cs index a2904da..ef94955 100644 --- a/BCnEncTests.Shared/Bc7BlockTests.cs +++ b/BCnEncTests.Shared/Bc7BlockTests.cs @@ -96,10 +96,13 @@ public void DecodeErrorBlock() private void Type0Pack(Span output) { var subsetEndpoints = new[] { + //subset 1 new byte[]{0b1111, 0, 0}, new byte[]{0b1000, 0, 0}, + // subset 2 new byte[]{0, 0b1111, 0}, new byte[]{0, 0b1000, 0}, + //subset 3 new byte[]{0, 0, 0b1111}, new byte[]{0, 0, 0b1000} }; @@ -163,8 +166,10 @@ private void Type0Pack(Span output) private void Type1Pack(Span output) { var subsetEndpoints = new[] { + //subset 1 new byte[]{0b111111, 0b0100, 0}, new byte[]{0b0100, 0b111111, 0}, + // subset 2 new byte[]{0, 0, 0b111111}, new byte[]{0, 0, 0b1010} }; @@ -186,10 +191,13 @@ private void Type1Pack(Span output) private void Type2Pack(Span output) { var subsetEndpoints = new[] { + //subset 1 new byte[]{0b11111, 0, 0}, new byte[]{0b01000, 0, 0}, + // subset 2 new byte[]{0, 0b11111, 0}, new byte[]{0, 0b01000, 0}, + //subset 3 new byte[]{0, 0, 0b11111}, new byte[]{0, 0, 0b01000} }; @@ -210,8 +218,10 @@ private void Type2Pack(Span output) private void Type3Pack(Span output) { var subsetEndpoints = new[] { + //subset 1 new byte[]{0b1111111, 0b10100, 0}, new byte[]{0b10100, 0b1111111, 0}, + // subset 2 new byte[]{0, 0, 0b1111111}, new byte[]{0, 0, 0b11010} }; @@ -233,6 +243,7 @@ private void Type3Pack(Span output) private void Type4Pack(Span output) { var colorEndpoints = new[] { + //subset 1 new byte[]{0b11111, 0, 0b0100}, new byte[]{0, 0b11111, 0b0100} }; @@ -265,6 +276,7 @@ private void Type4Pack(Span output) private void Type5Pack(Span output) { var colorEndpoints = new[] { + //subset 1 new byte[]{0b1111111, 0b0100, 0}, new byte[]{0, 0, 0b1111111} }; @@ -294,6 +306,7 @@ private void Type5Pack(Span output) private void Type6Pack(Span output) { var colorEndpoints = new[] { + //subset 1 new byte[]{0b1111111, 0b0100, 0, 0b1111111}, new byte[]{0, 0, 0b1111111, 0b1111} }; @@ -314,8 +327,10 @@ private void Type6Pack(Span output) private void Type7Pack(Span output) { var colorEndpoints = new[] { + //subset 1 new byte[]{0b11111, 0b0100, 0, 0b11111}, new byte[]{0, 0b11111, 0, 0b111}, + //subset 2 new byte[]{0b11111, 0b0100, 0, 0b1111}, new byte[]{0b10100, 0, 0b11111, 0b11111} }; From 26a2c1e199691154bf0ccea8c3c5394b912da34e Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:31:59 +0200 Subject: [PATCH 26/30] Don't use LangVersion 8.0 for .NET Framework tests --- .../BCnEncTests.Framework.csproj | 1 - BCnEncTests.Framework/HdrImageTests.cs | 50 ++++++----- BCnEncTests.Framework/Support/ImageLoader.cs | 18 ++-- BCnEncTests.Framework/Support/TestHelper.cs | 89 +++++++++++-------- BCnEncTests.Shared/Bc6EncoderTests.cs | 12 ++- BCnEncTests.Shared/Bc6HDecoderTests.cs | 70 +++++++++------ BCnEncTests.Shared/Bc7BlockTests.cs | 6 +- BCnEncTests.Shared/ClusterTests.cs | 6 +- BCnEncTests.Shared/DdsReadTests.cs | 19 ++-- BCnEncTests.Shared/EncodingTest.cs | 6 +- BCnEncTests.Shared/ProgressTests.cs | 6 +- BCnEncTests.Shared/RawTests.cs | 72 +++++++-------- 12 files changed, 203 insertions(+), 152 deletions(-) diff --git a/BCnEncTests.Framework/BCnEncTests.Framework.csproj b/BCnEncTests.Framework/BCnEncTests.Framework.csproj index e14d3cc..c928b90 100644 --- a/BCnEncTests.Framework/BCnEncTests.Framework.csproj +++ b/BCnEncTests.Framework/BCnEncTests.Framework.csproj @@ -2,7 +2,6 @@ net481 - 8.0 false true diff --git a/BCnEncTests.Framework/HdrImageTests.cs b/BCnEncTests.Framework/HdrImageTests.cs index 613b104..75e9c8e 100644 --- a/BCnEncTests.Framework/HdrImageTests.cs +++ b/BCnEncTests.Framework/HdrImageTests.cs @@ -15,25 +15,27 @@ public class HdrImageTests [Fact] public void LoadHdr() { - using var stream = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara.hdr"); - var hdrImg = HdrImage.Read(stream); - Assert.True(hdrImg.width > 0); - Assert.True(hdrImg.height > 0); - Assert.True(hdrImg.pixels.Length == hdrImg.width * hdrImg.height); - - var rgba = new ColorRgba32[hdrImg.pixels.Length]; - for (var i = 0; i < hdrImg.pixels.Length; i++) + using (var stream = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara.hdr")) { - var p = hdrImg.pixels[i]; - rgba[i] = new ColorRgba32( - (byte)(Math.Max(0, Math.Min(1, p.r)) * 255 + 0.5f), - (byte)(Math.Max(0, Math.Min(1, p.g)) * 255 + 0.5f), - (byte)(Math.Max(0, Math.Min(1, p.b)) * 255 + 0.5f), - 255); + var hdrImg = HdrImage.Read(stream); + Assert.True(hdrImg.width > 0); + Assert.True(hdrImg.height > 0); + Assert.True(hdrImg.pixels.Length == hdrImg.width * hdrImg.height); + + var rgba = new ColorRgba32[hdrImg.pixels.Length]; + for (var i = 0; i < hdrImg.pixels.Length; i++) + { + var p = hdrImg.pixels[i]; + rgba[i] = new ColorRgba32( + (byte)(Math.Max(0, Math.Min(1, p.r)) * 255 + 0.5f), + (byte)(Math.Max(0, Math.Min(1, p.g)) * 255 + 0.5f), + (byte)(Math.Max(0, Math.Min(1, p.b)) * 255 + 0.5f), + 255); + } + var converted = new Memory2D(rgba, hdrImg.height, hdrImg.width); + var reference = ImageLoader.LoadTestImage("../../../../BCnEncTests/testImages/test_hdr_kiara.png"); + TestHelper.AssertImagesEqual(reference, converted, CompressionQuality.BestQuality); } - var converted = new Memory2D(rgba, hdrImg.height, hdrImg.width); - var reference = ImageLoader.LoadTestImage("../../../../BCnEncTests/testImages/test_hdr_kiara.png"); - TestHelper.AssertImagesEqual(reference, converted, CompressionQuality.BestQuality); } [Fact] @@ -47,13 +49,15 @@ public async Task DecodeAllMipMapsHdrStreamAsync() var decoder = new BcDecoder(); var input = HdrLoader.TestHdrKiara; var ktxWithMips = encoder.EncodeToKtxHdr(new Memory2D(input.pixels, input.height, input.width)); - using var ms = new MemoryStream(); - ktxWithMips.Write(ms); - ms.Position = 0; + using (var ms = new MemoryStream()) + { + ktxWithMips.Write(ms); + ms.Position = 0; - var images = await decoder.DecodeAllMipMapsHdr2DAsync(ms); - Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length); - Assert.True(images.Length > 1); + var images = await decoder.DecodeAllMipMapsHdr2DAsync(ms); + Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length); + Assert.True(images.Length > 1); + } } } } diff --git a/BCnEncTests.Framework/Support/ImageLoader.cs b/BCnEncTests.Framework/Support/ImageLoader.cs index 2dc9958..0a90454 100644 --- a/BCnEncTests.Framework/Support/ImageLoader.cs +++ b/BCnEncTests.Framework/Support/ImageLoader.cs @@ -35,8 +35,10 @@ public static class ImageLoader internal static Memory2D LoadTestImage(string filename) { - using var bmp = new Bitmap(filename); - return FromBitmap(bmp); + using (var bmp = new Bitmap(filename)) + { + return FromBitmap(bmp); + } } internal static unsafe Memory2D FromBitmap(Bitmap bmp) @@ -72,8 +74,10 @@ public static class DdsLoader internal static DdsFile LoadDdsFile(string filename) { - using var fs = File.OpenRead(filename); - return DdsFile.Load(fs); + using (var fs = File.OpenRead(filename)) + { + return DdsFile.Load(fs); + } } } @@ -91,8 +95,10 @@ public static class KtxLoader internal static KtxFile LoadKtxFile(string filename) { - using var fs = File.OpenRead(filename); - return KtxFile.Load(fs); + using (var fs = File.OpenRead(filename)) + { + return KtxFile.Load(fs); + } } } } diff --git a/BCnEncTests.Framework/Support/TestHelper.cs b/BCnEncTests.Framework/Support/TestHelper.cs index e5e2280..ba9cc8c 100644 --- a/BCnEncTests.Framework/Support/TestHelper.cs +++ b/BCnEncTests.Framework/Support/TestHelper.cs @@ -51,8 +51,10 @@ public static void ExecuteDecodingTest(KtxFile file, string outputFile) Assert.Equal((uint)pixels.Width, file.header.PixelWidth); Assert.Equal((uint)pixels.Height, file.header.PixelHeight); - using var outFs = File.OpenWrite(outputFile); - SaveAsPng(pixels, outFs); + using (var outFs = File.OpenWrite(outputFile)) + { + SaveAsPng(pixels, outFs); + } } #region Dds @@ -70,15 +72,16 @@ public static void ExecuteDdsWritingTest(Memory2D[] images, Compres encoder.OutputOptions.Format = format; encoder.OutputOptions.FileFormat = OutputFileFormat.Dds; - using var fs = File.OpenWrite(outputFile); - - if (images.Length == 1) + using (var fs = File.OpenWrite(outputFile)) { - encoder.EncodeToStream(images[0], fs); - } - else - { - encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); + if (images.Length == 1) + { + encoder.EncodeToStream(images[0], fs); + } + else + { + encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); + } } } @@ -102,8 +105,10 @@ public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string Assert.Contains(pixels, x => x.a == 0); } - using var outFs = File.OpenWrite(string.Format(outputFile, i)); - SaveAsPng(images[i], outFs); + using (var outFs = File.OpenWrite(string.Format(outputFile, i))) + { + SaveAsPng(images[i], outFs); + } } } @@ -128,26 +133,30 @@ await Assert.ThrowsAnyAsync(() => public static float DecodeKtxCheckPSNR(string filename, Memory2D original) { - using var fs = File.OpenRead(filename); - var ktx = KtxFile.Load(fs); - var decoder = new BcDecoder() + using (var fs = File.OpenRead(filename)) { - OutputOptions = { Bc4Component = ColorComponent.Luminance } - }; - var decoded = decoder.Decode2D(ktx); + var ktx = KtxFile.Load(fs); + var decoder = new BcDecoder() + { + OutputOptions = { Bc4Component = ColorComponent.Luminance } + }; + var decoded = decoder.Decode2D(ktx); - return CalculatePSNR(original, decoded); + return CalculatePSNR(original, decoded); + } } public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original) { - using var fs = File.OpenRead(filename); - var ktx = KtxFile.Load(fs); - var decoder = new BcDecoder(); + using (var fs = File.OpenRead(filename)) + { + var ktx = KtxFile.Load(fs); + var decoder = new BcDecoder(); - var decoded = decoder.DecodeHdr(ktx); + var decoded = decoder.DecodeHdr(ktx); - return ImageQuality.CalculateLogRMSE(original.pixels, decoded); + return ImageQuality.CalculateLogRMSE(original.pixels, decoded); + } } public static void ExecuteEncodingTest(Memory2D image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output) @@ -234,25 +243,27 @@ public static unsafe void SaveAsPng(Memory2D image, Stream stream) { int width = image.Width; int height = image.Height; - using var bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - var data = bmp.LockBits(new Rectangle(0, 0, width, height), - ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - byte* ptr = (byte*)data.Scan0; - var span = image.Span; - for (int y = 0; y < height; y++) + using (var bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb)) { - for (int x = 0; x < width; x++) + var data = bmp.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + byte* ptr = (byte*)data.Scan0; + var span = image.Span; + for (int y = 0; y < height; y++) { - var c = span[y, x]; - ptr[0] = c.b; - ptr[1] = c.g; - ptr[2] = c.r; - ptr[3] = c.a; - ptr += 4; + for (int x = 0; x < width; x++) + { + var c = span[y, x]; + ptr[0] = c.b; + ptr[1] = c.g; + ptr[2] = c.r; + ptr[3] = c.a; + ptr += 4; + } } + bmp.UnlockBits(data); + bmp.Save(stream, ImageFormat.Png); } - bmp.UnlockBits(data); - bmp.Save(stream, ImageFormat.Png); } public static void SaveAsPng(ColorRgbFloat[] pixels, int width, int height, Stream stream) diff --git a/BCnEncTests.Shared/Bc6EncoderTests.cs b/BCnEncTests.Shared/Bc6EncoderTests.cs index 6a5bcad..ced39c6 100644 --- a/BCnEncTests.Shared/Bc6EncoderTests.cs +++ b/BCnEncTests.Shared/Bc6EncoderTests.cs @@ -353,8 +353,10 @@ public void EncodeToKtx() var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory); - using var fs = File.OpenWrite("encoding_bc6_ktx.ktx"); - ktx.Write(fs); + using (var fs = File.OpenWrite("encoding_bc6_ktx.ktx")) + { + ktx.Write(fs); + } } [Fact] @@ -367,8 +369,10 @@ public void EncodeToDds() var dds = encoder.EncodeToDdsHdr(HdrLoader.TestHdrKiara.PixelMemory); - using var fs = File.OpenWrite("encoding_bc6_dds.dds"); - dds.Write(fs); + using (var fs = File.OpenWrite("encoding_bc6_dds.dds")) + { + dds.Write(fs); + } } [Fact] diff --git a/BCnEncTests.Shared/Bc6HDecoderTests.cs b/BCnEncTests.Shared/Bc6HDecoderTests.cs index 75aa7c3..2cbb609 100644 --- a/BCnEncTests.Shared/Bc6HDecoderTests.cs +++ b/BCnEncTests.Shared/Bc6HDecoderTests.cs @@ -31,11 +31,15 @@ public void DecodeDds() Assert.Equal(hdr.pixels.Length, decoded.Length); hdr.pixels = decoded; - using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr"); - hdr.Write(sfs); + using (var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr")) + { + hdr.Write(sfs); + } - using var pngFs = File.OpenWrite("decoding_test_dds_bc6h.png"); - TestHelper.SaveAsPng(decoded, width, height, pngFs); + using (var pngFs = File.OpenWrite("decoding_test_dds_bc6h.png")) + { + TestHelper.SaveAsPng(decoded, width, height, pngFs); + } TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output); } @@ -53,11 +57,15 @@ public void DecodeKtx() Assert.Equal(hdr.pixels.Length, decoded.Length); hdr.pixels = decoded; - using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr"); - hdr.Write(sfs); + using (var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr")) + { + hdr.Write(sfs); + } - using var pngFs = File.OpenWrite("decoding_test_ktx_bc6h.png"); - TestHelper.SaveAsPng(decoded, width, height, pngFs); + using (var pngFs = File.OpenWrite("decoding_test_ktx_bc6h.png")) + { + TestHelper.SaveAsPng(decoded, width, height, pngFs); + } TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.BestQuality, output); } @@ -71,28 +79,32 @@ public void AllBlocksDecodesExact() var decoder = new BcDecoder(); var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds); + Stream fs; #if NETCOREAPP - using var fs = File.OpenRead("../../../testImages/test_hdr_kiara_dds_float16_data.bin"); + fs = File.OpenRead("../../../testImages/test_hdr_kiara_dds_float16_data.bin"); #else - using var fs = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin"); + fs = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin"); #endif - using var ms = new MemoryStream(); - fs.CopyTo(ms); - var length = (int)ms.Position; - - var bytes = ms.GetBuffer().AsSpan(0, length); - var halfs = MemoryMarshal.Cast(bytes); - Assert.Equal(halfs.Length / 4, decoded.Length); - - for (var i = 0; i < decoded.Length; i++) + using (fs) + using (var ms = new MemoryStream()) { - float r = halfs[i * 4 + 0]; - float g = halfs[i * 4 + 1]; - float b = halfs[i * 4 + 2]; - - Assert.Equal(r, decoded[i].r); - Assert.Equal(g, decoded[i].g); - Assert.Equal(b, decoded[i].b); + fs.CopyTo(ms); + var length = (int)ms.Position; + + var bytes = ms.GetBuffer().AsSpan(0, length); + var halfs = MemoryMarshal.Cast(bytes); + Assert.Equal(halfs.Length / 4, decoded.Length); + + for (var i = 0; i < decoded.Length; i++) + { + float r = halfs[i * 4 + 0]; + float g = halfs[i * 4 + 1]; + float b = halfs[i * 4 + 2]; + + Assert.Equal(r, decoded[i].r); + Assert.Equal(g, decoded[i].g); + Assert.Equal(b, decoded[i].b); + } } } @@ -152,8 +164,10 @@ public void DecodeErrorBlock() Assert.Contains(new ColorRgbFloat(1, 0, 1), decoded); HdrImage image = new HdrImage(new Span2D(decoded, height * 4, width * 4)); - using var fs = File.OpenWrite("test_decode_bc6h_error.hdr"); - image.Write(fs); + using (var fs = File.OpenWrite("test_decode_bc6h_error.hdr")) + { + image.Write(fs); + } } } } diff --git a/BCnEncTests.Shared/Bc7BlockTests.cs b/BCnEncTests.Shared/Bc7BlockTests.cs index ef94955..8ac34ea 100644 --- a/BCnEncTests.Shared/Bc7BlockTests.cs +++ b/BCnEncTests.Shared/Bc7BlockTests.cs @@ -87,8 +87,10 @@ public void DecodeErrorBlock() Assert.Contains(new ColorRgba32(255, 0, 255), pixels); var decoded = new Memory2D(pixels, height * 4, width * 4); - using var fs = File.OpenWrite("test_decode_bc7_error.png"); - TestHelper.SaveAsPng(decoded, fs); + using (var fs = File.OpenWrite("test_decode_bc7_error.png")) + { + TestHelper.SaveAsPng(decoded, fs); + } } #region Type Packs diff --git a/BCnEncTests.Shared/ClusterTests.cs b/BCnEncTests.Shared/ClusterTests.cs index d8c20b8..a30ab52 100644 --- a/BCnEncTests.Shared/ClusterTests.cs +++ b/BCnEncTests.Shared/ClusterTests.cs @@ -46,8 +46,10 @@ public void Clusterize() } var result = new Memory2D(pixels, height, width); - using var fs = File.OpenWrite("test_cluster.png"); - TestHelper.SaveAsPng(result, fs); + using (var fs = File.OpenWrite("test_cluster.png")) + { + TestHelper.SaveAsPng(result, fs); + } } } } diff --git a/BCnEncTests.Shared/DdsReadTests.cs b/BCnEncTests.Shared/DdsReadTests.cs index 90c9279..3c2a35e 100644 --- a/BCnEncTests.Shared/DdsReadTests.cs +++ b/BCnEncTests.Shared/DdsReadTests.cs @@ -36,15 +36,18 @@ public void ReadBc7() [Fact] public void ReadFromStream() { - using var fs = File.OpenRead(DdsLoader.TestDecompressBc1Name); - - var decoder = new BcDecoder(); - var images = decoder.DecodeAllMipMaps2D(fs); - - for (var i = 0; i < images.Length; i++) + using (var fs = File.OpenRead(DdsLoader.TestDecompressBc1Name)) { - using var outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png"); - TestHelper.SaveAsPng(images[i], outFs); + var decoder = new BcDecoder(); + var images = decoder.DecodeAllMipMaps2D(fs); + + for (var i = 0; i < images.Length; i++) + { + using (var outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png")) + { + TestHelper.SaveAsPng(images[i], outFs); + } + } } } } diff --git a/BCnEncTests.Shared/EncodingTest.cs b/BCnEncTests.Shared/EncodingTest.cs index 055de03..e4f3edd 100644 --- a/BCnEncTests.Shared/EncodingTest.cs +++ b/BCnEncTests.Shared/EncodingTest.cs @@ -308,8 +308,10 @@ public void WriteCubeMapFile() encoder.OutputOptions.GenerateMipMaps = true; encoder.OutputOptions.Format = CompressionFormat.Bc1; - using var fs = File.OpenWrite(filename); - encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); + using (var fs = File.OpenWrite(filename)) + { + encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs); + } } } } diff --git a/BCnEncTests.Shared/ProgressTests.cs b/BCnEncTests.Shared/ProgressTests.cs index c82777b..83dab93 100644 --- a/BCnEncTests.Shared/ProgressTests.cs +++ b/BCnEncTests.Shared/ProgressTests.cs @@ -31,8 +31,10 @@ private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Memory2D diff --git a/README.md b/README.md index 367c927..d8da209 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A Cross-platform BCn / DXT encoding libary for .NET # What is it? -BCnEncoder.NET is a library for compressing rgba images to different block-compressed formats. It has no native dependencies and is .NET Standard 2.1 compatible. +BCnEncoder.NET is a library for compressing rgba images to different block-compressed formats. It has no native dependencies and is .NET Standard 2.1 & 2.0 compatible. Supported formats are: - Raw unsigned byte R, RG, RGB and RGBA formats @@ -21,10 +21,16 @@ Supported formats are: The current state of this library is in development but quite usable. I'm planning on implementing support for more codecs and different algorithms. The current version is capable of encoding and decoding BC1-BC7 images in both KTX or DDS formats. +.NET Standard 2.0 support is experimental, and relies on [PolySharp](https://www.nuget.org/packages/PolySharp) & [Microsoft.Bcl.Numerics](https://www.nuget.org/packages/Microsoft.Bcl.Numerics). + Please note, that the API might change between versions. + # Dependencies Current dependencies are: * [CommunityToolkit.HighPerformance](https://www.nuget.org/packages/CommunityToolkit.HighPerformance/) licensed under the [MIT](https://opensource.org/licenses/MIT) license for Span2D and Memory2D types. +Additionally, for .NET Standard 2.0: +* [PolySharp](https://www.nuget.org/packages/PolySharp) licensed under the [MIT](https://opensource.org/licenses/MIT) license. +* [Microsoft.Bcl.Numerics](https://www.nuget.org/packages/Microsoft.Bcl.Numerics) licensed under the [MIT](https://opensource.org/licenses/MIT) license. # Image library extensions This library has extension packages available for the following image libraries: From 74eda8a330759d81e6307f19aefc547e40687825 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:37:42 +0200 Subject: [PATCH 29/30] Add missing space in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d8da209..2e157c7 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Please note, that the API might change between versions. # Dependencies Current dependencies are: * [CommunityToolkit.HighPerformance](https://www.nuget.org/packages/CommunityToolkit.HighPerformance/) licensed under the [MIT](https://opensource.org/licenses/MIT) license for Span2D and Memory2D types. + Additionally, for .NET Standard 2.0: * [PolySharp](https://www.nuget.org/packages/PolySharp) licensed under the [MIT](https://opensource.org/licenses/MIT) license. * [Microsoft.Bcl.Numerics](https://www.nuget.org/packages/Microsoft.Bcl.Numerics) licensed under the [MIT](https://opensource.org/licenses/MIT) license. From 51d3199b3bc374e0bdda3947d9f347cb6235a9a0 Mon Sep 17 00:00:00 2001 From: Nominom <7089998+Nominom@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:45:33 +0200 Subject: [PATCH 30/30] Add bugfix to release notes --- BCnEnc.Net/BCnEncoder.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BCnEnc.Net/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj index 94038b7..64bb373 100644 --- a/BCnEnc.Net/BCnEncoder.csproj +++ b/BCnEnc.Net/BCnEncoder.csproj @@ -33,7 +33,8 @@ Supported formats are: BCn BC BC1 BC2 BC3 BC4 BC5 BC6 BC6H BC7 BPTC RGTC S3TC DXT1 DXT3 DXT5 ktx dds texture compression encoding decoding decompression image gpu https://github.com/Nominom/BCnEncoder.NET 2.3.0 -- Add .NET Standard 2.0 support +- Add .NET Standard 2.0 support +- Fix some DecodeAllMipMaps methods not decoding all MipMaps