From cf9978cc310627b3a23e7365aa6a94d1ea508586 Mon Sep 17 00:00:00 2001 From: DummkopfOfHachtenduden Date: Tue, 6 Dec 2022 10:17:44 +0100 Subject: [PATCH 1/3] Added support for BGR, BGR565, BGRA4444 and BGRA5551 --- BCnEnc.Net/Decoder/BcDecoder.cs | 38 +++++ BCnEnc.Net/Decoder/RawDecoder.cs | 125 ++++++++++++++- BCnEnc.Net/Shared/Colors.cs | 200 ++++++++++++++++++++++++ BCnEnc.Net/Shared/CompressionFormat.cs | 142 ++++++++++------- BCnEnc.Net/Shared/ImageFiles/DdsFile.cs | 37 +++++ 5 files changed, 483 insertions(+), 59 deletions(-) diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index 6c37ad9..94ff729 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -1557,6 +1557,10 @@ private bool IsSupportedRawFormat(CompressionFormat format) case CompressionFormat.Rgb: case CompressionFormat.Rgba: case CompressionFormat.Bgra: + case CompressionFormat.Bgr: + case CompressionFormat.B5G6R5: + case CompressionFormat.B5G5R5A1: + case CompressionFormat.B4G4R4A4: return true; default: @@ -1670,6 +1674,18 @@ private IRawDecoder GetRawDecoder(CompressionFormat format) case CompressionFormat.Rgba: return new RawRgbaDecoder(); + case CompressionFormat.B4G4R4A4: + return new RawB4G4R4A4Decoder(); + + case CompressionFormat.B5G5R5A1: + return new RawB5G5R5A1Decoder(); + + case CompressionFormat.B5G6R5: + return new RawB5G6R5Decoder(); + + case CompressionFormat.Bgr: + return new RawBgrDecoder(); + case CompressionFormat.Bgra: return new RawBgraDecoder(); @@ -1736,6 +1752,12 @@ public int GetBlockSize(CompressionFormat format) case CompressionFormat.Rgba: return 4; + case CompressionFormat.B4G4R4A4: + case CompressionFormat.B5G5R5A1: + case CompressionFormat.B5G6R5: + return 2; + + case CompressionFormat.Bgr: case CompressionFormat.Bgra: return 4; @@ -1863,6 +1885,18 @@ private CompressionFormat GetCompressionFormat(DdsFile file) case DxgiFormat.DxgiFormatR8G8B8A8Unorm: return CompressionFormat.Rgba; + case DxgiFormat.DxgiFormatB4G4R4A4Unorm: + return CompressionFormat.B4G4R4A4; + + case DxgiFormat.DxgiFormatB5G5R5A1Unorm: + return CompressionFormat.B5G5R5A1; + + case DxgiFormat.DxgiFormatB5G6R5Unorm: + return CompressionFormat.B5G6R5; + + case DxgiFormat.DxgiFormatB8G8R8X8Unorm: + return CompressionFormat.Bgr; + case DxgiFormat.DxgiFormatB8G8R8A8Unorm: return CompressionFormat.Bgra; @@ -1931,6 +1965,9 @@ private int GetBufferSize(CompressionFormat format, int pixelWidth, int pixelHei return pixelWidth * pixelHeight; case CompressionFormat.Rg: + case CompressionFormat.B5G6R5: + case CompressionFormat.B5G5R5A1: + case CompressionFormat.B4G4R4A4: return 2 * pixelWidth * pixelHeight; case CompressionFormat.Rgb: @@ -1938,6 +1975,7 @@ private int GetBufferSize(CompressionFormat format, int pixelWidth, int pixelHei case CompressionFormat.Rgba: case CompressionFormat.Bgra: + case CompressionFormat.Bgr: return 4 * pixelWidth * pixelHeight; case CompressionFormat.Bc1: diff --git a/BCnEnc.Net/Decoder/RawDecoder.cs b/BCnEnc.Net/Decoder/RawDecoder.cs index 880485d..95eaf68 100644 --- a/BCnEnc.Net/Decoder/RawDecoder.cs +++ b/BCnEnc.Net/Decoder/RawDecoder.cs @@ -1,6 +1,8 @@ -using System; using BCnEncoder.Shared; +using System; +using System.Runtime.InteropServices; + namespace BCnEncoder.Decoder { internal interface IRawDecoder @@ -188,4 +190,125 @@ public ColorRgba32[] Decode(ReadOnlyMemory data, OperationContext context) return output; } } + + /// + /// A class to decode data to BGRX components. + /// + public class RawBgrDecoder : 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 / 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 = 255; + } + + return output; + } + } + + /// + /// A class to decode data to B4G4R4A4 components. + /// + public class RawB4G4R4A4Decoder : 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 = MemoryMarshal.Cast(data.Span); + for (var i = 0; i < output.Length; i++) + { + context.CancellationToken.ThrowIfCancellationRequested(); + + var color4444 = new ColorRgb4444(span[i]); + output[i] = new ColorRgba32(color4444.R, color4444.G, color4444.B, color4444.A); + } + return output; + } + } + + /// + /// A class to decode data to B5G5R5A1 components. + /// + public class RawB5G5R5A1Decoder : 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 = MemoryMarshal.Cast(data.Span); + for (var i = 0; i < output.Length; i++) + { + context.CancellationToken.ThrowIfCancellationRequested(); + + var color5551 = new ColorRgb555(span[i]); + output[i] = new ColorRgba32(color5551.R, color5551.G, color5551.B, color5551.Mode == 0 ? (byte)0 : (byte)255); + } + return output; + } + } + + /// + /// A class to decode data to B5G6R5 components. + /// + public class RawB5G6R5Decoder : 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 = MemoryMarshal.Cast(data.Span); + for (var i = 0; i < output.Length; i++) + { + context.CancellationToken.ThrowIfCancellationRequested(); + + var color56 = new ColorRgb565(span[i]); + output[i] = color56.ToColorRgba32(); + } + return output; + } + } + + } diff --git a/BCnEnc.Net/Shared/Colors.cs b/BCnEnc.Net/Shared/Colors.cs index 565301d..459b0f1 100644 --- a/BCnEnc.Net/Shared/Colors.cs +++ b/BCnEnc.Net/Shared/Colors.cs @@ -597,6 +597,196 @@ public float CalcDistWeighted(ColorYCbCr other, float yWeight = 4) } } + internal struct ColorRgb4444 : IEquatable + { + public bool Equals(ColorRgb4444 other) + { + return data == other.data; + } + + public override bool Equals(object obj) + { + return obj is ColorRgb4444 other && Equals(other); + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + public static bool operator ==(ColorRgb4444 left, ColorRgb4444 right) + { + return left.Equals(right); + } + + public static bool operator !=(ColorRgb4444 left, ColorRgb4444 right) + { + return !left.Equals(right); + } + + private const ushort AlphaMask = 0b1111_0000_0000_0000; + private const int AlphaShift = 12; + private const ushort RedMask = 0b0000_1111_0000_0000; + private const int RedShift = 8; + private const ushort GreenMask = 0b0000_0000_1111_0000; + private const int GreenShift = 4; + private const ushort BlueMask = 0b0000_0000_0000_1111; + + public ushort data; + + public byte A + { + readonly get + { + var a4 = (data & AlphaMask) >> AlphaShift; + return (byte)((a4 << 4) | (a4 >> 0)); + } + set + { + var a4 = value; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (a4 << AlphaShift)); + } + } + + public byte R + { + readonly get + { + var r4 = (data & RedMask) >> RedShift; + return (byte)((r4 << 4) | (r4 >> 0)); + } + set + { + var r4 = value >> 3; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (r4 << RedShift)); + } + } + + public byte G + { + readonly get + { + var g4 = (data & GreenMask) >> GreenShift; + return (byte)((g4 << 4) | (g4 >> 0)); + } + set + { + var g4 = value >> 3; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (g4 << GreenShift)); + } + } + + public byte B + { + readonly get + { + var b4 = data & BlueMask; + return (byte)((b4 << 4) | (b4 >> 0)); + } + set + { + var b4 = value >> 3; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | b4); + } + } + + public int RawA + { + readonly get => (data & AlphaMask) >> AlphaShift; + set + { + if (value > 15) value = 15; + if (value < 0) value = 0; + data = (ushort)(data & ~AlphaMask); + data = (ushort)(data | (value << AlphaShift)); + } + } + + public int RawR + { + readonly get => (data & RedMask) >> RedShift; + set + { + if (value > 15) value = 15; + if (value < 0) value = 0; + data = (ushort)(data & ~RedMask); + data = (ushort)(data | (value << RedShift)); + } + } + + public int RawG + { + readonly get => (data & GreenMask) >> GreenShift; + set + { + if (value > 15) value = 15; + if (value < 0) value = 0; + data = (ushort)(data & ~GreenMask); + data = (ushort)(data | (value << GreenShift)); + } + } + + public int RawB + { + readonly get => data & BlueMask; + set + { + if (value > 15) value = 15; + if (value < 0) value = 0; + data = (ushort)(data & ~BlueMask); + data = (ushort)(data | value); + } + } + + public ColorRgb4444(ushort value) + { + data = value; + } + + public ColorRgb4444(byte r, byte g, byte b) + { + data = 0; + R = r; + G = g; + B = b; + } + + public ColorRgb4444(Vector3 colorVector) + { + data = 0; + R = ByteHelper.ClampToByte(colorVector.X * 255); + G = ByteHelper.ClampToByte(colorVector.Y * 255); + B = ByteHelper.ClampToByte(colorVector.Z * 255); + } + + public ColorRgb4444(ColorRgb24 color) + { + data = 0; + R = color.r; + G = color.g; + B = color.b; + } + + public readonly ColorRgb24 ToColorRgb24() + { + return new ColorRgb24(R, G, B); + } + + public override string ToString() + { + return $"r : {R} g : {G} b : {B} a : {A}"; + } + + public ColorRgba32 ToColorRgba32() + { + return new ColorRgba32(R, G, B, A); + } + } + internal struct ColorRgb555 : IEquatable { public bool Equals(ColorRgb555 other) @@ -730,6 +920,11 @@ public int RawB } } + public ColorRgb555(ushort value) + { + data = value; + } + public ColorRgb555(byte r, byte g, byte b) { data = 0; @@ -886,6 +1081,11 @@ public int RawB } } + public ColorRgb565(ushort value) + { + data = value; + } + public ColorRgb565(byte r, byte g, byte b) { data = 0; diff --git a/BCnEnc.Net/Shared/CompressionFormat.cs b/BCnEnc.Net/Shared/CompressionFormat.cs index 9d7545d..b2a108c 100644 --- a/BCnEnc.Net/Shared/CompressionFormat.cs +++ b/BCnEnc.Net/Shared/CompressionFormat.cs @@ -1,55 +1,77 @@ namespace BCnEncoder.Shared { - public enum CompressionFormat - { - /// - /// Raw unsigned byte 8-bit Luminance data - /// - R, - /// - /// Raw unsigned byte 16-bit RG data - /// - Rg, - /// - /// Raw unsigned byte 24-bit RGB data - /// - Rgb, - /// - /// Raw unsigned byte 32-bit RGBA data - /// - Rgba, + public enum CompressionFormat + { + /// + /// Raw unsigned byte 8-bit Luminance data + /// + R, + /// + /// Raw unsigned byte 16-bit RG data + /// + Rg, + /// + /// Raw unsigned byte 24-bit RGB data + /// + Rgb, + /// + /// Raw unsigned byte 32-bit RGBA data + /// + Rgba, + + /// + /// Raw unsigned byte 16-bit B4G4R4A4 data + /// + B4G4R4A4, + + /// + /// Raw unsigned byte 16-bit B5G6R5 data + /// + B5G6R5, + + /// + /// Raw unsigned byte 16-bit B5G5R5A1 data + /// + B5G5R5A1, + + /// + /// Raw unsigned byte 32-bit BGRX data + /// + Bgr, + /// /// Raw unsigned byte 32-bit BGRA data /// Bgra, + /// /// BC1 / DXT1 with no alpha. Very widely supported and good compression ratio. /// Bc1, - /// - /// BC1 / DXT1 with 1-bit of alpha. - /// - Bc1WithAlpha, - /// - /// BC2 / DXT3 encoding with alpha. Good for sharp alpha transitions. - /// - Bc2, - /// - /// BC3 / DXT5 encoding with alpha. Good for smooth alpha transitions. - /// - Bc3, - /// - /// BC4 single-channel encoding. Only luminance is encoded. - /// - Bc4, - /// - /// BC5 dual-channel encoding. Only red and green channels are encoded. - /// - Bc5, - /// - /// BC6H / BPTC unsigned float encoding. Can compress HDR textures without alpha. Does not support negative values. - /// - Bc6U, + /// + /// BC1 / DXT1 with 1-bit of alpha. + /// + Bc1WithAlpha, + /// + /// BC2 / DXT3 encoding with alpha. Good for sharp alpha transitions. + /// + Bc2, + /// + /// BC3 / DXT5 encoding with alpha. Good for smooth alpha transitions. + /// + Bc3, + /// + /// BC4 single-channel encoding. Only luminance is encoded. + /// + Bc4, + /// + /// BC5 dual-channel encoding. Only red and green channels are encoded. + /// + Bc5, + /// + /// BC6H / BPTC unsigned float encoding. Can compress HDR textures without alpha. Does not support negative values. + /// + Bc6U, /// /// BC6H / BPTC signed float encoding. Can compress HDR textures without alpha. Supports negative values. /// @@ -73,26 +95,30 @@ public enum CompressionFormat /// /// Unknown format /// - Unknown + Unknown, } - public static class CompressionFormatExtensions - { - public static bool IsCompressedFormat(this CompressionFormat format) - { - switch (format) - { - case CompressionFormat.R: - case CompressionFormat.Rg: - case CompressionFormat.Rgb: - case CompressionFormat.Rgba: + public static class CompressionFormatExtensions + { + public static bool IsCompressedFormat(this CompressionFormat format) + { + switch (format) + { + case CompressionFormat.R: + case CompressionFormat.Rg: + case CompressionFormat.Rgb: + case CompressionFormat.Rgba: + case CompressionFormat.B5G6R5: + case CompressionFormat.B4G4R4A4: + case CompressionFormat.B5G5R5A1: + case CompressionFormat.Bgr: case CompressionFormat.Bgra: - return false; + return false; - default: - return true; - } - } + default: + return true; + } + } public static bool IsHdrFormat(this CompressionFormat format) { diff --git a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs index a1b3172..8284209 100644 --- a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs +++ b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs @@ -486,6 +486,24 @@ public DxgiFormat DxgiFormat return DxgiFormat.DxgiFormatB8G8R8A8Unorm; } } + else if (dwRgbBitCount == 16) + { + if (dwRBitMask == 0b0_11111_00000_00000 && + dwGBitMask == 0b0_00000_11111_00000 && + dwBBitMask == 0b0_00000_00000_11111 && + dwABitMask == 0b1_00000_00000_00000) + { + return DxgiFormat.DxgiFormatB5G5R5A1Unorm; + } + + if (dwRBitMask == 0b0000_1111_0000_0000 && + dwGBitMask == 0b0000_0000_1111_0000 && + dwBBitMask == 0b0000_0000_0000_1111 && + dwABitMask == 0b1111_0000_0000_0000) + { + return DxgiFormat.DxgiFormatB4G4R4A4Unorm; + } + } } else //RGB { @@ -496,6 +514,25 @@ public DxgiFormat DxgiFormat return DxgiFormat.DxgiFormatB8G8R8X8Unorm; } } + else if (dwRgbBitCount == 16) + { + if (dwRBitMask == 0b0_11111_00000_00000 && + dwGBitMask == 0b0_00000_11111_00000 && + dwBBitMask == 0b0_00000_00000_11111) + { + // B5G5R5A1 without DdpfAlphaPixels and dwABitMask = 0. + // There is no DxgiFormat that matches. + // Visual Studio reports it as B5G5R5A1 + return DxgiFormat.DxgiFormatB5G5R5A1Unorm; + } + + if (dwRBitMask == 0b11111_000000_00000 && + dwGBitMask == 0b00000_111111_00000 && + dwBBitMask == 0b00000_000000_11111) + { + return DxgiFormat.DxgiFormatB5G6R5Unorm; + } + } } } else if (dwFlags.HasFlag(PixelFormatFlags.DdpfLuminance)) // R/RG From a91c92d56367fed50d6e05a8af55078d17f673ae Mon Sep 17 00:00:00 2001 From: DummkopfOfHachtenduden Date: Wed, 7 Dec 2022 13:54:21 +0100 Subject: [PATCH 2/3] More work on various Bgr formats. - Added RawEncoders for Bgr, Bgr565, Bgra4444 and Bgra5551 - Added tests for Bgr, Bgr565, Bgra4444 and Bgra5551 - Fixed DecodeInternal relying on dwMipMapCount being set correctly when DDS_HEADER_FLAGS_MIPMAP is not set. --- BCnEnc.Net/Decoder/BcDecoder.cs | 10 +- BCnEnc.Net/Encoder/BcEncoder.cs | 33 ++-- BCnEnc.Net/Encoder/RawEncoders.cs | 199 +++++++++++++++++++++++- BCnEnc.Net/Shared/Colors.cs | 13 +- BCnEnc.Net/Shared/ImageFiles/DdsFile.cs | 70 ++++++++- BCnEncTests/BgrTests.cs | 46 ++++++ BCnEncTests/BgraTests.cs | 68 +++++++- BCnEncTests/Support/TestHelper.cs | 24 +-- 8 files changed, 425 insertions(+), 38 deletions(-) create mode 100644 BCnEncTests/BgrTests.cs diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index 94ff729..00a771e 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -1,13 +1,15 @@ using BCnEncoder.Decoder.Options; using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; + +using Microsoft.Toolkit.HighPerformance; + using System; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; namespace BCnEncoder.Decoder { @@ -1160,7 +1162,7 @@ private ColorRgba32[][] DecodeInternal(KtxFile file, bool allMipMaps, Cancellati /// An array of decoded Rgba32 images. private ColorRgba32[][] DecodeInternal(DdsFile file, bool allMipMaps, CancellationToken token) { - var mipMaps = allMipMaps ? file.header.dwMipMapCount : 1; + var mipMaps = allMipMaps ? file.header.MipMapCount : 1; var colors = new ColorRgba32[mipMaps][]; var context = new OperationContext @@ -1795,7 +1797,7 @@ public int GetBlockSize(CompressionFormat format) case CompressionFormat.Unknown: return 0; - + default: throw new ArgumentOutOfRangeException(nameof(format), format, null); } diff --git a/BCnEnc.Net/Encoder/BcEncoder.cs b/BCnEnc.Net/Encoder/BcEncoder.cs index 5e17c35..31e9026 100644 --- a/BCnEnc.Net/Encoder/BcEncoder.cs +++ b/BCnEnc.Net/Encoder/BcEncoder.cs @@ -1,14 +1,16 @@ +using BCnEncoder.Encoder.Bptc; +using BCnEncoder.Encoder.Options; +using BCnEncoder.Shared; +using BCnEncoder.Shared.ImageFiles; + +using Microsoft.Toolkit.HighPerformance; + using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using BCnEncoder.Encoder.Bptc; -using BCnEncoder.Encoder.Options; -using BCnEncoder.Shared; -using BCnEncoder.Shared.ImageFiles; -using Microsoft.Toolkit.HighPerformance; namespace BCnEncoder.Encoder { @@ -610,7 +612,7 @@ public int GetBlockSize() if (compressedEncoder == null) { var hdrEncoder = GetFloatBlockEncoder(OutputOptions.Format); - + if (hdrEncoder == null) { throw new NotSupportedException($"This format is either not supported or does not use block compression: {OutputOptions.Format}"); @@ -1319,7 +1321,7 @@ private DdsFile EncodeCubeMapToDdsInternalHdr(ReadOnlyMemory2D ri { DdsFile output; IBcBlockEncoder compressedEncoder = null; - + var faces = new[] { right, left, top, down, back, front }; var width = right.Width; @@ -1337,7 +1339,7 @@ private DdsFile EncodeCubeMapToDdsInternalHdr(ReadOnlyMemory2D ri output = new DdsFile(ddsHeader, dxt10Header); if (OutputOptions.DdsBc1WriteAlphaFlag && - OutputOptions.Format == CompressionFormat.Bc1WithAlpha) + OutputOptions.Format == CompressionFormat.Bc1WithAlpha) { output.header.ddsPixelFormat.dwFlags |= PixelFormatFlags.DdpfAlphaPixels; } @@ -1373,7 +1375,7 @@ private DdsFile EncodeCubeMapToDdsInternalHdr(ReadOnlyMemory2D ri for (var mip = 0; mip < numMipMaps; mip++) { byte[] encoded; - + var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip], out var blocksWidth, out var blocksHeight); encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.Quality, context); @@ -2173,6 +2175,19 @@ private IRawEncoder GetRawEncoder(CompressionFormat format) case CompressionFormat.Rgba: return new RawRgbaEncoder(); + + case CompressionFormat.Bgr: + return new RawBgrEncoder(); + + case CompressionFormat.B5G6R5: + return new RawBgr565Encoder(); + + case CompressionFormat.B4G4R4A4: + return new RawBgr4444Encoder(); + + case CompressionFormat.B5G5R5A1: + return new RawBgr5551Encoder(); + case CompressionFormat.Bgra: return new RawBgraEncoder(); diff --git a/BCnEnc.Net/Encoder/RawEncoders.cs b/BCnEnc.Net/Encoder/RawEncoders.cs index 139014b..f7f2d13 100644 --- a/BCnEnc.Net/Encoder/RawEncoders.cs +++ b/BCnEnc.Net/Encoder/RawEncoders.cs @@ -1,7 +1,9 @@ -using System; using BCnEncoder.Shared; using BCnEncoder.Shared.ImageFiles; +using System; +using System.Runtime.InteropServices; + namespace BCnEncoder.Encoder { internal interface IRawEncoder @@ -263,4 +265,199 @@ public DxgiFormat GetDxgiFormat() return DxgiFormat.DxgiFormatB8G8R8A8Unorm; } } + + internal class RawBgrEncoder : IRawEncoder + { + public byte[] Encode(ReadOnlyMemory pixels) + { + var span = pixels.Span; + + var output = new byte[pixels.Length * 4]; + for (var i = 0; i < pixels.Length; i++) + { + output[i * 4] = span[i].b; + output[i * 4 + 1] = span[i].g; + output[i * 4 + 2] = span[i].r; + output[i * 4 + 3] = 255; + } + return output; + } + + public GlInternalFormat GetInternalFormat() + { + return GlInternalFormat.GlBgra8Extension; + } + + public GlFormat GetBaseInternalFormat() + { + return GlFormat.GlBgra; + } + + public GlFormat GetGlFormat() + { + return GlFormat.GlBgra; + } + + public GlType GetGlType() + { + return GlType.GlByte; + } + + public uint GetGlTypeSize() + { + return 1; + } + + public DxgiFormat GetDxgiFormat() + { + return DxgiFormat.DxgiFormatB8G8R8X8Unorm; + } + } + + internal class RawBgr565Encoder : IRawEncoder + { + public byte[] Encode(ReadOnlyMemory pixels) + { + var span = pixels.Span; + + var output = new byte[pixels.Length * 2]; + var outputSpan = MemoryMarshal.Cast(output.AsSpan()); + for (var i = 0; i < pixels.Length; i++) + { + var color565 = new ColorRgb565(span[i].r, span[i].g, span[i].b); + outputSpan[i] = color565.data; + } + + return output; + } + + public GlInternalFormat GetInternalFormat() + { + throw new NotSupportedException("Bgr565 is not supported in ktx"); + } + + public GlFormat GetBaseInternalFormat() + { + throw new NotSupportedException("Bgr565 is not supported in ktx"); + } + + public GlFormat GetGlFormat() + { + throw new NotSupportedException("Bgr565 is not supported in ktx"); + } + + public GlType GetGlType() + { + throw new NotSupportedException("Bgr565 is not supported in ktx"); + } + + public uint GetGlTypeSize() + { + throw new NotSupportedException("Bgr565 is not supported in ktx"); + } + + public DxgiFormat GetDxgiFormat() + { + return DxgiFormat.DxgiFormatB5G6R5Unorm; + } + } + + internal class RawBgr4444Encoder : IRawEncoder + { + public byte[] Encode(ReadOnlyMemory pixels) + { + var span = pixels.Span; + + var output = new byte[pixels.Length * 2]; + var outputSpan = MemoryMarshal.Cast(output.AsSpan()); + for (var i = 0; i < pixels.Length; i++) + { + var color4444 = new ColorRgb4444(span[i].r, span[i].g, span[i].b, span[i].a); + outputSpan[i] = color4444.data; + } + + return output; + } + + public GlInternalFormat GetInternalFormat() + { + return GlInternalFormat.GlBgra8Extension; + } + + public GlFormat GetBaseInternalFormat() + { + return GlFormat.GlBgra; + } + + public GlFormat GetGlFormat() + { + return GlFormat.GlBgra; + } + + public GlType GetGlType() + { + return GlType.GlByte; + } + + public uint GetGlTypeSize() + { + return 1; + } + + public DxgiFormat GetDxgiFormat() + { + return DxgiFormat.DxgiFormatB4G4R4A4Unorm; + } + } + + internal class RawBgr5551Encoder : IRawEncoder + { + public byte[] Encode(ReadOnlyMemory pixels) + { + var span = pixels.Span; + + var output = new byte[pixels.Length * 2]; + var outputSpan = MemoryMarshal.Cast(output.AsSpan()); + for (var i = 0; i < pixels.Length; i++) + { + var color5551 = new ColorRgb555(span[i].r, span[i].g, span[i].b) + { + Mode = span[i].a != 0 ? (byte)1 : (byte)0, + }; + outputSpan[i] = color5551.data; + } + + return output; + } + + public GlInternalFormat GetInternalFormat() + { + return GlInternalFormat.GlBgra8Extension; + } + + public GlFormat GetBaseInternalFormat() + { + return GlFormat.GlBgra; + } + + public GlFormat GetGlFormat() + { + return GlFormat.GlBgra; + } + + public GlType GetGlType() + { + return GlType.GlByte; + } + + public uint GetGlTypeSize() + { + return 1; + } + + public DxgiFormat GetDxgiFormat() + { + return DxgiFormat.DxgiFormatB5G5R5A1Unorm; + } + } } diff --git a/BCnEnc.Net/Shared/Colors.cs b/BCnEnc.Net/Shared/Colors.cs index 459b0f1..2590dd4 100644 --- a/BCnEnc.Net/Shared/Colors.cs +++ b/BCnEnc.Net/Shared/Colors.cs @@ -311,7 +311,7 @@ public ColorRgba32 ToRgba32() } } - + public struct ColorRgbFloat : IEquatable { public float r, g, b; @@ -643,7 +643,7 @@ readonly get } set { - var a4 = value; + var a4 = value >> 4; data = (ushort)(data & ~AlphaMask); data = (ushort)(data | (a4 << AlphaShift)); } @@ -658,7 +658,7 @@ readonly get } set { - var r4 = value >> 3; + var r4 = value >> 4; data = (ushort)(data & ~RedMask); data = (ushort)(data | (r4 << RedShift)); } @@ -673,7 +673,7 @@ readonly get } set { - var g4 = value >> 3; + var g4 = value >> 4; data = (ushort)(data & ~GreenMask); data = (ushort)(data | (g4 << GreenShift)); } @@ -688,7 +688,7 @@ readonly get } set { - var b4 = value >> 3; + var b4 = value >> 4; data = (ushort)(data & ~BlueMask); data = (ushort)(data | b4); } @@ -747,12 +747,13 @@ public ColorRgb4444(ushort value) data = value; } - public ColorRgb4444(byte r, byte g, byte b) + public ColorRgb4444(byte r, byte g, byte b, byte a) { data = 0; R = r; G = g; B = b; + A = a; } public ColorRgb4444(Vector3 colorVector) diff --git a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs index 8284209..02ac4d3 100644 --- a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs +++ b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs @@ -190,18 +190,20 @@ public unsafe struct DdsHeader public uint dwCaps4; public uint dwReserved2; + public uint MipMapCount => (dwFlags & HeaderFlags.DdsdMipmapcount) != 0u ? MipMapCount : 1u; + public static (DdsHeader, DdsHeaderDx10) InitializeCompressed(int width, int height, DxgiFormat format, bool preferDxt10Header) { var header = new DdsHeader(); var dxt10Header = new DdsHeaderDx10(); header.dwSize = 124; - header.dwFlags = HeaderFlags.Required; + header.dwFlags = HeaderFlags.Required | HeaderFlags.DdsdMipmapcount; header.dwWidth = (uint)width; header.dwHeight = (uint)height; header.dwDepth = 1; header.dwMipMapCount = 1; - header.dwCaps = HeaderCaps.DdscapsTexture; + header.dwCaps = HeaderCaps.DdscapsTexture | HeaderCaps.DdscapsMipmap; if (preferDxt10Header) { @@ -346,12 +348,12 @@ public static DdsHeader InitializeUncompressed(int width, int height, DxgiFormat var header = new DdsHeader(); header.dwSize = 124; - header.dwFlags = HeaderFlags.Required | HeaderFlags.DdsdPitch; + header.dwFlags = HeaderFlags.Required | HeaderFlags.DdsdPitch | HeaderFlags.DdsdMipmapcount; header.dwWidth = (uint)width; header.dwHeight = (uint)height; header.dwDepth = 1; header.dwMipMapCount = 1; - header.dwCaps = HeaderCaps.DdscapsTexture; + header.dwCaps = HeaderCaps.DdscapsTexture | HeaderCaps.DdscapsMipmap; if (format == DxgiFormat.DxgiFormatR8Unorm) { @@ -390,6 +392,19 @@ public static DdsHeader InitializeUncompressed(int width, int height, DxgiFormat }; header.dwPitchOrLinearSize = (uint)((width * 32 + 7) / 8); } + else if (format == DxgiFormat.DxgiFormatB8G8R8X8Unorm) + { + header.ddsPixelFormat = new DdsPixelFormat() + { + dwSize = 32, + dwFlags = PixelFormatFlags.DdpfRgb, + dwRgbBitCount = 32, + dwRBitMask = 0b11111111_00000000_00000000, + dwGBitMask = 0b00000000_11111111_00000000, + dwBBitMask = 0b00000000_00000000_11111111, + }; + header.dwPitchOrLinearSize = (uint)((width * 32 + 7) / 8); + } else if (format == DxgiFormat.DxgiFormatB8G8R8A8Unorm) { header.ddsPixelFormat = new DdsPixelFormat() @@ -404,6 +419,47 @@ public static DdsHeader InitializeUncompressed(int width, int height, DxgiFormat }; header.dwPitchOrLinearSize = (uint)((width * 32 + 7) / 8); } + else if (format == DxgiFormat.DxgiFormatB5G6R5Unorm) + { + header.ddsPixelFormat = new DdsPixelFormat() + { + dwSize = 32, + dwFlags = PixelFormatFlags.DdpfRgb, + dwRgbBitCount = 16, + dwRBitMask = 0b11111_000000_00000, + dwGBitMask = 0b00000_111111_00000, + dwBBitMask = 0b00000_000000_11111, + }; + header.dwPitchOrLinearSize = (uint)((width * 16 + 7) / 8); + } + else if (format == DxgiFormat.DxgiFormatB5G5R5A1Unorm) + { + header.ddsPixelFormat = new DdsPixelFormat() + { + dwSize = 32, + dwFlags = PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, + dwRgbBitCount = 16, + dwABitMask = 0b1_00000_00000_00000, + dwRBitMask = 0b0_11111_00000_00000, + dwGBitMask = 0b0_00000_11111_00000, + dwBBitMask = 0b0_00000_00000_11111, + }; + header.dwPitchOrLinearSize = (uint)((width * 16 + 7) / 8); + } + else if (format == DxgiFormat.DxgiFormatB4G4R4A4Unorm) + { + header.ddsPixelFormat = new DdsPixelFormat() + { + dwSize = 32, + dwFlags = PixelFormatFlags.DdpfRgb | PixelFormatFlags.DdpfAlphaPixels, + dwRgbBitCount = 16, + dwABitMask = 0b1111_0000_0000_0000, + dwRBitMask = 0b0000_1111_0000_0000, + dwGBitMask = 0b0000_0000_1111_0000, + dwBBitMask = 0b0000_0000_0000_1111, + }; + header.dwPitchOrLinearSize = (uint)((width * 16 + 7) / 8); + } else { throw new NotImplementedException("This Format is not implemented in this method"); @@ -416,7 +472,7 @@ public static DdsHeader InitializeUncompressed(int width, int height, DxgiFormat public struct DdsPixelFormat { public static readonly uint Dx10 = MakeFourCc('D', 'X', '1', '0'); - + public static readonly uint Dxt1 = MakeFourCc('D', 'X', 'T', '1'); public static readonly uint Dxt2 = MakeFourCc('D', 'X', 'T', '2'); public static readonly uint Dxt3 = MakeFourCc('D', 'X', 'T', '3'); @@ -527,8 +583,8 @@ public DxgiFormat DxgiFormat } if (dwRBitMask == 0b11111_000000_00000 && - dwGBitMask == 0b00000_111111_00000 && - dwBBitMask == 0b00000_000000_11111) + dwGBitMask == 0b00000_111111_00000 && + dwBBitMask == 0b00000_000000_11111) { return DxgiFormat.DxgiFormatB5G6R5Unorm; } diff --git a/BCnEncTests/BgrTests.cs b/BCnEncTests/BgrTests.cs new file mode 100644 index 0000000..0239e69 --- /dev/null +++ b/BCnEncTests/BgrTests.cs @@ -0,0 +1,46 @@ +using BCnEncoder.Decoder; +using BCnEncoder.Encoder; +using BCnEncoder.ImageSharp; +using BCnEncoder.Shared; + +using BCnEncTests.Support; + +using Xunit; + +namespace BCnEncTests +{ + public class BgrTests + { + [Fact] + public void Bgr565DdsDecode() + { + // Arrange + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.B5G6R5); + var original = ImageLoader.TestLenna; + + // Act + var dds = encoder.EncodeToDds(original); + var image = decoder.DecodeToImageRgba32(dds); + + // Assert + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void BgrDdsDecode() + { + // Arrange + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.Bgr); + var original = ImageLoader.TestLenna; + + // Act + var dds = encoder.EncodeToDds(original); + var image = decoder.DecodeToImageRgba32(dds); + + // Assert + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + } +} diff --git a/BCnEncTests/BgraTests.cs b/BCnEncTests/BgraTests.cs index 7ae666e..2c051d1 100644 --- a/BCnEncTests/BgraTests.cs +++ b/BCnEncTests/BgraTests.cs @@ -2,7 +2,11 @@ using BCnEncoder.Encoder; using BCnEncoder.ImageSharp; using BCnEncoder.Shared; + using BCnEncTests.Support; + +using System.IO; + using Xunit; namespace BCnEncTests @@ -21,7 +25,37 @@ public void BgraDdsDecode() var dds = encoder.EncodeToDds(original); var image = decoder.DecodeToImageRgba32(dds); - + // Assert + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void Bgra4444DdsDecode() + { + // Arrange + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.B4G4R4A4); + var original = ImageLoader.TestLenna; + + // Act + var dds = encoder.EncodeToDds(original); + var image = decoder.DecodeToImageRgba32(dds); + + // Assert + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void Bgra5551DdsDecode() + { + // Arrange + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.B5G5R5A1); + var original = ImageLoader.TestLenna; + + // Act + var dds = encoder.EncodeToDds(original); + var image = decoder.DecodeToImageRgba32(dds); // Assert TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); @@ -43,6 +77,38 @@ public void BgraAlphaDdsDecode() TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); } + [Fact] + public void Bgra4444AlphaDdsDecode() + { + // Arrange + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.B4G4R4A4); + var original = ImageLoader.TestAlphaGradient1; + + // Act + var dds = encoder.EncodeToDds(original); + var image = decoder.DecodeToImageRgba32(dds); + + // Assert + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + + [Fact] + public void Bgra5551AlphaDdsDecode() + { + // Arrange + var decoder = new BcDecoder(); + var encoder = new BcEncoder(CompressionFormat.B5G5R5A1); + var original = ImageLoader.TestTransparentSprite1; + + // Act + var dds = encoder.EncodeToDds(original); + var image = decoder.DecodeToImageRgba32(dds); + + // Assert + TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality); + } + [Fact] public void BgraKtxDecode() { diff --git a/BCnEncTests/Support/TestHelper.cs b/BCnEncTests/Support/TestHelper.cs index 5c92096..49658e3 100644 --- a/BCnEncTests/Support/TestHelper.cs +++ b/BCnEncTests/Support/TestHelper.cs @@ -1,16 +1,20 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using BCnEncoder.Decoder; using BCnEncoder.Encoder; using BCnEncoder.ImageSharp; using BCnEncoder.Shared; using BCnEncoder.Shared.ImageFiles; + using Microsoft.Toolkit.HighPerformance; + using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + using Xunit; using Xunit.Abstractions; @@ -30,7 +34,7 @@ public static void AssertPixelsEqual(Span originalPixels, Span p public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null) { - var rmse = ImageQuality.CalculateLogRMSE(originalPixels,pixels); + var rmse = ImageQuality.CalculateLogRMSE(originalPixels, pixels); AssertRMSE(rmse, quality, output); } @@ -89,7 +93,7 @@ public static void ExecuteDdsWritingTest(Image[] images, CompressionForm public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string outputFile, bool assertAlpha = false) { Assert.Equal(format, file.header.ddsPixelFormat.DxgiFormat); - Assert.Equal(file.header.dwMipMapCount, (uint)file.Faces[0].MipMaps.Length); + Assert.Equal(file.header.MipMapCount, (uint)file.Faces[0].MipMaps.Length); var decoder = new BcDecoder(); decoder.InputOptions.DdsBc1ExpectAlpha = assertAlpha; @@ -151,7 +155,7 @@ public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original) var decoder = new BcDecoder() { }; - + var decoded = decoder.DecodeHdr(ktx); return ImageQuality.CalculateLogRMSE(original.pixels, decoded); @@ -192,9 +196,9 @@ public static void ExecuteHdrEncodingTest(HdrImage image, CompressionFormat form private static float CalculatePSNR(Image original, Image decoded, bool countAlpha = true) { - var pixels = GetSinglePixelArrayAsColors(original); + var pixels = GetSinglePixelArrayAsColors(original); var pixels2 = GetSinglePixelArrayAsColors(decoded); - + return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, countAlpha); } From 8dbbfa088075f0147d84254d83bab54883192079 Mon Sep 17 00:00:00 2001 From: DummkopfOfHachtenduden Date: Wed, 7 Dec 2022 17:21:27 +0100 Subject: [PATCH 3/3] Fixed MipMapCount StackOverflow. --- BCnEnc.Net/Shared/ImageFiles/DdsFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs index 02ac4d3..1faed6e 100644 --- a/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs +++ b/BCnEnc.Net/Shared/ImageFiles/DdsFile.cs @@ -190,7 +190,7 @@ public unsafe struct DdsHeader public uint dwCaps4; public uint dwReserved2; - public uint MipMapCount => (dwFlags & HeaderFlags.DdsdMipmapcount) != 0u ? MipMapCount : 1u; + public uint MipMapCount => (dwFlags & HeaderFlags.DdsdMipmapcount) != 0u ? dwMipMapCount : 1u; public static (DdsHeader, DdsHeaderDx10) InitializeCompressed(int width, int height, DxgiFormat format, bool preferDxt10Header) {