diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..1bf2168
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = tab
+indent_size = 4
+
+[*.{diff,md}]
+trim_trailing_whitespace = false
\ No newline at end of file
diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml
index 7308a9d..96d29b4 100644
--- a/.github/workflows/dotnetcore.yml
+++ b/.github/workflows/dotnetcore.yml
@@ -1,25 +1,35 @@
-name: Tests
+name: build and test
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
+ paths:
+ - '**.cs'
+ - '**.csproj'
+
+env:
+ DOTNET_VERSION: '9.0.x' # The .NET SDK version to use
jobs:
- build:
+ build-and-test:
- runs-on: ubuntu-latest
+ name: build-and-test
+ runs-on: windows-latest
steps:
- - uses: actions/checkout@v2
- - name: Setup .NET Core
- uses: actions/setup-dotnet@v1
+ - uses: actions/checkout@v3
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
with:
- dotnet-version: 3.1.101
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
- name: Install dependencies
run: dotnet restore
+
- name: Build
run: dotnet build --configuration Release --no-restore
+
- name: Test
- run: dotnet test --no-restore --verbosity normal
+ run: dotnet test --no-restore --verbosity normal
\ No newline at end of file
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/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj
index dc6a69f..64bb373 100644
--- a/BCnEnc.Net/BCnEncoder.csproj
+++ b/BCnEnc.Net/BCnEncoder.csproj
@@ -1,8 +1,8 @@
- netstandard2.1
-
+ netstandard2.1;netstandard2.0
+ 8.0
true
true
snupkg
@@ -12,11 +12,11 @@
MIT OR Unlicense
false
- 1.2.1
+ 2.3.0
Nominom
BCnEncoder.Net
- BCnEncoder.NET is a library for compressing rgba images to different block-compressed formats. Both ktx and dds output formats are supported. 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. Both ktx and dds output file-formats are supported. 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
@@ -25,13 +25,16 @@ Supported formats are:
BC3 (S3TC DXT5)
BC4 (RGTC1)
BC5 (RGTC2)
+ BC6 (BPTC-FLOAT)
BC7 (BPTC)
BCnEncoder.Net
https://github.com/Nominom/BCnEncoder.NET
git
- BCn BC BC1 BC2 BC3 BC4 BC5 BC7 BPTC RGTC S3TC DXT1 DXT3 DXT5 ktx dds texture compression encoding decoding decompression image gpu
+ 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
- Added xml documentation to nuget.
+ 2.3.0
+- Add .NET Standard 2.0 support
+- Fix some DecodeAllMipMaps methods not decoding all MipMaps
@@ -44,8 +47,15 @@ Supported formats are:
-
-
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/BCnEnc.Net/Decoder/Bc7Decoder.cs b/BCnEnc.Net/Decoder/Bc7Decoder.cs
deleted file mode 100644
index 11a72b6..0000000
--- a/BCnEnc.Net/Decoder/Bc7Decoder.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
-using BCnEncoder.Shared;
-
-namespace BCnEncoder.Decoder
-{
- internal class Bc7Decoder : IBcBlockDecoder
- {
- public RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) {
- blockWidth = (int)MathF.Ceiling(pixelWidth / 4.0f);
- blockHeight = (int)MathF.Ceiling(pixelHeight / 4.0f);
-
- if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) {
- throw new InvalidDataException();
- }
-
- var encodedBlocks = MemoryMarshal.Cast(data);
-
- RawBlock4X4Rgba32[,] output = new RawBlock4X4Rgba32[blockWidth, blockHeight];
-
- for (int x = 0; x < blockWidth; x++) {
- for (int y = 0; y < blockHeight; y++) {
- var rawBlock = encodedBlocks[x + y * blockWidth];
- output[x, y] = rawBlock.Decode();
- }
- }
-
- return output;
- }
- }
-}
diff --git a/BCnEnc.Net/Decoder/BcBlockDecoder.cs b/BCnEnc.Net/Decoder/BcBlockDecoder.cs
index 5bfa778..7706639 100644
--- a/BCnEnc.Net/Decoder/BcBlockDecoder.cs
+++ b/BCnEnc.Net/Decoder/BcBlockDecoder.cs
@@ -1,156 +1,190 @@
-using System;
+using System;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
using BCnEncoder.Shared;
namespace BCnEncoder.Decoder
{
- internal interface IBcBlockDecoder {
- RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth,
- out int blockHeight);
+ internal interface IBcBlockDecoder where T : unmanaged
+ {
+ T[] Decode(ReadOnlyMemory data, OperationContext context);
+ T DecodeBlock(ReadOnlySpan data);
}
- internal class Bc1NoAlphaDecoder : IBcBlockDecoder {
- public RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) {
- blockWidth = (int)MathF.Ceiling(pixelWidth / 4.0f);
- blockHeight = (int)MathF.Ceiling(pixelHeight / 4.0f);
+ internal abstract class BaseBcBlockDecoder : IBcBlockDecoder where T : unmanaged where TBlock : unmanaged
+ {
+ private static readonly object lockObj = new object();
- if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) {
- throw new InvalidDataException();
- }
-
- var encodedBlocks = MemoryMarshal.Cast(data);
+ public TBlock[] Decode(ReadOnlyMemory data, OperationContext context)
+ {
- RawBlock4X4Rgba32[,] output = new RawBlock4X4Rgba32[blockWidth, blockHeight];
-
- for (int x = 0; x < blockWidth; x++) {
- for (int y = 0; y < blockHeight; y++) {
- output[x, y] = encodedBlocks[x + y * blockWidth].Decode(false);
- }
+ if (data.Length % Unsafe.SizeOf() != 0)
+ {
+ throw new InvalidDataException("Given data does not align with the block length.");
}
- return output;
- }
- }
-
- internal class Bc1ADecoder : IBcBlockDecoder {
- public RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) {
- blockWidth = (int)MathF.Ceiling(pixelWidth / 4.0f);
- blockHeight = (int)MathF.Ceiling(pixelHeight / 4.0f);
-
- if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) {
- throw new InvalidDataException();
+ var blockCount = data.Length / Unsafe.SizeOf();
+ var output = new TBlock[blockCount];
+
+ var currentBlocks = 0;
+ if (context.IsParallel)
+ {
+ var options = new ParallelOptions
+ {
+ CancellationToken = context.CancellationToken,
+ MaxDegreeOfParallelism = context.TaskCount
+ };
+ Parallel.For(0, blockCount, options, i =>
+ {
+ var encodedBlocks = MemoryMarshal.Cast(data.Span);
+ output[i] = DecodeBlock(encodedBlocks[i]);
+
+ if (context.Progress != null)
+ {
+ lock (lockObj)
+ {
+ context.Progress.Report(++currentBlocks);
+ }
+ }
+ });
}
+ else
+ {
+ var encodedBlocks = MemoryMarshal.Cast(data.Span);
+ for (var i = 0; i < blockCount; i++)
+ {
+ context.CancellationToken.ThrowIfCancellationRequested();
- var encodedBlocks = MemoryMarshal.Cast(data);
+ output[i] = DecodeBlock(encodedBlocks[i]);
- RawBlock4X4Rgba32[,] output = new RawBlock4X4Rgba32[blockWidth, blockHeight];
-
- for (int x = 0; x < blockWidth; x++) {
- for (int y = 0; y < blockHeight; y++) {
- output[x, y] = encodedBlocks[x + y * blockWidth].Decode(true);
+ context.Progress?.Report(++currentBlocks);
}
}
return output;
}
- }
-
- internal class Bc2Decoder : IBcBlockDecoder {
- public RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) {
- blockWidth = (int)MathF.Ceiling(pixelWidth / 4.0f);
- blockHeight = (int)MathF.Ceiling(pixelHeight / 4.0f);
-
- if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) {
- throw new InvalidDataException();
- }
- var encodedBlocks = MemoryMarshal.Cast(data);
-
- RawBlock4X4Rgba32[,] output = new RawBlock4X4Rgba32[blockWidth, blockHeight];
+ public TBlock DecodeBlock(ReadOnlySpan data)
+ {
+ var encodedBlock = MemoryMarshal.Cast(data)[0];
+ return DecodeBlock(encodedBlock);
+ }
- for (int x = 0; x < blockWidth; x++) {
- for (int y = 0; y < blockHeight; y++) {
- output[x, y] = encodedBlocks[x + y * blockWidth].Decode();
- }
- }
+ protected abstract TBlock DecodeBlock(T block);
+ }
- return output;
+ internal class Bc1NoAlphaDecoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4Rgba32 DecodeBlock(Bc1Block block)
+ {
+ return block.Decode(false);
}
}
- internal class Bc3Decoder : IBcBlockDecoder {
- public RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) {
- blockWidth = (int)MathF.Ceiling(pixelWidth / 4.0f);
- blockHeight = (int)MathF.Ceiling(pixelHeight / 4.0f);
-
- if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) {
- throw new InvalidDataException();
- }
-
- var encodedBlocks = MemoryMarshal.Cast(data);
-
- RawBlock4X4Rgba32[,] output = new RawBlock4X4Rgba32[blockWidth, blockHeight];
-
- for (int x = 0; x < blockWidth; x++) {
- for (int y = 0; y < blockHeight; y++) {
- output[x, y] = encodedBlocks[x + y * blockWidth].Decode();
- }
- }
+ internal class Bc1ADecoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4Rgba32 DecodeBlock(Bc1Block block)
+ {
+ return block.Decode(true);
+ }
+ }
- return output;
+ internal class Bc2Decoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4Rgba32 DecodeBlock(Bc2Block block)
+ {
+ return block.Decode();
}
}
- internal class Bc4Decoder : IBcBlockDecoder {
- private readonly bool redAsLuminance;
- public Bc4Decoder(bool redAsLuminance) {
- this.redAsLuminance = redAsLuminance;
+ internal class Bc3Decoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4Rgba32 DecodeBlock(Bc3Block block)
+ {
+ return block.Decode();
}
+ }
- public RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) {
- blockWidth = (int)MathF.Ceiling(pixelWidth / 4.0f);
- blockHeight = (int)MathF.Ceiling(pixelHeight / 4.0f);
+ internal class Bc4Decoder : BaseBcBlockDecoder
+ {
+ private readonly ColorComponent component;
- if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) {
- throw new InvalidDataException();
- }
+ public Bc4Decoder(ColorComponent component)
+ {
+ this.component = component;
+ }
- var encodedBlocks = MemoryMarshal.Cast(data);
+ protected override RawBlock4X4Rgba32 DecodeBlock(Bc4Block block)
+ {
+ return block.Decode(component);
+ }
+ }
- RawBlock4X4Rgba32[,] output = new RawBlock4X4Rgba32[blockWidth, blockHeight];
+ internal class Bc5Decoder : BaseBcBlockDecoder
+ {
+ private readonly ColorComponent component1;
+ private readonly ColorComponent component2;
- for (int x = 0; x < blockWidth; x++) {
- for (int y = 0; y < blockHeight; y++) {
- output[x, y] = encodedBlocks[x + y * blockWidth].Decode(redAsLuminance);
- }
- }
+ public Bc5Decoder(ColorComponent component1, ColorComponent component2)
+ {
+ this.component1 = component1;
+ this.component2 = component2;
+ }
- return output;
+ protected override RawBlock4X4Rgba32 DecodeBlock(Bc5Block block)
+ {
+ return block.Decode(component1, component2);
}
}
- internal class Bc5Decoder : IBcBlockDecoder {
-
- public RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) {
- blockWidth = (int)MathF.Ceiling(pixelWidth / 4.0f);
- blockHeight = (int)MathF.Ceiling(pixelHeight / 4.0f);
+ internal class Bc6UDecoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4RgbFloat DecodeBlock(Bc6Block block)
+ {
+ return block.Decode(false);
+ }
+ }
- if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) {
- throw new InvalidDataException();
- }
+ internal class Bc6SDecoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4RgbFloat DecodeBlock(Bc6Block block)
+ {
+ return block.Decode(true);
+ }
+ }
- var encodedBlocks = MemoryMarshal.Cast(data);
+ internal class Bc7Decoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4Rgba32 DecodeBlock(Bc7Block block)
+ {
+ return block.Decode();
+ }
+ }
- RawBlock4X4Rgba32[,] output = new RawBlock4X4Rgba32[blockWidth, blockHeight];
+ internal class AtcDecoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4Rgba32 DecodeBlock(AtcBlock block)
+ {
+ return block.Decode();
+ }
+ }
- for (int x = 0; x < blockWidth; x++) {
- for (int y = 0; y < blockHeight; y++) {
- output[x, y] = encodedBlocks[x + y * blockWidth].Decode();
- }
- }
+ internal class AtcExplicitAlphaDecoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4Rgba32 DecodeBlock(AtcExplicitAlphaBlock block)
+ {
+ return block.Decode();
+ }
+ }
- return output;
+ internal class AtcInterpolatedAlphaDecoder : BaseBcBlockDecoder
+ {
+ protected override RawBlock4X4Rgba32 DecodeBlock(AtcInterpolatedAlphaBlock block)
+ {
+ return block.Decode();
}
}
}
diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs
index 2b50ee1..c11ec62 100644
--- a/BCnEnc.Net/Decoder/BcDecoder.cs
+++ b/BCnEnc.Net/Decoder/BcDecoder.cs
@@ -1,437 +1,2020 @@
-using System;
-using System.IO;
-using System.Text;
+using BCnEncoder.Decoder.Options;
using BCnEncoder.Shared;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using BCnEncoder.Shared.ImageFiles;
+using CommunityToolkit.HighPerformance;
+
+namespace BCnEncoder.Decoder
+{
+ ///
+ /// Decodes compressed files into Rgba Format.
+ ///
+ public class BcDecoder
+ {
+ ///
+ /// The input options of the decoder.
+ ///
+ public DecoderInputOptions InputOptions { get; } = new DecoderInputOptions();
+
+ ///
+ /// The options for the decoder.
+ ///
+ public DecoderOptions Options { get; } = new DecoderOptions();
+
+ ///
+ /// The output options of the decoder.
+ ///
+ public DecoderOutputOptions OutputOptions { get; } = new DecoderOutputOptions();
+ #region LDR
+ #region Async Api
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method will read the expected amount of bytes from the given input stream and decode it.
+ /// Make sure there is no file header information left in the stream before the encoded data.
+ ///
+ /// The stream containing the encoded data.
+ /// The Format the encoded data is in.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeRawAsync(Stream inputStream, CompressionFormat format, int pixelWidth, int pixelHeight, CancellationToken token = default)
+ {
+ var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)];
+ inputStream.Read(dataArray, 0, dataArray.Length);
+
+ return Task.Run(() => DecodeRawInternal(dataArray, pixelWidth, pixelHeight, format, token), token);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ ///
+ /// The containing the encoded data.
+ /// The Format the encoded data is in.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeRawAsync(ReadOnlyMemory input, CompressionFormat format, int pixelWidth, int pixelHeight, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeRawInternal(input, pixelWidth, pixelHeight, format, token), token);
+ }
+
+ ///
+ /// Decode the main image from a Ktx file.
+ ///
+ /// The loaded Ktx file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeAsync(KtxFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternal(file, false, token)[0], token);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Ktx file.
+ ///
+ /// The loaded Ktx file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeAllMipMapsAsync(KtxFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternal(file, true, token), token);
+ }
+
+ ///
+ /// Decode the main image from a Dds file.
+ ///
+ /// The loaded Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeAsync(DdsFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternal(file, false, token)[0], token);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Dds file.
+ ///
+ /// The loaded Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeAllMipMapsAsync(DdsFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternal(file, true, token), token);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method will read the expected amount of bytes from the given input stream and decode it.
+ /// Make sure there is no file header information left in the stream before the encoded data.
+ ///
+ /// The stream containing the raw encoded data.
+ /// The Format the encoded data is in.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> DecodeRaw2DAsync(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default)
+ {
+ var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)];
+ inputStream.Read(dataArray, 0, dataArray.Length);
+
+ return Task.Run(() => DecodeRawInternal(dataArray, pixelWidth, pixelHeight, format, token)
+ .AsMemory().AsMemory2D(pixelHeight, pixelWidth), token);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ ///
+ /// The containing the encoded data.
+ /// The Format the encoded data is in.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> DecodeRaw2DAsync(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeRawInternal(input, pixelWidth, pixelHeight, format, token)
+ .AsMemory().AsMemory2D(pixelHeight, pixelWidth), token);
+ }
+
+ ///
+ /// Read a Ktx or Dds file from a stream and decode the main image from it.
+ /// The type of file will be detected automatically.
+ ///
+ /// The stream containing a Ktx or Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> Decode2DAsync(Stream inputStream, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeFromStreamInternal2D(inputStream, false, token)[0], token);
+ }
+
+ ///
+ /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it.
+ /// The type of file will be detected automatically.
+ ///
+ /// The stream containing a Ktx or Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task[]> DecodeAllMipMaps2DAsync(Stream inputStream, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeFromStreamInternal2D(inputStream, true, token), token);
+ }
+
+ ///
+ /// Decode the main image from a Ktx file.
+ ///
+ /// The loaded Ktx file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> Decode2DAsync(KtxFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternal(file, false, token)[0]
+ .AsMemory().AsMemory2D((int)file.header.PixelHeight, (int)file.header.PixelWidth), token);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Ktx file.
+ ///
+ /// The loaded Ktx file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task[]> DecodeAllMipMaps2DAsync(KtxFile file, CancellationToken token = default)
+ {
+ return Task.Run(() =>
+ {
+ var decoded = DecodeInternal(file, true, token);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+ return mem2Ds;
+ }, token);
+ }
+
+ ///
+ /// Decode the main image from a Dds file.
+ ///
+ /// The loaded Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> Decode2DAsync(DdsFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternal(file, false, token)[0]
+ .AsMemory().AsMemory2D((int)file.header.dwHeight, (int)file.header.dwWidth), token);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Dds file.
+ ///
+ /// The loaded Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task[]> DecodeAllMipMaps2DAsync(DdsFile file, CancellationToken token = default)
+ {
+ return Task.Run(() =>
+ {
+ var decoded = DecodeInternal(file, true, token);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.Faces[0].MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+ return mem2Ds;
+ }, token);
+ }
+
+ #endregion
+
+ #region Sync API
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method will read the expected amount of bytes from the given input stream and decode it.
+ /// Make sure there is no file header information left in the stream before the encoded data.
+ ///
+ /// The stream containing the raw encoded data.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The Format the encoded data is in.
+ /// The decoded image.
+ public ColorRgba32[] DecodeRaw(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format)
+ {
+ var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)];
+ inputStream.Read(dataArray, 0, dataArray.Length);
+
+ return DecodeRaw(dataArray, pixelWidth, pixelHeight, format);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ ///
+ /// The byte array containing the raw encoded data.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The Format the encoded data is in.
+ /// The decoded image.
+ public ColorRgba32[] DecodeRaw(byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format)
+ {
+ return DecodeRawInternal(input, pixelWidth, pixelHeight, format, default);
+ }
+
+ ///
+ /// Decode the main image from a Ktx file.
+ ///
+ /// The loaded Ktx file.
+ /// The decoded image.
+ public ColorRgba32[] Decode(KtxFile file)
+ {
+ return DecodeInternal(file, false, default)[0];
+ }
+
+ ///
+ /// Decode all available mipmaps from a Ktx file.
+ ///
+ /// The loaded Ktx file.
+ /// An array of decoded images.
+ public ColorRgba32[][] DecodeAllMipMaps(KtxFile file)
+ {
+ return DecodeInternal(file, true, default);
+ }
+
+ ///
+ /// Decode the main image from a Dds file.
+ ///
+ /// The loaded Dds file.
+ /// The decoded image.
+ public ColorRgba32[] Decode(DdsFile file)
+ {
+ return DecodeInternal(file, false, default)[0];
+ }
+
+ ///
+ /// Decode all available mipmaps from a Dds file.
+ ///
+ /// The loaded Dds file.
+ /// An array of decoded images.
+ public ColorRgba32[][] DecodeAllMipMaps(DdsFile file)
+ {
+ return DecodeInternal(file, true, default);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method will read the expected amount of bytes from the given input stream and decode it.
+ /// Make sure there is no file header information left in the stream before the encoded data.
+ ///
+ /// The stream containing the encoded data.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The Format the encoded data is in.
+ /// The decoded image.
+ public Memory2D DecodeRaw2D(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format)
+ {
+ var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)];
+ inputStream.Read(dataArray, 0, dataArray.Length);
+
+ var decoded = DecodeRaw(dataArray, pixelWidth, pixelHeight, format);
+ return decoded.AsMemory().AsMemory2D(pixelHeight, pixelWidth);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ ///
+ /// The byte array containing the raw encoded data.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The Format the encoded data is in.
+ /// The decoded image.
+ public Memory2D DecodeRaw2D(byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format)
+ {
+ var decoded = DecodeRawInternal(input, pixelWidth, pixelHeight, format, default);
+ return decoded.AsMemory().AsMemory2D(pixelHeight, pixelWidth);
+ }
+
+ ///
+ /// Read a Ktx or Dds file from a stream and decode the main image from it.
+ /// The type of file will be detected automatically.
+ ///
+ /// The stream containing a Ktx or Dds file.
+ /// The decoded image.
+ public Memory2D Decode2D(Stream inputStream)
+ {
+ return DecodeFromStreamInternal2D(inputStream, false, default)[0];
+ }
+
+ ///
+ /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it.
+ /// The type of file will be detected automatically.
+ ///
+ /// The stream containing a Ktx or Dds file.
+ /// An array of decoded images.
+ public Memory2D[] DecodeAllMipMaps2D(Stream inputStream)
+ {
+ return DecodeFromStreamInternal2D(inputStream, true, default);
+ }
+
+ ///
+ /// Decode the main image from a Ktx file.
+ ///
+ /// The loaded Ktx file.
+ /// The decoded image.
+ public Memory2D Decode2D(KtxFile file)
+ {
+ return DecodeInternal(file, false, default)[0].AsMemory().AsMemory2D((int)file.header.PixelHeight, (int)file.header.PixelWidth);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Ktx file.
+ ///
+ /// The loaded Ktx file.
+ /// An array of decoded images.
+ public Memory2D[] DecodeAllMipMaps2D(KtxFile file)
+ {
+ var decoded = DecodeInternal(file, true, default);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+ return mem2Ds;
+ }
+
+ ///
+ /// Decode the main image from a Dds file.
+ ///
+ /// The loaded Dds file.
+ /// The decoded image.
+ public Memory2D Decode2D(DdsFile file)
+ {
+ return DecodeInternal(file, false, default)[0].AsMemory().AsMemory2D((int)file.header.dwHeight, (int)file.header.dwWidth);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Dds file.
+ ///
+ /// The loaded Dds file.
+ /// An array of decoded images.
+ public Memory2D[] DecodeAllMipMaps2D(DdsFile file)
+ {
+ var decoded = DecodeInternal(file, true, default);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.Faces[0].MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+ return mem2Ds;
+ }
+
+ ///
+ /// Decode a single block from raw bytes and return it as a .
+ /// Input Span size needs to equal the block size.
+ /// To get the block size (in bytes) of the compression format used, see .
+ ///
+ /// The encoded block in bytes.
+ /// The compression format used.
+ /// The decoded 4x4 block.
+ public Memory2D DecodeBlock(ReadOnlySpan blockData, CompressionFormat format)
+ {
+ var output = new ColorRgba32[4, 4];
+ DecodeBlockInternal(blockData, format, output);
+ return output;
+ }
+
+ ///
+ /// Decode a single block from raw bytes and write it to the given output span.
+ /// Output span size must be exactly 4x4 and input Span size needs to equal the block size.
+ /// To get the block size (in bytes) of the compression format used, see .
+ ///
+ /// The encoded block in bytes.
+ /// The compression format used.
+ /// The destination span of the decoded data.
+ public void DecodeBlock(ReadOnlySpan blockData, CompressionFormat format, Span2D outputSpan)
+ {
+ if (outputSpan.Width != 4 || outputSpan.Height != 4)
+ {
+ throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4");
+ }
+ DecodeBlockInternal(blockData, format, outputSpan);
+ }
+
+ ///
+ /// Decode a single block from a stream and write it to the given output span.
+ /// Output span size must be exactly 4x4.
+ ///
+ /// The stream to read encoded blocks from.
+ /// The compression format used.
+ /// The destination span of the decoded data.
+ /// The number of bytes read from the stream. Zero (0) if reached the end of stream.
+ public int DecodeBlock(Stream inputStream, CompressionFormat format, Span2D outputSpan)
+ {
+ if (outputSpan.Width != 4 || outputSpan.Height != 4)
+ {
+ throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4");
+ }
+
+ Span input = stackalloc byte[16];
+ input = input.Slice(0, GetBlockSize(format));
+
+ var bytesRead = inputStream.Read(input);
+
+ if (bytesRead == 0)
+ {
+ return 0; //End of stream
+ }
+
+ if (bytesRead != input.Length)
+ {
+ throw new Exception("Input stream does not have enough data available for a full block.");
+ }
+
+ DecodeBlockInternal(input, format, outputSpan);
+ return bytesRead;
+ }
+
+ ///
+ /// Check whether a file is encoded in a supported format.
+ ///
+ /// The loaded ktx file to check
+ /// If the format of the file is one of the supported formats.
+ public bool IsSupportedFormat(KtxFile file)
+ {
+ return GetCompressionFormat(file.header.GlInternalFormat) != CompressionFormat.Unknown;
+ }
+
+ ///
+ /// Check whether a file is encoded in a supported format.
+ ///
+ /// The loaded dds file to check
+ /// If the format of the file is one of the supported formats.
+ public bool IsSupportedFormat(DdsFile file)
+ {
+ return GetCompressionFormat(file) != CompressionFormat.Unknown;
+ }
+
+ ///
+ /// Gets the format of the file.
+ ///
+ /// The loaded ktx file to check
+ /// The of the file.
+ public CompressionFormat GetFormat(KtxFile file)
+ {
+ return GetCompressionFormat(file.header.GlInternalFormat);
+ }
+
+ ///
+ /// Gets the format of the file.
+ ///
+ /// The loaded dds file to check
+ /// The of the file.
+ public CompressionFormat GetFormat(DdsFile file)
+ {
+ return GetCompressionFormat(file);
+ }
+
+
+ #endregion
+ #endregion
+
+ #region HDR
+ #region Async Api
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method will read the expected amount of bytes from the given input stream and decode it.
+ /// Make sure there is no file header information left in the stream before the encoded data.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream containing the encoded data.
+ /// The Format the encoded data is in.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeRawHdrAsync(Stream inputStream, CompressionFormat format, int pixelWidth, int pixelHeight, CancellationToken token = default)
+ {
+ var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)];
+ inputStream.Read(dataArray, 0, dataArray.Length);
+
+ return Task.Run(() => DecodeRawInternalHdr(dataArray, pixelWidth, pixelHeight, format, token), token);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The containing the encoded data.
+ /// The Format the encoded data is in.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeRawHdrAsync(ReadOnlyMemory input, CompressionFormat format, int pixelWidth, int pixelHeight, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeRawInternalHdr(input, pixelWidth, pixelHeight, format, token), token);
+ }
+
+ ///
+ /// Decode the main image from a Ktx file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Ktx file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeHdrAsync(KtxFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternalHdr(file, false, token)[0], token);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Ktx file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Ktx file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeAllMipMapsHdrAsync(KtxFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternalHdr(file, true, token), token);
+ }
+
+ ///
+ /// Decode the main image from a Dds file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeHdrAsync(DdsFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternalHdr(file, false, token)[0], token);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Dds file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task DecodeAllMipMapsHdrAsync(DdsFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternalHdr(file, true, token), token);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method will read the expected amount of bytes from the given input stream and decode it.
+ /// Make sure there is no file header information left in the stream before the encoded data.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream containing the raw encoded data.
+ /// The Format the encoded data is in.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> DecodeRawHdr2DAsync(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default)
+ {
+ var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)];
+ inputStream.Read(dataArray, 0, dataArray.Length);
+
+ return Task.Run(() => DecodeRawInternalHdr(dataArray, pixelWidth, pixelHeight, format, token)
+ .AsMemory().AsMemory2D(pixelHeight, pixelWidth), token);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The containing the encoded data.
+ /// The Format the encoded data is in.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> DecodeRawHdr2DAsync(ReadOnlyMemory input, int pixelWidth, int pixelHeight, CompressionFormat format, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeRawInternalHdr(input, pixelWidth, pixelHeight, format, token)
+ .AsMemory().AsMemory2D(pixelHeight, pixelWidth), token);
+ }
+
+ ///
+ /// Read a Ktx or Dds file from a stream and decode the main image from it.
+ /// The type of file will be detected automatically.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream containing a Ktx or Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> DecodeHdr2DAsync(Stream inputStream, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeFromStreamInternalHdr2D(inputStream, false, token)[0], token);
+ }
+
+ ///
+ /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it.
+ /// The type of file will be detected automatically.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream containing a Ktx or Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task[]> DecodeAllMipMapsHdr2DAsync(Stream inputStream, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeFromStreamInternalHdr2D(inputStream, true, token), token);
+ }
+
+ ///
+ /// Decode the main image from a Ktx file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Ktx file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> DecodeHdr2DAsync(KtxFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternalHdr(file, false, token)[0]
+ .AsMemory().AsMemory2D((int)file.header.PixelHeight, (int)file.header.PixelWidth), token);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Ktx file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Ktx file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task[]> DecodeAllMipMapsHdr2DAsync(KtxFile file, CancellationToken token = default)
+ {
+ return Task.Run(() =>
+ {
+ var decoded = DecodeInternalHdr(file, true, token);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+ return mem2Ds;
+ }, token);
+ }
+
+ ///
+ /// Decode the main image from a Dds file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task> DecodeHdr2DAsync(DdsFile file, CancellationToken token = default)
+ {
+ return Task.Run(() => DecodeInternalHdr(file, false, token)[0]
+ .AsMemory().AsMemory2D((int)file.header.dwHeight, (int)file.header.dwWidth), token);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Dds file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Dds file.
+ /// The cancellation token for this asynchronous operation.
+ /// The awaitable operation to retrieve the decoded image.
+ public Task[]> DecodeAllMipMapsHdr2DAsync(DdsFile file, CancellationToken token = default)
+ {
+ return Task.Run(() =>
+ {
+ var decoded = DecodeInternalHdr(file, true, token);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.Faces[0].MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+ return mem2Ds;
+ }, token);
+ }
+
+ #endregion
+
+ #region Sync API
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method will read the expected amount of bytes from the given input stream and decode it.
+ /// Make sure there is no file header information left in the stream before the encoded data.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream containing the raw encoded data.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The Format the encoded data is in.
+ /// The decoded image.
+ public ColorRgbFloat[] DecodeRawHdr(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format)
+ {
+ var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)];
+ inputStream.Read(dataArray, 0, dataArray.Length);
+
+ return DecodeRawHdr(dataArray, pixelWidth, pixelHeight, format);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The byte array containing the raw encoded data.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The Format the encoded data is in.
+ /// The decoded image.
+ public ColorRgbFloat[] DecodeRawHdr(byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format)
+ {
+ return DecodeRawInternalHdr(input, pixelWidth, pixelHeight, format, default);
+ }
+
+ ///
+ /// Decode the main image from a Ktx file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Ktx file.
+ /// The decoded image.
+ public ColorRgbFloat[] DecodeHdr(KtxFile file)
+ {
+ return DecodeInternalHdr(file, false, default)[0];
+ }
+
+ ///
+ /// Decode all available mipmaps from a Ktx file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Ktx file.
+ /// An array of decoded images.
+ public ColorRgbFloat[][] DecodeAllMipMapsHdr(KtxFile file)
+ {
+ return DecodeInternalHdr(file, true, default);
+ }
+
+ ///
+ /// Decode the main image from a Dds file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Dds file.
+ /// The decoded image.
+ public ColorRgbFloat[] DecodeHdr(DdsFile file)
+ {
+ return DecodeInternalHdr(file, false, default)[0];
+ }
+
+ ///
+ /// Decode all available mipmaps from a Dds file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Dds file.
+ /// An array of decoded images.
+ public ColorRgbFloat[][] DecodeAllMipMapsHdr(DdsFile file)
+ {
+ return DecodeInternalHdr(file, true, default);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method will read the expected amount of bytes from the given input stream and decode it.
+ /// Make sure there is no file header information left in the stream before the encoded data.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream containing the encoded data.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The Format the encoded data is in.
+ /// The decoded image.
+ public Memory2D DecodeRawHdr2D(Stream inputStream, int pixelWidth, int pixelHeight, CompressionFormat format)
+ {
+ var dataArray = new byte[GetBufferSize(format, pixelWidth, pixelHeight)];
+ inputStream.Read(dataArray, 0, dataArray.Length);
+
+ var decoded = DecodeRawHdr(dataArray, pixelWidth, pixelHeight, format);
+ return decoded.AsMemory().AsMemory2D(pixelHeight, pixelWidth);
+ }
+
+ ///
+ /// Decode a single encoded image from raw bytes.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The byte array containing the raw encoded data.
+ /// The pixelWidth of the image.
+ /// The pixelHeight of the image.
+ /// The Format the encoded data is in.
+ /// The decoded image.
+ public Memory2D DecodeRawHdr2D(byte[] input, int pixelWidth, int pixelHeight, CompressionFormat format)
+ {
+ var decoded = DecodeRawInternalHdr(input, pixelWidth, pixelHeight, format, default);
+ return decoded.AsMemory().AsMemory2D(pixelHeight, pixelWidth);
+ }
+
+ ///
+ /// Read a Ktx or Dds file from a stream and decode the main image from it.
+ /// The type of file will be detected automatically.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream containing a Ktx or Dds file.
+ /// The decoded image.
+ public Memory2D DecodeHdr2D(Stream inputStream)
+ {
+ return DecodeFromStreamInternalHdr2D(inputStream, false, default)[0];
+ }
+
+ ///
+ /// Read a Ktx or Dds file from a stream and decode all available mipmaps from it.
+ /// The type of file will be detected automatically.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream containing a Ktx or Dds file.
+ /// An array of decoded images.
+ public Memory2D[] DecodeAllMipMapsHdr2D(Stream inputStream)
+ {
+ return DecodeFromStreamInternalHdr2D(inputStream, true, default);
+ }
+
+ ///
+ /// Decode the main image from a Ktx file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Ktx file.
+ /// The decoded image.
+ public Memory2D DecodeHdr2D(KtxFile file)
+ {
+ return DecodeInternalHdr(file, false, default)[0].AsMemory().AsMemory2D((int)file.header.PixelHeight, (int)file.header.PixelWidth);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Ktx file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Ktx file.
+ /// An array of decoded images.
+ public Memory2D[] DecodeAllMipMapsHdr2D(KtxFile file)
+ {
+ var decoded = DecodeInternalHdr(file, true, default);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+ return mem2Ds;
+ }
+
+ ///
+ /// Decode the main image from a Dds file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Dds file.
+ /// The decoded image.
+ public Memory2D DecodeHdr2D(DdsFile file)
+ {
+ return DecodeInternalHdr(file, false, default)[0].AsMemory().AsMemory2D((int)file.header.dwHeight, (int)file.header.dwWidth);
+ }
+
+ ///
+ /// Decode all available mipmaps from a Dds file.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The loaded Dds file.
+ /// An array of decoded images.
+ public Memory2D[] DecodeAllMipMapsHdr2D(DdsFile file)
+ {
+ var decoded = DecodeInternalHdr(file, true, default);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.Faces[0].MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+ return mem2Ds;
+ }
+
+ ///
+ /// Decode a single block from raw bytes and return it as a .
+ /// Input Span size needs to equal the block size.
+ /// To get the block size (in bytes) of the compression format used, see .
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The encoded block in bytes.
+ /// The compression format used.
+ /// The decoded 4x4 block.
+ public Memory2D DecodeBlockHdr(ReadOnlySpan blockData, CompressionFormat format)
+ {
+ var output = new ColorRgbFloat[4, 4];
+ DecodeBlockInternalHdr(blockData, format, output);
+ return output;
+ }
+
+ ///
+ /// Decode a single block from raw bytes and write it to the given output span.
+ /// Output span size must be exactly 4x4 and input Span size needs to equal the block size.
+ /// To get the block size (in bytes) of the compression format used, see .
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The encoded block in bytes.
+ /// The compression format used.
+ /// The destination span of the decoded data.
+ public void DecodeBlockHdr(ReadOnlySpan blockData, CompressionFormat format, Span2D outputSpan)
+ {
+ if (outputSpan.Width != 4 || outputSpan.Height != 4)
+ {
+ throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4");
+ }
+ DecodeBlockInternalHdr(blockData, format, outputSpan);
+ }
+
+ ///
+ /// Decode a single block from a stream and write it to the given output span.
+ /// Output span size must be exactly 4x4.
+ /// This method is only for compressed Hdr formats. Please use the non-Hdr methods for other formats.
+ ///
+ /// The stream to read encoded blocks from.
+ /// The compression format used.
+ /// The destination span of the decoded data.
+ /// The number of bytes read from the stream. Zero (0) if reached the end of stream.
+ public int DecodeBlockHdr(Stream inputStream, CompressionFormat format, Span2D outputSpan)
+ {
+ if (outputSpan.Width != 4 || outputSpan.Height != 4)
+ {
+ throw new ArgumentException($"Single block decoding needs an output span of exactly 4x4");
+ }
+
+ Span input = stackalloc byte[16];
+ input = input.Slice(0, GetBlockSize(format));
+
+ var bytesRead = inputStream.Read(input);
+
+ if (bytesRead == 0)
+ {
+ return 0; //End of stream
+ }
+
+ if (bytesRead != input.Length)
+ {
+ throw new Exception("Input stream does not have enough data available for a full block.");
+ }
+
+ DecodeBlockInternalHdr(input, format, outputSpan);
+ return bytesRead;
+ }
+
+ ///
+ /// Check whether a file is encoded in a supported HDR format.
+ ///
+ /// The loaded ktx file to check
+ /// If the format of the file is one of the supported HDR formats.
+ public bool IsHdrFormat(KtxFile file)
+ {
+ return GetCompressionFormat(file.header.GlInternalFormat).IsHdrFormat();
+ }
+
+ ///
+ /// Check whether a file is encoded in a supported HDR format.
+ ///
+ /// The loaded dds file to check
+ /// If the format of the file is one of the supported HDR formats.
+ public bool IsHdrFormat(DdsFile file)
+ {
+ return GetCompressionFormat(file).IsHdrFormat();
+ }
+
+ #endregion
+ #endregion
+ ///
+ /// Load a stream and extract either the main image or all mip maps.
+ ///
+ /// The stream containing the image file.
+ /// If all mip maps or only the main image should be decoded.
+ /// The cancellation token for this operation. Can be default, if the operation is not asynchronous.
+ /// An array of decoded Rgba32 images.
+ private Memory2D[] DecodeFromStreamInternal2D(Stream stream, bool allMipMaps, CancellationToken token)
+ {
+ var format = ImageFile.DetermineImageFormat(stream);
+
+ switch (format)
+ {
+ case ImageFileFormat.Dds:
+ {
+ var file = DdsFile.Load(stream);
+ var decoded = DecodeInternal(file, allMipMaps, token);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.Faces[0].MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+
+ return mem2Ds;
+ }
+
+ case ImageFileFormat.Ktx:
+ {
+ var file = KtxFile.Load(stream);
+ var decoded = DecodeInternal(file, allMipMaps, token);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+
+ return mem2Ds;
+ }
+
+ default:
+ throw new InvalidOperationException("Unknown image format.");
+ }
+ }
+
+ ///
+ /// Load a KTX file and extract either the main image or all mip maps.
+ ///
+ /// The Ktx file to decode.
+ /// If all mip maps or only the main image should be decoded.
+ /// The cancellation token for this operation. Can be default, if the operation is not asynchronous.
+ /// 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][];
+
+ var context = new OperationContext
+ {
+ CancellationToken = token,
+ IsParallel = Options.IsParallel,
+ TaskCount = Options.TaskCount
+ };
+
+ // Calculate total blocks
+ var blockSize = GetBlockSize(file.header.GlInternalFormat);
+ var totalBlocks = file.MipMaps.Take(mipMaps).Sum(m => m.Faces[0].Data.Length / blockSize);
+
+ context.Progress = new OperationProgress(Options.Progress, totalBlocks);
+
+ if (IsSupportedRawFormat(file.header.GlInternalFormat))
+ {
+ var decoder = GetRawDecoder(file.header.GlInternalFormat);
+
+ for (var mip = 0; mip < mipMaps; mip++)
+ {
+ var data = file.MipMaps[mip].Faces[0].Data;
+
+ colors[mip] = decoder.Decode(data, context);
+
+ context.Progress.SetProcessedBlocks(file.MipMaps.Take(mip + 1).Sum(x => x.Faces[0].Data.Length / blockSize));
+ }
+ }
+ else
+ {
+ var decoder = GetRgba32Decoder(file.header.GlInternalFormat);
+ var format = GetCompressionFormat(file.header.GlInternalFormat);
+ if (format.IsHdrFormat())
+ {
+ throw new NotSupportedException($"This Format is not an RGBA32 compatible format: {format}, please use the HDR versions of the decode methods.");
+ }
+ if (decoder == null)
+ {
+ throw new NotSupportedException($"This Format is not supported: {file.header.GlInternalFormat}");
+ }
+
+ for (var mip = 0; mip < mipMaps; mip++)
+ {
+ var data = file.MipMaps[mip].Faces[0].Data;
+ var pixelWidth = file.MipMaps[mip].Width;
+ var pixelHeight = file.MipMaps[mip].Height;
+
+ var blocks = decoder.Decode(data, context);
+
+ colors[mip] = ImageToBlocks.ColorsFromRawBlocks(blocks, (int)pixelWidth, (int)pixelHeight);
+
+ context.Progress.SetProcessedBlocks(file.MipMaps.Take(mip + 1).Sum(x => x.Faces[0].Data.Length / blockSize));
+ }
+ }
+
+ return colors;
+ }
+
+ ///
+ /// Load a DDS file and extract either the main image or all mip maps.
+ ///
+ /// The Dds file to decode.
+ /// If all mip maps or only the main image should be decoded.
+ /// The cancellation token for this operation. Can be default, if the operation is not asynchronous.
+ /// 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
+ mipMaps = Math.Max(1, mipMaps);
+
+ var colors = new ColorRgba32[mipMaps][];
+
+ var context = new OperationContext
+ {
+ CancellationToken = token,
+ IsParallel = Options.IsParallel,
+ TaskCount = Options.TaskCount
+ };
+
+ // Calculate total blocks
+ var blockSize = GetBlockSize(file);
+ var totalBlocks = file.Faces[0].MipMaps.Take((int)mipMaps).Sum(m => m.Data.Length / blockSize);
+
+ context.Progress = new OperationProgress(Options.Progress, totalBlocks);
+
+ if (IsSupportedRawFormat(file))
+ {
+ var decoder = GetRawDecoder(file);
+
+ for (var mip = 0; mip < mipMaps; mip++)
+ {
+ var data = file.Faces[0].MipMaps[mip].Data;
+
+ colors[mip] = decoder.Decode(data, context);
+
+ context.Progress.SetProcessedBlocks(file.Faces[0].MipMaps.Take(mip + 1).Sum(x => x.Data.Length / blockSize));
+ }
+ }
+ else
+ {
+ var dxtFormat = file.header.ddsPixelFormat.IsDxt10Format
+ ? file.dx10Header.dxgiFormat
+ : file.header.ddsPixelFormat.DxgiFormat;
+ var format = GetCompressionFormat(file);
+ var decoder = GetRgba32Decoder(format);
+
+ if (format.IsHdrFormat())
+ {
+ throw new NotSupportedException($"This Format is not an RGBA32 compatible format: {format}, please use the HDR versions of the decode methods.");
+ }
+ if (decoder == null)
+ {
+ throw new NotSupportedException($"This Format is not supported: {dxtFormat}");
+ }
+
+ for (var mip = 0; mip < mipMaps; mip++)
+ {
+ var data = file.Faces[0].MipMaps[mip].Data;
+ var pixelWidth = file.Faces[0].MipMaps[mip].Width;
+ var pixelHeight = file.Faces[0].MipMaps[mip].Height;
+
+ var blocks = decoder.Decode(data, context);
+
+ var image = ImageToBlocks.ColorsFromRawBlocks(blocks, (int)pixelWidth, (int)pixelHeight);
+
+ colors[mip] = image;
+
+ context.Progress.SetProcessedBlocks(file.Faces[0].MipMaps.Take(mip + 1).Sum(x => x.Data.Length / blockSize));
+ }
+ }
+
+ return colors;
+ }
+
+ ///
+ /// Decode raw encoded image asynchronously.
+ ///
+ /// The containing the encoded data.
+ /// The width of the image.
+ /// The height of the image.
+ /// The Format the encoded data is in.
+ /// The cancellation token for this operation. May be default, if the operation is not asynchronous.
+ /// 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.");
+ }
+
+ var context = new OperationContext
+ {
+ CancellationToken = token,
+ IsParallel = Options.IsParallel,
+ TaskCount = Options.TaskCount
+ };
+
+ // Calculate total blocks
+ var blockSize = GetBlockSize(format);
+ var totalBlocks = input.Length / blockSize;
+
+ context.Progress = new OperationProgress(Options.Progress, totalBlocks);
+
+ var isCompressedFormat = format.IsCompressedFormat();
+ if (isCompressedFormat)
+ {
+ // DecodeInternal as compressed data
+ var decoder = GetRgba32Decoder(format);
+
+ if (format.IsHdrFormat())
+ {
+ throw new NotSupportedException($"This Format is not an RGBA32 compatible format: {format}, please use the HDR versions of the decode methods.");
+ }
+ if (decoder == null)
+ {
+ throw new NotSupportedException($"This Format is not supported: {format}");
+ }
+
+ var blocks = decoder.Decode(input, context);
+
+ return ImageToBlocks.ColorsFromRawBlocks(blocks, pixelWidth, pixelHeight); ;
+ }
+
+ // DecodeInternal as raw data
+ var rawDecoder = GetRawDecoder(format);
+
+ return rawDecoder.Decode(input, context);
+ }
+
+ private void DecodeBlockInternal(ReadOnlySpan blockData, CompressionFormat format, Span2D outputSpan)
+ {
+ var decoder = GetRgba32Decoder(format);
+ if (format.IsHdrFormat())
+ {
+ throw new NotSupportedException($"This Format is not an RGBA32 compatible format: {format}, please use the HDR versions of the decode methods.");
+ }
+ if (decoder == null)
+ {
+ throw new NotSupportedException($"This Format is not supported: {format}");
+ }
+ if (blockData.Length != GetBlockSize(format))
+ {
+ throw new ArgumentException("The size of the input buffer does not align with the compression format.");
+ }
+
+ var rawBlock = decoder.DecodeBlock(blockData);
+ var pixels = rawBlock.AsSpan;
+
+ pixels.Slice(0, 4).CopyTo(outputSpan.GetRowSpan(0));
+ pixels.Slice(4, 4).CopyTo(outputSpan.GetRowSpan(1));
+ pixels.Slice(8, 4).CopyTo(outputSpan.GetRowSpan(2));
+ pixels.Slice(12, 4).CopyTo(outputSpan.GetRowSpan(3));
+ }
+
+ #region Hdr internals
+
+ ///
+ /// Load a stream and extract either the main image or all mip maps.
+ ///
+ /// The stream containing the image file.
+ /// If all mip maps or only the main image should be decoded.
+ /// The cancellation token for this operation. Can be default, if the operation is not asynchronous.
+ /// An array of decoded Rgba32 images.
+ private Memory2D[] DecodeFromStreamInternalHdr2D(Stream stream, bool allMipMaps, CancellationToken token)
+ {
+ var format = ImageFile.DetermineImageFormat(stream);
+
+ switch (format)
+ {
+ case ImageFileFormat.Dds:
+ {
+ var file = DdsFile.Load(stream);
+ var decoded = DecodeInternalHdr(file, allMipMaps, token);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.Faces[0].MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+
+ return mem2Ds;
+ }
+
+ case ImageFileFormat.Ktx:
+ {
+ var file = KtxFile.Load(stream);
+ var decoded = DecodeInternalHdr(file, allMipMaps, token);
+ var mem2Ds = new Memory2D[decoded.Length];
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ var mip = file.MipMaps[i];
+ mem2Ds[i] = decoded[i].AsMemory().AsMemory2D((int)mip.Height, (int)mip.Width);
+ }
+
+ return mem2Ds;
+ }
+
+ default:
+ throw new InvalidOperationException("Unknown image format.");
+ }
+ }
+
+ ///
+ /// Load a KTX file and extract either the main image or all mip maps.
+ ///
+ /// The Ktx file to decode.
+ /// If all mip maps or only the main image should be decoded.
+ /// The cancellation token for this operation. Can be default, if the operation is not asynchronous.
+ /// 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][];
+
+ var context = new OperationContext
+ {
+ CancellationToken = token,
+ IsParallel = Options.IsParallel,
+ TaskCount = Options.TaskCount
+ };
+
+ // Calculate total blocks
+ var blockSize = GetBlockSize(file.header.GlInternalFormat);
+ var totalBlocks = file.MipMaps.Take(mipMaps).Sum(m => m.Faces[0].Data.Length / blockSize);
+
+ context.Progress = new OperationProgress(Options.Progress, totalBlocks);
+
+ var decoder = GetRgbFloatDecoder(file.header.GlInternalFormat);
+ var format = GetCompressionFormat(file.header.GlInternalFormat);
+ if (!format.IsHdrFormat())
+ {
+ throw new NotSupportedException($"This Format is not an HDR format: {format}, please use the non-HDR versions of the decode methods.");
+ }
+ if (decoder == null)
+ {
+ throw new NotSupportedException($"This Format is not supported: {file.header.GlInternalFormat}");
+ }
+
+ for (var mip = 0; mip < mipMaps; mip++)
+ {
+ var data = file.MipMaps[mip].Faces[0].Data;
+ var pixelWidth = file.MipMaps[mip].Width;
+ var pixelHeight = file.MipMaps[mip].Height;
+
+ var blocks = decoder.Decode(data, context);
+
+ colors[mip] = ImageToBlocks.ColorsFromRawBlocks(blocks, (int)pixelWidth, (int)pixelHeight);
+
+ context.Progress.SetProcessedBlocks(file.MipMaps.Take(mip + 1).Sum(x => x.Faces[0].Data.Length / blockSize));
+ }
+
+ return colors;
+ }
-namespace BCnEncoder.Decoder
-{
- public class DecoderInputOptions {
///
- /// The DDS file format doesn't seem to have a standard for indicating whether a BC1 texture
- /// includes 1bit of alpha. This option will assume that all Bc1 textures contain alpha.
- /// If this option is false, but the dds header includes a DDPF_ALPHAPIXELS flag, alpha will be included.
- /// Default is true.
+ /// Load a DDS file and extract either the main image or all mip maps.
///
- public bool ddsBc1ExpectAlpha = true;
- }
+ /// The Dds file to decode.
+ /// If all mip maps or only the main image should be decoded.
+ /// The cancellation token for this operation. Can be default, if the operation is not asynchronous.
+ /// 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
+ {
+ CancellationToken = token,
+ IsParallel = Options.IsParallel,
+ TaskCount = Options.TaskCount
+ };
+
+ // Calculate total blocks
+ var blockSize = GetBlockSize(file);
+ var totalBlocks = file.Faces[0].MipMaps.Take((int)mipMaps).Sum(m => m.Data.Length / blockSize);
+
+ context.Progress = new OperationProgress(Options.Progress, totalBlocks);
+
+
+ var format = GetCompressionFormat(file);
+ var decoder = GetRgbFloatDecoder(format);
+
+ if (!format.IsHdrFormat())
+ {
+ throw new NotSupportedException($"This Format is not an HDR format: {format}, please use the non-HDR versions of the decode methods.");
+ }
+ if (decoder == null)
+ {
+ throw new NotSupportedException($"This Format is not supported: {dxtFormat}");
+ }
+
+ for (var mip = 0; mip < mipMaps; mip++)
+ {
+ var data = file.Faces[0].MipMaps[mip].Data;
+ var pixelWidth = file.Faces[0].MipMaps[mip].Width;
+ var pixelHeight = file.Faces[0].MipMaps[mip].Height;
+
+ var blocks = decoder.Decode(data, context);
+
+ var image = ImageToBlocks.ColorsFromRawBlocks(blocks, (int)pixelWidth, (int)pixelHeight);
+
+ colors[mip] = image;
+
+ context.Progress.SetProcessedBlocks(file.Faces[0].MipMaps.Take(mip + 1).Sum(x => x.Data.Length / blockSize));
+ }
+
+ return colors;
+ }
- public class DecoderOutputOptions {
///
- /// If true, when decoding from a format that only includes a red channel,
- /// output pixels will have all colors set to the same value (greyscale). Default is true.
+ /// Decode raw encoded image asynchronously.
///
- public bool redAsLuminance = true;
- }
+ /// The containing the encoded data.
+ /// The width of the image.
+ /// The height of the image.
+ /// The Format the encoded data is in.
+ /// The cancellation token for this operation. May be default, if the operation is not asynchronous.
+ /// 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.");
+ }
- ///
- /// Decodes compressed files into Rgba format.
- ///
- public class BcDecoder
- {
- public DecoderOutputOptions OutputOptions { get; set; } = new DecoderOutputOptions();
- public DecoderInputOptions InputOptions { get; set; } = new DecoderInputOptions();
+ var context = new OperationContext
+ {
+ CancellationToken = token,
+ IsParallel = Options.IsParallel,
+ TaskCount = Options.TaskCount
+ };
- private bool IsSupportedRawFormat(GlInternalFormat format)
+ // Calculate total blocks
+ var blockSize = GetBlockSize(format);
+ var totalBlocks = input.Length / blockSize;
+
+ context.Progress = new OperationProgress(Options.Progress, totalBlocks);
+
+ var decoder = GetRgbFloatDecoder(format);
+
+ if (!format.IsHdrFormat())
+ {
+ throw new NotSupportedException($"This Format is not an HDR format: {format}, please use the non-HDR versions of the decode methods.");
+ }
+ if (decoder == null)
+ {
+ throw new NotSupportedException($"This Format is not supported: {format}");
+ }
+
+ var blocks = decoder.Decode(input, context);
+
+ return ImageToBlocks.ColorsFromRawBlocks(blocks, pixelWidth, pixelHeight);
+ }
+
+ private void DecodeBlockInternalHdr(ReadOnlySpan blockData, CompressionFormat format, Span2D outputSpan)
{
- switch (format)
+ var decoder = GetRgbFloatDecoder(format);
+ if (!format.IsHdrFormat())
{
- case GlInternalFormat.GL_R8:
- case GlInternalFormat.GL_RG8:
- case GlInternalFormat.GL_RGB8:
- case GlInternalFormat.GL_RGBA8:
- return true;
- default:
- return false;
+ throw new NotSupportedException($"This Format is not an HDR format: {format}, please use the non-HDR versions of the decode methods.");
+ }
+ if (decoder == null)
+ {
+ throw new NotSupportedException($"This Format is not supported: {format}");
}
+ if (blockData.Length != GetBlockSize(format))
+ {
+ throw new ArgumentException("The size of the input buffer does not align with the compression format.");
+ }
+
+ var rawBlock = decoder.DecodeBlock(blockData);
+ var pixels = rawBlock.AsSpan;
+
+ pixels.Slice(0, 4).CopyTo(outputSpan.GetRowSpan(0));
+ pixels.Slice(4, 4).CopyTo(outputSpan.GetRowSpan(1));
+ pixels.Slice(8, 4).CopyTo(outputSpan.GetRowSpan(2));
+ pixels.Slice(12, 4).CopyTo(outputSpan.GetRowSpan(3));
+ }
+ #endregion
+
+ #region Support
+
+ #region Is supported format
+
+ private bool IsSupportedRawFormat(GlInternalFormat format)
+ {
+ return IsSupportedRawFormat(GetCompressionFormat(format));
+ }
+
+ private bool IsSupportedRawFormat(DdsFile file)
+ {
+ return IsSupportedRawFormat(GetCompressionFormat(file));
}
- private bool IsSupportedRawFormat(DXGI_FORMAT format)
+ private bool IsSupportedRawFormat(CompressionFormat format)
{
switch (format)
{
- case DXGI_FORMAT.DXGI_FORMAT_R8_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM:
+ case CompressionFormat.R:
+ case CompressionFormat.Rg:
+ case CompressionFormat.Rgb:
+ case CompressionFormat.Rgba:
+ case CompressionFormat.Bgra:
return true;
+
default:
return false;
}
}
- private IBcBlockDecoder GetDecoder(GlInternalFormat format)
+ #endregion
+
+ #region Get decoder
+
+ private IBcBlockDecoder GetRgba32Decoder(GlInternalFormat format)
+ {
+ return GetRgba32Decoder(GetCompressionFormat(format));
+ }
+
+ private IBcBlockDecoder GetRgba32Decoder(DdsFile file)
+ {
+ return GetRgba32Decoder(GetCompressionFormat(file));
+ }
+
+ private IBcBlockDecoder GetRgba32Decoder(CompressionFormat format)
{
switch (format)
{
- case GlInternalFormat.GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case CompressionFormat.Bc1:
return new Bc1NoAlphaDecoder();
- case GlInternalFormat.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+
+ case CompressionFormat.Bc1WithAlpha:
return new Bc1ADecoder();
- case GlInternalFormat.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+
+ case CompressionFormat.Bc2:
return new Bc2Decoder();
- case GlInternalFormat.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+
+ case CompressionFormat.Bc3:
return new Bc3Decoder();
- case GlInternalFormat.GL_COMPRESSED_RED_RGTC1_EXT:
- return new Bc4Decoder(OutputOptions.redAsLuminance);
- case GlInternalFormat.GL_COMPRESSED_RED_GREEN_RGTC2_EXT:
- return new Bc5Decoder();
- case GlInternalFormat.GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
- return new Bc7Decoder();
- // TODO: Not sure what to do with SRGB input.
- case GlInternalFormat.GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
+
+ case CompressionFormat.Bc4:
+ return new Bc4Decoder(OutputOptions.Bc4Component);
+
+ case CompressionFormat.Bc5:
+ return new Bc5Decoder(OutputOptions.Bc5Component1, OutputOptions.Bc5Component2);
+
+ case CompressionFormat.Bc7:
return new Bc7Decoder();
+
+ case CompressionFormat.Atc:
+ return new AtcDecoder();
+
+ case CompressionFormat.AtcExplicitAlpha:
+ return new AtcExplicitAlphaDecoder();
+
+ case CompressionFormat.AtcInterpolatedAlpha:
+ return new AtcInterpolatedAlphaDecoder();
+
default:
return null;
}
}
- private IBcBlockDecoder GetDecoder(DXGI_FORMAT format, DdsHeader header)
+ private IBcBlockDecoder GetRgbFloatDecoder(GlInternalFormat format)
+ {
+ return GetRgbFloatDecoder(GetCompressionFormat(format));
+ }
+
+ private IBcBlockDecoder GetRgbFloatDecoder(DdsFile file)
+ {
+ return GetRgbFloatDecoder(GetCompressionFormat(file));
+ }
+
+ private IBcBlockDecoder GetRgbFloatDecoder(CompressionFormat format)
{
switch (format)
{
- case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM_SRGB:
- case DXGI_FORMAT.DXGI_FORMAT_BC1_TYPELESS:
- if ((header.ddsPixelFormat.dwFlags & PixelFormatFlags.DDPF_ALPHAPIXELS) != 0) {
- return new Bc1ADecoder();
- }
- else if(InputOptions.ddsBc1ExpectAlpha){
- return new Bc1ADecoder();
- }
- else {
- return new Bc1NoAlphaDecoder();
- }
-
- case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM_SRGB:
- case DXGI_FORMAT.DXGI_FORMAT_BC2_TYPELESS:
- return new Bc2Decoder();
- case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM_SRGB:
- case DXGI_FORMAT.DXGI_FORMAT_BC3_TYPELESS:
- return new Bc3Decoder();
- case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC4_SNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC4_TYPELESS:
- return new Bc4Decoder(OutputOptions.redAsLuminance);
- case DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC5_SNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC5_TYPELESS:
- return new Bc5Decoder();
- case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM_SRGB:
- case DXGI_FORMAT.DXGI_FORMAT_BC7_TYPELESS:
- return new Bc7Decoder();
+ case CompressionFormat.Bc6S:
+ return new Bc6SDecoder();
+ case CompressionFormat.Bc6U:
+ return new Bc6UDecoder();
default:
return null;
}
}
+ #endregion
+
+ #region Get raw decoder
+
private IRawDecoder GetRawDecoder(GlInternalFormat format)
{
- switch (format)
- {
- case GlInternalFormat.GL_R8:
- return new RawRDecoder(OutputOptions.redAsLuminance);
- case GlInternalFormat.GL_RG8:
- return new RawRGDecoder();
- case GlInternalFormat.GL_RGB8:
- return new RawRGBDecoder();
- case GlInternalFormat.GL_RGBA8:
- return new RawRGBADecoder();
- default:
- return null;
- }
+ return GetRawDecoder(GetCompressionFormat(format));
+ }
+
+ private IRawDecoder GetRawDecoder(DdsFile file)
+ {
+ return GetRawDecoder(GetCompressionFormat(file));
}
- private IRawDecoder GetRawDecoder(DXGI_FORMAT format)
+ private IRawDecoder GetRawDecoder(CompressionFormat format)
{
switch (format)
{
- case DXGI_FORMAT.DXGI_FORMAT_R8_UNORM:
- return new RawRDecoder(OutputOptions.redAsLuminance);
- case DXGI_FORMAT.DXGI_FORMAT_R8G8_UNORM:
- return new RawRGDecoder();
- case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM:
- return new RawRGBADecoder();
- default:
- return null;
- }
- }
+ case CompressionFormat.R:
+ return new RawRDecoder(OutputOptions.RedAsLuminance);
- ///
- /// Read a Ktx or a Dds file from a stream and decode it.
- ///
- public Image Decode(Stream inputStream) {
- var position = inputStream.Position;
- try {
- if (inputStream is FileStream fs) {
- var extension = Path.GetExtension(fs.Name).ToLower();
- if (extension == ".ktx") {
- KtxFile file = KtxFile.Load(inputStream);
- return Decode(file);
- }
- else if (extension == ".dds") {
- DdsFile file = DdsFile.Load(inputStream);
- return Decode(file);
- }
- }
+ case CompressionFormat.Rg:
+ return new RawRgDecoder();
- bool isDDS = false;
- using (var br = new BinaryReader(inputStream, Encoding.UTF8, true)) {
- var magic = br.ReadUInt32();
- if (magic == 0x20534444U) {
- isDDS = true;
- }
- }
+ case CompressionFormat.Rgb:
+ return new RawRgbDecoder();
- inputStream.Seek(position, SeekOrigin.Begin);
+ case CompressionFormat.Rgba:
+ return new RawRgbaDecoder();
- if (isDDS) {
- DdsFile dds = DdsFile.Load(inputStream);
- return Decode(dds);
- }
- else {
- KtxFile ktx = KtxFile.Load(inputStream);
- return Decode(ktx);
- }
- }
- catch (Exception) {
- inputStream.Seek(position, SeekOrigin.Begin);
- throw;
+ case CompressionFormat.Bgra:
+ return new RawBgraDecoder();
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
+ #endregion
+
+ #region Get block size
+
///
- /// Read a Ktx or a Dds file from a stream and decode it.
+ /// Gets the number of total blocks in an image with the given pixel width and height.
///
- public Image[] DecodeAllMipMaps(Stream inputStream) {
- var position = inputStream.Position;
- try {
- if (inputStream is FileStream fs) {
- var extension = Path.GetExtension(fs.Name).ToLower();
- if (extension == ".ktx") {
- KtxFile file = KtxFile.Load(inputStream);
- return DecodeAllMipMaps(file);
- }
- else if (extension == ".dds") {
- DdsFile file = DdsFile.Load(inputStream);
- return DecodeAllMipMaps(file);
- }
- }
+ /// The pixel width of the image
+ /// The pixel height of the image
+ /// The total number of blocks.
+ public int GetBlockCount(int pixelWidth, int pixelHeight)
+ {
+ return ImageToBlocks.CalculateNumOfBlocks(pixelWidth, pixelHeight);
+ }
- bool isDDS = false;
- using (var br = new BinaryReader(inputStream, Encoding.UTF8, true)) {
- var magic = br.ReadUInt32();
- if (magic == 0x20534444U) {
- isDDS = true;
- }
- }
+ ///
+ /// Gets the number of blocks in an image with the given pixel width and height.
+ ///
+ /// The pixel width of the image
+ /// The pixel height of the image
+ /// The amount of blocks in the x-axis
+ /// The amount of blocks in the y-axis
+ public void GetBlockCount(int pixelWidth, int pixelHeight, out int blocksWidth, out int blocksHeight)
+ {
+ ImageToBlocks.CalculateNumOfBlocks(pixelWidth, pixelHeight, out blocksWidth, out blocksHeight);
+ }
- inputStream.Seek(position, SeekOrigin.Begin);
+ private int GetBlockSize(GlInternalFormat format)
+ {
+ return GetBlockSize(GetCompressionFormat(format));
+ }
- if (isDDS) {
- DdsFile dds = DdsFile.Load(inputStream);
- return DecodeAllMipMaps(dds);
- }
- else {
- KtxFile ktx = KtxFile.Load(inputStream);
- return DecodeAllMipMaps(ktx);
- }
- }
- catch (Exception) {
- inputStream.Seek(position, SeekOrigin.Begin);
- throw;
- }
+ private int GetBlockSize(DdsFile file)
+ {
+ return GetBlockSize(GetCompressionFormat(file));
}
///
- /// Read a KtxFile and decode it.
+ /// Get the size of blocks for the given compression format in bytes.
///
- public Image Decode(KtxFile file)
+ /// The compression format used.
+ /// The size of a single block in bytes.
+ public int GetBlockSize(CompressionFormat format)
{
- if (IsSupportedRawFormat(file.Header.GlInternalFormat)) {
- var decoder = GetRawDecoder(file.Header.GlInternalFormat);
- var data = file.MipMaps[0].Faces[0].Data;
- var pixelWidth = file.MipMaps[0].Width;
- var pixelHeight = file.MipMaps[0].Height;
+ switch (format)
+ {
+ case CompressionFormat.R:
+ return 1;
- var image = new Image((int)pixelWidth, (int)pixelHeight);
- var output = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight);
- var pixels = image.GetPixelSpan();
+ case CompressionFormat.Rg:
+ return 2;
- output.CopyTo(pixels);
- return image;
- }
- else {
- var decoder = GetDecoder(file.Header.GlInternalFormat);
- if (decoder == null)
- {
- throw new NotSupportedException($"This format is not supported: {file.Header.GlInternalFormat}");
- }
+ case CompressionFormat.Rgb:
+ return 3;
- var data = file.MipMaps[0].Faces[0].Data;
- var pixelWidth = file.MipMaps[0].Width;
- var pixelHeight = file.MipMaps[0].Height;
+ case CompressionFormat.Rgba:
+ return 4;
- var blocks = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight, out var blockWidth, out var blockHeight);
+ case CompressionFormat.Bgra:
+ return 4;
- return ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight,
- (int)pixelWidth, (int)pixelHeight);
- }
- }
+ case CompressionFormat.Bc1:
+ case CompressionFormat.Bc1WithAlpha:
+ return Unsafe.SizeOf();
- ///
- /// Read a KtxFile and decode it.
- ///
- public Image[] DecodeAllMipMaps(KtxFile file)
- {
- if (IsSupportedRawFormat(file.Header.GlInternalFormat)) {
- var decoder = GetRawDecoder(file.Header.GlInternalFormat);
- var images = new Image[file.MipMaps.Count];
+ case CompressionFormat.Bc2:
+ return Unsafe.SizeOf();
- for (int mip = 0; mip < file.MipMaps.Count; mip++) {
- var data = file.MipMaps[mip].Faces[0].Data;
- var pixelWidth = file.MipMaps[mip].Width;
- var pixelHeight = file.MipMaps[mip].Height;
+ case CompressionFormat.Bc3:
+ return Unsafe.SizeOf();
- var image = new Image((int)pixelWidth, (int)pixelHeight);
- var output = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight);
- var pixels = image.GetPixelSpan();
+ case CompressionFormat.Bc4:
+ return Unsafe.SizeOf();
- output.CopyTo(pixels);
- images[mip] = image;
- }
-
- return images;
- }
- else {
- var decoder = GetDecoder(file.Header.GlInternalFormat);
- if (decoder == null)
- {
- throw new NotSupportedException($"This format is not supported: {file.Header.GlInternalFormat}");
- }
- var images = new Image[file.MipMaps.Count];
+ case CompressionFormat.Bc5:
+ return Unsafe.SizeOf();
- for (int mip = 0; mip < file.MipMaps.Count; mip++) {
+ case CompressionFormat.Bc6S:
+ case CompressionFormat.Bc6U:
+ return Unsafe.SizeOf();
- var data = file.MipMaps[mip].Faces[0].Data;
- var pixelWidth = file.MipMaps[mip].Width;
- var pixelHeight = file.MipMaps[mip].Height;
+ case CompressionFormat.Bc7:
+ return Unsafe.SizeOf();
- var blocks = decoder.Decode(data, (int) pixelWidth, (int) pixelHeight, out var blockWidth,
- out var blockHeight);
+ case CompressionFormat.Atc:
+ return Unsafe.SizeOf();
- var image = ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight,
- (int)pixelWidth, (int)pixelHeight);
- images[mip] = image;
- }
- return images;
+ case CompressionFormat.AtcExplicitAlpha:
+ return Unsafe.SizeOf();
+
+ case CompressionFormat.AtcInterpolatedAlpha:
+ return Unsafe.SizeOf();
+
+ case CompressionFormat.Unknown:
+ return 0;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
- ///
- /// Read a DdsFile and decode it
- ///
- public Image Decode(DdsFile file)
+ #endregion
+
+ private CompressionFormat GetCompressionFormat(GlInternalFormat format)
{
- if (IsSupportedRawFormat(file.Header.ddsPixelFormat.DxgiFormat)) {
- var decoder = GetRawDecoder(file.Header.ddsPixelFormat.DxgiFormat);
- var data = file.Faces[0].MipMaps[0].Data;
- var pixelWidth = file.Faces[0].Width;
- var pixelHeight = file.Faces[0].Height;
+ switch (format)
+ {
+ case GlInternalFormat.GlR8:
+ return CompressionFormat.R;
- var image = new Image((int)pixelWidth, (int)pixelHeight);
- var output = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight);
- var pixels = image.GetPixelSpan();
+ case GlInternalFormat.GlRg8:
+ return CompressionFormat.Rg;
- output.CopyTo(pixels);
- return image;
- }
- else {
- DXGI_FORMAT format = DXGI_FORMAT.DXGI_FORMAT_UNKNOWN;
- if (file.Header.ddsPixelFormat.IsDxt10Format) {
- format = file.Dxt10Header.dxgiFormat;
- }
- else {
- format = file.Header.ddsPixelFormat.DxgiFormat;
- }
- IBcBlockDecoder decoder = GetDecoder(format, file.Header);
-
- if (decoder == null)
- {
- throw new NotSupportedException($"This format is not supported: {format}");
- }
+ case GlInternalFormat.GlRgb8:
+ return CompressionFormat.Rgb;
+
+ case GlInternalFormat.GlRgba8:
+ return CompressionFormat.Rgba;
+
+ // HINT: Bgra is not supported by default. The format enum is added by an extension by Apple.
+ case GlInternalFormat.GlBgra8Extension:
+ return CompressionFormat.Bgra;
+
+ case GlInternalFormat.GlCompressedRgbS3TcDxt1Ext:
+ return CompressionFormat.Bc1;
+
+ case GlInternalFormat.GlCompressedRgbaS3TcDxt1Ext:
+ return CompressionFormat.Bc1WithAlpha;
+
+ case GlInternalFormat.GlCompressedRgbaS3TcDxt3Ext:
+ return CompressionFormat.Bc2;
+
+ case GlInternalFormat.GlCompressedRgbaS3TcDxt5Ext:
+ return CompressionFormat.Bc3;
- var data = file.Faces[0].MipMaps[0].Data;
- var pixelWidth = file.Faces[0].Width;
- var pixelHeight = file.Faces[0].Height;
+ case GlInternalFormat.GlCompressedRedRgtc1Ext:
+ return CompressionFormat.Bc4;
- var blocks = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight, out var blockWidth, out var blockHeight);
+ case GlInternalFormat.GlCompressedRedGreenRgtc2Ext:
+ return CompressionFormat.Bc5;
- return ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight,
- (int)pixelWidth, (int)pixelHeight);
+ case GlInternalFormat.GlCompressedRgbBptcUnsignedFloatArb:
+ return CompressionFormat.Bc6U;
+
+ case GlInternalFormat.GlCompressedRgbBptcSignedFloatArb:
+ return CompressionFormat.Bc6S;
+
+ // TODO: Not sure what to do with SRGB input.
+ case GlInternalFormat.GlCompressedRgbaBptcUnormArb:
+ case GlInternalFormat.GlCompressedSrgbAlphaBptcUnormArb:
+ return CompressionFormat.Bc7;
+
+ case GlInternalFormat.GlCompressedRgbAtc:
+ return CompressionFormat.Atc;
+
+ case GlInternalFormat.GlCompressedRgbaAtcExplicitAlpha:
+ return CompressionFormat.AtcExplicitAlpha;
+
+ case GlInternalFormat.GlCompressedRgbaAtcInterpolatedAlpha:
+ return CompressionFormat.AtcInterpolatedAlpha;
+
+ default:
+ return CompressionFormat.Unknown;
}
}
- ///
- /// Read a DdsFile and decode it
- ///
- public Image[] DecodeAllMipMaps(DdsFile file)
+ private CompressionFormat GetCompressionFormat(DdsFile file)
{
- if (IsSupportedRawFormat(file.Header.ddsPixelFormat.DxgiFormat)) {
- var decoder = GetRawDecoder(file.Header.ddsPixelFormat.DxgiFormat);
+ var format = file.header.ddsPixelFormat.IsDxt10Format ?
+ file.dx10Header.dxgiFormat :
+ file.header.ddsPixelFormat.DxgiFormat;
- var images = new Image[file.Header.dwMipMapCount];
+ switch (format)
+ {
+ case DxgiFormat.DxgiFormatR8Unorm:
+ return CompressionFormat.R;
- for (int mip = 0; mip < file.Header.dwMipMapCount; mip++) {
- var data = file.Faces[0].MipMaps[mip].Data;
- var pixelWidth = file.Faces[0].MipMaps[mip].Width;
- var pixelHeight = file.Faces[0].MipMaps[mip].Height;
+ case DxgiFormat.DxgiFormatR8G8Unorm:
+ return CompressionFormat.Rg;
- var image = new Image((int) pixelWidth, (int) pixelHeight);
- var output = decoder.Decode(data, (int) pixelWidth, (int) pixelHeight);
- var pixels = image.GetPixelSpan();
+ // HINT: R8G8B8 has no DxgiFormat to convert from
- output.CopyTo(pixels);
- images[mip] = image;
- }
- return images;
+ case DxgiFormat.DxgiFormatR8G8B8A8Unorm:
+ return CompressionFormat.Rgba;
+
+ case DxgiFormat.DxgiFormatB8G8R8A8Unorm:
+ return CompressionFormat.Bgra;
+
+ case DxgiFormat.DxgiFormatBc1Unorm:
+ case DxgiFormat.DxgiFormatBc1UnormSrgb:
+ case DxgiFormat.DxgiFormatBc1Typeless:
+ if (file.header.ddsPixelFormat.dwFlags.HasFlag(PixelFormatFlags.DdpfAlphaPixels))
+ return CompressionFormat.Bc1WithAlpha;
+
+ if (InputOptions.DdsBc1ExpectAlpha)
+ return CompressionFormat.Bc1WithAlpha;
+
+ return CompressionFormat.Bc1;
+
+ case DxgiFormat.DxgiFormatBc2Unorm:
+ case DxgiFormat.DxgiFormatBc2UnormSrgb:
+ case DxgiFormat.DxgiFormatBc2Typeless:
+ return CompressionFormat.Bc2;
+
+ case DxgiFormat.DxgiFormatBc3Unorm:
+ case DxgiFormat.DxgiFormatBc3UnormSrgb:
+ case DxgiFormat.DxgiFormatBc3Typeless:
+ return CompressionFormat.Bc3;
+
+ case DxgiFormat.DxgiFormatBc4Unorm:
+ case DxgiFormat.DxgiFormatBc4Snorm:
+ case DxgiFormat.DxgiFormatBc4Typeless:
+ return CompressionFormat.Bc4;
+
+ case DxgiFormat.DxgiFormatBc5Unorm:
+ case DxgiFormat.DxgiFormatBc5Snorm:
+ case DxgiFormat.DxgiFormatBc5Typeless:
+ return CompressionFormat.Bc5;
+
+ case DxgiFormat.DxgiFormatBc6HTypeless:
+ case DxgiFormat.DxgiFormatBc6HUf16:
+ return CompressionFormat.Bc6U;
+
+ case DxgiFormat.DxgiFormatBc6HSf16:
+ return CompressionFormat.Bc6S;
+
+ case DxgiFormat.DxgiFormatBc7Unorm:
+ case DxgiFormat.DxgiFormatBc7UnormSrgb:
+ case DxgiFormat.DxgiFormatBc7Typeless:
+ return CompressionFormat.Bc7;
+
+ case DxgiFormat.DxgiFormatAtcExt:
+ return CompressionFormat.Atc;
+
+ case DxgiFormat.DxgiFormatAtcExplicitAlphaExt:
+ return CompressionFormat.AtcExplicitAlpha;
+
+ case DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt:
+ return CompressionFormat.AtcInterpolatedAlpha;
+
+ default:
+ return CompressionFormat.Unknown;
}
- else {
- DXGI_FORMAT format = DXGI_FORMAT.DXGI_FORMAT_UNKNOWN;
- if (file.Header.ddsPixelFormat.IsDxt10Format) {
- format = file.Dxt10Header.dxgiFormat;
- }
- else {
- format = file.Header.ddsPixelFormat.DxgiFormat;
- }
- IBcBlockDecoder decoder = GetDecoder(format, file.Header);
-
- if (decoder == null)
- {
- throw new NotSupportedException($"This format is not supported: {format}");
- }
- var images = new Image[file.Header.dwMipMapCount];
+ }
- for (int mip = 0; mip < file.Header.dwMipMapCount; mip++) {
- var data = file.Faces[0].MipMaps[mip].Data;
- var pixelWidth = file.Faces[0].MipMaps[mip].Width;
- var pixelHeight = file.Faces[0].MipMaps[mip].Height;
+ private int GetBufferSize(CompressionFormat format, int pixelWidth, int pixelHeight)
+ {
+ switch (format)
+ {
+ case CompressionFormat.R:
+ return pixelWidth * pixelHeight;
- var blocks = decoder.Decode(data, (int) pixelWidth, (int) pixelHeight, out var blockWidth,
- out var blockHeight);
+ case CompressionFormat.Rg:
+ return 2 * pixelWidth * pixelHeight;
- var image = ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight,
- (int)pixelWidth, (int)pixelHeight);
+ case CompressionFormat.Rgb:
+ return 3 * pixelWidth * pixelHeight;
- images[mip] = image;
- }
+ case CompressionFormat.Rgba:
+ case CompressionFormat.Bgra:
+ return 4 * pixelWidth * pixelHeight;
+
+ case CompressionFormat.Bc1:
+ case CompressionFormat.Bc1WithAlpha:
+ case CompressionFormat.Bc2:
+ case CompressionFormat.Bc3:
+ case CompressionFormat.Bc4:
+ case CompressionFormat.Bc5:
+ case CompressionFormat.Bc6S:
+ case CompressionFormat.Bc6U:
+ case CompressionFormat.Bc7:
+ case CompressionFormat.Atc:
+ case CompressionFormat.AtcExplicitAlpha:
+ case CompressionFormat.AtcInterpolatedAlpha:
+ return GetBlockSize(format) * ImageToBlocks.CalculateNumOfBlocks(pixelWidth, pixelHeight);
- return images;
+ case CompressionFormat.Unknown:
+ return 0;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
+
+ #endregion
}
}
diff --git a/BCnEnc.Net/Decoder/BcFloatBlockDecoder.cs b/BCnEnc.Net/Decoder/BcFloatBlockDecoder.cs
new file mode 100644
index 0000000..b585bc3
--- /dev/null
+++ b/BCnEnc.Net/Decoder/BcFloatBlockDecoder.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BCnEncoder.Decoder
+{
+ internal class Bc6FloatBlockDecoder
+ {
+ private static readonly object lockObj = new object();
+
+ }
+}
diff --git a/BCnEnc.Net/Decoder/Options/DecoderInputOptions.cs b/BCnEnc.Net/Decoder/Options/DecoderInputOptions.cs
new file mode 100644
index 0000000..ed2e650
--- /dev/null
+++ b/BCnEnc.Net/Decoder/Options/DecoderInputOptions.cs
@@ -0,0 +1,16 @@
+namespace BCnEncoder.Decoder.Options
+{
+ ///
+ /// A class for the decoder input options.
+ ///
+ public class DecoderInputOptions
+ {
+ ///
+ /// The DDS file Format doesn't seem to have a standard for indicating whether a BC1 texture
+ /// includes 1bit of alpha. This option will assume that all Bc1 textures contain alpha.
+ /// If this option is false, but the dds header includes a DDPF_ALPHAPIXELS flag, alpha will be included.
+ /// Default is true.
+ ///
+ public bool DdsBc1ExpectAlpha { get; set; } = true;
+ }
+}
diff --git a/BCnEnc.Net/Decoder/Options/DecoderOptions.cs b/BCnEnc.Net/Decoder/Options/DecoderOptions.cs
new file mode 100644
index 0000000..f265000
--- /dev/null
+++ b/BCnEnc.Net/Decoder/Options/DecoderOptions.cs
@@ -0,0 +1,30 @@
+using System;
+using BCnEncoder.Shared;
+
+namespace BCnEncoder.Decoder.Options
+{
+ ///
+ /// General options for the decoder.
+ ///
+ public class DecoderOptions
+ {
+ ///
+ /// Whether the blocks should be decoded in parallel. This can be much faster than single-threaded decoding,
+ /// but is slow if multiple textures are being processed at the same time.
+ /// When a debugger is attached, the decoder defaults to single-threaded operation to ease debugging.
+ /// Default is false.
+ ///
+ /// Parallel execution will be ignored in RawDecoders, due to minimal performance gain.
+ public bool IsParallel { get; set; }
+
+ ///
+ /// Determines how many tasks should be used for parallel processing.
+ ///
+ public int TaskCount { get; set; } = Environment.ProcessorCount;
+
+ ///
+ /// The progress context for the operation.
+ ///
+ public IProgress Progress { get; set; }
+ }
+}
diff --git a/BCnEnc.Net/Decoder/Options/DecoderOutputOptions.cs b/BCnEnc.Net/Decoder/Options/DecoderOutputOptions.cs
new file mode 100644
index 0000000..f221800
--- /dev/null
+++ b/BCnEnc.Net/Decoder/Options/DecoderOutputOptions.cs
@@ -0,0 +1,33 @@
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+
+namespace BCnEncoder.Decoder.Options
+{
+ ///
+ /// A class for the decoder output options.
+ ///
+ public class DecoderOutputOptions
+ {
+ ///
+ /// If true, when decoding from R8 raw format,
+ /// output pixels will have all colors set to the same value (greyscale).
+ /// Default is true. (Does not apply to BC4 format.)
+ ///
+ public bool RedAsLuminance { get; set; } = true;
+
+ ///
+ /// The color channel to populate with the values of a BC4 block.
+ ///
+ public ColorComponent Bc4Component { get; set; } = ColorComponent.R;
+
+ ///
+ /// The color channel to populate with the values of the first BC5 block.
+ ///
+ public ColorComponent Bc5Component1 { get; set; } = ColorComponent.R;
+
+ ///
+ /// The color channel to populate with the values of the second BC5 block.
+ ///
+ public ColorComponent Bc5Component2 { get; set; } = ColorComponent.G;
+ }
+}
diff --git a/BCnEnc.Net/Decoder/RawDecoder.cs b/BCnEnc.Net/Decoder/RawDecoder.cs
index 75f77fe..880485d 100644
--- a/BCnEnc.Net/Decoder/RawDecoder.cs
+++ b/BCnEnc.Net/Decoder/RawDecoder.cs
@@ -1,75 +1,190 @@
-using System;
-using SixLabors.ImageSharp.PixelFormats;
+using System;
+using BCnEncoder.Shared;
namespace BCnEncoder.Decoder
{
- internal interface IRawDecoder {
- Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight);
+ internal interface IRawDecoder
+ {
+ ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context);
}
- public class RawRDecoder : IRawDecoder {
+ ///
+ /// A class to decode data to R components.
+ ///
+ public class RawRDecoder : IRawDecoder
+ {
private readonly bool redAsLuminance;
- public RawRDecoder(bool redAsLuminance) {
+
+ ///
+ /// Create a new instance of .
+ ///
+ /// If the decoded component should be used as the red component or luminance.
+ public RawRDecoder(bool redAsLuminance)
+ {
this.redAsLuminance = redAsLuminance;
}
- public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) {
- Rgba32[] output = new Rgba32[pixelWidth * pixelHeight];
- for (int i = 0; i < output.Length; i++) {
- if (redAsLuminance) {
- output[i].R = data[i];
- output[i].G = data[i];
- output[i].B = data[i];
+ ///
+ /// Decode the data to color components.
+ ///
+ /// The data to decode.
+ /// The context of the current operation.
+ /// The decoded color components.
+ public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context)
+ {
+ var output = new ColorRgba32[data.Length];
+
+ // HINT: Ignoring parallel execution since we wouldn't gain performance from it.
+
+ var span = data.Span;
+ for (var i = 0; i < output.Length; i++)
+ {
+ context.CancellationToken.ThrowIfCancellationRequested();
+
+ if (redAsLuminance)
+ {
+ output[i].r = span[i];
+ output[i].g = span[i];
+ output[i].b = span[i];
}
- else {
- output[i].R = data[i];
- output[i].G = 0;
- output[i].B = 0;
+ else
+ {
+ output[i].r = span[i];
+ output[i].g = 0;
+ output[i].b = 0;
}
- output[i].A = 255;
+
+ output[i].a = 255;
+ }
+
+ return output;
+ }
+ }
+
+ ///
+ /// A class to decode data to RG components.
+ ///
+ public class RawRgDecoder : IRawDecoder
+ {
+ ///
+ /// Decode the data to color components.
+ ///
+ /// The data to decode.
+ /// The context of the current operation.
+ /// The decoded color components.
+ public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context)
+ {
+ var output = new ColorRgba32[data.Length / 2];
+
+ // HINT: Ignoring parallel execution since we wouldn't gain performance from it.
+
+ var span = data.Span;
+ for (var i = 0; i < output.Length; i++)
+ {
+ context.CancellationToken.ThrowIfCancellationRequested();
+
+ output[i].r = span[i * 2];
+ output[i].g = span[i * 2 + 1];
+ output[i].b = 0;
+ output[i].a = 255;
}
+
return output;
}
}
- public class RawRGDecoder : IRawDecoder
+ ///
+ /// A class to decode data to RGB components.
+ ///
+ public class RawRgbDecoder : IRawDecoder
{
- public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) {
- Rgba32[] output = new Rgba32[pixelWidth * pixelHeight];
- for (int i = 0; i < output.Length; i++) {
- output[i].R = data[i * 2];
- output[i].G = data[i * 2 + 1];
- output[i].B = 0;
- output[i].A = 255;
+ ///
+ /// Decode the data to color components.
+ ///
+ /// The data to decode.
+ /// The context of the current operation.
+ /// The decoded color components.
+ public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context)
+ {
+ var output = new ColorRgba32[data.Length / 3];
+
+ // HINT: Ignoring parallel execution since we wouldn't gain performance from it.
+
+ var span = data.Span;
+ for (var i = 0; i < output.Length; i++)
+ {
+ context.CancellationToken.ThrowIfCancellationRequested();
+
+ output[i].r = span[i * 3];
+ output[i].g = span[i * 3 + 1];
+ output[i].b = span[i * 3 + 2];
+ output[i].a = 255;
}
+
return output;
}
}
- public class RawRGBDecoder : IRawDecoder
+ ///
+ /// A class to decode data to RGBA components.
+ ///
+ public class RawRgbaDecoder : IRawDecoder
{
- public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) {
- Rgba32[] output = new Rgba32[pixelWidth * pixelHeight];
- for (int i = 0; i < output.Length; i++) {
- output[i].R = data[i * 3];
- output[i].G = data[i * 3 + 1];
- output[i].B = data[i * 3 + 2];
- output[i].A = 255;
+ ///
+ /// Decode the data to color components.
+ ///
+ /// The data to decode.
+ /// The context of the current operation.
+ /// The decoded color components.
+ public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context)
+ {
+ var output = new ColorRgba32[data.Length / 4];
+
+ // HINT: Ignoring parallel execution since we wouldn't gain performance from it.
+
+ var span = data.Span;
+ for (var i = 0; i < output.Length; i++)
+ {
+ context.CancellationToken.ThrowIfCancellationRequested();
+
+ output[i].r = span[i * 4];
+ output[i].g = span[i * 4 + 1];
+ output[i].b = span[i * 4 + 2];
+ output[i].a = span[i * 4 + 3];
}
+
return output;
}
}
- public class RawRGBADecoder : IRawDecoder
+ ///
+ /// A class to decode data to BGRA components.
+ ///
+ public class RawBgraDecoder : IRawDecoder
{
- public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) {
- Rgba32[] output = new Rgba32[pixelWidth * pixelHeight];
- for (int i = 0; i < output.Length; i++) {
- output[i].R = data[i * 4];
- output[i].G = data[i * 4 + 1];
- output[i].B = data[i * 4 + 2];
- output[i].A = data[i * 4 + 3];
+ ///
+ /// Decode the data to color components.
+ ///
+ /// The data to decode.
+ /// The context of the current operation.
+ /// The decoded color components.
+ public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context)
+ {
+ var output = new ColorRgba32[data.Length / 4];
+
+ // HINT: Ignoring parallel execution since we wouldn't gain performance from it.
+
+ var span = data.Span;
+ for (var i = 0; i < output.Length; i++)
+ {
+ context.CancellationToken.ThrowIfCancellationRequested();
+
+ output[i].b = span[i * 4];
+ output[i].g = span[i * 4 + 1];
+ output[i].r = span[i * 4 + 2];
+ output[i].a = span[i * 4 + 3];
}
+
return output;
}
}
diff --git a/BCnEnc.Net/Encoder/AtcBlockEncoder.cs b/BCnEnc.Net/Encoder/AtcBlockEncoder.cs
new file mode 100644
index 0000000..2c3dde2
--- /dev/null
+++ b/BCnEnc.Net/Encoder/AtcBlockEncoder.cs
@@ -0,0 +1,136 @@
+using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
+
+namespace BCnEncoder.Encoder
+{
+ internal unsafe class AtcBlockEncoder : BaseBcBlockEncoder
+ {
+ private readonly Bc1BlockEncoder bc1BlockEncoder;
+
+ public AtcBlockEncoder()
+ {
+ bc1BlockEncoder = new Bc1BlockEncoder();
+ }
+
+ public override AtcBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
+ {
+ var atcBlock = new AtcBlock();
+
+ // EncodeBlock with BC1 first
+ var bc1Block = bc1BlockEncoder.EncodeBlock(block, quality);
+
+ // Atc specific modifications to BC1
+ // According to http://www.guildsoftware.com/papers/2012.Converting.DXTC.to.Atc.pdf
+
+ // Change color0 from rgb565 to rgb555 with method 0
+ atcBlock.color0 = new ColorRgb555(bc1Block.color0.R, bc1Block.color0.G, bc1Block.color0.B);
+ atcBlock.color1 = bc1Block.color1;
+
+ // Remap color indices from BC1 to ATC
+ var remap = stackalloc byte[] { 0, 3, 1, 2 };
+ for (var i = 0; i < 16; i++)
+ {
+ atcBlock[i] = remap[bc1Block[i]];
+ }
+
+ return atcBlock;
+ }
+
+ public override GlInternalFormat GetInternalFormat()
+ {
+ return GlInternalFormat.GlCompressedRgbAtc;
+ }
+
+ public override GlFormat GetBaseInternalFormat()
+ {
+ return GlFormat.GlRgb;
+ }
+
+ public override DxgiFormat GetDxgiFormat()
+ {
+ return DxgiFormat.DxgiFormatAtcExt;
+ }
+ }
+
+ internal class AtcExplicitAlphaBlockEncoder : BaseBcBlockEncoder
+ {
+ private readonly AtcBlockEncoder atcBlockEncoder;
+
+ public AtcExplicitAlphaBlockEncoder()
+ {
+ atcBlockEncoder = new AtcBlockEncoder();
+ }
+
+ public override AtcExplicitAlphaBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
+ {
+ var atcBlock = atcBlockEncoder.EncodeBlock(block, quality);
+
+ // EncodeBlock alpha
+ var bc2AlphaBlock = new Bc2AlphaBlock();
+ for (var i = 0; i < 16; i++)
+ {
+ bc2AlphaBlock.SetAlpha(i, block[i].a);
+ }
+
+ return new AtcExplicitAlphaBlock
+ {
+ alphas = bc2AlphaBlock,
+ colors = atcBlock
+ };
+ }
+
+ public override GlInternalFormat GetInternalFormat()
+ {
+ return GlInternalFormat.GlCompressedRgbaAtcExplicitAlpha;
+ }
+
+ public override GlFormat GetBaseInternalFormat()
+ {
+ return GlFormat.GlRgba;
+ }
+
+ public override DxgiFormat GetDxgiFormat()
+ {
+ return DxgiFormat.DxgiFormatAtcExplicitAlphaExt;
+ }
+ }
+
+ internal class AtcInterpolatedAlphaBlockEncoder : BaseBcBlockEncoder
+ {
+ private readonly Bc4ComponentBlockEncoder bc4BlockEncoder;
+ private readonly AtcBlockEncoder atcBlockEncoder;
+
+ public AtcInterpolatedAlphaBlockEncoder()
+ {
+ bc4BlockEncoder = new Bc4ComponentBlockEncoder(ColorComponent.A);
+ atcBlockEncoder = new AtcBlockEncoder();
+ }
+
+ public override AtcInterpolatedAlphaBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
+ {
+ var bc4Block = bc4BlockEncoder.EncodeBlock(block, quality);
+ var atcBlock = atcBlockEncoder.EncodeBlock(block, quality);
+
+ return new AtcInterpolatedAlphaBlock
+ {
+ alphas = bc4Block,
+ colors = atcBlock
+ };
+ }
+
+ public override GlInternalFormat GetInternalFormat()
+ {
+ return GlInternalFormat.GlCompressedRgbaAtcInterpolatedAlpha;
+ }
+
+ public override GlFormat GetBaseInternalFormat()
+ {
+ return GlFormat.GlRgba;
+ }
+
+ public override DxgiFormat GetDxgiFormat()
+ {
+ return DxgiFormat.DxgiFormatAtcInterpolatedAlphaExt;
+ }
+ }
+}
diff --git a/BCnEnc.Net/Encoder/BaseBcBlockEncoder.cs b/BCnEnc.Net/Encoder/BaseBcBlockEncoder.cs
new file mode 100644
index 0000000..df5b62b
--- /dev/null
+++ b/BCnEnc.Net/Encoder/BaseBcBlockEncoder.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
+
+namespace BCnEncoder.Encoder
+{
+ internal abstract class BaseBcBlockEncoder : IBcBlockEncoder where T : unmanaged where TBlock : unmanaged
+ {
+ private static readonly object lockObj = new object();
+
+ public byte[] Encode(TBlock[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, OperationContext context)
+ {
+ var outputData = new byte[blockWidth * blockHeight * Unsafe.SizeOf()];
+
+ var currentBlocks = 0;
+ if (context.IsParallel)
+ {
+ var options = new ParallelOptions
+ {
+ CancellationToken = context.CancellationToken,
+ MaxDegreeOfParallelism = context.TaskCount
+ };
+ Parallel.For(0, blocks.Length, options, i =>
+ {
+ var outputBlocks = MemoryMarshal.Cast(outputData);
+ outputBlocks[i] = EncodeBlock(blocks[i], quality);
+
+ if (context.Progress != null)
+ {
+ lock (lockObj)
+ {
+ context.Progress.Report(++currentBlocks);
+ }
+ }
+ });
+ }
+ else
+ {
+ var outputBlocks = MemoryMarshal.Cast(outputData);
+ for (var i = 0; i < blocks.Length; i++)
+ {
+ context.CancellationToken.ThrowIfCancellationRequested();
+
+ outputBlocks[i] = EncodeBlock(blocks[i], quality);
+
+ context.Progress?.Report(++currentBlocks);
+ }
+ }
+
+ return outputData;
+ }
+
+ public void EncodeBlock(TBlock block, CompressionQuality quality, Span output)
+ {
+ if (output.Length != Unsafe.SizeOf())
+ {
+ throw new Exception("Cannot encode block! Output buffer is not the correct size.");
+ }
+ var encoded = EncodeBlock(block, quality);
+ MemoryMarshal.Cast(output)[0] = encoded;
+ }
+
+ public abstract GlInternalFormat GetInternalFormat();
+ public abstract GlFormat GetBaseInternalFormat();
+ public abstract DxgiFormat GetDxgiFormat();
+ public int GetBlockSize()
+ {
+ return Unsafe.SizeOf();
+ }
+
+ public abstract T EncodeBlock(TBlock block, CompressionQuality quality);
+ }
+}
diff --git a/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs
index 2763d89..2904ede 100644
--- a/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs
+++ b/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs
@@ -1,46 +1,20 @@
-using System;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
+using System;
using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
namespace BCnEncoder.Encoder
{
- internal class Bc1BlockEncoder : IBcBlockEncoder
+ internal class Bc1BlockEncoder : BaseBcBlockEncoder
{
-
- public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, EncodingQuality quality, bool parallel)
- {
- byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()];
- Span outputBlocks = MemoryMarshal.Cast(outputData);
-
- if (parallel)
- {
- Parallel.For(0, blocks.Length, i =>
- {
- Span outputBlocks = MemoryMarshal.Cast(outputData);
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- });
- }
- else
- {
- for (int i = 0; i < blocks.Length; i++)
- {
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- }
- }
-
- return outputData;
- }
-
- private Bc1Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality)
+ public override Bc1Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
{
switch (quality)
{
- case EncodingQuality.Fast:
+ case CompressionQuality.Fast:
return Bc1BlockEncoderFast.EncodeBlock(block);
- case EncodingQuality.Balanced:
+ case CompressionQuality.Balanced:
return Bc1BlockEncoderBalanced.EncodeBlock(block);
- case EncodingQuality.BestQuality:
+ case CompressionQuality.BestQuality:
return Bc1BlockEncoderSlow.EncodeBlock(block);
default:
@@ -48,26 +22,26 @@ private Bc1Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality)
}
}
- public GlInternalFormat GetInternalFormat()
+ public override GlInternalFormat GetInternalFormat()
{
- return GlInternalFormat.GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+ return GlInternalFormat.GlCompressedRgbS3TcDxt1Ext;
}
- public GLFormat GetBaseInternalFormat()
+ public override GlFormat GetBaseInternalFormat()
{
- return GLFormat.GL_RGB;
+ return GlFormat.GlRgb;
}
- public DXGI_FORMAT GetDxgiFormat()
+ public override DxgiFormat GetDxgiFormat()
{
- return DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM;
+ return DxgiFormat.DxgiFormatBc1Unorm;
}
#region Encoding private stuff
private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0, ColorRgb565 color1, out float error, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f)
{
- Bc1Block output = new Bc1Block();
+ var output = new Bc1Block();
var pixels = rawBlock.AsSpan;
@@ -81,17 +55,17 @@ private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0
stackalloc ColorRgb24[] {
c0,
c1,
- c0 * (1.0 / 2.0) + c1 * (1.0 / 2.0),
+ c0.InterpolateHalf(c1),
new ColorRgb24(0, 0, 0)
} : stackalloc ColorRgb24[] {
c0,
c1,
- c0 * (2.0 / 3.0) + c1 * (1.0 / 3.0),
- c0 * (1.0 / 3.0) + c1 * (2.0 / 3.0)
+ c0.InterpolateThird(c1, 1),
+ c0.InterpolateThird(c1, 2)
};
error = 0;
- for (int i = 0; i < 16; i++)
+ for (var i = 0; i < 16; i++)
{
var color = pixels[i];
output[i] = ColorChooser.ChooseClosestColor4(colors, color, rWeight, gWeight, bWeight, out var e);
@@ -111,14 +85,14 @@ private static class Bc1BlockEncoderFast
internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
- Bc1Block output = new Bc1Block();
+ var output = new Bc1Block();
var pixels = rawBlock.AsSpan;
RgbBoundingBox.Create565(pixels, out var min, out var max);
- ColorRgb565 c0 = max;
- ColorRgb565 c1 = min;
+ var c0 = max;
+ var c1 = min;
output = TryColors(rawBlock, c0, c1, out var error);
@@ -126,15 +100,16 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
}
}
- private static class Bc1BlockEncoderBalanced {
- private const int maxTries = 24 * 2;
- private const float errorThreshold = 0.05f;
+ private static class Bc1BlockEncoderBalanced
+ {
+ private const int MaxTries = 24 * 2;
+ private const float ErrorThreshold = 0.05f;
internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
- PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa);
+ PcaVectors.Create(pixels, out var mean, out var pa);
PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max);
var c0 = max;
@@ -147,20 +122,21 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = c;
}
- Bc1Block best = TryColors(rawBlock, c0, c1, out float bestError);
-
- for (int i = 0; i < maxTries; i++) {
+ var best = TryColors(rawBlock, c0, c1, out var bestError);
+
+ for (var i = 0; i < MaxTries; i++)
+ {
var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i);
-
+
if (newC0.data < newC1.data)
{
var c = newC0;
newC0 = newC1;
newC1 = c;
}
-
+
var block = TryColors(rawBlock, newC0, newC1, out var error);
-
+
if (error < bestError)
{
best = block;
@@ -169,7 +145,8 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = newC1;
}
- if (bestError < errorThreshold) {
+ if (bestError < ErrorThreshold)
+ {
break;
}
}
@@ -180,14 +157,14 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
private static class Bc1BlockEncoderSlow
{
- private const int maxTries = 9999;
- private const float errorThreshold = 0.01f;
+ private const int MaxTries = 9999;
+ private const float ErrorThreshold = 0.01f;
internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
- PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa);
+ PcaVectors.Create(pixels, out var mean, out var pa);
PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max);
var c0 = max;
@@ -200,20 +177,21 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = c;
}
- Bc1Block best = TryColors(rawBlock, c0, c1, out float bestError);
+ var best = TryColors(rawBlock, c0, c1, out var bestError);
- int lastChanged = 0;
+ var lastChanged = 0;
- for (int i = 0; i < maxTries; i++) {
+ for (var i = 0; i < MaxTries; i++)
+ {
var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i);
-
+
if (newC0.data < newC1.data)
{
var c = newC0;
newC0 = newC1;
newC1 = c;
}
-
+
var block = TryColors(rawBlock, newC0, newC1, out var error);
lastChanged++;
@@ -227,7 +205,8 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
lastChanged = 0;
}
- if (bestError < errorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) {
+ if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount)
+ {
break;
}
}
@@ -239,42 +218,17 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
#endregion
}
- internal class Bc1AlphaBlockEncoder : IBcBlockEncoder
+ internal class Bc1AlphaBlockEncoder : BaseBcBlockEncoder
{
-
- public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, EncodingQuality quality, bool parallel)
- {
- byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()];
- Span outputBlocks = MemoryMarshal.Cast(outputData);
-
- if (parallel)
- {
- Parallel.For(0, blocks.Length, i =>
- {
- Span outputBlocks = MemoryMarshal.Cast(outputData);
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- });
- }
- else
- {
- for (int i = 0; i < blocks.Length; i++)
- {
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- }
- }
-
- return outputData;
- }
-
- private Bc1Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality)
+ public override Bc1Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
{
switch (quality)
{
- case EncodingQuality.Fast:
+ case CompressionQuality.Fast:
return Bc1AlphaBlockEncoderFast.EncodeBlock(block);
- case EncodingQuality.Balanced:
+ case CompressionQuality.Balanced:
return Bc1AlphaBlockEncoderBalanced.EncodeBlock(block);
- case EncodingQuality.BestQuality:
+ case CompressionQuality.BestQuality:
return Bc1AlphaBlockEncoderSlow.EncodeBlock(block);
default:
@@ -282,24 +236,26 @@ private Bc1Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality)
}
}
- public GlInternalFormat GetInternalFormat()
+ public override GlInternalFormat GetInternalFormat()
{
- return GlInternalFormat.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ return GlInternalFormat.GlCompressedRgbaS3TcDxt1Ext;
}
- public GLFormat GetBaseInternalFormat()
+ public override GlFormat GetBaseInternalFormat()
{
- return GLFormat.GL_RGBA;
+ return GlFormat.GlRgba;
}
- public DXGI_FORMAT GetDxgiFormat()
+ public override DxgiFormat GetDxgiFormat()
{
- return DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM;
+ return DxgiFormat.DxgiFormatBc1Unorm;
}
+ #region Encoding private stuff
+
private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0, ColorRgb565 color1, out float error, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f)
{
- Bc1Block output = new Bc1Block();
+ var output = new Bc1Block();
var pixels = rawBlock.AsSpan;
@@ -309,23 +265,23 @@ private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0
var c0 = color0.ToColorRgb24();
var c1 = color1.ToColorRgb24();
- bool hasAlpha = output.HasAlphaOrBlack;
+ var hasAlpha = output.HasAlphaOrBlack;
ReadOnlySpan colors = hasAlpha ?
stackalloc ColorRgb24[] {
c0,
c1,
- c0 * (1.0 / 2.0) + c1 * (1.0 / 2.0),
+ c0.InterpolateHalf(c1),
new ColorRgb24(0, 0, 0)
} : stackalloc ColorRgb24[] {
c0,
c1,
- c0 * (2.0 / 3.0) + c1 * (1.0 / 3.0),
- c0 * (1.0 / 3.0) + c1 * (2.0 / 3.0)
+ c0.InterpolateThird(c1, 1),
+ c0.InterpolateThird(c1, 2)
};
error = 0;
- for (int i = 0; i < 16; i++)
+ for (var i = 0; i < 16; i++)
{
var color = pixels[i];
output[i] = ColorChooser.ChooseClosestColor4AlphaCutoff(colors, color, rWeight, gWeight, bWeight,
@@ -336,6 +292,7 @@ stackalloc ColorRgb24[] {
return output;
}
+ #endregion
#region Encoders
@@ -344,16 +301,16 @@ private static class Bc1AlphaBlockEncoderFast
internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
- Bc1Block output = new Bc1Block();
+ var output = new Bc1Block();
var pixels = rawBlock.AsSpan;
- bool hasAlpha = rawBlock.HasTransparentPixels();
+ var hasAlpha = rawBlock.HasTransparentPixels();
RgbBoundingBox.Create565AlphaCutoff(pixels, out var min, out var max);
- ColorRgb565 c0 = max;
- ColorRgb565 c1 = min;
+ var c0 = max;
+ var c1 = min;
if (hasAlpha && c0.data > c1.data)
{
@@ -370,17 +327,17 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
private static class Bc1AlphaBlockEncoderBalanced
{
- private const int maxTries = 24 * 2;
- private const float errorThreshold = 0.05f;
+ private const int MaxTries = 24 * 2;
+ private const float ErrorThreshold = 0.05f;
internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
- bool hasAlpha = rawBlock.HasTransparentPixels();
+ var hasAlpha = rawBlock.HasTransparentPixels();
- PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa);
+ PcaVectors.Create(pixels, out var mean, out var pa);
PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max);
var c0 = max;
@@ -391,30 +348,35 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
var c = c0;
c0 = c1;
c1 = c;
- }else if (hasAlpha && c1.data < c0.data) {
+ }
+ else if (hasAlpha && c1.data < c0.data)
+ {
var c = c0;
c0 = c1;
c1 = c;
}
- Bc1Block best = TryColors(rawBlock, c0, c1, out float bestError);
-
- for (int i = 0; i < maxTries; i++) {
+ var best = TryColors(rawBlock, c0, c1, out var bestError);
+
+ for (var i = 0; i < MaxTries; i++)
+ {
var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i);
-
+
if (!hasAlpha && newC0.data < newC1.data)
{
var c = newC0;
newC0 = newC1;
newC1 = c;
- }else if (hasAlpha && newC1.data < newC0.data) {
+ }
+ else if (hasAlpha && newC1.data < newC0.data)
+ {
var c = newC0;
newC0 = newC1;
newC1 = c;
}
-
+
var block = TryColors(rawBlock, newC0, newC1, out var error);
-
+
if (error < bestError)
{
best = block;
@@ -423,7 +385,8 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = newC1;
}
- if (bestError < errorThreshold) {
+ if (bestError < ErrorThreshold)
+ {
break;
}
}
@@ -434,16 +397,16 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
private static class Bc1AlphaBlockEncoderSlow
{
- private const int maxTries = 9999;
- private const float errorThreshold = 0.05f;
+ private const int MaxTries = 9999;
+ private const float ErrorThreshold = 0.05f;
internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
- bool hasAlpha = rawBlock.HasTransparentPixels();
+ var hasAlpha = rawBlock.HasTransparentPixels();
- PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa);
+ PcaVectors.Create(pixels, out var mean, out var pa);
PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max);
var c0 = max;
@@ -454,29 +417,34 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
var c = c0;
c0 = c1;
c1 = c;
- }else if (hasAlpha && c1.data < c0.data) {
+ }
+ else if (hasAlpha && c1.data < c0.data)
+ {
var c = c0;
c0 = c1;
c1 = c;
}
- Bc1Block best = TryColors(rawBlock, c0, c1, out float bestError);
+ var best = TryColors(rawBlock, c0, c1, out var bestError);
- int lastChanged = 0;
- for (int i = 0; i < maxTries; i++) {
+ var lastChanged = 0;
+ for (var i = 0; i < MaxTries; i++)
+ {
var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i);
-
+
if (!hasAlpha && newC0.data < newC1.data)
{
var c = newC0;
newC0 = newC1;
newC1 = c;
- }else if (hasAlpha && newC1.data < newC0.data) {
+ }
+ else if (hasAlpha && newC1.data < newC0.data)
+ {
var c = newC0;
newC0 = newC1;
newC1 = c;
}
-
+
var block = TryColors(rawBlock, newC0, newC1, out var error);
lastChanged++;
@@ -490,7 +458,8 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
lastChanged = 0;
}
- if (bestError < errorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) {
+ if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount)
+ {
break;
}
}
diff --git a/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs
index 60d50dc..33b56af 100644
--- a/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs
+++ b/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs
@@ -1,46 +1,20 @@
-using System;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
+using System;
using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
namespace BCnEncoder.Encoder
{
- internal class Bc2BlockEncoder : IBcBlockEncoder
+ internal class Bc2BlockEncoder : BaseBcBlockEncoder
{
-
- public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, EncodingQuality quality, bool parallel)
- {
- byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()];
- Span outputBlocks = MemoryMarshal.Cast(outputData);
-
- if (parallel)
- {
- Parallel.For(0, blocks.Length, i =>
- {
- Span outputBlocks = MemoryMarshal.Cast(outputData);
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- });
- }
- else
- {
- for (int i = 0; i < blocks.Length; i++)
- {
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- }
- }
-
- return outputData;
- }
-
- private Bc2Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality)
+ public override Bc2Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
{
switch (quality)
{
- case EncodingQuality.Fast:
+ case CompressionQuality.Fast:
return Bc2BlockEncoderFast.EncodeBlock(block);
- case EncodingQuality.Balanced:
+ case CompressionQuality.Balanced:
return Bc2BlockEncoderBalanced.EncodeBlock(block);
- case EncodingQuality.BestQuality:
+ case CompressionQuality.BestQuality:
return Bc2BlockEncoderSlow.EncodeBlock(block);
default:
@@ -48,25 +22,26 @@ private Bc2Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality)
}
}
- public GlInternalFormat GetInternalFormat()
+ public override GlInternalFormat GetInternalFormat()
{
- return GlInternalFormat.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ return GlInternalFormat.GlCompressedRgbaS3TcDxt3Ext;
}
- public GLFormat GetBaseInternalFormat()
+ public override GlFormat GetBaseInternalFormat()
{
- return GLFormat.GL_RGBA;
+ return GlFormat.GlRgba;
}
- public DXGI_FORMAT GetDxgiFormat() {
- return DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM;
+ public override DxgiFormat GetDxgiFormat()
+ {
+ return DxgiFormat.DxgiFormatBc2Unorm;
}
#region Encoding private stuff
private static Bc2Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0, ColorRgb565 color1, out float error, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f)
{
- Bc2Block output = new Bc2Block();
+ var output = new Bc2Block();
var pixels = rawBlock.AsSpan;
@@ -77,17 +52,17 @@ private static Bc2Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0
var c1 = color1.ToColorRgb24();
ReadOnlySpan colors = stackalloc ColorRgb24[] {
- c0,
- c1,
- c0 * (2.0 / 3.0) + c1 * (1.0 / 3.0),
- c0 * (1.0 / 3.0) + c1 * (2.0 / 3.0)
- };
+ c0,
+ c1,
+ c0.InterpolateThird(c1, 1),
+ c0.InterpolateThird(c1, 2)
+ };
error = 0;
- for (int i = 0; i < 16; i++)
+ for (var i = 0; i < 16; i++)
{
var color = pixels[i];
- output.SetAlpha(i, color.A);
+ output.SetAlpha(i, color.a);
output[i] = ColorChooser.ChooseClosestColor4(colors, color, rWeight, gWeight, bWeight, out var e);
error += e;
}
@@ -109,9 +84,9 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
PcaVectors.Create(pixels, out var mean, out var principalAxis);
PcaVectors.GetMinMaxColor565(pixels, mean, principalAxis, out var min, out var max);
-
- ColorRgb565 c0 = max;
- ColorRgb565 c1 = min;
+
+ var c0 = max;
+ var c1 = min;
var output = TryColors(rawBlock, c0, c1, out var _);
@@ -119,27 +94,29 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
}
}
- private static class Bc2BlockEncoderBalanced {
- private const int maxTries = 24 * 2;
- private const float errorThreshold = 0.05f;
+ private static class Bc2BlockEncoderBalanced
+ {
+ private const int MaxTries = 24 * 2;
+ private const float ErrorThreshold = 0.05f;
internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
- PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa);
+ PcaVectors.Create(pixels, out var mean, out var pa);
PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max);
var c0 = max;
var c1 = min;
- Bc2Block best = TryColors(rawBlock, c0, c1, out float bestError);
-
- for (int i = 0; i < maxTries; i++) {
+ var best = TryColors(rawBlock, c0, c1, out var bestError);
+
+ for (var i = 0; i < MaxTries; i++)
+ {
var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i);
-
+
var block = TryColors(rawBlock, newC0, newC1, out var error);
-
+
if (error < bestError)
{
best = block;
@@ -148,7 +125,8 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = newC1;
}
- if (bestError < errorThreshold) {
+ if (bestError < ErrorThreshold)
+ {
break;
}
}
@@ -159,15 +137,15 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
private static class Bc2BlockEncoderSlow
{
- private const int maxTries = 9999;
- private const float errorThreshold = 0.01f;
+ private const int MaxTries = 9999;
+ private const float ErrorThreshold = 0.01f;
internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
- PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa);
+ PcaVectors.Create(pixels, out var mean, out var pa);
PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max);
var c0 = max;
@@ -180,20 +158,21 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = c;
}
- Bc2Block best = TryColors(rawBlock, c0, c1, out float bestError);
+ var best = TryColors(rawBlock, c0, c1, out var bestError);
- int lastChanged = 0;
+ var lastChanged = 0;
- for (int i = 0; i < maxTries; i++) {
+ for (var i = 0; i < MaxTries; i++)
+ {
var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i);
-
+
if (newC0.data < newC1.data)
{
var c = newC0;
newC0 = newC1;
newC1 = c;
}
-
+
var block = TryColors(rawBlock, newC0, newC1, out var error);
lastChanged++;
@@ -207,7 +186,8 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
lastChanged = 0;
}
- if (bestError < errorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) {
+ if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount)
+ {
break;
}
}
@@ -215,7 +195,7 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
return best;
}
}
-
+
#endregion
}
}
diff --git a/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs
index de10edd..d12e55c 100644
--- a/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs
+++ b/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs
@@ -1,47 +1,22 @@
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
+using System;
using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
namespace BCnEncoder.Encoder
{
- internal class Bc3BlockEncoder : IBcBlockEncoder
+ internal class Bc3BlockEncoder : BaseBcBlockEncoder
{
+ private static readonly Bc4ComponentBlockEncoder bc4BlockEncoder = new Bc4ComponentBlockEncoder(ColorComponent.A);
- public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, EncodingQuality quality, bool parallel)
- {
- byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()];
- Span outputBlocks = MemoryMarshal.Cast(outputData);
-
- if (parallel)
- {
- Parallel.For(0, blocks.Length, i =>
- {
- Span outputBlocks = MemoryMarshal.Cast(outputData);
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- });
- }
- else
- {
- for (int i = 0; i < blocks.Length; i++)
- {
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- }
- }
-
- return outputData;
- }
-
- private Bc3Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality)
+ public override Bc3Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
{
switch (quality)
{
- case EncodingQuality.Fast:
+ case CompressionQuality.Fast:
return Bc3BlockEncoderFast.EncodeBlock(block);
- case EncodingQuality.Balanced:
+ case CompressionQuality.Balanced:
return Bc3BlockEncoderBalanced.EncodeBlock(block);
- case EncodingQuality.BestQuality:
+ case CompressionQuality.BestQuality:
return Bc3BlockEncoderSlow.EncodeBlock(block);
default:
@@ -49,25 +24,26 @@ private Bc3Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality)
}
}
- public GlInternalFormat GetInternalFormat()
+ public override GlInternalFormat GetInternalFormat()
{
- return GlInternalFormat.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ return GlInternalFormat.GlCompressedRgbaS3TcDxt5Ext;
}
- public GLFormat GetBaseInternalFormat()
+ public override GlFormat GetBaseInternalFormat()
{
- return GLFormat.GL_RGBA;
+ return GlFormat.GlRgba;
}
- public DXGI_FORMAT GetDxgiFormat() {
- return DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM;
+ public override DxgiFormat GetDxgiFormat()
+ {
+ return DxgiFormat.DxgiFormatBc3Unorm;
}
#region Encoding private stuff
private static Bc3Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0, ColorRgb565 color1, out float error, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f)
{
- Bc3Block output = new Bc3Block();
+ var output = new Bc3Block();
var pixels = rawBlock.AsSpan;
@@ -80,12 +56,12 @@ private static Bc3Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0
ReadOnlySpan colors = stackalloc ColorRgb24[] {
c0,
c1,
- c0 * (2.0 / 3.0) + c1 * (1.0 / 3.0),
- c0 * (1.0 / 3.0) + c1 * (2.0 / 3.0)
+ c0.InterpolateThird(c1, 1),
+ c0.InterpolateThird(c1, 2)
};
error = 0;
- for (int i = 0; i < 16; i++)
+ for (var i = 0; i < 16; i++)
{
var color = pixels[i];
output[i] = ColorChooser.ChooseClosestColor4(colors, color, rWeight, gWeight, bWeight, out var e);
@@ -95,172 +71,12 @@ private static Bc3Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0
return output;
}
- private static Bc3Block FindAlphaValues(Bc3Block colorBlock, RawBlock4X4Rgba32 rawBlock, int variations) {
- var pixels = rawBlock.AsSpan;
-
- //Find min and max alpha
- byte minAlpha = 255;
- byte maxAlpha = 0;
- bool hasExtremeValues = false;
- for (int i = 0; i < pixels.Length; i++) {
- if (pixels[i].A < 255 && pixels[i].A > 0) {
- if (pixels[i].A < minAlpha) minAlpha = pixels[i].A;
- if (pixels[i].A > maxAlpha) maxAlpha = pixels[i].A;
- }
- else {
- hasExtremeValues = true;
- }
- }
-
-
- int SelectAlphaIndices(ref Bc3Block block) {
- int cumulativeError = 0;
- var a0 = block.Alpha0;
- var a1 = block.Alpha1;
- Span alphas = a0 > a1 ? stackalloc byte[] {
- a0,
- a1,
- (byte) (6/7.0 * a0 + 1/7.0 * a1),
- (byte) (5/7.0 * a0 + 2/7.0 * a1),
- (byte) (4/7.0 * a0 + 3/7.0 * a1),
- (byte) (3/7.0 * a0 + 4/7.0 * a1),
- (byte) (2/7.0 * a0 + 5/7.0 * a1),
- (byte) (1/7.0 * a0 + 6/7.0 * a1),
- } : stackalloc byte[] {
- a0,
- a1,
- (byte) (4/5.0 * a0 + 1/5.0 * a1),
- (byte) (3/5.0 * a0 + 2/5.0 * a1),
- (byte) (2/5.0 * a0 + 3/5.0 * a1),
- (byte) (1/5.0 * a0 + 4/5.0 * a1),
- 0,
- 255
- };
- var pixels = rawBlock.AsSpan;
- for (int i = 0; i < pixels.Length; i++) {
- byte bestIndex = 0;
- int bestError = Math.Abs(pixels[i].A - alphas[0]);
- for (byte j = 1; j < alphas.Length; j++) {
- int error = Math.Abs(pixels[i].A - alphas[j]);
- if (error < bestError) {
- bestIndex = j;
- bestError = error;
- }
- if (bestError == 0) break;
- }
- block.SetAlphaIndex(i, bestIndex);
- cumulativeError += bestError * bestError;
- }
-
- return cumulativeError;
- }
-
- //everything is either fully opaque or fully transparent
- if (hasExtremeValues && minAlpha == 255 && maxAlpha == 0) {
- colorBlock.Alpha0 = 0;
- colorBlock.Alpha1 = 255;
- var error = SelectAlphaIndices(ref colorBlock);
- Debug.Assert(0 == error);
- return colorBlock;
- }
-
- var best = colorBlock;
- best.Alpha0 = maxAlpha;
- best.Alpha1 = minAlpha;
- int bestError = SelectAlphaIndices(ref best);
- if (bestError == 0) {
- return best;
- }
- for (byte i = 1; i < variations; i++) {
- {
- byte a0 = ByteHelper.ClampToByte(maxAlpha - i * 2);
- byte a1 = ByteHelper.ClampToByte(minAlpha + i * 2);
- var block = colorBlock;
- block.Alpha0 = hasExtremeValues ? a1 : a0;
- block.Alpha1 = hasExtremeValues ? a0 : a1;
- int error = SelectAlphaIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- }
- }
- {
- byte a0 = ByteHelper.ClampToByte(maxAlpha + i * 2);
- byte a1 = ByteHelper.ClampToByte(minAlpha - i * 2);
- var block = colorBlock;
- block.Alpha0 = hasExtremeValues ? a1 : a0;
- block.Alpha1 = hasExtremeValues ? a0 : a1;
- int error = SelectAlphaIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- }
- }
- {
- byte a0 = ByteHelper.ClampToByte(maxAlpha);
- byte a1 = ByteHelper.ClampToByte(minAlpha - i * 2);
- var block = colorBlock;
- block.Alpha0 = hasExtremeValues ? a1 : a0;
- block.Alpha1 = hasExtremeValues ? a0 : a1;
- int error = SelectAlphaIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- }
- }
- {
- byte a0 = ByteHelper.ClampToByte(maxAlpha + i * 2);
- byte a1 = ByteHelper.ClampToByte(minAlpha);
- var block = colorBlock;
- block.Alpha0 = hasExtremeValues ? a1 : a0;
- block.Alpha1 = hasExtremeValues ? a0 : a1;
- int error = SelectAlphaIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- }
- }
- {
- byte a0 = ByteHelper.ClampToByte(maxAlpha);
- byte a1 = ByteHelper.ClampToByte(minAlpha + i * 2);
- var block = colorBlock;
- block.Alpha0 = hasExtremeValues ? a1 : a0;
- block.Alpha1 = hasExtremeValues ? a0 : a1;
- int error = SelectAlphaIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- }
- }
- {
- byte a0 = ByteHelper.ClampToByte(maxAlpha - i * 2);
- byte a1 = ByteHelper.ClampToByte(minAlpha);
- var block = colorBlock;
- block.Alpha0 = hasExtremeValues ? a1 : a0;
- block.Alpha1 = hasExtremeValues ? a0 : a1;
- int error = SelectAlphaIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- }
- }
-
- if (bestError < 10) {
- break;
- }
- }
-
- return best;
- }
-
-
#endregion
#region Encoders
private static class Bc3BlockEncoderFast
{
-
internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
@@ -268,8 +84,8 @@ internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
PcaVectors.Create(pixels, out var mean, out var principalAxis);
PcaVectors.GetMinMaxColor565(pixels, mean, principalAxis, out var min, out var max);
- ColorRgb565 c0 = max;
- ColorRgb565 c1 = min;
+ var c0 = max;
+ var c1 = min;
if (c0.data <= c1.data)
{
@@ -278,34 +94,36 @@ internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = c;
}
- var output = TryColors(rawBlock, c0, c1, out float _);
- output = FindAlphaValues(output, rawBlock, 3);
+ var output = TryColors(rawBlock, c0, c1, out _);
+ output.alphaBlock = bc4BlockEncoder.EncodeBlock(rawBlock, CompressionQuality.Fast);
return output;
}
}
- private static class Bc3BlockEncoderBalanced {
- private const int maxTries = 24 * 2;
- private const float errorThreshold = 0.05f;
+ private static class Bc3BlockEncoderBalanced
+ {
+ private const int MaxTries = 24 * 2;
+ private const float ErrorThreshold = 0.05f;
internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
- PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa);
+ PcaVectors.Create(pixels, out var mean, out var pa);
PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max);
var c0 = max;
var c1 = min;
- Bc3Block best = TryColors(rawBlock, c0, c1, out float bestError);
-
- for (int i = 0; i < maxTries; i++) {
+ var best = TryColors(rawBlock, c0, c1, out var bestError);
+
+ for (var i = 0; i < MaxTries; i++)
+ {
var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i);
-
+
var block = TryColors(rawBlock, newC0, newC1, out var error);
-
+
if (error < bestError)
{
best = block;
@@ -314,26 +132,27 @@ internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = newC1;
}
- if (bestError < errorThreshold) {
+ if (bestError < ErrorThreshold)
+ {
break;
}
}
- best = FindAlphaValues(best, rawBlock, 5);
+ best.alphaBlock = bc4BlockEncoder.EncodeBlock(rawBlock, CompressionQuality.Balanced);
return best;
}
}
private static class Bc3BlockEncoderSlow
{
- private const int maxTries = 9999;
- private const float errorThreshold = 0.01f;
+ private const int MaxTries = 9999;
+ private const float ErrorThreshold = 0.01f;
internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
{
var pixels = rawBlock.AsSpan;
- PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa);
+ PcaVectors.Create(pixels, out var mean, out var pa);
PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max);
var c0 = max;
@@ -346,20 +165,21 @@ internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
c1 = c;
}
- Bc3Block best = TryColors(rawBlock, c0, c1, out float bestError);
+ var best = TryColors(rawBlock, c0, c1, out var bestError);
- int lastChanged = 0;
+ var lastChanged = 0;
- for (int i = 0; i < maxTries; i++) {
+ for (var i = 0; i < MaxTries; i++)
+ {
var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i);
-
+
if (newC0.data < newC1.data)
{
var c = newC0;
newC0 = newC1;
newC1 = c;
}
-
+
var block = TryColors(rawBlock, newC0, newC1, out var error);
lastChanged++;
@@ -373,12 +193,13 @@ internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock)
lastChanged = 0;
}
- if (bestError < errorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount) {
+ if (bestError < ErrorThreshold || lastChanged > ColorVariationGenerator.VarPatternCount)
+ {
break;
}
}
- best = FindAlphaValues(best, rawBlock, 8);
+ best.alphaBlock = bc4BlockEncoder.EncodeBlock(rawBlock, CompressionQuality.BestQuality);
return best;
}
}
diff --git a/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs
index 44d151b..fc52784 100644
--- a/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs
+++ b/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs
@@ -1,125 +1,134 @@
-using System;
+using System;
using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
namespace BCnEncoder.Encoder
{
- internal class Bc4BlockEncoder : IBcBlockEncoder {
+ internal class Bc4BlockEncoder : BaseBcBlockEncoder
+ {
+ private readonly Bc4ComponentBlockEncoder bc4Encoder;
- private readonly bool luminanceAsRed;
- public Bc4BlockEncoder(bool luminanceAsRed) {
- this.luminanceAsRed = luminanceAsRed;
+ public Bc4BlockEncoder(ColorComponent component)
+ {
+ bc4Encoder = new Bc4ComponentBlockEncoder(component);
}
- public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, EncodingQuality quality,
- bool parallel) {
- byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()];
- Span outputBlocks = MemoryMarshal.Cast(outputData);
+ public override Bc4Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
+ {
+ var output = new Bc4Block
+ {
+ componentBlock = bc4Encoder.EncodeBlock(block, quality)
+ };
- if (parallel) {
- Parallel.For(0, blocks.Length, i => {
- Span outputBlocks = MemoryMarshal.Cast(outputData);
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- });
- }
- else {
- for (int i = 0; i < blocks.Length; i++) {
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- }
- }
-
- return outputData;
+ return output;
}
- private Bc4Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality) {
- Bc4Block output = new Bc4Block();
- byte[] colors = new byte[16];
- var pixels = block.AsSpan;
- for (int i = 0; i < 16; i++) {
- if (luminanceAsRed) {
- colors[i] = (byte)(new ColorYCbCr(pixels[i]).y * 255);
- }
- else {
- colors[i] = pixels[i].R;
- }
- }
- switch (quality) {
- case EncodingQuality.Fast:
- return FindRedValues(output, colors, 3);
- case EncodingQuality.Balanced:
- return FindRedValues(output, colors, 4);
- case EncodingQuality.BestQuality:
- return FindRedValues(output, colors, 8);
+ public override GlInternalFormat GetInternalFormat()
+ {
+ return GlInternalFormat.GlCompressedRedRgtc1Ext;
+ }
- default:
- throw new ArgumentOutOfRangeException(nameof(quality), quality, null);
- }
+ public override GlFormat GetBaseInternalFormat()
+ {
+ return GlFormat.GlRed;
}
- public GlInternalFormat GetInternalFormat() {
- return GlInternalFormat.GL_COMPRESSED_RED_RGTC1_EXT;
+ public override DxgiFormat GetDxgiFormat()
+ {
+ return DxgiFormat.DxgiFormatBc4Unorm;
}
+ }
+
+ internal class Bc4ComponentBlockEncoder
+ {
+ private readonly ColorComponent component;
- public GLFormat GetBaseInternalFormat() {
- return GLFormat.GL_RED;
+ public Bc4ComponentBlockEncoder(ColorComponent component)
+ {
+ this.component = component;
}
- public DXGI_FORMAT GetDxgiFormat() {
- return DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM;
+ public Bc4ComponentBlock EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
+ {
+ var output = new Bc4ComponentBlock();
+
+ var pixels = block.AsSpan;
+ var colors = new byte[pixels.Length];
+
+ for (var i = 0; i < pixels.Length; i++)
+ colors[i] = ComponentHelper.ColorToComponent(pixels[i], component);
+
+ switch (quality)
+ {
+ case CompressionQuality.Fast:
+ return FindComponentValues(output, colors, 3);
+ case CompressionQuality.Balanced:
+ return FindComponentValues(output, colors, 4);
+ case CompressionQuality.BestQuality:
+ return FindComponentValues(output, colors, 8);
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(quality), quality, null);
+ }
}
#region Encoding private stuff
- private static Bc4Block FindRedValues(Bc4Block colorBlock, byte[] pixels, int variations) {
+ private static Bc4ComponentBlock FindComponentValues(Bc4ComponentBlock colorBlock, byte[] pixels, int variations)
+ {
//Find min and max alpha
byte min = 255;
byte max = 0;
- bool hasExtremeValues = false;
- for (int i = 0; i < pixels.Length; i++) {
- if (pixels[i] < 255 && pixels[i] > 0) {
+ var hasExtremeValues = false;
+ for (var i = 0; i < pixels.Length; i++)
+ {
+ if (pixels[i] < 255 && pixels[i] > 0)
+ {
if (pixels[i] < min) min = pixels[i];
if (pixels[i] > max) max = pixels[i];
}
- else {
+ else
+ {
hasExtremeValues = true;
}
}
- int SelectIndices(ref Bc4Block block) {
- int cumulativeError = 0;
- var c0 = block.Red0;
- var c1 = block.Red1;
- Span colors = c0 > c1
- ? stackalloc byte[] {
- c0,
- c1,
- (byte) (6 / 7.0 * c0 + 1 / 7.0 * c1),
- (byte) (5 / 7.0 * c0 + 2 / 7.0 * c1),
- (byte) (4 / 7.0 * c0 + 3 / 7.0 * c1),
- (byte) (3 / 7.0 * c0 + 4 / 7.0 * c1),
- (byte) (2 / 7.0 * c0 + 5 / 7.0 * c1),
- (byte) (1 / 7.0 * c0 + 6 / 7.0 * c1),
- }
- : stackalloc byte[] {
- c0,
- c1,
- (byte) (4 / 5.0 * c0 + 1 / 5.0 * c1),
- (byte) (3 / 5.0 * c0 + 2 / 5.0 * c1),
- (byte) (2 / 5.0 * c0 + 3 / 5.0 * c1),
- (byte) (1 / 5.0 * c0 + 4 / 5.0 * c1),
- 0,
- 255
- };
- for (int i = 0; i < pixels.Length; i++) {
+ int SelectIndices(ref Bc4ComponentBlock block)
+ {
+ var cumulativeError = 0;
+ var c0 = block.Endpoint0;
+ var c1 = block.Endpoint1;
+ var colors = c0 > c1 ? stackalloc byte[] {
+ c0,
+ c1,
+ c0.InterpolateSeventh(c1, 1),
+ c0.InterpolateSeventh(c1, 2),
+ c0.InterpolateSeventh(c1, 3),
+ c0.InterpolateSeventh(c1, 4),
+ c0.InterpolateSeventh(c1, 5),
+ c0.InterpolateSeventh(c1, 6)
+ } : stackalloc byte[] {
+ c0,
+ c1,
+ c0.InterpolateFifth(c1, 1),
+ c0.InterpolateFifth(c1, 2),
+ c0.InterpolateFifth(c1, 3),
+ c0.InterpolateFifth(c1, 4),
+ 0,
+ 255
+ };
+ for (var i = 0; i < pixels.Length; i++)
+ {
byte bestIndex = 0;
- int bestError = Math.Abs(pixels[i] - colors[0]);
- for (byte j = 1; j < colors.Length; j++) {
- int error = Math.Abs(pixels[i] - colors[j]);
- if (error < bestError) {
+ var bestError = Math.Abs(pixels[i] - colors[0]);
+ for (byte j = 1; j < colors.Length; j++)
+ {
+ var error = Math.Abs(pixels[i] - colors[j]);
+ if (error < bestError)
+ {
bestIndex = j;
bestError = error;
}
@@ -127,7 +136,7 @@ int SelectIndices(ref Bc4Block block) {
if (bestError == 0) break;
}
- block.SetRedIndex(i, bestIndex);
+ block.SetComponentIndex(i, bestIndex);
cumulativeError += bestError * bestError;
}
@@ -135,31 +144,35 @@ int SelectIndices(ref Bc4Block block) {
}
//everything is either fully black or fully red
- if (hasExtremeValues && min == 255 && max == 0) {
- colorBlock.Red0 = 0;
- colorBlock.Red1 = 255;
+ if (hasExtremeValues && min == 255 && max == 0)
+ {
+ colorBlock.Endpoint0 = 0;
+ colorBlock.Endpoint1 = 255;
var error = SelectIndices(ref colorBlock);
Debug.Assert(0 == error);
return colorBlock;
}
var best = colorBlock;
- best.Red0 = max;
- best.Red1 = min;
- int bestError = SelectIndices(ref best);
- if (bestError == 0) {
+ best.Endpoint0 = max;
+ best.Endpoint1 = min;
+ var bestError = SelectIndices(ref best);
+ if (bestError == 0)
+ {
return best;
}
- for (byte i = (byte)variations; i > 0; i--) {
+ for (var i = (byte)variations; i > 0; i--)
+ {
{
- byte c0 = ByteHelper.ClampToByte(max - i);
- byte c1 = ByteHelper.ClampToByte(min + i);
+ var c0 = ByteHelper.ClampToByte(max - i);
+ var c1 = ByteHelper.ClampToByte(min + i);
var block = colorBlock;
- block.Red0 = hasExtremeValues ? c1 : c0;
- block.Red1 = hasExtremeValues ? c0 : c1;
- int error = SelectIndices(ref block);
- if (error < bestError) {
+ block.Endpoint0 = hasExtremeValues ? c1 : c0;
+ block.Endpoint1 = hasExtremeValues ? c0 : c1;
+ var error = SelectIndices(ref block);
+ if (error < bestError)
+ {
best = block;
bestError = error;
max = c0;
@@ -167,13 +180,14 @@ int SelectIndices(ref Bc4Block block) {
}
}
{
- byte c0 = ByteHelper.ClampToByte(max + i);
- byte c1 = ByteHelper.ClampToByte(min - i);
+ var c0 = ByteHelper.ClampToByte(max + i);
+ var c1 = ByteHelper.ClampToByte(min - i);
var block = colorBlock;
- block.Red0 = hasExtremeValues ? c1 : c0;
- block.Red1 = hasExtremeValues ? c0 : c1;
- int error = SelectIndices(ref block);
- if (error < bestError) {
+ block.Endpoint0 = hasExtremeValues ? c1 : c0;
+ block.Endpoint1 = hasExtremeValues ? c0 : c1;
+ var error = SelectIndices(ref block);
+ if (error < bestError)
+ {
best = block;
bestError = error;
max = c0;
@@ -181,13 +195,14 @@ int SelectIndices(ref Bc4Block block) {
}
}
{
- byte c0 = ByteHelper.ClampToByte(max);
- byte c1 = ByteHelper.ClampToByte(min - i);
+ var c0 = ByteHelper.ClampToByte(max);
+ var c1 = ByteHelper.ClampToByte(min - i);
var block = colorBlock;
- block.Red0 = hasExtremeValues ? c1 : c0;
- block.Red1 = hasExtremeValues ? c0 : c1;
- int error = SelectIndices(ref block);
- if (error < bestError) {
+ block.Endpoint0 = hasExtremeValues ? c1 : c0;
+ block.Endpoint1 = hasExtremeValues ? c0 : c1;
+ var error = SelectIndices(ref block);
+ if (error < bestError)
+ {
best = block;
bestError = error;
max = c0;
@@ -195,13 +210,14 @@ int SelectIndices(ref Bc4Block block) {
}
}
{
- byte c0 = ByteHelper.ClampToByte(max + i);
- byte c1 = ByteHelper.ClampToByte(min);
+ var c0 = ByteHelper.ClampToByte(max + i);
+ var c1 = ByteHelper.ClampToByte(min);
var block = colorBlock;
- block.Red0 = hasExtremeValues ? c1 : c0;
- block.Red1 = hasExtremeValues ? c0 : c1;
- int error = SelectIndices(ref block);
- if (error < bestError) {
+ block.Endpoint0 = hasExtremeValues ? c1 : c0;
+ block.Endpoint1 = hasExtremeValues ? c0 : c1;
+ var error = SelectIndices(ref block);
+ if (error < bestError)
+ {
best = block;
bestError = error;
max = c0;
@@ -209,13 +225,14 @@ int SelectIndices(ref Bc4Block block) {
}
}
{
- byte c0 = ByteHelper.ClampToByte(max);
- byte c1 = ByteHelper.ClampToByte(min + i);
+ var c0 = ByteHelper.ClampToByte(max);
+ var c1 = ByteHelper.ClampToByte(min + i);
var block = colorBlock;
- block.Red0 = hasExtremeValues ? c1 : c0;
- block.Red1 = hasExtremeValues ? c0 : c1;
- int error = SelectIndices(ref block);
- if (error < bestError) {
+ block.Endpoint0 = hasExtremeValues ? c1 : c0;
+ block.Endpoint1 = hasExtremeValues ? c0 : c1;
+ var error = SelectIndices(ref block);
+ if (error < bestError)
+ {
best = block;
bestError = error;
max = c0;
@@ -223,13 +240,14 @@ int SelectIndices(ref Bc4Block block) {
}
}
{
- byte c0 = ByteHelper.ClampToByte(max - i);
- byte c1 = ByteHelper.ClampToByte(min);
+ var c0 = ByteHelper.ClampToByte(max - i);
+ var c1 = ByteHelper.ClampToByte(min);
var block = colorBlock;
- block.Red0 = hasExtremeValues ? c1 : c0;
- block.Red1 = hasExtremeValues ? c0 : c1;
- int error = SelectIndices(ref block);
- if (error < bestError) {
+ block.Endpoint0 = hasExtremeValues ? c1 : c0;
+ block.Endpoint1 = hasExtremeValues ? c0 : c1;
+ var error = SelectIndices(ref block);
+ if (error < bestError)
+ {
best = block;
bestError = error;
max = c0;
@@ -237,7 +255,8 @@ int SelectIndices(ref Bc4Block block) {
}
}
- if (bestError < 5) {
+ if (bestError < 5)
+ {
break;
}
}
@@ -245,7 +264,6 @@ int SelectIndices(ref Bc4Block block) {
return best;
}
-
#endregion
}
}
diff --git a/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs
index f091e5e..6f335b4 100644
--- a/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs
+++ b/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs
@@ -1,317 +1,41 @@
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
namespace BCnEncoder.Encoder
{
- internal class Bc5BlockEncoder : IBcBlockEncoder {
+ internal class Bc5BlockEncoder : BaseBcBlockEncoder
+ {
+ private readonly Bc4ComponentBlockEncoder redBlockEncoder;
+ private readonly Bc4ComponentBlockEncoder greenBlockEncoder;
- public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, EncodingQuality quality, bool parallel)
+ public Bc5BlockEncoder(ColorComponent component1, ColorComponent component2)
{
- byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()];
- Span outputBlocks = MemoryMarshal.Cast(outputData);
-
- if (parallel) {
- Parallel.For(0, blocks.Length, i => {
- Span outputBlocks = MemoryMarshal.Cast(outputData);
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- });
- }
- else {
- for (int i = 0; i < blocks.Length; i++) {
- outputBlocks[i] = EncodeBlock(blocks[i], quality);
- }
- }
-
- return outputData;
- }
-
- private Bc5Block EncodeBlock(RawBlock4X4Rgba32 block, EncodingQuality quality) {
- Bc5Block output = new Bc5Block();
- byte[] reds = new byte[16];
- byte[] greens = new byte[16];
- var pixels = block.AsSpan;
- for (int i = 0; i < 16; i++) {
- reds[i] = pixels[i].R;
- greens[i] = pixels[i].G;
- }
-
- int variations = 0;
- int errorThreshold = 0;
- switch (quality) {
- case EncodingQuality.Fast:
- variations = 3;
- errorThreshold = 5;
- break;
- case EncodingQuality.Balanced:
- variations = 5;
- errorThreshold = 1;
- break;
- case EncodingQuality.BestQuality:
- variations = 8;
- errorThreshold = 0;
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(quality), quality, null);
- }
-
-
- output = FindValues(output, reds, variations, errorThreshold,
- (block, i, idx) => {
- block.SetRedIndex(i, idx);
- return block;
- },
- (block, col) => {
- block.Red0 = col;
- return block;
- },
- (block, col) => {
- block.Red1 = col;
- return block;
- },
- (block) => {
- return block.Red0;
- },
- (block) => {
- return block.Red1;
- }
- );
- output = FindValues(output, greens, variations, errorThreshold,
- (block, i, idx) => {
- block.SetGreenIndex(i, idx);
- return block;
- },
- (block, col) => {
- block.Green0 = col;
- return block;
- },
- (block, col) => {
- block.Green1 = col;
- return block;
- },
- (block) => {
- return block.Green0;
- },
- (block) => {
- return block.Green1;
- });
- return output;
+ redBlockEncoder = new Bc4ComponentBlockEncoder(component1);
+ greenBlockEncoder = new Bc4ComponentBlockEncoder(component2);
}
- public GlInternalFormat GetInternalFormat() {
- return GlInternalFormat.GL_COMPRESSED_RED_GREEN_RGTC2_EXT;
+ public override Bc5Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality)
+ {
+ return new Bc5Block
+ {
+ redBlock = redBlockEncoder.EncodeBlock(block, quality),
+ greenBlock = greenBlockEncoder.EncodeBlock(block, quality)
+ };
}
- public GLFormat GetBaseInternalFormat() {
- return GLFormat.GL_RG;
+ public override GlInternalFormat GetInternalFormat()
+ {
+ return GlInternalFormat.GlCompressedRedGreenRgtc2Ext;
}
- public DXGI_FORMAT GetDxgiFormat() {
- return DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM;
+ public override GlFormat GetBaseInternalFormat()
+ {
+ return GlFormat.GlRg;
}
- #region Encoding private stuff
-
- private static Bc5Block FindValues(Bc5Block colorBlock, byte[] pixels, int variations, int errorThreshold,
- Func indexSetter,
- Func col0Setter,
- Func col1Setter,
- Func col0Getter,
- Func col1Getter) {
-
- //Find min and max alpha
- byte min = 255;
- byte max = 0;
- bool hasExtremeValues = false;
- for (int i = 0; i < pixels.Length; i++) {
- if (pixels[i] < 255 && pixels[i] > 0) {
- if (pixels[i] < min) min = pixels[i];
- if (pixels[i] > max) max = pixels[i];
- }
- else {
- hasExtremeValues = true;
- }
- }
-
-
- int SelectIndices(ref Bc5Block block) {
- int cumulativeError = 0;
- //var c0 = block.Red0;
- //var c1 = block.Red1;
- var c0 = col0Getter(block);
- var c1 = col1Getter(block);
- Span colors = c0 > c1
- ? stackalloc byte[] {
- c0,
- c1,
- (byte) (6 / 7.0 * c0 + 1 / 7.0 * c1),
- (byte) (5 / 7.0 * c0 + 2 / 7.0 * c1),
- (byte) (4 / 7.0 * c0 + 3 / 7.0 * c1),
- (byte) (3 / 7.0 * c0 + 4 / 7.0 * c1),
- (byte) (2 / 7.0 * c0 + 5 / 7.0 * c1),
- (byte) (1 / 7.0 * c0 + 6 / 7.0 * c1),
- }
- : stackalloc byte[] {
- c0,
- c1,
- (byte) (4 / 5.0 * c0 + 1 / 5.0 * c1),
- (byte) (3 / 5.0 * c0 + 2 / 5.0 * c1),
- (byte) (2 / 5.0 * c0 + 3 / 5.0 * c1),
- (byte) (1 / 5.0 * c0 + 4 / 5.0 * c1),
- 0,
- 255
- };
- for (int i = 0; i < pixels.Length; i++) {
- byte bestIndex = 0;
- int bestError = Math.Abs(pixels[i] - colors[0]);
- for (byte j = 1; j < colors.Length; j++) {
- int error = Math.Abs(pixels[i] - colors[j]);
- if (error < bestError) {
- bestIndex = j;
- bestError = error;
- }
-
- if (bestError == 0) break;
- }
-
- block = indexSetter(block, i, bestIndex);
- //block.SetRedIndex(i, bestIndex);
- cumulativeError += bestError * bestError;
- }
-
- return cumulativeError;
- }
-
- //everything is either fully black or fully red
- if (hasExtremeValues && min == 255 && max == 0) {
- //colorBlock.Red0 = 0;
- //colorBlock.Red1 = 255;
- colorBlock = col0Setter(colorBlock, 0);
- colorBlock = col1Setter(colorBlock, 255);
- var error = SelectIndices(ref colorBlock);
- Debug.Assert(0 == error);
- return colorBlock;
- }
-
- var best = colorBlock;
- //best.Red0 = max;
- //best.Red1 = min;
- best = col0Setter(best, max);
- best = col1Setter(best, min);
- int bestError = SelectIndices(ref best);
- if (bestError == 0) {
- return best;
- }
-
- for (byte i = (byte)variations; i > 0; i--) {
- {
- byte c0 = ByteHelper.ClampToByte(max - i);
- byte c1 = ByteHelper.ClampToByte(min + i);
- var block = colorBlock;
- //block.Red0 = hasExtremeValues ? c1 : c0;
- //block.Red1 = hasExtremeValues ? c0 : c1;
- block = col0Setter(block, hasExtremeValues ? c1 : c0);
- block = col1Setter(block, hasExtremeValues ? c0 : c1);
- int error = SelectIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- max = c0;
- min = c1;
- }
- }
- {
- byte c0 = ByteHelper.ClampToByte(max + i);
- byte c1 = ByteHelper.ClampToByte(min - i);
- var block = colorBlock;
- //block.Red0 = hasExtremeValues ? c1 : c0;
- //block.Red1 = hasExtremeValues ? c0 : c1;
- block = col0Setter(block, hasExtremeValues ? c1 : c0);
- block = col1Setter(block, hasExtremeValues ? c0 : c1);
- int error = SelectIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- max = c0;
- min = c1;
- }
- }
- {
- byte c0 = ByteHelper.ClampToByte(max);
- byte c1 = ByteHelper.ClampToByte(min - i);
- var block = colorBlock;
- //block.Red0 = hasExtremeValues ? c1 : c0;
- //block.Red1 = hasExtremeValues ? c0 : c1;
- block = col0Setter(block, hasExtremeValues ? c1 : c0);
- block = col1Setter(block, hasExtremeValues ? c0 : c1);
- int error = SelectIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- max = c0;
- min = c1;
- }
- }
- {
- byte c0 = ByteHelper.ClampToByte(max + i);
- byte c1 = ByteHelper.ClampToByte(min);
- var block = colorBlock;
- //block.Red0 = hasExtremeValues ? c1 : c0;
- //block.Red1 = hasExtremeValues ? c0 : c1;
- block = col0Setter(block, hasExtremeValues ? c1 : c0);
- block = col1Setter(block, hasExtremeValues ? c0 : c1);
- int error = SelectIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- max = c0;
- min = c1;
- }
- }
- {
- byte c0 = ByteHelper.ClampToByte(max);
- byte c1 = ByteHelper.ClampToByte(min + i);
- var block = colorBlock;
- //block.Red0 = hasExtremeValues ? c1 : c0;
- //block.Red1 = hasExtremeValues ? c0 : c1;
- block = col0Setter(block, hasExtremeValues ? c1 : c0);
- block = col1Setter(block, hasExtremeValues ? c0 : c1);
- int error = SelectIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- max = c0;
- min = c1;
- }
- }
- {
- byte c0 = ByteHelper.ClampToByte(max - i);
- byte c1 = ByteHelper.ClampToByte(min);
- var block = colorBlock;
- //block.Red0 = hasExtremeValues ? c1 : c0;
- //block.Red1 = hasExtremeValues ? c0 : c1;
- block = col0Setter(block, hasExtremeValues ? c1 : c0);
- block = col1Setter(block, hasExtremeValues ? c0 : c1);
- int error = SelectIndices(ref block);
- if (error < bestError) {
- best = block;
- bestError = error;
- max = c0;
- min = c1;
- }
- }
-
- if (bestError <= errorThreshold) {
- break;
- }
- }
-
- return best;
+ public override DxgiFormat GetDxgiFormat()
+ {
+ return DxgiFormat.DxgiFormatBc5Unorm;
}
-
-
- #endregion
}
}
diff --git a/BCnEnc.Net/Encoder/BcEncoder.cs b/BCnEnc.Net/Encoder/BcEncoder.cs
index 9b55e7f..bcd6ba2 100644
--- a/BCnEnc.Net/Encoder/BcEncoder.cs
+++ b/BCnEnc.Net/Encoder/BcEncoder.cs
@@ -1,476 +1,1844 @@
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using BCnEncoder.Encoder.Bc7;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using BCnEncoder.Encoder.Bptc;
+using BCnEncoder.Encoder.Options;
using BCnEncoder.Shared;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
+using BCnEncoder.Shared.ImageFiles;
+using CommunityToolkit.HighPerformance;
namespace BCnEncoder.Encoder
{
- public class EncoderInputOptions
+ ///
+ /// The pixel format determining the rgba layout of input data in .
+ ///
+ public enum PixelFormat
{
///
- /// If true, when encoding to a format that only includes a red channel,
- /// use the pixel luminance instead of just the red channel. Default is false.
+ /// 8 bits per channel RGBA.
+ ///
+ Rgba32,
+
+ ///
+ /// 8 bits per channel BGRA.
+ ///
+ Bgra32,
+
+ ///
+ /// 8 bits per channel ARGB.
///
- public bool luminanceAsRed = false;
+ Argb32,
+
+ ///
+ /// 8 bits per channel RGB.
+ ///
+ Rgb24,
+
+ ///
+ /// 8 bits per channel BGR.
+ ///
+ Bgr24
}
- public class EncoderOutputOptions
- {
- ///
- /// Whether to generate mipMaps. Default is true.
- ///
- public bool generateMipMaps = true;
- ///
- /// The maximum number of mipmap levels to generate. -1 or 0 is unbounded.
- /// Default is -1.
- ///
- public int maxMipMapLevel = -1;
- ///
- /// The compression format to use. Default is BC1.
- ///
- public CompressionFormat format = CompressionFormat.BC1;
- ///
- /// The quality of the encoding. Use either fast or balanced for testing.
- /// Fast can be used for near real-time encoding for most algorithms.
- /// Use bestQuality when needed. Default is balanced.
- ///
- public EncodingQuality quality = EncodingQuality.Balanced;
- ///
- /// The output file format of the data. Either Ktx or Dds.
- /// Default is Ktx.
- ///
- public OutputFileFormat fileFormat = OutputFileFormat.Ktx;
- ///
- /// The DDS file format doesn't seem to have a standard for indicating whether a BC1 texture
- /// includes 1bit of alpha. This option will write DDPF_ALPHAPIXELS flag to the header
- /// to indicate the presence of an alpha channel. Some programs read and write this flag,
- /// but some programs don't like it and get confused. Your mileage may vary.
- /// Default is false.
- ///
- public bool ddsBc1WriteAlphaFlag = false;
- }
+ ///
+ /// Handles all encoding of images into compressed or uncompressed formats. For decoding,
+ ///
+ public class BcEncoder
+ {
+ ///
+ /// The input options of the encoder.
+ ///
+ public EncoderInputOptions InputOptions { get; } = new EncoderInputOptions();
+
+ ///
+ /// The output options of the encoder.
+ ///
+ public EncoderOutputOptions OutputOptions { get; } = new EncoderOutputOptions();
+
+ ///
+ /// The encoder options.
+ ///
+ public EncoderOptions Options { get; } = new EncoderOptions();
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The block compression Format to encode an image with.
+ public BcEncoder(CompressionFormat format = CompressionFormat.Bc1)
+ {
+ OutputOptions.Format = format;
+ }
+
+ #region LDR
+ #region Async Api
+
+ ///
+ /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream asynchronously.
+ ///
+ /// The input to encode represented by a .
+ /// The width of the image.
+ /// The height of the image.
+ /// The pixel format the given data is in.
+ /// The stream to write the encoded image to.
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ public Task EncodeToStreamAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, Stream outputStream, CancellationToken token = default)
+ {
+ return Task.Run(() =>
+ {
+ EncodeToStreamInternal(ByteToColorMemory(input.Span, width, height, format), outputStream, token);
+ }, token);
+ }
+
+ ///
+ /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream asynchronously.
+ ///
+ /// The input to encode represented by a .
+ /// The stream to write the encoded image to.
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ public Task EncodeToStreamAsync(ReadOnlyMemory2D input, Stream outputStream, CancellationToken token = default)
+ {
+ return Task.Run(() =>
+ {
+ EncodeToStreamInternal(input, outputStream, default);
+ }, token);
+ }
+
+ ///
+ /// Encodes all mipmap levels into a Ktx file asynchronously.
+ ///
+ /// The input to encode represented by a .
+ /// The width of the image.
+ /// The height of the image.
+ /// The pixel format the given data is in.
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// The Ktx file containing the encoded image.
+ public Task EncodeToKtxAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeToKtxInternal(ByteToColorMemory(input.Span, width, height, format), token), token);
+ }
+
+ ///
+ /// Encodes all mipmap levels into a Ktx file asynchronously.
+ ///
+ /// The input to encode represented by a .
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// The Ktx file containing the encoded image.
+ public Task EncodeToKtxAsync(ReadOnlyMemory2D input, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeToKtxInternal(input, token), token);
+ }
+
+ ///
+ /// Encodes all mipmap levels into a Dds file asynchronously.
+ ///
+ /// The input to encode represented by a .
+ /// The width of the image.
+ /// The height of the image.
+ /// The pixel format the given data is in.
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// The Dds file containing the encoded image.
+ public Task EncodeToDdsAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeToDdsInternal(ByteToColorMemory(input.Span, width, height, format), token), token);
+ }
+
+ ///
+ /// Encodes all mipmap levels into a Dds file asynchronously.
+ ///
+ /// The input to encode represented by a .
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// The Dds file containing the encoded image.
+ public Task EncodeToDdsAsync(ReadOnlyMemory2D input, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeToDdsInternal(input, token), token);
+ }
+
+ ///
+ /// Encodes all mipmap levels into a list of byte buffers asynchronously. This data does not contain any file headers, just the raw encoded pixel data.
+ ///
+ /// The input to encode represented by a .
+ /// The width of the image.
+ /// The height of the image.
+ /// The pixel format the given data is in.
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// A list of raw encoded mipmap input.
+ public Task EncodeToRawBytesAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeToRawInternal(ByteToColorMemory(input.Span, width, height, format), token), token);
+ }
+
+ ///
+ /// Encodes all mipmap levels into an array of byte buffers asynchronously. This data does not contain any file headers, just the raw encoded pixel data.
+ ///
+ /// The input to encode represented by a .
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// A list of raw encoded mipmap input.
+ /// To get the width and height of the encoded mip levels, see .
+ public Task EncodeToRawBytesAsync(ReadOnlyMemory2D input, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeToRawInternal(input, token), token);
+ }
+
+ ///
+ /// Encodes a single mip level of the input image to a byte buffer asynchronously. This data does not contain any file headers, just the raw encoded pixel data.
+ ///
+ /// The input to encode represented by a .
+ /// The width of the image.
+ /// The height of the image.
+ /// The pixel format the given data is in.
+ /// The mipmap to encode.
+ /// The cancellation token for this operation. Can be default, if the operation is not asynchronous.
+ /// The raw encoded input.
+ /// To get the width and height of the encoded mip level, see .
+ public Task EncodeToRawBytesAsync(ReadOnlyMemory input, int width, int height, PixelFormat format, int mipLevel, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeToRawInternal(ByteToColorMemory(input.Span, width, height, format), mipLevel, out _, out _, token), token);
+ }
+
+ ///
+ /// Encodes a single mip level of the input image to a byte buffer asynchronously. This data does not contain any file headers, just the raw encoded pixel data.
+ ///
+ /// The input to encode represented by a .
+ /// The mipmap to encode.
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// The raw encoded input.
+ /// To get the width and height of the encoded mip level, see .
+ public Task EncodeToRawBytesAsync(ReadOnlyMemory2D input, int mipLevel, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeToRawInternal(input, mipLevel, out _, out _, token), token);
+ }
+
+ ///
+ /// Encodes all mipMaps of a cubeMap image to a stream asynchronously either in ktx or dds format.
+ /// The format can be set in .
+ /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z.
+ ///
+ /// The positive X-axis face of the cubeMap
+ /// The negative X-axis face of the cubeMap
+ /// The positive Y-axis face of the cubeMap
+ /// The negative Y-axis face of the cubeMap
+ /// The positive Z-axis face of the cubeMap
+ /// The negative Z-axis face of the cubeMap
+ /// The stream to write the encoded image to.
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// A .
+ public Task EncodeCubeMapToStreamAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left,
+ ReadOnlyMemory2D top, ReadOnlyMemory2D down,
+ ReadOnlyMemory2D back, ReadOnlyMemory2D front, Stream outputStream, CancellationToken token = default)
+ {
+ return Task.Run(() => EncodeCubeMapToStreamInternal(right, left, top, down, back, front, outputStream, token), token);
+ }
+
+ ///
+ /// Encodes all mipMaps of a cubeMap image to a asynchronously.
+ /// The format can be set in .
+ /// Order of faces is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z.
+ ///
+ /// The positive X-axis face of the cubeMap
+ /// The negative X-axis face of the cubeMap
+ /// The positive Y-axis face of the cubeMap
+ /// The negative Y-axis face of the cubeMap
+ /// The positive Z-axis face of the cubeMap
+ /// The negative Z-axis face of the cubeMap
+ /// The cancellation token for this operation. Can be default if cancellation is not needed.
+ /// A of type .
+ public Task EncodeCubeMapToKtxAsync(ReadOnlyMemory2D right, ReadOnlyMemory2D left,
+ ReadOnlyMemory2D