diff --git a/BCnEnc.Net/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj index da61c8d..a1b7e6a 100644 --- a/BCnEnc.Net/BCnEncoder.csproj +++ b/BCnEnc.Net/BCnEncoder.csproj @@ -1,37 +1,31 @@  - netstandard2.1 + netstandard1.1;net45 true true snupkg true - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + bin\Debug\netstandard1.1\BCnEncoderNet45.xml MIT OR Unlicense false - 1.2.3 + 0.0.1 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. - -Supported formats are: - Raw unsigned byte R, RG, RGB and RGBA formats - BC1 (S3TC DXT1) - BC2 (S3TC DXT3) - BC3 (S3TC DXT5) - BC4 (RGTC1) - BC5 (RGTC2) - BC7 (BPTC) - BCnEncoder.Net + BCnEncoder.Net45 + BCnEncoder.NET is a library for compressing rgba images to different block-compressed formats. Both ktx and dds output formats are supported. +This version is a .NET Framework 4.5 compatible legacy version. If you're using .NET Core, please use the main package instead. + + BCnEncoder.Net45 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 https://github.com/Nominom/BCnEncoder.NET - Upgraded ImageSharp dependency from 1.0.0 beta 7 to version 1.0.1 + Initial .Net Framework 4.5 support version. + BCnEncoderNet45 @@ -44,8 +38,8 @@ Supported formats are: - + diff --git a/BCnEnc.Net/Decoder/Bc7Decoder.cs b/BCnEnc.Net/Decoder/Bc7Decoder.cs index 11a72b6..47c8d10 100644 --- a/BCnEnc.Net/Decoder/Bc7Decoder.cs +++ b/BCnEnc.Net/Decoder/Bc7Decoder.cs @@ -7,26 +7,30 @@ 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); + public unsafe RawBlock4X4Rgba32[,] Decode(byte[] data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) { + blockWidth = (int)Math.Ceiling(pixelWidth / 4.0f); + blockHeight = (int)Math.Ceiling(pixelHeight / 4.0f); - if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) { + if (data.Length != (blockWidth * blockHeight * sizeof(Bc7Block))) { 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(); + fixed (byte* iDataBytes = data) + { + Bc7Block* encodedBlocks = (Bc7Block*)iDataBytes; + + for (int x = 0; x < blockWidth; x++) + { + for (int y = 0; y < blockHeight; y++) + { + output[x, y] = encodedBlocks[x + y * blockWidth].Decode(); + } } } - return output; + return output; } } } diff --git a/BCnEnc.Net/Decoder/BcBlockDecoder.cs b/BCnEnc.Net/Decoder/BcBlockDecoder.cs index 5bfa778..110fed4 100644 --- a/BCnEnc.Net/Decoder/BcBlockDecoder.cs +++ b/BCnEnc.Net/Decoder/BcBlockDecoder.cs @@ -5,27 +5,36 @@ namespace BCnEncoder.Decoder { - internal interface IBcBlockDecoder { - RawBlock4X4Rgba32[,] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight, out int blockWidth, + internal interface IBcBlockDecoder + { + RawBlock4X4Rgba32[,] Decode(byte[] data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight); } - 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 class Bc1NoAlphaDecoder : IBcBlockDecoder + { + public unsafe RawBlock4X4Rgba32[,] Decode(byte[] data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) + { + blockWidth = (int)Math.Ceiling(pixelWidth / 4.0f); + blockHeight = (int)Math.Ceiling(pixelHeight / 4.0f); - if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) { + if (data.Length != (blockWidth * blockHeight * sizeof(Bc1Block))) + { 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(false); + fixed (byte* iDataBytes = data) + { + Bc1Block* encodedBlocks = (Bc1Block*)iDataBytes; + + for (int x = 0; x < blockWidth; x++) + { + for (int y = 0; y < blockHeight; y++) + { + output[x, y] = encodedBlocks[x + y * blockWidth].Decode(false); + } } } @@ -33,22 +42,30 @@ internal class Bc1NoAlphaDecoder : IBcBlockDecoder { } } - 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); + internal class Bc1ADecoder : IBcBlockDecoder + { + public unsafe RawBlock4X4Rgba32[,] Decode(byte[] data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) + { + blockWidth = (int)Math.Ceiling(pixelWidth / 4.0f); + blockHeight = (int)Math.Ceiling(pixelHeight / 4.0f); - if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) { + if (data.Length != (blockWidth * blockHeight * sizeof(Bc1Block))) + { 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(true); + fixed (byte* iDataBytes = data) + { + Bc1Block* encodedBlocks = (Bc1Block*)iDataBytes; + + for (int x = 0; x < blockWidth; x++) + { + for (int y = 0; y < blockHeight; y++) + { + output[x, y] = encodedBlocks[x + y * blockWidth].Decode(true); + } } } @@ -56,22 +73,30 @@ internal class Bc1ADecoder : IBcBlockDecoder { } } - 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); + internal class Bc2Decoder : IBcBlockDecoder + { + public unsafe RawBlock4X4Rgba32[,] Decode(byte[] data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) + { + blockWidth = (int)Math.Ceiling(pixelWidth / 4.0f); + blockHeight = (int)Math.Ceiling(pixelHeight / 4.0f); - if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) { + if (data.Length != (blockWidth * blockHeight * sizeof(Bc2Block))) + { 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(); + fixed (byte* iDataBytes = data) + { + Bc2Block* encodedBlocks = (Bc2Block*)iDataBytes; + + for (int x = 0; x < blockWidth; x++) + { + for (int y = 0; y < blockHeight; y++) + { + output[x, y] = encodedBlocks[x + y * blockWidth].Decode(); + } } } @@ -79,22 +104,30 @@ internal class Bc2Decoder : IBcBlockDecoder { } } - 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); + internal class Bc3Decoder : IBcBlockDecoder + { + public unsafe RawBlock4X4Rgba32[,] Decode(byte[] data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) + { + blockWidth = (int)Math.Ceiling(pixelWidth / 4.0f); + blockHeight = (int)Math.Ceiling(pixelHeight / 4.0f); - if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) { + if (data.Length != (blockWidth * blockHeight * sizeof(Bc3Block))) + { 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(); + fixed (byte* iDataBytes = data) + { + Bc3Block* encodedBlocks = (Bc3Block*)iDataBytes; + + for (int x = 0; x < blockWidth; x++) + { + for (int y = 0; y < blockHeight; y++) + { + output[x, y] = encodedBlocks[x + y * blockWidth].Decode(); + } } } @@ -102,27 +135,36 @@ internal class Bc3Decoder : IBcBlockDecoder { } } - internal class Bc4Decoder : IBcBlockDecoder { + internal class Bc4Decoder : IBcBlockDecoder + { private readonly bool redAsLuminance; - public Bc4Decoder(bool redAsLuminance) { + public Bc4Decoder(bool redAsLuminance) + { this.redAsLuminance = redAsLuminance; } - 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); + public unsafe RawBlock4X4Rgba32[,] Decode(byte[] data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) + { + blockWidth = (int)Math.Ceiling(pixelWidth / 4.0f); + blockHeight = (int)Math.Ceiling(pixelHeight / 4.0f); - if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) { + if (data.Length != (blockWidth * blockHeight * sizeof(Bc4Block))) + { 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(redAsLuminance); + fixed (byte* iDataBytes = data) + { + Bc4Block* encodedBlocks = (Bc4Block*)iDataBytes; + + for (int x = 0; x < blockWidth; x++) + { + for (int y = 0; y < blockHeight; y++) + { + output[x, y] = encodedBlocks[x + y * blockWidth].Decode(redAsLuminance); + } } } @@ -130,23 +172,31 @@ public Bc4Decoder(bool redAsLuminance) { } } - internal class Bc5Decoder : IBcBlockDecoder { + 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); + public unsafe RawBlock4X4Rgba32[,] Decode(byte[] data, int pixelWidth, int pixelHeight, out int blockWidth, out int blockHeight) + { + blockWidth = (int)Math.Ceiling(pixelWidth / 4.0f); + blockHeight = (int)Math.Ceiling(pixelHeight / 4.0f); - if (data.Length != (blockWidth * blockHeight * Marshal.SizeOf())) { + if (data.Length != (blockWidth * blockHeight * sizeof(Bc5Block))) + { 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(); + fixed (byte* iDataBytes = data) + { + Bc5Block* encodedBlocks = (Bc5Block*)iDataBytes; + + for (int x = 0; x < blockWidth; x++) + { + for (int y = 0; y < blockHeight; y++) + { + output[x, y] = encodedBlocks[x + y * blockWidth].Decode(); + } } } diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs index d768a85..2e55b11 100644 --- a/BCnEnc.Net/Decoder/BcDecoder.cs +++ b/BCnEnc.Net/Decoder/BcDecoder.cs @@ -2,13 +2,11 @@ using System.IO; using System.Text; using BCnEncoder.Shared; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Decoder { - public class DecoderInputOptions { + 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. @@ -18,7 +16,8 @@ public class DecoderInputOptions { public bool ddsBc1ExpectAlpha = true; } - public class DecoderOutputOptions { + 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. @@ -26,6 +25,13 @@ public class DecoderOutputOptions { public bool redAsLuminance = true; } + public struct DecodedMipMap + { + public int Width; + public int Height; + public byte[] data; + } + /// /// Decodes compressed files into Rgba format. /// @@ -87,6 +93,29 @@ private IBcBlockDecoder GetDecoder(GlInternalFormat format) } } + private IBcBlockDecoder GetDecoder(CompressionFormat format) + { + switch (format) + { + case CompressionFormat.BC1: + return new Bc1NoAlphaDecoder(); + case CompressionFormat.BC1WithAlpha: + return new Bc1ADecoder(); + case CompressionFormat.BC2: + return new Bc2Decoder(); + case CompressionFormat.BC3: + return new Bc3Decoder(); + case CompressionFormat.BC4: + return new Bc4Decoder(OutputOptions.redAsLuminance); + case CompressionFormat.BC5: + return new Bc5Decoder(); + case CompressionFormat.BC7: + return new Bc7Decoder(); + default: + return null; + } + } + private IBcBlockDecoder GetDecoder(DXGI_FORMAT format, DdsHeader header) { switch (format) @@ -94,16 +123,19 @@ private IBcBlockDecoder GetDecoder(DXGI_FORMAT format, DdsHeader header) 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) { + if ((header.ddsPixelFormat.dwFlags & PixelFormatFlags.DDPF_ALPHAPIXELS) != 0) + { return new Bc1ADecoder(); } - else if(InputOptions.ddsBc1ExpectAlpha){ + else if (InputOptions.ddsBc1ExpectAlpha) + { return new Bc1ADecoder(); } - else { + 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: @@ -164,41 +196,37 @@ private IRawDecoder GetRawDecoder(DXGI_FORMAT format) /// /// Read a Ktx or a Dds file from a stream and decode it. /// - public Image Decode(Stream inputStream) { + public DecodedMipMap 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); - } - } + try + { bool isDDS = false; - using (var br = new BinaryReader(inputStream, Encoding.UTF8, true)) { + using (var br = new BinaryReader(inputStream, Encoding.UTF8, true)) + { var magic = br.ReadUInt32(); - if (magic == 0x20534444U) { + if (magic == 0x20534444U) + { isDDS = true; } } inputStream.Seek(position, SeekOrigin.Begin); - if (isDDS) { + if (isDDS) + { DdsFile dds = DdsFile.Load(inputStream); return Decode(dds); } - else { + else + { KtxFile ktx = KtxFile.Load(inputStream); return Decode(ktx); } } - catch (Exception) { + catch (Exception) + { inputStream.Seek(position, SeekOrigin.Begin); throw; } @@ -207,41 +235,37 @@ public Image Decode(Stream inputStream) { /// /// Read a Ktx or a Dds file from a stream and decode it. /// - public Image[] DecodeAllMipMaps(Stream inputStream) { + public DecodedMipMap[] 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); - } - } + try + { bool isDDS = false; - using (var br = new BinaryReader(inputStream, Encoding.UTF8, true)) { + using (var br = new BinaryReader(inputStream, Encoding.UTF8, true)) + { var magic = br.ReadUInt32(); - if (magic == 0x20534444U) { + if (magic == 0x20534444U) + { isDDS = true; } } inputStream.Seek(position, SeekOrigin.Begin); - if (isDDS) { + if (isDDS) + { DdsFile dds = DdsFile.Load(inputStream); return DecodeAllMipMaps(dds); } - else { + else + { KtxFile ktx = KtxFile.Load(inputStream); return DecodeAllMipMaps(ktx); } } - catch (Exception) { + catch (Exception) + { inputStream.Seek(position, SeekOrigin.Begin); throw; } @@ -250,24 +274,35 @@ public Image[] DecodeAllMipMaps(Stream inputStream) { /// /// Read a KtxFile and decode it. /// - public Image Decode(KtxFile file) + public DecodedMipMap Decode(KtxFile file) { - if (IsSupportedRawFormat(file.Header.GlInternalFormat)) { + 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; - var image = new Image((int)pixelWidth, (int)pixelHeight); + var image = new byte[(int)pixelWidth * (int)pixelHeight * 4]; var output = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight); - if (!image.TryGetSinglePixelSpan(out var pixels)) { - throw new Exception("Cannot get pixel span."); + + for (int i = 0; i < output.Length; i++) + { + image[i * 4 + 0] = output[i].R; + image[i * 4 + 1] = output[i].G; + image[i * 4 + 2] = output[i].B; + image[i * 4 + 3] = output[i].A; } - output.CopyTo(pixels); - return image; + return new DecodedMipMap() + { + Width = (int)pixelWidth, + Height = (int)pixelHeight, + data = image + }; } - else { + else + { var decoder = GetDecoder(file.Header.GlInternalFormat); if (decoder == null) { @@ -280,57 +315,80 @@ public Image Decode(KtxFile file) var blocks = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight, out var blockWidth, out var blockHeight); - return ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, - (int)pixelWidth, (int)pixelHeight); + return new DecodedMipMap() + { + Width = (int)pixelWidth, + Height = (int)pixelHeight, + data = ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, + (int)pixelWidth, (int)pixelHeight) + }; } } /// /// Read a KtxFile and decode it. /// - public Image[] DecodeAllMipMaps(KtxFile file) + public DecodedMipMap[] DecodeAllMipMaps(KtxFile file) { - if (IsSupportedRawFormat(file.Header.GlInternalFormat)) { + if (IsSupportedRawFormat(file.Header.GlInternalFormat)) + { var decoder = GetRawDecoder(file.Header.GlInternalFormat); - var images = new Image[file.MipMaps.Count]; + var images = new DecodedMipMap[file.MipMaps.Count]; - for (int mip = 0; mip < file.MipMaps.Count; mip++) { + 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; - var image = new Image((int)pixelWidth, (int)pixelHeight); + var image = new byte[(int)pixelWidth * (int)pixelHeight * 4]; var output = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight); - if (!image.TryGetSinglePixelSpan(out var pixels)) { - throw new Exception("Cannot get pixel span."); - } - output.CopyTo(pixels); - images[mip] = image; + for (int i = 0; i < output.Length; i++) + { + image[i * 4 + 0] = output[i].R; + image[i * 4 + 1] = output[i].G; + image[i * 4 + 2] = output[i].B; + image[i * 4 + 3] = output[i].A; + } + + images[mip] = new DecodedMipMap() + { + Width = (int)pixelWidth, + Height = (int)pixelHeight, + data = image + }; } - + return images; } - else { + 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]; + var images = new DecodedMipMap[file.MipMaps.Count]; - for (int mip = 0; mip < file.MipMaps.Count; mip++) { + 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; - var blocks = decoder.Decode(data, (int) pixelWidth, (int) pixelHeight, out var blockWidth, + var blocks = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight, out var blockWidth, out var blockHeight); - var image = ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, + var image = ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, (int)pixelWidth, (int)pixelHeight); - images[mip] = image; + images[mip] = new DecodedMipMap() + { + Width = (int)pixelWidth, + Height = (int)pixelHeight, + data = image + }; } return images; } @@ -339,33 +397,46 @@ public Image[] DecodeAllMipMaps(KtxFile file) /// /// Read a DdsFile and decode it /// - public Image Decode(DdsFile file) + public DecodedMipMap Decode(DdsFile file) { - if (IsSupportedRawFormat(file.Header.ddsPixelFormat.DxgiFormat)) { + 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; - var image = new Image((int)pixelWidth, (int)pixelHeight); + var image = new byte[(int)pixelWidth * (int)pixelHeight * 4]; var output = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight); - if (!image.TryGetSinglePixelSpan(out var pixels)) { - throw new Exception("Cannot get pixel span."); - } - output.CopyTo(pixels); - return image; + for (int i = 0; i < output.Length; i++) + { + image[i * 4 + 0] = output[i].R; + image[i * 4 + 1] = output[i].G; + image[i * 4 + 2] = output[i].B; + image[i * 4 + 3] = output[i].A; + } + + return new DecodedMipMap() + { + Width = (int)pixelWidth, + Height = (int)pixelHeight, + data = image + }; } - else { + else + { DXGI_FORMAT format = DXGI_FORMAT.DXGI_FORMAT_UNKNOWN; - if (file.Header.ddsPixelFormat.IsDxt10Format) { + if (file.Header.ddsPixelFormat.IsDxt10Format) + { format = file.Dxt10Header.dxgiFormat; } - else { + else + { format = file.Header.ddsPixelFormat.DxgiFormat; } IBcBlockDecoder decoder = GetDecoder(format, file.Header); - + if (decoder == null) { throw new NotSupportedException($"This format is not supported: {format}"); @@ -377,69 +448,112 @@ public Image Decode(DdsFile file) var blocks = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight, out var blockWidth, out var blockHeight); - return ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, - (int)pixelWidth, (int)pixelHeight); + return new DecodedMipMap() + { + Width = (int)pixelWidth, + Height = (int)pixelHeight, + data = ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, + (int)pixelWidth, (int)pixelHeight) + }; } } /// /// Read a DdsFile and decode it /// - public Image[] DecodeAllMipMaps(DdsFile file) + public DecodedMipMap[] DecodeAllMipMaps(DdsFile file) { - if (IsSupportedRawFormat(file.Header.ddsPixelFormat.DxgiFormat)) { + if (IsSupportedRawFormat(file.Header.ddsPixelFormat.DxgiFormat)) + { var decoder = GetRawDecoder(file.Header.ddsPixelFormat.DxgiFormat); - var images = new Image[file.Header.dwMipMapCount]; + var images = new DecodedMipMap[file.Header.dwMipMapCount]; - for (int mip = 0; mip < file.Header.dwMipMapCount; mip++) { + 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; - var image = new Image((int) pixelWidth, (int) pixelHeight); - var output = decoder.Decode(data, (int) pixelWidth, (int) pixelHeight); - if (!image.TryGetSinglePixelSpan(out var pixels)) { - throw new Exception("Cannot get pixel span."); - } + var image = new byte[(int)pixelWidth * (int)pixelHeight * 4]; + var output = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight); - output.CopyTo(pixels); - images[mip] = image; + for (int i = 0; i < output.Length; i++) + { + image[i * 4 + 0] = output[i].R; + image[i * 4 + 1] = output[i].G; + image[i * 4 + 2] = output[i].B; + image[i * 4 + 3] = output[i].A; + } + images[mip] = new DecodedMipMap() + { + Width = (int)pixelWidth, + Height = (int)pixelHeight, + data = image + }; } return images; } - else { + else + { DXGI_FORMAT format = DXGI_FORMAT.DXGI_FORMAT_UNKNOWN; - if (file.Header.ddsPixelFormat.IsDxt10Format) { + if (file.Header.ddsPixelFormat.IsDxt10Format) + { format = file.Dxt10Header.dxgiFormat; } - else { + 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]; + var images = new DecodedMipMap[file.Header.dwMipMapCount]; - for (int mip = 0; mip < file.Header.dwMipMapCount; mip++) { + 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; - var blocks = decoder.Decode(data, (int) pixelWidth, (int) pixelHeight, out var blockWidth, + var blocks = decoder.Decode(data, (int)pixelWidth, (int)pixelHeight, out var blockWidth, out var blockHeight); - var image = ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, + var image = ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, (int)pixelWidth, (int)pixelHeight); - images[mip] = image; + images[mip] = new DecodedMipMap() + { + Width = (int)pixelWidth, + Height = (int)pixelHeight, + data = image + }; } return images; } } + + /// + /// Read raw block compressed data and decode it to RGBA. + /// + public byte[] DecodeRawData(byte[] bcData, int imageWidth, int imageHeight, CompressionFormat format) + { + + var decoder = GetDecoder(format); + if (decoder == null) + { + throw new NotSupportedException($"This format is not supported: {format}"); + } + + var blocks = decoder.Decode(bcData, imageWidth, imageHeight, out var blockWidth, out var blockHeight); + + return ImageToBlocks.ImageFromRawBlocks(blocks, blockWidth, blockHeight, + imageWidth, imageHeight); + + } } } diff --git a/BCnEnc.Net/Decoder/RawDecoder.cs b/BCnEnc.Net/Decoder/RawDecoder.cs index 75f77fe..1a56fca 100644 --- a/BCnEnc.Net/Decoder/RawDecoder.cs +++ b/BCnEnc.Net/Decoder/RawDecoder.cs @@ -1,10 +1,10 @@ using System; -using SixLabors.ImageSharp.PixelFormats; +using BCnEncoder.Shared; namespace BCnEncoder.Decoder { internal interface IRawDecoder { - Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight); + Rgba32[] Decode(byte[] data, int pixelWidth, int pixelHeight); } public class RawRDecoder : IRawDecoder { @@ -13,7 +13,7 @@ public RawRDecoder(bool redAsLuminance) { this.redAsLuminance = redAsLuminance; } - public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) { + public Rgba32[] Decode(byte[] data, int pixelWidth, int pixelHeight) { Rgba32[] output = new Rgba32[pixelWidth * pixelHeight]; for (int i = 0; i < output.Length; i++) { if (redAsLuminance) { @@ -34,7 +34,7 @@ public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) public class RawRGDecoder : IRawDecoder { - public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) { + public Rgba32[] Decode(byte[] 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]; @@ -48,7 +48,7 @@ public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) public class RawRGBDecoder : IRawDecoder { - public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) { + public Rgba32[] Decode(byte[] 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]; @@ -62,7 +62,7 @@ public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) public class RawRGBADecoder : IRawDecoder { - public Rgba32[] Decode(ReadOnlySpan data, int pixelWidth, int pixelHeight) { + public Rgba32[] Decode(byte[] 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]; diff --git a/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs index 0a0c95d..eb76e9a 100644 --- a/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc1BlockEncoder.cs @@ -8,24 +8,17 @@ namespace BCnEncoder.Encoder internal class Bc1BlockEncoder : IBcBlockEncoder { - public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) + public unsafe byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality 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 + byte[] outputData = new byte[blockWidth * blockHeight * sizeof(Bc1Block)]; + fixed (byte* oDataBytes = outputData) { - for (int i = 0; i < blocks.Length; i++) + Bc1Block* oDataBlocks = (Bc1Block*)oDataBytes; + int oDataBlocksLength = outputData.Length / sizeof(Bc1Block); + + for (int i = 0; i < oDataBlocksLength; i++) { - outputBlocks[i] = EncodeBlock(blocks[i], quality); + oDataBlocks[i] = EncodeBlock(blocks[i], quality); } } @@ -69,7 +62,7 @@ private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 { Bc1Block output = new Bc1Block(); - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; output.color0 = color0; output.color1 = color1; @@ -77,13 +70,13 @@ private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 var c0 = color0.ToColorRgb24(); var c1 = color1.ToColorRgb24(); - ReadOnlySpan colors = output.HasAlphaOrBlack ? - stackalloc ColorRgb24[] { + ColorRgb24[] colors = output.HasAlphaOrBlack ? + new ColorRgb24[] { c0, c1, c0 * (1.0 / 2.0) + c1 * (1.0 / 2.0), new ColorRgb24(0, 0, 0) - } : stackalloc ColorRgb24[] { + } : new ColorRgb24[] { c0, c1, c0 * (2.0 / 3.0) + c1 * (1.0 / 3.0), @@ -113,7 +106,7 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { Bc1Block output = new Bc1Block(); - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; RgbBoundingBox.Create565(pixels, out var min, out var max); @@ -132,7 +125,7 @@ private static class Bc1BlockEncoderBalanced { internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa); PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); @@ -150,7 +143,7 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) Bc1Block best = TryColors(rawBlock, c0, c1, out float bestError); for (int i = 0; i < maxTries; i++) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); + ColorVariationGenerator.Variate565(c0, c1, i, out var newC0, out var newC1); if (newC0.data < newC1.data) { @@ -185,7 +178,7 @@ private static class Bc1BlockEncoderSlow internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa); PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); @@ -205,8 +198,8 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) int lastChanged = 0; for (int i = 0; i < maxTries; i++) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - + ColorVariationGenerator.Variate565(c0, c1, i, out var newC0, out var newC1); + if (newC0.data < newC1.data) { var c = newC0; @@ -242,24 +235,17 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) internal class Bc1AlphaBlockEncoder : IBcBlockEncoder { - public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) + public unsafe byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) { - byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()]; - Span outputBlocks = MemoryMarshal.Cast(outputData); - - if (parallel) + byte[] outputData = new byte[blockWidth * blockHeight * sizeof(Bc1Block)]; + fixed (byte* oDataBytes = outputData) { - 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++) + Bc1Block* oDataBlocks = (Bc1Block*) oDataBytes; + int oDataBlocksLength = outputData.Length / sizeof(Bc1Block); + + for (int i = 0; i < oDataBlocksLength; i++) { - outputBlocks[i] = EncodeBlock(blocks[i], quality); + oDataBlocks[i] = EncodeBlock(blocks[i], quality); } } @@ -301,7 +287,7 @@ private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 { Bc1Block output = new Bc1Block(); - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; output.color0 = color0; output.color1 = color1; @@ -311,13 +297,13 @@ private static Bc1Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 bool hasAlpha = output.HasAlphaOrBlack; - ReadOnlySpan colors = hasAlpha ? - stackalloc ColorRgb24[] { + ColorRgb24[] colors = hasAlpha ? + new ColorRgb24[] { c0, c1, c0 * (1.0 / 2.0) + c1 * (1.0 / 2.0), new ColorRgb24(0, 0, 0) - } : stackalloc ColorRgb24[] { + } : new ColorRgb24[] { c0, c1, c0 * (2.0 / 3.0) + c1 * (1.0 / 3.0), @@ -346,7 +332,7 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { Bc1Block output = new Bc1Block(); - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; bool hasAlpha = rawBlock.HasTransparentPixels(); @@ -376,7 +362,7 @@ private static class Bc1AlphaBlockEncoderBalanced internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; bool hasAlpha = rawBlock.HasTransparentPixels(); @@ -400,8 +386,8 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) Bc1Block best = TryColors(rawBlock, c0, c1, out float bestError); for (int i = 0; i < maxTries; i++) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - + ColorVariationGenerator.Variate565(c0, c1, i, out var newC0, out var newC1); + if (!hasAlpha && newC0.data < newC1.data) { var c = newC0; @@ -439,7 +425,7 @@ private static class Bc1AlphaBlockEncoderSlow internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; bool hasAlpha = rawBlock.HasTransparentPixels(); @@ -464,8 +450,8 @@ internal static Bc1Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) int lastChanged = 0; for (int i = 0; i < maxTries; i++) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - + ColorVariationGenerator.Variate565(c0, c1, i, out var newC0, out var newC1); + if (!hasAlpha && newC0.data < newC1.data) { var c = newC0; diff --git a/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs index 2820452..e84ab77 100644 --- a/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc2BlockEncoder.cs @@ -8,24 +8,17 @@ namespace BCnEncoder.Encoder internal class Bc2BlockEncoder : IBcBlockEncoder { - public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) + public unsafe byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) { - byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()]; - Span outputBlocks = MemoryMarshal.Cast(outputData); - - if (parallel) + byte[] outputData = new byte[blockWidth * blockHeight * sizeof(Bc2Block)]; + fixed (byte* oDataBytes = outputData) { - 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++) + Bc2Block* oDataBlocks = (Bc2Block*)oDataBytes; + int oDataBlocksLength = outputData.Length / sizeof(Bc2Block); + + for (int i = 0; i < oDataBlocksLength; i++) { - outputBlocks[i] = EncodeBlock(blocks[i], quality); + oDataBlocks[i] = EncodeBlock(blocks[i], quality); } } @@ -68,7 +61,7 @@ private static Bc2Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 { Bc2Block output = new Bc2Block(); - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; output.color0 = color0; output.color1 = color1; @@ -76,7 +69,7 @@ private static Bc2Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 var c0 = color0.ToColorRgb24(); var c1 = color1.ToColorRgb24(); - ReadOnlySpan colors = stackalloc ColorRgb24[] { + ColorRgb24[] colors = new ColorRgb24[] { c0, c1, c0 * (2.0 / 3.0) + c1 * (1.0 / 3.0), @@ -105,7 +98,7 @@ private static class Bc2BlockEncoderFast internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; PcaVectors.Create(pixels, out var mean, out var principalAxis); PcaVectors.GetMinMaxColor565(pixels, mean, principalAxis, out var min, out var max); @@ -125,7 +118,7 @@ private static class Bc2BlockEncoderBalanced { internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa); PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); @@ -136,8 +129,8 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) Bc2Block best = TryColors(rawBlock, c0, c1, out float bestError); for (int i = 0; i < maxTries; i++) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - + ColorVariationGenerator.Variate565(c0, c1, i, out var newC0, out var newC1); + var block = TryColors(rawBlock, newC0, newC1, out var error); if (error < bestError) @@ -165,7 +158,7 @@ private static class Bc2BlockEncoderSlow internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa); PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); @@ -185,8 +178,8 @@ internal static Bc2Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) int lastChanged = 0; for (int i = 0; i < maxTries; i++) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - + ColorVariationGenerator.Variate565(c0, c1, i, out var newC0, out var newC1); + if (newC0.data < newC1.data) { var c = newC0; diff --git a/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs index 5957da8..fdc4bc9 100644 --- a/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc3BlockEncoder.cs @@ -9,24 +9,17 @@ namespace BCnEncoder.Encoder internal class Bc3BlockEncoder : IBcBlockEncoder { - public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) + public unsafe byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality 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 + byte[] outputData = new byte[blockWidth * blockHeight * sizeof(Bc3Block)]; + fixed (byte* oDataBytes = outputData) { - for (int i = 0; i < blocks.Length; i++) + Bc3Block* oDataBlocks = (Bc3Block*)oDataBytes; + int oDataBlocksLength = outputData.Length / sizeof(Bc3Block); + + for (int i = 0; i < oDataBlocksLength; i++) { - outputBlocks[i] = EncodeBlock(blocks[i], quality); + oDataBlocks[i] = EncodeBlock(blocks[i], quality); } } @@ -69,7 +62,7 @@ private static Bc3Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 { Bc3Block output = new Bc3Block(); - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; output.color0 = color0; output.color1 = color1; @@ -77,7 +70,7 @@ private static Bc3Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 var c0 = color0.ToColorRgb24(); var c1 = color1.ToColorRgb24(); - ReadOnlySpan colors = stackalloc ColorRgb24[] { + ColorRgb24[] colors = new ColorRgb24[] { c0, c1, c0 * (2.0 / 3.0) + c1 * (1.0 / 3.0), @@ -95,8 +88,54 @@ private static Bc3Block TryColors(RawBlock4X4Rgba32 rawBlock, ColorRgb565 color0 return output; } + private static int SelectAlphaIndices(RawBlock4X4Rgba32 rawBlock, ref Bc3Block block) + { + int cumulativeError = 0; + var a0 = block.Alpha0; + var a1 = block.Alpha1; + byte[] alphas = a0 > a1 ? new 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), + } : new 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.AsArray; + 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; + } + private static Bc3Block FindAlphaValues(Bc3Block colorBlock, RawBlock4X4Rgba32 rawBlock, int variations) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; //Find min and max alpha byte minAlpha = 255; @@ -113,53 +152,11 @@ private static Bc3Block FindAlphaValues(Bc3Block colorBlock, RawBlock4X4Rgba32 r } - 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); + var error = SelectAlphaIndices(rawBlock, ref colorBlock); Debug.Assert(0 == error); return colorBlock; } @@ -167,7 +164,7 @@ int SelectAlphaIndices(ref Bc3Block block) { var best = colorBlock; best.Alpha0 = maxAlpha; best.Alpha1 = minAlpha; - int bestError = SelectAlphaIndices(ref best); + int bestError = SelectAlphaIndices(rawBlock, ref best); if (bestError == 0) { return best; } @@ -178,7 +175,7 @@ int SelectAlphaIndices(ref Bc3Block block) { var block = colorBlock; block.Alpha0 = hasExtremeValues ? a1 : a0; block.Alpha1 = hasExtremeValues ? a0 : a1; - int error = SelectAlphaIndices(ref block); + int error = SelectAlphaIndices(rawBlock, ref block); if (error < bestError) { best = block; bestError = error; @@ -190,7 +187,7 @@ int SelectAlphaIndices(ref Bc3Block block) { var block = colorBlock; block.Alpha0 = hasExtremeValues ? a1 : a0; block.Alpha1 = hasExtremeValues ? a0 : a1; - int error = SelectAlphaIndices(ref block); + int error = SelectAlphaIndices(rawBlock, ref block); if (error < bestError) { best = block; bestError = error; @@ -202,7 +199,7 @@ int SelectAlphaIndices(ref Bc3Block block) { var block = colorBlock; block.Alpha0 = hasExtremeValues ? a1 : a0; block.Alpha1 = hasExtremeValues ? a0 : a1; - int error = SelectAlphaIndices(ref block); + int error = SelectAlphaIndices(rawBlock, ref block); if (error < bestError) { best = block; bestError = error; @@ -214,7 +211,7 @@ int SelectAlphaIndices(ref Bc3Block block) { var block = colorBlock; block.Alpha0 = hasExtremeValues ? a1 : a0; block.Alpha1 = hasExtremeValues ? a0 : a1; - int error = SelectAlphaIndices(ref block); + int error = SelectAlphaIndices(rawBlock, ref block); if (error < bestError) { best = block; bestError = error; @@ -226,7 +223,7 @@ int SelectAlphaIndices(ref Bc3Block block) { var block = colorBlock; block.Alpha0 = hasExtremeValues ? a1 : a0; block.Alpha1 = hasExtremeValues ? a0 : a1; - int error = SelectAlphaIndices(ref block); + int error = SelectAlphaIndices(rawBlock, ref block); if (error < bestError) { best = block; bestError = error; @@ -238,7 +235,7 @@ int SelectAlphaIndices(ref Bc3Block block) { var block = colorBlock; block.Alpha0 = hasExtremeValues ? a1 : a0; block.Alpha1 = hasExtremeValues ? a0 : a1; - int error = SelectAlphaIndices(ref block); + int error = SelectAlphaIndices(rawBlock, ref block); if (error < bestError) { best = block; bestError = error; @@ -263,7 +260,7 @@ private static class Bc3BlockEncoderFast internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; PcaVectors.Create(pixels, out var mean, out var principalAxis); PcaVectors.GetMinMaxColor565(pixels, mean, principalAxis, out var min, out var max); @@ -291,7 +288,7 @@ private static class Bc3BlockEncoderBalanced { internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa); PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); @@ -302,8 +299,8 @@ internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) Bc3Block best = TryColors(rawBlock, c0, c1, out float bestError); for (int i = 0; i < maxTries; i++) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - + ColorVariationGenerator.Variate565(c0, c1, i, out var newC0, out var newC1); + var block = TryColors(rawBlock, newC0, newC1, out var error); if (error < bestError) @@ -331,7 +328,7 @@ private static class Bc3BlockEncoderSlow internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) { - var pixels = rawBlock.AsSpan; + var pixels = rawBlock.AsArray; PcaVectors.Create(pixels, out System.Numerics.Vector3 mean, out System.Numerics.Vector3 pa); PcaVectors.GetMinMaxColor565(pixels, mean, pa, out var min, out var max); @@ -351,8 +348,8 @@ internal static Bc3Block EncodeBlock(RawBlock4X4Rgba32 rawBlock) int lastChanged = 0; for (int i = 0; i < maxTries; i++) { - var (newC0, newC1) = ColorVariationGenerator.Variate565(c0, c1, i); - + ColorVariationGenerator.Variate565(c0, c1, i, out var newC0, out var newC1); + if (newC0.data < newC1.data) { var c = newC0; diff --git a/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs index 456907d..740a665 100644 --- a/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc4BlockEncoder.cs @@ -13,20 +13,17 @@ public Bc4BlockEncoder(bool luminanceAsRed) { this.luminanceAsRed = luminanceAsRed; } - public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, + public unsafe byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality 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); + byte[] outputData = new byte[blockWidth * blockHeight * sizeof(Bc4Block)]; + fixed (byte* oDataBytes = outputData) + { + Bc4Block* oDataBlocks = (Bc4Block*)oDataBytes; + int oDataBlocksLength = outputData.Length / sizeof(Bc4Block); + + for (int i = 0; i < oDataBlocksLength; i++) + { + oDataBlocks[i] = EncodeBlock(blocks[i], quality); } } @@ -36,7 +33,7 @@ public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight private Bc4Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality) { Bc4Block output = new Bc4Block(); byte[] colors = new byte[16]; - var pixels = block.AsSpan; + var pixels = block.AsArray; for (int i = 0; i < 16; i++) { if (luminanceAsRed) { colors[i] = (byte)(new ColorYCbCr(pixels[i]).y * 255); @@ -71,6 +68,55 @@ public DXGI_FORMAT GetDxgiFormat() { } #region Encoding private stuff + + private static int SelectIndices(ref Bc4Block block, byte[] pixels) + { + int cumulativeError = 0; + var c0 = block.Red0; + var c1 = block.Red1; + byte[] colors = c0 > c1 + ? new 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), + } + : new 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.SetRedIndex(i, bestIndex); + cumulativeError += bestError * bestError; + } + + return cumulativeError; + } private static Bc4Block FindRedValues(Bc4Block colorBlock, byte[] pixels, int variations) { @@ -88,57 +134,11 @@ private static Bc4Block FindRedValues(Bc4Block colorBlock, byte[] pixels, int va } } - - 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++) { - 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.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; - var error = SelectIndices(ref colorBlock); + var error = SelectIndices(ref colorBlock, pixels); Debug.Assert(0 == error); return colorBlock; } @@ -146,7 +146,7 @@ int SelectIndices(ref Bc4Block block) { var best = colorBlock; best.Red0 = max; best.Red1 = min; - int bestError = SelectIndices(ref best); + int bestError = SelectIndices(ref best, pixels); if (bestError == 0) { return best; } @@ -158,7 +158,7 @@ int SelectIndices(ref Bc4Block block) { var block = colorBlock; block.Red0 = hasExtremeValues ? c1 : c0; block.Red1 = hasExtremeValues ? c0 : c1; - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels); if (error < bestError) { best = block; bestError = error; @@ -172,7 +172,7 @@ int SelectIndices(ref Bc4Block block) { var block = colorBlock; block.Red0 = hasExtremeValues ? c1 : c0; block.Red1 = hasExtremeValues ? c0 : c1; - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels); if (error < bestError) { best = block; bestError = error; @@ -186,7 +186,7 @@ int SelectIndices(ref Bc4Block block) { var block = colorBlock; block.Red0 = hasExtremeValues ? c1 : c0; block.Red1 = hasExtremeValues ? c0 : c1; - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels); if (error < bestError) { best = block; bestError = error; @@ -200,7 +200,7 @@ int SelectIndices(ref Bc4Block block) { var block = colorBlock; block.Red0 = hasExtremeValues ? c1 : c0; block.Red1 = hasExtremeValues ? c0 : c1; - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels); if (error < bestError) { best = block; bestError = error; @@ -214,7 +214,7 @@ int SelectIndices(ref Bc4Block block) { var block = colorBlock; block.Red0 = hasExtremeValues ? c1 : c0; block.Red1 = hasExtremeValues ? c0 : c1; - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels); if (error < bestError) { best = block; bestError = error; @@ -228,7 +228,7 @@ int SelectIndices(ref Bc4Block block) { var block = colorBlock; block.Red0 = hasExtremeValues ? c1 : c0; block.Red1 = hasExtremeValues ? c0 : c1; - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels); if (error < bestError) { best = block; bestError = error; diff --git a/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs b/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs index de1c28a..e2d25d7 100644 --- a/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs +++ b/BCnEnc.Net/Encoder/Bc5BlockEncoder.cs @@ -8,20 +8,17 @@ namespace BCnEncoder.Encoder { internal class Bc5BlockEncoder : IBcBlockEncoder { - public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) + public unsafe byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) { - byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()]; - Span outputBlocks = MemoryMarshal.Cast(outputData); + byte[] outputData = new byte[blockWidth * blockHeight * sizeof(Bc5Block)]; + fixed (byte* oDataBytes = outputData) + { + Bc5Block* oDataBlocks = (Bc5Block*)oDataBytes; + int oDataBlocksLength = outputData.Length / sizeof(Bc5Block); - 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); + for (int i = 0; i < oDataBlocksLength; i++) + { + oDataBlocks[i] = EncodeBlock(blocks[i], quality); } } @@ -32,7 +29,7 @@ private Bc5Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality Bc5Block output = new Bc5Block(); byte[] reds = new byte[16]; byte[] greens = new byte[16]; - var pixels = block.AsSpan; + var pixels = block.AsArray; for (int i = 0; i < 16; i++) { reds[i] = pixels[i].R; greens[i] = pixels[i].G; @@ -59,43 +56,43 @@ private Bc5Block EncodeBlock(RawBlock4X4Rgba32 block, CompressionQuality quality output = FindValues(output, reds, variations, errorThreshold, - (block, i, idx) => { - block.SetRedIndex(i, idx); - return block; + (blck, i, idx) => { + blck.SetRedIndex(i, idx); + return blck; }, - (block, col) => { - block.Red0 = col; - return block; + (blck, col) => { + blck.Red0 = col; + return blck; }, - (block, col) => { - block.Red1 = col; - return block; + (blck, col) => { + blck.Red1 = col; + return blck; }, - (block) => { - return block.Red0; + (blck) => { + return blck.Red0; }, - (block) => { - return block.Red1; + (blck) => { + return blck.Red1; } ); output = FindValues(output, greens, variations, errorThreshold, - (block, i, idx) => { - block.SetGreenIndex(i, idx); - return block; + (blck, i, idx) => { + blck.SetGreenIndex(i, idx); + return blck; }, - (block, col) => { - block.Green0 = col; - return block; + (blck, col) => { + blck.Green0 = col; + return blck; }, - (block, col) => { - block.Green1 = col; - return block; + (blck, col) => { + blck.Green1 = col; + return blck; }, - (block) => { - return block.Green0; + (blck) => { + return blck.Green0; }, - (block) => { - return block.Green1; + (blck) => { + return blck.Green1; }); return output; } @@ -114,6 +111,61 @@ public DXGI_FORMAT GetDxgiFormat() { #region Encoding private stuff + private static int SelectIndices(ref Bc5Block block, byte[] pixels, + Func indexSetter, + Func col0Getter, + Func col1Getter) + { + int cumulativeError = 0; + //var c0 = block.Red0; + //var c1 = block.Red1; + var c0 = col0Getter(block); + var c1 = col1Getter(block); + byte[] colors = c0 > c1 + ? new 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), + } + : new 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; + } + private static Bc5Block FindValues(Bc5Block colorBlock, byte[] pixels, int variations, int errorThreshold, Func indexSetter, Func col0Setter, @@ -135,62 +187,13 @@ private static Bc5Block FindValues(Bc5Block colorBlock, byte[] pixels, int varia } } - - 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); + var error = SelectIndices(ref colorBlock, pixels, indexSetter, col0Getter, col1Getter); Debug.Assert(0 == error); return colorBlock; } @@ -200,7 +203,7 @@ int SelectIndices(ref Bc5Block block) { //best.Red1 = min; best = col0Setter(best, max); best = col1Setter(best, min); - int bestError = SelectIndices(ref best); + int bestError = SelectIndices(ref best, pixels, indexSetter, col0Getter, col1Getter); if (bestError == 0) { return best; } @@ -214,7 +217,7 @@ int SelectIndices(ref Bc5Block block) { //block.Red1 = hasExtremeValues ? c0 : c1; block = col0Setter(block, hasExtremeValues ? c1 : c0); block = col1Setter(block, hasExtremeValues ? c0 : c1); - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels, indexSetter, col0Getter, col1Getter); if (error < bestError) { best = block; bestError = error; @@ -230,7 +233,7 @@ int SelectIndices(ref Bc5Block block) { //block.Red1 = hasExtremeValues ? c0 : c1; block = col0Setter(block, hasExtremeValues ? c1 : c0); block = col1Setter(block, hasExtremeValues ? c0 : c1); - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels, indexSetter, col0Getter, col1Getter); if (error < bestError) { best = block; bestError = error; @@ -246,7 +249,7 @@ int SelectIndices(ref Bc5Block block) { //block.Red1 = hasExtremeValues ? c0 : c1; block = col0Setter(block, hasExtremeValues ? c1 : c0); block = col1Setter(block, hasExtremeValues ? c0 : c1); - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels, indexSetter, col0Getter, col1Getter); if (error < bestError) { best = block; bestError = error; @@ -262,7 +265,7 @@ int SelectIndices(ref Bc5Block block) { //block.Red1 = hasExtremeValues ? c0 : c1; block = col0Setter(block, hasExtremeValues ? c1 : c0); block = col1Setter(block, hasExtremeValues ? c0 : c1); - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels, indexSetter, col0Getter, col1Getter); if (error < bestError) { best = block; bestError = error; @@ -278,7 +281,7 @@ int SelectIndices(ref Bc5Block block) { //block.Red1 = hasExtremeValues ? c0 : c1; block = col0Setter(block, hasExtremeValues ? c1 : c0); block = col1Setter(block, hasExtremeValues ? c0 : c1); - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels, indexSetter, col0Getter, col1Getter); if (error < bestError) { best = block; bestError = error; @@ -294,7 +297,7 @@ int SelectIndices(ref Bc5Block block) { //block.Red1 = hasExtremeValues ? c0 : c1; block = col0Setter(block, hasExtremeValues ? c1 : c0); block = col1Setter(block, hasExtremeValues ? c0 : c1); - int error = SelectIndices(ref block); + int error = SelectIndices(ref block, pixels, indexSetter, col0Getter, col1Getter); if (error < bestError) { best = block; bestError = error; diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Encoder.cs index cde1c20..e78ea92 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Encoder.cs @@ -9,29 +9,20 @@ namespace BCnEncoder.Encoder.Bc7 internal class Bc7Encoder : IBcBlockEncoder { - public byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) + public unsafe byte[] Encode(RawBlock4X4Rgba32[] blocks, int blockWidth, int blockHeight, CompressionQuality quality, bool parallel) { - byte[] outputData = new byte[blockWidth * blockHeight * Marshal.SizeOf()]; - Span outputBlocks = MemoryMarshal.Cast(outputData); - - - if (parallel) + byte[] outputData = new byte[blockWidth * blockHeight * sizeof(Bc7Block)]; + fixed (byte* oDataBytes = outputData) { - 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++) + Bc7Block* oDataBlocks = (Bc7Block*)oDataBytes; + int oDataBlocksLength = outputData.Length / sizeof(Bc7Block); + + for (int i = 0; i < oDataBlocksLength; i++) { - outputBlocks[i] = EncodeBlock(blocks[i], quality); + oDataBlocks[i] = EncodeBlock(blocks[i], quality); } } - return outputData; } @@ -55,10 +46,10 @@ private static ClusterIndices4X4 CreateClusterIndexBlock(RawBlock4X4Rgba32 raw, ClusterIndices4X4 indexBlock = new ClusterIndices4X4(); - var indices = LinearClustering.ClusterPixels(raw.AsSpan, 4, 4, + var indices = LinearClustering.ClusterPixels(raw.AsArray, 4, 4, numClusters, 1, 10, false); - var output = indexBlock.AsSpan; + var output = indexBlock.AsArray; for (int i = 0; i < output.Length; i++) { output[i] = indices[i]; diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7EncodingHelpers.cs b/BCnEnc.Net/Encoder/Bc7/Bc7EncodingHelpers.cs index 24371cd..ff55fc8 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7EncodingHelpers.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7EncodingHelpers.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Runtime.InteropServices; using BCnEncoder.Shared; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Encoder.Bc7 { @@ -13,26 +12,115 @@ internal struct ClusterIndices4X4 public int i02, i12, i22, i32; public int i03, i13, i23, i33; - public Span AsSpan => MemoryMarshal.CreateSpan(ref i00, 16); + public int[] AsArray => new int[] + { + i00, i10, i20, i30, + i01, i11, i21, i31, + i02, i12, i22, i32, + i03, i13, i23, i33 + }; public int this[int x, int y] { - get => AsSpan[x + y * 4]; - set => AsSpan[x + y * 4] = value; + get + { + switch (x) + { + case 0: + switch (y) + { + case 0: return i00; + case 1: return i01; + case 2: return i02; + case 3: return i03; + } + break; + case 1: + switch (y) + { + case 0: return i10; + case 1: return i11; + case 2: return i12; + case 3: return i13; + } + break; + case 2: + switch (y) + { + case 0: return i20; + case 1: return i21; + case 2: return i22; + case 3: return i23; + } + break; + case 3: + switch (y) + { + case 0: return i30; + case 1: return i31; + case 2: return i32; + case 3: return i33; + } + break; + } + return default; + } + set + { + switch (x) + { + case 0: + switch (y) + { + case 0: i00 = value; break; + case 1: i01 = value; break; + case 2: i02 = value; break; + case 3: i03 = value; break; + } + break; + case 1: + switch (y) + { + case 0: i10 = value; break; + case 1: i11 = value; break; + case 2: i12 = value; break; + case 3: i13 = value; break; + } + break; + case 2: + switch (y) + { + case 0: i20 = value; break; + case 1: i21 = value; break; + case 2: i22 = value; break; + case 3: i23 = value; break; + } + break; + case 3: + switch (y) + { + case 0: i30 = value; break; + case 1: i31 = value; break; + case 2: i32 = value; break; + case 3: i33 = value; break; + } + break; + } + } } public int this[int index] { - get => AsSpan[index]; - set => AsSpan[index] = value; + get => this[index % 4, (int)Math.Floor(index / 4.0)]; + set => this[index % 4, (int)Math.Floor(index / 4.0)] = value; } - + public int NumClusters { get { - var t = AsSpan; - Span clusters = stackalloc int[16]; + var t = AsArray; + int[] clusters = new int[16]; int distinct = 0; for (int i = 0; i < 16; i++) { @@ -64,9 +152,9 @@ public ClusterIndices4X4 Reduce(out int numClusters) { var result = new ClusterIndices4X4(); numClusters = NumClusters; - Span mapKey = stackalloc int[numClusters]; - var indices = AsSpan; - var outIndices = result.AsSpan; + int[] mapKey = new int[numClusters]; + var indices = AsArray; + var outIndices = result.AsArray; int next = 0; for (int i = 0; i < 16; i++) { @@ -97,113 +185,139 @@ internal static class Bc7EncodingHelpers { private static int[] varPatternRAlpha = new int[] { 1, -1, 1, 0, 0, -1, 0, 0, 0, 0 }; private static int[] varPatternRNoAlpha = new int[] { 1, -1, 1, 0, 0, -1, 0, 0 }; - + private static int[] varPatternGAlpha = new int[] { 1, -1, 0, 1, 0, 0, -1, 0, 0, 0 }; private static int[] varPatternGNoAlpha = new int[] { 1, -1, 0, 1, 0, 0, -1, 0 }; - + private static int[] varPatternBAlpha = new int[] { 1, -1, 0, 0, 1, 0, 0, -1, 0, 0 }; private static int[] varPatternBNoAlpha = new int[] { 1, -1, 0, 0, 1, 0, 0, -1 }; - + private static int[] varPatternAAlpha = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 1, -1 }; private static int[] varPatternANoAlpha = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }; - public static bool TypeHasPBits(Bc7BlockType type) => type switch + public static bool TypeHasPBits(Bc7BlockType type) { - Bc7BlockType.Type0 => true, - Bc7BlockType.Type1 => true, - Bc7BlockType.Type3 => true, - Bc7BlockType.Type6 => true, - Bc7BlockType.Type7 => true, - _ => false - }; + switch (type) + { + case (Bc7BlockType.Type0): return true; + case (Bc7BlockType.Type1): return true; + case (Bc7BlockType.Type3): return true; + case (Bc7BlockType.Type6): return true; + case (Bc7BlockType.Type7): return true; + default: return false; + } + } - public static bool TypeHasSharedPBits(Bc7BlockType type) => type switch + public static bool TypeHasSharedPBits(Bc7BlockType type) { - Bc7BlockType.Type1 => true, - _ => false - }; + return type == Bc7BlockType.Type1; + } /// /// Includes PBit /// - public static int GetColorComponentPrecisionWithPBit(Bc7BlockType type) => type switch + public static int GetColorComponentPrecisionWithPBit(Bc7BlockType type) { - Bc7BlockType.Type0 => 5, - Bc7BlockType.Type1 => 7, - Bc7BlockType.Type2 => 5, - Bc7BlockType.Type3 => 8, - Bc7BlockType.Type4 => 5, - Bc7BlockType.Type5 => 7, - Bc7BlockType.Type6 => 8, - Bc7BlockType.Type7 => 6, - _ => 0 - }; + switch (type) + { + case (Bc7BlockType.Type0): return 5; + case (Bc7BlockType.Type1): return 7; + case (Bc7BlockType.Type2): return 5; + case (Bc7BlockType.Type3): return 8; + case (Bc7BlockType.Type4): return 5; + case (Bc7BlockType.Type5): return 7; + case (Bc7BlockType.Type6): return 8; + case (Bc7BlockType.Type7): return 6; + default: return 0; + } + } /// /// Includes PBit /// - public static int GetAlphaComponentPrecisionWithPBit(Bc7BlockType type) => type switch + public static int GetAlphaComponentPrecisionWithPBit(Bc7BlockType type) { - - Bc7BlockType.Type4 => 6, - Bc7BlockType.Type5 => 8, - Bc7BlockType.Type6 => 8, - Bc7BlockType.Type7 => 6, - _ => 0 - }; + switch (type) + { + case + Bc7BlockType.Type4: + return 6; + case + Bc7BlockType.Type5: + return 8; + case + Bc7BlockType.Type6: + return 8; + case + Bc7BlockType.Type7: + return 6; + default: return 0; + } + } /// /// Does not include pBit /// - public static int GetColorComponentPrecision(Bc7BlockType type) => type switch + public static int GetColorComponentPrecision(Bc7BlockType type) { - Bc7BlockType.Type0 => 4, - Bc7BlockType.Type1 => 6, - Bc7BlockType.Type2 => 5, - Bc7BlockType.Type3 => 7, - Bc7BlockType.Type4 => 5, - Bc7BlockType.Type5 => 7, - Bc7BlockType.Type6 => 7, - Bc7BlockType.Type7 => 5, - _ => 0 - }; + switch (type) + { + case Bc7BlockType.Type0: return 4; + case Bc7BlockType.Type1: return 6; + case Bc7BlockType.Type2: return 5; + case Bc7BlockType.Type3: return 7; + case Bc7BlockType.Type4: return 5; + case Bc7BlockType.Type5: return 7; + case Bc7BlockType.Type6: return 7; + case Bc7BlockType.Type7: return 5; + default: return 0; + } + } /// /// Does not include pBit /// - public static int GetAlphaComponentPrecision(Bc7BlockType type) => type switch + public static int GetAlphaComponentPrecision(Bc7BlockType type) { + switch (type) + { + case Bc7BlockType.Type4: return 6; + case Bc7BlockType.Type5: return 8; + case Bc7BlockType.Type6: return 7; + case Bc7BlockType.Type7: return 5; + default: return 0; + } + } - Bc7BlockType.Type4 => 6, - Bc7BlockType.Type5 => 8, - Bc7BlockType.Type6 => 7, - Bc7BlockType.Type7 => 5, - _ => 0 - }; - - public static int GetColorIndexBitCount(Bc7BlockType type, int type4IdxMode = 0) => type switch + public static int GetColorIndexBitCount(Bc7BlockType type, int type4IdxMode = 0) { - Bc7BlockType.Type0 => 3, - Bc7BlockType.Type1 => 3, - Bc7BlockType.Type2 => 2, - Bc7BlockType.Type3 => 2, - Bc7BlockType.Type4 when type4IdxMode == 0 => 2, - Bc7BlockType.Type4 when type4IdxMode == 1 => 3, - Bc7BlockType.Type5 => 2, - Bc7BlockType.Type6 => 4, - Bc7BlockType.Type7 => 2, - _ => 0 - }; - public static int GetAlphaIndexBitCount(Bc7BlockType type, int type4IdxMode = 0) => type switch + switch (type) + { + case Bc7BlockType.Type0: return 3; + case Bc7BlockType.Type1: return 3; + case Bc7BlockType.Type2: return 2; + case Bc7BlockType.Type3: return 2; + case Bc7BlockType.Type4: return type4IdxMode == 0 ? 2 : 3; + case Bc7BlockType.Type5: return 2; + case Bc7BlockType.Type6: return 4; + case Bc7BlockType.Type7: return 2; + default: return 0; + } + + } + + public static int GetAlphaIndexBitCount(Bc7BlockType type, int type4IdxMode = 0) { - Bc7BlockType.Type4 when type4IdxMode == 0 => 3, - Bc7BlockType.Type4 when type4IdxMode == 1 => 2, - Bc7BlockType.Type5 => 2, - Bc7BlockType.Type6 => 4, - Bc7BlockType.Type7 => 2, - _ => 0 - }; + switch (type) + { + case Bc7BlockType.Type4: return type4IdxMode == 0 ? 3 : 2; + case Bc7BlockType.Type5: return 2; + case Bc7BlockType.Type6: return 4; + case Bc7BlockType.Type7: return 2; + default: return 0; + } + } public static void ExpandEndpoints(Bc7BlockType type, ColorRgba32[] endpoints, byte[] pBits) @@ -302,9 +416,9 @@ public static int SelectBest2SubsetPartition(ClusterIndices4X4 reducedIndicesBlo int CalculatePartitionError(int partitionIndex) { int error = 0; - ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[partitionIndex]; - Span subset0 = stackalloc int[numDistinctClusters]; - Span subset1 = stackalloc int[numDistinctClusters]; + int[] partitionTable = Bc7Block.Subsets2PartitionTable[partitionIndex]; + int[] subset0 = new int[numDistinctClusters]; + int[] subset1 = new int[numDistinctClusters]; int max0Idx = 0; int max1Idx = 0; @@ -377,13 +491,13 @@ public static int[] Rank2SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock, { int[] output = Enumerable.Range(0, 64).ToArray(); - + int CalculatePartitionError(int partitionIndex) { int error = 0; - ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[partitionIndex]; - Span subset0 = stackalloc int[numDistinctClusters]; - Span subset1 = stackalloc int[numDistinctClusters]; + int[] partitionTable = Bc7Block.Subsets2PartitionTable[partitionIndex]; + int[] subset0 = new int[numDistinctClusters]; + int[] subset1 = new int[numDistinctClusters]; int max0Idx = 0; int max1Idx = 0; @@ -444,11 +558,11 @@ public static int SelectBest3SubsetPartition(ClusterIndices4X4 reducedIndicesBlo int CalculatePartitionError(int partitionIndex) { int error = 0; - ReadOnlySpan partitionTable = Bc7Block.Subsets3PartitionTable[partitionIndex]; + int[] partitionTable = Bc7Block.Subsets3PartitionTable[partitionIndex]; - Span subset0 = stackalloc int[numDistinctClusters]; - Span subset1 = stackalloc int[numDistinctClusters]; - Span subset2 = stackalloc int[numDistinctClusters]; + int[] subset0 = new int[numDistinctClusters]; + int[] subset1 = new int[numDistinctClusters]; + int[] subset2 = new int[numDistinctClusters]; int max0Idx = 0; int max1Idx = 0; int max2Idx = 0; @@ -539,11 +653,11 @@ public static int[] Rank3SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock, int CalculatePartitionError(int partitionIndex) { int error = 0; - ReadOnlySpan partitionTable = Bc7Block.Subsets3PartitionTable[partitionIndex]; + int[] partitionTable = Bc7Block.Subsets3PartitionTable[partitionIndex]; - Span subset0 = stackalloc int[numDistinctClusters]; - Span subset1 = stackalloc int[numDistinctClusters]; - Span subset2 = stackalloc int[numDistinctClusters]; + int[] subset0 = new int[numDistinctClusters]; + int[] subset1 = new int[numDistinctClusters]; + int[] subset2 = new int[numDistinctClusters]; int max0Idx = 0; int max1Idx = 0; int max2Idx = 0; @@ -613,19 +727,19 @@ public static void GetInitialUnscaledEndpoints(RawBlock4X4Rgba32 block, out Colo out ColorRgba32 ep1) { - var originalPixels = block.AsSpan; + var originalPixels = block.AsArray; PcaVectors.CreateWithAlpha(originalPixels, out var mean, out var pa); - PcaVectors.GetExtremePointsWithAlpha(block.AsSpan, mean, pa, out var min, out var max); + PcaVectors.GetExtremePointsWithAlpha(block.AsArray, mean, pa, out var min, out var max); ep0 = new ColorRgba32((byte)(min.X * 255), (byte)(min.Y * 255), (byte)(min.Z * 255), (byte)(min.W * 255)); ep1 = new ColorRgba32((byte)(max.X * 255), (byte)(max.Y * 255), (byte)(max.Z * 255), (byte)(max.W * 255)); } public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4Rgba32 block, out ColorRgba32 ep0, - out ColorRgba32 ep1, ReadOnlySpan partitionTable, int subsetIndex) + out ColorRgba32 ep1, int[] partitionTable, int subsetIndex) { - var originalPixels = block.AsSpan; + var originalPixels = block.AsArray; int count = 0; for (int i = 0; i < 16; i++) @@ -636,7 +750,7 @@ public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4Rgba32 block, } } - Span subsetColors = stackalloc Rgba32[count]; + Rgba32[] subsetColors = new Rgba32[count]; int next = 0; for (int i = 0; i < 16; i++) { @@ -647,7 +761,7 @@ public static void GetInitialUnscaledEndpointsForSubset(RawBlock4X4Rgba32 block, } PcaVectors.CreateWithAlpha(subsetColors, out var mean, out var pa); - PcaVectors.GetExtremePointsWithAlpha(block.AsSpan, mean, pa, out var min, out var max); + PcaVectors.GetExtremePointsWithAlpha(block.AsArray, mean, pa, out var min, out var max); ep0 = new ColorRgba32((byte)(min.X * 255), (byte)(min.Y * 255), (byte)(min.Z * 255), (byte)(min.W * 255)); ep1 = new ColorRgba32((byte)(max.X * 255), (byte)(max.Y * 255), (byte)(max.Z * 255), (byte)(max.W * 255)); @@ -708,16 +822,16 @@ public static ColorRgba32 InterpolateColor(ColorRgba32 endPointStart, ColorRgba3 byte InterpolateByte(byte e0, byte e1, int index, int indexPrecision) { if (indexPrecision == 0) return e0; - ReadOnlySpan aWeights2 = Bc7Block.colorInterpolationWeights2; - ReadOnlySpan aWeights3 = Bc7Block.colorInterpolationWeights3; - ReadOnlySpan aWeights4 = Bc7Block.colorInterpolationWeights4; - - if(indexPrecision == 2) - return (byte) (((64 - aWeights2[index])* (e0) + aWeights2[index]*(e1) + 32) >> 6); - else if(indexPrecision == 3) - return (byte) (((64 - aWeights3[index])*(e0) + aWeights3[index]*(e1) + 32) >> 6); + byte[] aWeights2 = Bc7Block.colorInterpolationWeights2; + byte[] aWeights3 = Bc7Block.colorInterpolationWeights3; + byte[] aWeights4 = Bc7Block.colorInterpolationWeights4; + + if (indexPrecision == 2) + return (byte)(((64 - aWeights2[index]) * (e0) + aWeights2[index] * (e1) + 32) >> 6); + else if (indexPrecision == 3) + return (byte)(((64 - aWeights3[index]) * (e0) + aWeights3[index] * (e1) + 32) >> 6); else // indexprecision == 4 - return (byte) (((64 - aWeights4[index])*(e0) + aWeights4[index]*(e1) + 32) >> 6); + return (byte)(((64 - aWeights4[index]) * (e0) + aWeights4[index] * (e1) + 32) >> 6); } ColorRgba32 result = new ColorRgba32( @@ -738,7 +852,7 @@ public static void ClampEndpoint(ref ColorRgba32 endpoint, byte colorMax, byte a if (endpoint.a > alphaMax) endpoint.a = alphaMax; } - private static int FindClosestColorIndex(ColorYCbCrAlpha color, ReadOnlySpan colors, out float bestError) + private static int FindClosestColorIndex(ColorYCbCrAlpha color, ColorYCbCrAlpha[] colors, out float bestError) { bestError = color.CalcDistWeighted(colors[0], 4, 2); int bestIndex = 0; @@ -754,7 +868,7 @@ private static int FindClosestColorIndex(ColorYCbCrAlpha color, ReadOnlySpan colors, out float bestError) + private static int FindClosestColorIndex(ColorYCbCr color, ColorYCbCr[] colors, out float bestError) { bestError = color.CalcDistWeighted(colors[0], 4); int bestIndex = 0; @@ -774,7 +888,7 @@ private static int FindClosestColorIndex(ColorYCbCr color, ReadOnlySpan alphas, out float bestError) + private static int FindClosestAlphaIndex(byte alpha, byte[] alphas, out float bestError) { bestError = (alpha - alphas[0]) * (alpha - alphas[0]); int bestIndex = 0; @@ -797,15 +911,15 @@ private static int FindClosestAlphaIndex(byte alpha, ReadOnlySpan alphas, private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw, ColorRgba32 ep0, ColorRgba32 ep1, - ReadOnlySpan partitionTable, int subsetIndex, int type4IdxMode) + int[] partitionTable, int subsetIndex, int type4IdxMode) { int colorIndexPrecision = GetColorIndexBitCount(type, type4IdxMode); int alphaIndexPrecision = GetAlphaIndexBitCount(type, type4IdxMode); if (type == Bc7BlockType.Type4 || type == Bc7BlockType.Type5) { //separate indices for color and alpha - Span colors = stackalloc ColorYCbCr[1 << colorIndexPrecision]; - Span alphas = stackalloc byte[1 << alphaIndexPrecision]; + ColorYCbCr[] colors = new ColorYCbCr[1 << colorIndexPrecision]; + byte[] alphas = new byte[1 << alphaIndexPrecision]; for (int i = 0; i < colors.Length; i++) { @@ -819,7 +933,7 @@ private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw i, 0, alphaIndexPrecision).a; } - var pixels = raw.AsSpan; + var pixels = raw.AsArray; float error = 0; for (int i = 0; i < 16; i++) @@ -836,14 +950,14 @@ private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw } else { - Span colors = stackalloc ColorYCbCrAlpha[1 << colorIndexPrecision]; + ColorYCbCrAlpha[] colors = new ColorYCbCrAlpha[1 << colorIndexPrecision]; for (int i = 0; i < colors.Length; i++) { colors[i] = new ColorYCbCrAlpha(InterpolateColor(ep0, ep1, i, i, colorIndexPrecision, alphaIndexPrecision)); } - var pixels = raw.AsSpan; + var pixels = raw.AsArray; float error = 0; float count = 0; @@ -865,8 +979,8 @@ private static float TrySubsetEndpoints(Bc7BlockType type, RawBlock4X4Rgba32 raw } - public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, ColorRgba32 ep0, ColorRgba32 ep1, ReadOnlySpan partitionTable, int subsetIndex, - Span indicesToFill) + public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, ColorRgba32 ep0, ColorRgba32 ep1, int[] partitionTable, int subsetIndex, + byte[] indicesToFill) { int colorIndexPrecision = GetColorIndexBitCount(type); int alphaIndexPrecision = GetAlphaIndexBitCount(type); @@ -877,14 +991,14 @@ public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, C } else { - Span colors = stackalloc ColorYCbCrAlpha[1 << colorIndexPrecision]; + ColorYCbCrAlpha[] colors = new ColorYCbCrAlpha[1 << colorIndexPrecision]; for (int i = 0; i < colors.Length; i++) { colors[i] = new ColorYCbCrAlpha(InterpolateColor(ep0, ep1, i, i, colorIndexPrecision, alphaIndexPrecision)); } - var pixels = raw.AsSpan; + var pixels = raw.AsArray; for (int i = 0; i < 16; i++) { @@ -903,15 +1017,15 @@ public static void FillSubsetIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, C /// Used for Modes 4 and 5 /// public static void FillAlphaColorIndices(Bc7BlockType type, RawBlock4X4Rgba32 raw, ColorRgba32 ep0, ColorRgba32 ep1, - Span colorIndicesToFill, Span alphaIndicesToFill, int idxMode = 0) + byte[] colorIndicesToFill, byte[] alphaIndicesToFill, int idxMode = 0) { int colorIndexPrecision = GetColorIndexBitCount(type, idxMode); int alphaIndexPrecision = GetAlphaIndexBitCount(type, idxMode); if (type == Bc7BlockType.Type4 || type == Bc7BlockType.Type5) { - Span colors = stackalloc ColorYCbCr[1 << colorIndexPrecision]; - Span alphas = stackalloc byte[1 << alphaIndexPrecision]; + ColorYCbCr[] colors = new ColorYCbCr[1 << colorIndexPrecision]; + byte[] alphas = new byte[1 << alphaIndexPrecision]; for (int i = 0; i < colors.Length; i++) { @@ -925,7 +1039,7 @@ public static void FillAlphaColorIndices(Bc7BlockType type, RawBlock4X4Rgba32 ra i, 0, alphaIndexPrecision).a; } - var pixels = raw.AsSpan; + var pixels = raw.AsArray; for (int i = 0; i < 16; i++) { @@ -945,7 +1059,7 @@ public static void FillAlphaColorIndices(Bc7BlockType type, RawBlock4X4Rgba32 ra } public static void OptimizeSubsetEndpointsWithPBit(Bc7BlockType type, RawBlock4X4Rgba32 raw, ref ColorRgba32 ep0, ref ColorRgba32 ep1, ref byte pBit0, ref byte pBit1, - int variation, ReadOnlySpan partitionTable, int subsetIndex, bool variatePBits, bool variateAlpha, int type4IdxMode = 0) + int variation, int[] partitionTable, int subsetIndex, bool variatePBits, bool variateAlpha, int type4IdxMode = 0) { byte colorMax = (byte)((1 << GetColorComponentPrecision(type)) - 1); @@ -956,16 +1070,16 @@ public static void OptimizeSubsetEndpointsWithPBit(Bc7BlockType type, RawBlock4X ExpandEndpoint(type, ep1, pBit1), partitionTable, subsetIndex, type4IdxMode ); - ReadOnlySpan varPatternR = variateAlpha + int[] varPatternR = variateAlpha ? varPatternRAlpha : varPatternRNoAlpha; - ReadOnlySpan varPatternG = variateAlpha + int[] varPatternG = variateAlpha ? varPatternGAlpha : varPatternGNoAlpha; - ReadOnlySpan varPatternB = variateAlpha + int[] varPatternB = variateAlpha ? varPatternBAlpha : varPatternBNoAlpha; - ReadOnlySpan varPatternA = variateAlpha + int[] varPatternA = variateAlpha ? varPatternAAlpha : varPatternANoAlpha; @@ -1093,8 +1207,8 @@ public static RawBlock4X4Rgba32 RotateBlockColors(RawBlock4X4Rgba32 block, int r } RawBlock4X4Rgba32 rotated = new RawBlock4X4Rgba32(); - var pixels = block.AsSpan; - var output = rotated.AsSpan; + var pixels = block.AsArray; + var output = rotated.AsArray; for (int i = 0; i < 16; i++) { var c = pixels[i]; diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Mode0Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Mode0Encoder.cs index 1469173..c578585 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Mode0Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Mode0Encoder.cs @@ -18,7 +18,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio ColorRgba32[] endpoints = new ColorRgba32[6]; byte[] pBits = new byte[6]; - ReadOnlySpan partitionTable = Bc7Block.Subsets3PartitionTable[bestPartition]; + int[] partitionTable = Bc7Block.Subsets3PartitionTable[bestPartition]; byte[] indices = new byte[16]; diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Mode1Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Mode1Encoder.cs index e8d9a51..c26a1c0 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Mode1Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Mode1Encoder.cs @@ -14,7 +14,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio ColorRgba32[] endpoints = new ColorRgba32[4]; byte[] pBits = new byte[2]; - ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[bestPartition]; + int[] partitionTable = Bc7Block.Subsets2PartitionTable[bestPartition]; byte[] indices = new byte[16]; diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Mode2Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Mode2Encoder.cs index dee4405..7a672b9 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Mode2Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Mode2Encoder.cs @@ -12,7 +12,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio const Bc7BlockType type = Bc7BlockType.Type2; ColorRgba32[] endpoints = new ColorRgba32[6]; - ReadOnlySpan partitionTable = Bc7Block.Subsets3PartitionTable[bestPartition]; + int[] partitionTable = Bc7Block.Subsets3PartitionTable[bestPartition]; byte[] indices = new byte[16]; diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Mode3Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Mode3Encoder.cs index 61dd40b..5bc50da 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Mode3Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Mode3Encoder.cs @@ -13,7 +13,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio ColorRgba32[] endpoints = new ColorRgba32[4]; byte[] pBits = new byte[4]; - ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[bestPartition]; + int[] partitionTable = Bc7Block.Subsets2PartitionTable[bestPartition]; byte[] indices = new byte[16]; diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Mode4Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Mode4Encoder.cs index c25df51..2e1f46d 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Mode4Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Mode4Encoder.cs @@ -6,14 +6,14 @@ namespace BCnEncoder.Encoder.Bc7 internal static class Bc7Mode4Encoder { - private static ReadOnlySpan partitionTable => new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + private static int[] partitionTable = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const int subset = 0; public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation) { var type = Bc7BlockType.Type4; - Span outputs = stackalloc Bc7Block[8]; + Bc7Block[] outputs = new Bc7Block[8]; for (int idxMode = 0; idxMode < 2; idxMode++) { diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Mode5Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Mode5Encoder.cs index 1b5c881..ceed6cb 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Mode5Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Mode5Encoder.cs @@ -5,14 +5,14 @@ namespace BCnEncoder.Encoder.Bc7 { internal static class Bc7Mode5Encoder { - private static ReadOnlySpan partitionTable => new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + private static int[] partitionTable = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const int subset = 0; public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation) { var type = Bc7BlockType.Type5; - Span outputs = stackalloc Bc7Block[4]; + Bc7Block[] outputs = new Bc7Block[4]; for (int rotation = 0; rotation < 4; rotation++) { var rotatedBlock = Bc7EncodingHelpers.RotateBlockColors(block, rotation); diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Mode6Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Mode6Encoder.cs index 844a51d..70e44e5 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Mode6Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Mode6Encoder.cs @@ -18,7 +18,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio ColorRgba32 scaledEp1 = Bc7EncodingHelpers.ScaleDownEndpoint(ep1, Bc7BlockType.Type6, false, out byte pBit1); - ReadOnlySpan partitionTable = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int[] partitionTable = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const int subset = 0; //Force 255 alpha if fully opaque diff --git a/BCnEnc.Net/Encoder/Bc7/Bc7Mode7Encoder.cs b/BCnEnc.Net/Encoder/Bc7/Bc7Mode7Encoder.cs index d58bf5e..2963eb6 100644 --- a/BCnEnc.Net/Encoder/Bc7/Bc7Mode7Encoder.cs +++ b/BCnEnc.Net/Encoder/Bc7/Bc7Mode7Encoder.cs @@ -13,7 +13,7 @@ public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariatio ColorRgba32[] endpoints = new ColorRgba32[4]; byte[] pBits = new byte[4]; - ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[bestPartition]; + int[] partitionTable = Bc7Block.Subsets2PartitionTable[bestPartition]; byte[] indices = new byte[16]; diff --git a/BCnEnc.Net/Encoder/BcEncoder.cs b/BCnEnc.Net/Encoder/BcEncoder.cs index 3424d55..a210fc3 100644 --- a/BCnEnc.Net/Encoder/BcEncoder.cs +++ b/BCnEnc.Net/Encoder/BcEncoder.cs @@ -4,9 +4,6 @@ using System.IO; using BCnEncoder.Encoder.Bc7; using BCnEncoder.Shared; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Encoder { @@ -21,15 +18,6 @@ public class EncoderInputOptions 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. /// @@ -103,36 +91,19 @@ private IBcBlockEncoder GetEncoder(CompressionFormat format) } } - private IRawEncoder GetRawEncoder(CompressionFormat format) - { - switch (format) - { - case CompressionFormat.R: - return new RawLuminanceEncoder(InputOptions.luminanceAsRed); - case CompressionFormat.RG: - return new RawRGEncoder(); - case CompressionFormat.RGB: - return new RawRGBEncoder(); - case CompressionFormat.RGBA: - return new RawRGBAEncoder(); - default: - throw new ArgumentOutOfRangeException(nameof(format), format, null); - } - } - /// /// Encodes all mipmap levels into a ktx or a dds file and writes it to the output stream. /// - public void Encode(Image inputImage, Stream outputStream) + public void Encode(byte[] inputImageRgba, int inputImageWidth, int inputImageHeight, Stream outputStream) { if (OutputOptions.fileFormat == OutputFileFormat.Ktx) { - KtxFile output = EncodeToKtx(inputImage); + KtxFile output = EncodeToKtx(inputImageRgba, inputImageWidth, inputImageHeight); output.Write(outputStream); } else if (OutputOptions.fileFormat == OutputFileFormat.Dds) { - DdsFile output = EncodeToDds(inputImage); + DdsFile output = EncodeToDds(inputImageRgba, inputImageWidth, inputImageHeight); output.Write(outputStream); } } @@ -140,73 +111,37 @@ public void Encode(Image inputImage, Stream outputStream) /// /// Encodes all mipmap levels into a Ktx file. /// - public KtxFile EncodeToKtx(Image inputImage) + public KtxFile EncodeToKtx(byte[] inputImageRgba, int inputImageWidth, int inputImageHeight) { KtxFile output; IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - if (OutputOptions.format.IsCompressedFormat()) + + compressedEncoder = GetEncoder(OutputOptions.format); + if (compressedEncoder == null) { - compressedEncoder = GetEncoder(OutputOptions.format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); - } - output = new KtxFile( - KtxHeader.InitializeCompressed(inputImage.Width, inputImage.Height, - compressedEncoder.GetInternalFormat(), - compressedEncoder.GetBaseInternalFormat())); + throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.format); - output = new KtxFile( - KtxHeader.InitializeUncompressed(inputImage.Width, inputImage.Height, - uncompressedEncoder.GetGlType(), - uncompressedEncoder.GetGlFormat(), - uncompressedEncoder.GetGlTypeSize(), - uncompressedEncoder.GetInternalFormat(), - uncompressedEncoder.GetBaseInternalFormat())); + output = new KtxFile( + KtxHeader.InitializeCompressed(inputImageWidth, inputImageHeight, + compressedEncoder.GetInternalFormat(), + compressedEncoder.GetBaseInternalFormat())); - } + uint numMipMaps = 1; - uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; - if (!OutputOptions.generateMipMaps) - { - numMipMaps = 1; - } - - var mipChain = MipMapper.GenerateMipChain(inputImage, ref numMipMaps); - - for (int i = 0; i < numMipMaps; i++) + byte[] encoded = null; + if (OutputOptions.format.IsCompressedFormat()) { - byte[] encoded = null; - if (OutputOptions.format.IsCompressedFormat()) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[i].Frames[0], out int blocksWidth, out int blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, - !Debugger.IsAttached && Options.multiThreaded); - } - else - { - if (!mipChain[i].TryGetSinglePixelSpan(out var mipPixels)) { - throw new Exception("Cannot get pixel span."); - } - encoded = uncompressedEncoder.Encode(mipPixels); - } - - output.MipMaps.Add(new KtxMipmap((uint)encoded.Length, - (uint)inputImage.Width, - (uint)inputImage.Height, 1)); - output.MipMaps[i].Faces[0] = new KtxMipFace(encoded, - (uint)inputImage.Width, - (uint)inputImage.Height); + var blocks = ImageToBlocks.ImageTo4X4(inputImageRgba, inputImageWidth, inputImageHeight, out int blocksWidth, out int blocksHeight); + encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, + !Debugger.IsAttached && Options.multiThreaded); } - foreach (var image in mipChain) - { - image.Dispose(); - } + output.MipMaps.Add(new KtxMipmap((uint)encoded.Length, + (uint)inputImageWidth, + (uint)inputImageHeight, 1)); + output.MipMaps[0].Faces[0] = new KtxMipFace(encoded, + (uint)inputImageWidth, + (uint)inputImageHeight); output.Header.NumberOfFaces = 1; output.Header.NumberOfMipmapLevels = numMipMaps; @@ -215,79 +150,43 @@ public KtxFile EncodeToKtx(Image inputImage) } /// - /// Encodes all mipmap levels into a Ktx file. + /// Encodes Rgba image into a Dds file. /// - public DdsFile EncodeToDds(Image inputImage) + public DdsFile EncodeToDds(byte[] inputImageRgba, int inputImageWidth, int inputImageHeight) { DdsFile output; IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - if (OutputOptions.format.IsCompressedFormat()) - { - compressedEncoder = GetEncoder(OutputOptions.format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); - } - - var (ddsHeader, dxt10Header) = DdsHeader.InitializeCompressed(inputImage.Width, inputImage.Height, - compressedEncoder.GetDxgiFormat()); - output = new DdsFile(ddsHeader, dxt10Header); - - if (OutputOptions.ddsBc1WriteAlphaFlag && - OutputOptions.format == CompressionFormat.BC1WithAlpha) - { - output.Header.ddsPixelFormat.dwFlags |= PixelFormatFlags.DDPF_ALPHAPIXELS; - } - } - else + compressedEncoder = GetEncoder(OutputOptions.format); + if (compressedEncoder == null) { - uncompressedEncoder = GetRawEncoder(OutputOptions.format); - var ddsHeader = DdsHeader.InitializeUncompressed(inputImage.Width, inputImage.Height, - uncompressedEncoder.GetDxgiFormat()); - output = new DdsFile(ddsHeader); + throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); } - uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; - if (!OutputOptions.generateMipMaps) + DdsHeaderDxt10 dxt10Header; + var ddsHeader = DdsHeader.InitializeCompressed(inputImageWidth, inputImageHeight, + compressedEncoder.GetDxgiFormat(), out dxt10Header); + + output = new DdsFile(ddsHeader, dxt10Header); + + if (OutputOptions.ddsBc1WriteAlphaFlag && + OutputOptions.format == CompressionFormat.BC1WithAlpha) { - numMipMaps = 1; + output.Header.ddsPixelFormat.dwFlags |= PixelFormatFlags.DDPF_ALPHAPIXELS; } - var mipChain = MipMapper.GenerateMipChain(inputImage, ref numMipMaps); + uint numMipMaps = 1; - for (int mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded = null; - if (OutputOptions.format.IsCompressedFormat()) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip].Frames[0], out int blocksWidth, out int blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, - !Debugger.IsAttached && Options.multiThreaded); - } - else - { - if (!mipChain[mip].TryGetSinglePixelSpan(out var mipPixels)) { - throw new Exception("Cannot get pixel span."); - } - encoded = uncompressedEncoder.Encode(mipPixels); - } - - if (mip == 0) - { - output.Faces.Add(new DdsFace((uint)inputImage.Width, (uint)inputImage.Height, - (uint)encoded.Length, (int)numMipMaps)); - } - - output.Faces[0].MipMaps[mip] = new DdsMipMap(encoded, - (uint)inputImage.Width, - (uint)inputImage.Height); - } + var blocks = ImageToBlocks.ImageTo4X4(inputImageRgba, inputImageWidth, inputImageHeight, out int blocksWidth, out int blocksHeight); + var encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, + !Debugger.IsAttached && Options.multiThreaded); - foreach (var image in mipChain) - { - image.Dispose(); - } + output.Faces.Add(new DdsFace((uint)inputImageWidth, (uint)inputImageHeight, + (uint)encoded.Length, (int)numMipMaps)); + + + output.Faces[0].MipMaps[0] = new DdsMipMap(encoded, + (uint)inputImageWidth, + (uint)inputImageHeight); output.Header.dwMipMapCount = numMipMaps; if (numMipMaps > 1) @@ -299,363 +198,324 @@ public DdsFile EncodeToDds(Image inputImage) } /// - /// Encodes all mipmap levels into a list of byte buffers. + /// Encodes an RGBA byte buffer to raw compressed bytes /// - public List EncodeToRawBytes(Image inputImage) + public byte[] EncodeToRawBytes(byte[] inputImageRgba, int inputImageWidth, int inputImageHeight, out int outputImageWidth, out int outputImageHeight) { - List output = new List(); - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - if (OutputOptions.format.IsCompressedFormat()) - { - compressedEncoder = GetEncoder(OutputOptions.format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); - } - - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.format); - - } - - uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; - if (!OutputOptions.generateMipMaps) + IBcBlockEncoder compressedEncoder = GetEncoder(OutputOptions.format); + if (compressedEncoder == null) { - numMipMaps = 1; + throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); } - var mipChain = MipMapper.GenerateMipChain(inputImage, ref numMipMaps); + var blocks = ImageToBlocks.ImageTo4X4(inputImageRgba, inputImageWidth, inputImageHeight, out int blocksWidth, out int blocksHeight); + var encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, + !Debugger.IsAttached && Options.multiThreaded); + outputImageWidth = blocksWidth * 4; + outputImageHeight = blocksHeight * 4; - for (int i = 0; i < numMipMaps; i++) - { - byte[] encoded = null; - if (OutputOptions.format.IsCompressedFormat()) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[i].Frames[0], out int blocksWidth, out int blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, - !Debugger.IsAttached && Options.multiThreaded); - } - else - { - if (!mipChain[i].TryGetSinglePixelSpan(out var mipPixels)) { - throw new Exception("Cannot get pixel span."); - } - encoded = uncompressedEncoder.Encode(mipPixels); - } - - output.Add(encoded); - } - - foreach (var image in mipChain) - { - image.Dispose(); - } - - return output; + return encoded; } /// /// Encodes a single mip level of the input image to a byte buffer. /// - public byte[] EncodeToRawBytes(Image inputImage, int mipLevel, out int mipWidth, out int mipHeight) - { - if (mipLevel < 0) - { - throw new ArgumentException($"{nameof(mipLevel)} cannot be less than zero."); - } - - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - if (OutputOptions.format.IsCompressedFormat()) - { - compressedEncoder = GetEncoder(OutputOptions.format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); - } - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.format); - - } - - uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; - if (!OutputOptions.generateMipMaps) - { - numMipMaps = 1; - } - - var mipChain = MipMapper.GenerateMipChain(inputImage, ref numMipMaps); - - if (mipLevel > numMipMaps - 1) - { - foreach (var image in mipChain) - { - image.Dispose(); - } - throw new ArgumentException($"{nameof(mipLevel)} cannot be more than number of mipmaps"); - } - - byte[] encoded = null; - if (OutputOptions.format.IsCompressedFormat()) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mipLevel].Frames[0], out int blocksWidth, out int blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, - !Debugger.IsAttached && Options.multiThreaded); - } - else - { - if (!mipChain[mipLevel].TryGetSinglePixelSpan(out var mipPixels)) { - throw new Exception("Cannot get pixel span."); - } - encoded = uncompressedEncoder.Encode(mipPixels); - } - - mipWidth = mipChain[mipLevel].Width; - mipHeight = mipChain[mipLevel].Height; - - foreach (var image in mipChain) - { - image.Dispose(); - } - - return encoded; - } + //public byte[] EncodeToRawBytes(Image inputImage, int mipLevel, out int mipWidth, out int mipHeight) + //{ + // if (mipLevel < 0) + // { + // throw new ArgumentException($"{nameof(mipLevel)} cannot be less than zero."); + // } + + // IBcBlockEncoder compressedEncoder = null; + // IRawEncoder uncompressedEncoder = null; + // if (OutputOptions.format.IsCompressedFormat()) + // { + // compressedEncoder = GetEncoder(OutputOptions.format); + // if (compressedEncoder == null) + // { + // throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); + // } + // } + // else + // { + // uncompressedEncoder = GetRawEncoder(OutputOptions.format); + + // } + + // uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; + // if (!OutputOptions.generateMipMaps) + // { + // numMipMaps = 1; + // } + + // var mipChain = MipMapper.GenerateMipChain(inputImage, ref numMipMaps); + + // if (mipLevel > numMipMaps - 1) + // { + // foreach (var image in mipChain) + // { + // image.Dispose(); + // } + // throw new ArgumentException($"{nameof(mipLevel)} cannot be more than number of mipmaps"); + // } + + // byte[] encoded = null; + // if (OutputOptions.format.IsCompressedFormat()) + // { + // var blocks = ImageToBlocks.ImageTo4X4(mipChain[mipLevel].Frames[0], out int blocksWidth, out int blocksHeight); + // encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, + // !Debugger.IsAttached && Options.multiThreaded); + // } + // else + // { + // if (!mipChain[mipLevel].TryGetSinglePixelSpan(out var mipPixels)) { + // throw new Exception("Cannot get pixel span."); + // } + // encoded = uncompressedEncoder.Encode(mipPixels); + // } + + // mipWidth = mipChain[mipLevel].Width; + // mipHeight = mipChain[mipLevel].Height; + + // foreach (var image in mipChain) + // { + // image.Dispose(); + // } + + // return encoded; + //} /// /// Encodes all cubemap faces and mipmap levels into Ktx file and writes it to the output stream. /// Order is +X, -X, +Y, -Y, +Z, -Z /// - public void EncodeCubeMap(Image right, Image left, Image top, Image down, - Image back, Image front, Stream outputStream) - { - if (OutputOptions.fileFormat == OutputFileFormat.Ktx) - { - KtxFile output = EncodeCubeMapToKtx(right, left, top, down, back, front); - output.Write(outputStream); - } - else if (OutputOptions.fileFormat == OutputFileFormat.Dds) - { - DdsFile output = EncodeCubeMapToDds(right, left, top, down, back, front); - output.Write(outputStream); - } - } + //public void EncodeCubeMap(Image right, Image left, Image top, Image down, + // Image back, Image front, Stream outputStream) + //{ + // if (OutputOptions.fileFormat == OutputFileFormat.Ktx) + // { + // KtxFile output = EncodeCubeMapToKtx(right, left, top, down, back, front); + // output.Write(outputStream); + // } + // else if (OutputOptions.fileFormat == OutputFileFormat.Dds) + // { + // DdsFile output = EncodeCubeMapToDds(right, left, top, down, back, front); + // output.Write(outputStream); + // } + //} /// /// Encodes all cubemap faces and mipmap levels into a Ktx file. /// Order is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. /// - public KtxFile EncodeCubeMapToKtx(Image right, Image left, Image top, Image down, - Image back, Image front) - { - KtxFile output; - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - - if (right.Width != left.Width || right.Width != top.Width || right.Width != down.Width - || right.Width != back.Width || right.Width != front.Width || - right.Height != left.Height || right.Height != top.Height || right.Height != down.Height - || right.Height != back.Height || right.Height != front.Height) - { - throw new ArgumentException("All input images of a cubemap should be the same size."); - } - - Image[] faces = new[] { right, left, top, down, back, front }; - - if (OutputOptions.format.IsCompressedFormat()) - { - compressedEncoder = GetEncoder(OutputOptions.format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); - } - output = new KtxFile( - KtxHeader.InitializeCompressed(right.Width, right.Height, - compressedEncoder.GetInternalFormat(), - compressedEncoder.GetBaseInternalFormat())); - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.format); - output = new KtxFile( - KtxHeader.InitializeUncompressed(right.Width, right.Height, - uncompressedEncoder.GetGlType(), - uncompressedEncoder.GetGlFormat(), - uncompressedEncoder.GetGlTypeSize(), - uncompressedEncoder.GetInternalFormat(), - uncompressedEncoder.GetBaseInternalFormat())); - - } - uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; - if (!OutputOptions.generateMipMaps) - { - numMipMaps = 1; - } - - uint mipLength = MipMapper.CalculateMipChainLength(right.Width, right.Height, numMipMaps); - for (uint i = 0; i < mipLength; i++) - { - output.MipMaps.Add(new KtxMipmap(0, 0, 0, (uint)faces.Length)); - } - - for (int f = 0; f < faces.Length; f++) - { - - var mipChain = MipMapper.GenerateMipChain(faces[f], ref numMipMaps); - - for (int i = 0; i < numMipMaps; i++) - { - byte[] encoded = null; - if (OutputOptions.format.IsCompressedFormat()) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[i].Frames[0], out int blocksWidth, out int blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, - !Debugger.IsAttached && Options.multiThreaded); - } - else - { - if (!mipChain[i].TryGetSinglePixelSpan(out var mipPixels)) { - throw new Exception("Cannot get pixel span."); - } - encoded = uncompressedEncoder.Encode(mipPixels); - } - - if (f == 0) - { - output.MipMaps[i] = new KtxMipmap((uint)encoded.Length, - (uint)mipChain[i].Width, - (uint)mipChain[i].Height, (uint)faces.Length); - } - - output.MipMaps[i].Faces[f] = new KtxMipFace(encoded, - (uint)mipChain[i].Width, - (uint)mipChain[i].Height); - } - - foreach (var image in mipChain) - { - image.Dispose(); - } - } - - output.Header.NumberOfFaces = (uint)faces.Length; - output.Header.NumberOfMipmapLevels = mipLength; - - return output; - } + //public KtxFile EncodeCubeMapToKtx(Image right, Image left, Image top, Image down, + // Image back, Image front) + //{ + // KtxFile output; + // IBcBlockEncoder compressedEncoder = null; + // IRawEncoder uncompressedEncoder = null; + + // if (right.Width != left.Width || right.Width != top.Width || right.Width != down.Width + // || right.Width != back.Width || right.Width != front.Width || + // right.Height != left.Height || right.Height != top.Height || right.Height != down.Height + // || right.Height != back.Height || right.Height != front.Height) + // { + // throw new ArgumentException("All input images of a cubemap should be the same size."); + // } + + // Image[] faces = new[] { right, left, top, down, back, front }; + + // if (OutputOptions.format.IsCompressedFormat()) + // { + // compressedEncoder = GetEncoder(OutputOptions.format); + // if (compressedEncoder == null) + // { + // throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); + // } + // output = new KtxFile( + // KtxHeader.InitializeCompressed(right.Width, right.Height, + // compressedEncoder.GetInternalFormat(), + // compressedEncoder.GetBaseInternalFormat())); + // } + // else + // { + // uncompressedEncoder = GetRawEncoder(OutputOptions.format); + // output = new KtxFile( + // KtxHeader.InitializeUncompressed(right.Width, right.Height, + // uncompressedEncoder.GetGlType(), + // uncompressedEncoder.GetGlFormat(), + // uncompressedEncoder.GetGlTypeSize(), + // uncompressedEncoder.GetInternalFormat(), + // uncompressedEncoder.GetBaseInternalFormat())); + + // } + // uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; + // if (!OutputOptions.generateMipMaps) + // { + // numMipMaps = 1; + // } + + // uint mipLength = MipMapper.CalculateMipChainLength(right.Width, right.Height, numMipMaps); + // for (uint i = 0; i < mipLength; i++) + // { + // output.MipMaps.Add(new KtxMipmap(0, 0, 0, (uint)faces.Length)); + // } + + // for (int f = 0; f < faces.Length; f++) + // { + + // var mipChain = MipMapper.GenerateMipChain(faces[f], ref numMipMaps); + + // for (int i = 0; i < numMipMaps; i++) + // { + // byte[] encoded = null; + // if (OutputOptions.format.IsCompressedFormat()) + // { + // var blocks = ImageToBlocks.ImageTo4X4(mipChain[i].Frames[0], out int blocksWidth, out int blocksHeight); + // encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, + // !Debugger.IsAttached && Options.multiThreaded); + // } + // else + // { + // if (!mipChain[i].TryGetSinglePixelSpan(out var mipPixels)) { + // throw new Exception("Cannot get pixel span."); + // } + // encoded = uncompressedEncoder.Encode(mipPixels); + // } + + // if (f == 0) + // { + // output.MipMaps[i] = new KtxMipmap((uint)encoded.Length, + // (uint)mipChain[i].Width, + // (uint)mipChain[i].Height, (uint)faces.Length); + // } + + // output.MipMaps[i].Faces[f] = new KtxMipFace(encoded, + // (uint)mipChain[i].Width, + // (uint)mipChain[i].Height); + // } + + // foreach (var image in mipChain) + // { + // image.Dispose(); + // } + // } + + // output.Header.NumberOfFaces = (uint)faces.Length; + // output.Header.NumberOfMipmapLevels = mipLength; + + // return output; + //} /// /// Encodes all cubemap faces and mipmap levels into a Dds file. /// Order is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. /// - public DdsFile EncodeCubeMapToDds(Image right, Image left, Image top, Image down, - Image back, Image front) - { - DdsFile output; - IBcBlockEncoder compressedEncoder = null; - IRawEncoder uncompressedEncoder = null; - - if (right.Width != left.Width || right.Width != top.Width || right.Width != down.Width - || right.Width != back.Width || right.Width != front.Width || - right.Height != left.Height || right.Height != top.Height || right.Height != down.Height - || right.Height != back.Height || right.Height != front.Height) - { - throw new ArgumentException("All input images of a cubemap should be the same size."); - } - - Image[] faces = new[] { right, left, top, down, back, front }; - - if (OutputOptions.format.IsCompressedFormat()) - { - compressedEncoder = GetEncoder(OutputOptions.format); - if (compressedEncoder == null) - { - throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); - } - - var (ddsHeader, dxt10Header) = DdsHeader.InitializeCompressed(right.Width, right.Height, - compressedEncoder.GetDxgiFormat()); - output = new DdsFile(ddsHeader, dxt10Header); - - if (OutputOptions.ddsBc1WriteAlphaFlag && - OutputOptions.format == CompressionFormat.BC1WithAlpha) - { - output.Header.ddsPixelFormat.dwFlags |= PixelFormatFlags.DDPF_ALPHAPIXELS; - } - } - else - { - uncompressedEncoder = GetRawEncoder(OutputOptions.format); - var ddsHeader = DdsHeader.InitializeUncompressed(right.Width, right.Height, - uncompressedEncoder.GetDxgiFormat()); - output = new DdsFile(ddsHeader); - } - - uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; - if (!OutputOptions.generateMipMaps) - { - numMipMaps = 1; - } - - for (int f = 0; f < faces.Length; f++) - { - - var mipChain = MipMapper.GenerateMipChain(faces[f], ref numMipMaps); - - - for (int mip = 0; mip < numMipMaps; mip++) - { - byte[] encoded = null; - if (OutputOptions.format.IsCompressedFormat()) - { - var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip].Frames[0], out int blocksWidth, out int blocksHeight); - encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, - !Debugger.IsAttached && Options.multiThreaded); - } - else - { - if (!mipChain[mip].TryGetSinglePixelSpan(out var mipPixels)) { - throw new Exception("Cannot get pixel span."); - } - encoded = uncompressedEncoder.Encode(mipPixels); - } - - if (mip == 0) - { - output.Faces.Add(new DdsFace((uint)mipChain[mip].Width, (uint)mipChain[mip].Height, - (uint)encoded.Length, mipChain.Count)); - } - - output.Faces[f].MipMaps[mip] = new DdsMipMap(encoded, - (uint)mipChain[mip].Width, - (uint)mipChain[mip].Height); - } - - foreach (var image in mipChain) - { - image.Dispose(); - } - } - - output.Header.dwCaps |= HeaderCaps.DDSCAPS_COMPLEX; - output.Header.dwMipMapCount = numMipMaps; - if (numMipMaps > 1) - { - output.Header.dwCaps |= HeaderCaps.DDSCAPS_MIPMAP; - } - output.Header.dwCaps2 |= HeaderCaps2.DDSCAPS2_CUBEMAP | - HeaderCaps2.DDSCAPS2_CUBEMAP_POSITIVEX | - HeaderCaps2.DDSCAPS2_CUBEMAP_NEGATIVEX | - HeaderCaps2.DDSCAPS2_CUBEMAP_POSITIVEY | - HeaderCaps2.DDSCAPS2_CUBEMAP_NEGATIVEY | - HeaderCaps2.DDSCAPS2_CUBEMAP_POSITIVEZ | - HeaderCaps2.DDSCAPS2_CUBEMAP_NEGATIVEZ; - - return output; - } + //public DdsFile EncodeCubeMapToDds(Image right, Image left, Image top, Image down, + // Image back, Image front) + //{ + // DdsFile output; + // IBcBlockEncoder compressedEncoder = null; + // IRawEncoder uncompressedEncoder = null; + + // if (right.Width != left.Width || right.Width != top.Width || right.Width != down.Width + // || right.Width != back.Width || right.Width != front.Width || + // right.Height != left.Height || right.Height != top.Height || right.Height != down.Height + // || right.Height != back.Height || right.Height != front.Height) + // { + // throw new ArgumentException("All input images of a cubemap should be the same size."); + // } + + // Image[] faces = new[] { right, left, top, down, back, front }; + + // if (OutputOptions.format.IsCompressedFormat()) + // { + // compressedEncoder = GetEncoder(OutputOptions.format); + // if (compressedEncoder == null) + // { + // throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); + // } + + // var (ddsHeader, dxt10Header) = DdsHeader.InitializeCompressed(right.Width, right.Height, + // compressedEncoder.GetDxgiFormat()); + // output = new DdsFile(ddsHeader, dxt10Header); + + // if (OutputOptions.ddsBc1WriteAlphaFlag && + // OutputOptions.format == CompressionFormat.BC1WithAlpha) + // { + // output.Header.ddsPixelFormat.dwFlags |= PixelFormatFlags.DDPF_ALPHAPIXELS; + // } + // } + // else + // { + // uncompressedEncoder = GetRawEncoder(OutputOptions.format); + // var ddsHeader = DdsHeader.InitializeUncompressed(right.Width, right.Height, + // uncompressedEncoder.GetDxgiFormat()); + // output = new DdsFile(ddsHeader); + // } + + // uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; + // if (!OutputOptions.generateMipMaps) + // { + // numMipMaps = 1; + // } + + // for (int f = 0; f < faces.Length; f++) + // { + + // var mipChain = MipMapper.GenerateMipChain(faces[f], ref numMipMaps); + + + // for (int mip = 0; mip < numMipMaps; mip++) + // { + // byte[] encoded = null; + // if (OutputOptions.format.IsCompressedFormat()) + // { + // var blocks = ImageToBlocks.ImageTo4X4(mipChain[mip].Frames[0], out int blocksWidth, out int blocksHeight); + // encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, + // !Debugger.IsAttached && Options.multiThreaded); + // } + // else + // { + // if (!mipChain[mip].TryGetSinglePixelSpan(out var mipPixels)) { + // throw new Exception("Cannot get pixel span."); + // } + // encoded = uncompressedEncoder.Encode(mipPixels); + // } + + // if (mip == 0) + // { + // output.Faces.Add(new DdsFace((uint)mipChain[mip].Width, (uint)mipChain[mip].Height, + // (uint)encoded.Length, mipChain.Count)); + // } + + // output.Faces[f].MipMaps[mip] = new DdsMipMap(encoded, + // (uint)mipChain[mip].Width, + // (uint)mipChain[mip].Height); + // } + + // foreach (var image in mipChain) + // { + // image.Dispose(); + // } + // } + + // output.Header.dwCaps |= HeaderCaps.DDSCAPS_COMPLEX; + // output.Header.dwMipMapCount = numMipMaps; + // if (numMipMaps > 1) + // { + // output.Header.dwCaps |= HeaderCaps.DDSCAPS_MIPMAP; + // } + // output.Header.dwCaps2 |= HeaderCaps2.DDSCAPS2_CUBEMAP | + // HeaderCaps2.DDSCAPS2_CUBEMAP_POSITIVEX | + // HeaderCaps2.DDSCAPS2_CUBEMAP_NEGATIVEX | + // HeaderCaps2.DDSCAPS2_CUBEMAP_POSITIVEY | + // HeaderCaps2.DDSCAPS2_CUBEMAP_NEGATIVEY | + // HeaderCaps2.DDSCAPS2_CUBEMAP_POSITIVEZ | + // HeaderCaps2.DDSCAPS2_CUBEMAP_NEGATIVEZ; + + // return output; + //} } } diff --git a/BCnEnc.Net/Encoder/ColorChooser.cs b/BCnEnc.Net/Encoder/ColorChooser.cs index ae4ac45..8f0429d 100644 --- a/BCnEnc.Net/Encoder/ColorChooser.cs +++ b/BCnEnc.Net/Encoder/ColorChooser.cs @@ -1,27 +1,26 @@ using System; using BCnEncoder.Shared; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Encoder { internal static class ColorChooser { - public static int ChooseClosestColor4(ReadOnlySpan colors, Rgba32 color, float rWeight, float gWeight, float bWeight, out float error) + public static int ChooseClosestColor4(ColorRgb24[] colors, Rgba32 color, float rWeight, float gWeight, float bWeight, out float error) { - ReadOnlySpan d = stackalloc float[4] { - MathF.Abs(colors[0].r - color.R) * rWeight - + MathF.Abs(colors[0].g - color.G) * gWeight - + MathF.Abs(colors[0].b - color.B) * bWeight, - MathF.Abs(colors[1].r - color.R) * rWeight - + MathF.Abs(colors[1].g - color.G) * gWeight - + MathF.Abs(colors[1].b - color.B) * bWeight, - MathF.Abs(colors[2].r - color.R) * rWeight - + MathF.Abs(colors[2].g - color.G) * gWeight - + MathF.Abs(colors[2].b - color.B) * bWeight, - MathF.Abs(colors[3].r - color.R) * rWeight - + MathF.Abs(colors[3].g - color.G) * gWeight - + MathF.Abs(colors[3].b - color.B) * bWeight, + float[] d = new float[4] { + Math.Abs(colors[0].r - color.R) * rWeight + + Math.Abs(colors[0].g - color.G) * gWeight + + Math.Abs(colors[0].b - color.B) * bWeight, + Math.Abs(colors[1].r - color.R) * rWeight + + Math.Abs(colors[1].g - color.G) * gWeight + + Math.Abs(colors[1].b - color.B) * bWeight, + Math.Abs(colors[2].r - color.R) * rWeight + + Math.Abs(colors[2].g - color.G) * gWeight + + Math.Abs(colors[2].b - color.B) * bWeight, + Math.Abs(colors[3].r - color.R) * rWeight + + Math.Abs(colors[3].g - color.G) * gWeight + + Math.Abs(colors[3].b - color.B) * bWeight, }; int b0 = d[0] > d[3] ? 1 : 0; @@ -40,7 +39,7 @@ public static int ChooseClosestColor4(ReadOnlySpan colors, Rgba32 co } - public static int ChooseClosestColor4AlphaCutoff(ReadOnlySpan colors, Rgba32 color, float rWeight, float gWeight, float bWeight, int alphaCutoff, bool hasAlpha, out float error) + public static int ChooseClosestColor4AlphaCutoff(ColorRgb24[] colors, Rgba32 color, float rWeight, float gWeight, float bWeight, int alphaCutoff, bool hasAlpha, out float error) { if (hasAlpha && color.A < alphaCutoff) @@ -49,21 +48,21 @@ public static int ChooseClosestColor4AlphaCutoff(ReadOnlySpan colors return 3; } - ReadOnlySpan d = stackalloc float[4] { - MathF.Abs(colors[0].r - color.R) * rWeight - + MathF.Abs(colors[0].g - color.G) * gWeight - + MathF.Abs(colors[0].b - color.B) * bWeight, - MathF.Abs(colors[1].r - color.R) * rWeight - + MathF.Abs(colors[1].g - color.G) * gWeight - + MathF.Abs(colors[1].b - color.B) * bWeight, - MathF.Abs(colors[2].r - color.R) * rWeight - + MathF.Abs(colors[2].g - color.G) * gWeight - + MathF.Abs(colors[2].b - color.B) * bWeight, + float[] d = new float[4] { + Math.Abs(colors[0].r - color.R) * rWeight + + Math.Abs(colors[0].g - color.G) * gWeight + + Math.Abs(colors[0].b - color.B) * bWeight, + Math.Abs(colors[1].r - color.R) * rWeight + + Math.Abs(colors[1].g - color.G) * gWeight + + Math.Abs(colors[1].b - color.B) * bWeight, + Math.Abs(colors[2].r - color.R) * rWeight + + Math.Abs(colors[2].g - color.G) * gWeight + + Math.Abs(colors[2].b - color.B) * bWeight, hasAlpha ? 999 : - MathF.Abs(colors[3].r - color.R) * rWeight - + MathF.Abs(colors[3].g - color.G) * gWeight - + MathF.Abs(colors[3].b - color.B) * bWeight, + Math.Abs(colors[3].r - color.R) * rWeight + + Math.Abs(colors[3].g - color.G) * gWeight + + Math.Abs(colors[3].b - color.B) * bWeight, }; int b0 = d[0] > d[2] ? 1 : 0; @@ -80,7 +79,7 @@ public static int ChooseClosestColor4AlphaCutoff(ReadOnlySpan colors return idx; } - public static int ChooseClosestColor(Span colors, Rgba32 color) + public static int ChooseClosestColor(ColorRgb24[] colors, Rgba32 color) { int closest = 0; int closestError = @@ -103,7 +102,7 @@ public static int ChooseClosestColor(Span colors, Rgba32 color) return closest; } - public static int ChooseClosestColor(Span colors, Rgba32 color) + public static int ChooseClosestColor(ColorRgba32[] colors, Rgba32 color) { int closest = 0; int closestError = @@ -128,7 +127,7 @@ public static int ChooseClosestColor(Span colors, Rgba32 color) return closest; } - public static int ChooseClosestColorAlphaCutOff(Span colors, Rgba32 color, byte alphaCutOff = 255 / 2) + public static int ChooseClosestColorAlphaCutOff(ColorRgba32[] colors, Rgba32 color, byte alphaCutOff = 255 / 2) { if (color.A <= alphaCutOff) { @@ -157,7 +156,7 @@ public static int ChooseClosestColorAlphaCutOff(Span colors, Rgba32 return closest; } - public static int ChooseClosestColor(Span colors, ColorYCbCr color, float luminanceMultiplier = 4) + public static int ChooseClosestColor(ColorYCbCr[] colors, ColorYCbCr color, float luminanceMultiplier = 4) { int closest = 0; float closestError = 0; @@ -165,9 +164,9 @@ public static int ChooseClosestColor(Span colors, ColorYCbCr color, for (int i = 0; i < colors.Length; i++) { - float error = MathF.Abs(colors[i].y - color.y) * luminanceMultiplier - + MathF.Abs(colors[i].cb - color.cb) - + MathF.Abs(colors[i].cr - color.cr); + float error = Math.Abs(colors[i].y - color.y) * luminanceMultiplier + + Math.Abs(colors[i].cb - color.cb) + + Math.Abs(colors[i].cr - color.cr); if (first) { closestError = error; @@ -182,7 +181,7 @@ public static int ChooseClosestColor(Span colors, ColorYCbCr color, return closest; } - public static int ChooseClosestColor(Span colors, Rgba32 color, float luminanceMultiplier = 4) + public static int ChooseClosestColor(ColorYCbCr[] colors, Rgba32 color, float luminanceMultiplier = 4) => ChooseClosestColor(colors, new ColorYCbCr(color), luminanceMultiplier); } } diff --git a/BCnEnc.Net/Encoder/ColorVariationGenerator.cs b/BCnEnc.Net/Encoder/ColorVariationGenerator.cs index 96185ed..99de392 100644 --- a/BCnEnc.Net/Encoder/ColorVariationGenerator.cs +++ b/BCnEnc.Net/Encoder/ColorVariationGenerator.cs @@ -14,7 +14,7 @@ internal static class ColorVariationGenerator private static int[] varPatternEp1B = new int[] { -1, 0, 0, -1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 1, 0, 0, -1 }; public static int VarPatternCount => varPatternEp0R.Length; - public static (ColorRgb565, ColorRgb565) Variate565(ColorRgb565 c0, ColorRgb565 c1, int i) { + public static void Variate565(ColorRgb565 c0, ColorRgb565 c1, int i, out ColorRgb565 outC0, out ColorRgb565 outC1) { int idx = i % varPatternEp0R.Length; var newEp0 = new ColorRgb565(); var newEp1 = new ColorRgb565(); @@ -27,7 +27,8 @@ public static (ColorRgb565, ColorRgb565) Variate565(ColorRgb565 c0, ColorRgb565 newEp1.RawG = ByteHelper.ClampToByte(c1.RawG + varPatternEp1G[idx]); newEp1.RawB = ByteHelper.ClampToByte(c1.RawB + varPatternEp1B[idx]); - return (newEp0, newEp1); + outC0 = newEp0; + outC1 = newEp1; } diff --git a/BCnEnc.Net/Encoder/IBcBlockEncoder.cs b/BCnEnc.Net/Encoder/IBcBlockEncoder.cs index 1801c16..4b6b170 100644 --- a/BCnEnc.Net/Encoder/IBcBlockEncoder.cs +++ b/BCnEnc.Net/Encoder/IBcBlockEncoder.cs @@ -1,6 +1,5 @@ using System; using BCnEncoder.Shared; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Encoder { diff --git a/BCnEnc.Net/Encoder/RawEncoders.cs b/BCnEnc.Net/Encoder/RawEncoders.cs deleted file mode 100644 index 0b5c16a..0000000 --- a/BCnEnc.Net/Encoder/RawEncoders.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using BCnEncoder.Shared; -using SixLabors.ImageSharp.PixelFormats; - -namespace BCnEncoder.Encoder -{ - internal interface IRawEncoder - { - byte[] Encode(ReadOnlySpan pixels); - GlInternalFormat GetInternalFormat(); - GLFormat GetBaseInternalFormat(); - GLFormat GetGlFormat(); - GLType GetGlType(); - uint GetGlTypeSize(); - DXGI_FORMAT GetDxgiFormat(); - } - - internal class RawLuminanceEncoder : IRawEncoder { - private readonly bool useLuminance; - - public RawLuminanceEncoder(bool useLuminance) { - this.useLuminance = useLuminance; - } - - public byte[] Encode(ReadOnlySpan pixels) { - byte[] output = new byte[pixels.Length]; - for (int i = 0; i < pixels.Length; i++) { - if (useLuminance) { - output[i] = (byte)(new ColorYCbCr(pixels[i]).y * 255); - } - else { - output[i] = pixels[i].R; - } - - } - return output; - } - - public GlInternalFormat GetInternalFormat() - => GlInternalFormat.GL_R8; - - public GLFormat GetBaseInternalFormat() - => GLFormat.GL_RED; - - public GLFormat GetGlFormat() => GLFormat.GL_RED; - - public GLType GetGlType() - => GLType.GL_BYTE; - - public uint GetGlTypeSize() - => 1; - - public DXGI_FORMAT GetDxgiFormat() => DXGI_FORMAT.DXGI_FORMAT_R8_UNORM; - } - - internal class RawRGEncoder : IRawEncoder - { - public byte[] Encode(ReadOnlySpan pixels) { - byte[] output = new byte[pixels.Length * 2]; - for (int i = 0; i < pixels.Length; i++) { - output[i * 2] = pixels[i].R; - output[i * 2 + 1] = pixels[i].G; - } - return output; - } - - public GlInternalFormat GetInternalFormat() - => GlInternalFormat.GL_RG8; - - public GLFormat GetBaseInternalFormat() - => GLFormat.GL_RG; - - public GLFormat GetGlFormat() => GLFormat.GL_RG; - - public GLType GetGlType() - => GLType.GL_BYTE; - - public uint GetGlTypeSize() - => 1; - - public DXGI_FORMAT GetDxgiFormat() => DXGI_FORMAT.DXGI_FORMAT_R8G8_UNORM; - } - - internal class RawRGBEncoder : IRawEncoder - { - public byte[] Encode(ReadOnlySpan pixels) { - byte[] output = new byte[pixels.Length * 3]; - for (int i = 0; i < pixels.Length; i++) { - output[i * 3] = pixels[i].R; - output[i * 3 + 1] = pixels[i].G; - output[i * 3 + 2] = pixels[i].B; - } - return output; - } - - public GlInternalFormat GetInternalFormat() - => GlInternalFormat.GL_RGB8; - - public GLFormat GetBaseInternalFormat() - => GLFormat.GL_RGB; - - public GLFormat GetGlFormat() => GLFormat.GL_RGB; - - public GLType GetGlType() - => GLType.GL_BYTE; - - public uint GetGlTypeSize() - => 1; - - public DXGI_FORMAT GetDxgiFormat() => throw new NotSupportedException("RGB format is not supported for dds files."); - } - - internal class RawRGBAEncoder : IRawEncoder - { - public byte[] Encode(ReadOnlySpan pixels) { - byte[] output = new byte[pixels.Length * 4]; - for (int i = 0; i < pixels.Length; i++) { - output[i * 4] = pixels[i].R; - output[i * 4 + 1] = pixels[i].G; - output[i * 4 + 2] = pixels[i].B; - output[i * 4 + 3] = pixels[i].A; - } - return output; - } - - public GlInternalFormat GetInternalFormat() - => GlInternalFormat.GL_RGBA8; - - public GLFormat GetBaseInternalFormat() - => GLFormat.GL_RGBA; - - public GLFormat GetGlFormat() => GLFormat.GL_RGBA; - - public GLType GetGlType() - => GLType.GL_BYTE; - - public uint GetGlTypeSize() - => 1; - - public DXGI_FORMAT GetDxgiFormat() => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM; - } -} diff --git a/BCnEnc.Net/Shared/ArrayExtensions.cs b/BCnEnc.Net/Shared/ArrayExtensions.cs new file mode 100644 index 0000000..41281c7 --- /dev/null +++ b/BCnEnc.Net/Shared/ArrayExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BCnEncoder.Shared +{ + internal static class ArrayExtensions + { + + public static void Fill(this T[] arr, T item) + { + for (int i = 0; i < arr.Length; i++) + { + arr[i] = item; + } + } + + public static void Clear(this T[] arr) + { + Fill(arr, default); + } + } +} diff --git a/BCnEnc.Net/Shared/Bc7Block.cs b/BCnEnc.Net/Shared/Bc7Block.cs index c63e1a8..eff404a 100644 --- a/BCnEnc.Net/Shared/Bc7Block.cs +++ b/BCnEnc.Net/Shared/Bc7Block.cs @@ -23,9 +23,9 @@ internal struct Bc7Block public ulong lowBits; public ulong highBits; - public static ReadOnlySpan colorInterpolationWeights2 => new byte[] { 0, 21, 43, 64 }; - public static ReadOnlySpan colorInterpolationWeights3 => new byte[] { 0, 9, 18, 27, 37, 46, 55, 64 }; - public static ReadOnlySpan colorInterpolationWeights4 => new byte[] { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; + public static byte[] colorInterpolationWeights2 = new byte[] { 0, 21, 43, 64 }; + public static byte[] colorInterpolationWeights3 = new byte[] { 0, 9, 18, 27, 37, 46, 55, 64 }; + public static byte[] colorInterpolationWeights4 = new byte[] { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; public static readonly int[][] Subsets2PartitionTable = { @@ -212,131 +212,203 @@ public Bc7BlockType Type } } - public int NumSubsets => Type switch + public int NumSubsets { - Bc7BlockType.Type0 => 3, - Bc7BlockType.Type1 => 2, - Bc7BlockType.Type2 => 3, - Bc7BlockType.Type3 => 2, - Bc7BlockType.Type7 => 2, - _ => 1 - }; + get + { + switch (Type) + { + case Bc7BlockType.Type0: return 3; + case Bc7BlockType.Type1: return 2; + case Bc7BlockType.Type2: return 3; + case Bc7BlockType.Type3: return 2; + case Bc7BlockType.Type7: return 2; + default: return 1; + } + } + } - public bool HasSubsets => Type switch + public bool HasSubsets { - Bc7BlockType.Type0 => true, - Bc7BlockType.Type1 => true, - Bc7BlockType.Type2 => true, - Bc7BlockType.Type3 => true, - Bc7BlockType.Type7 => true, - _ => false - }; + get + { + switch (Type) + { + case Bc7BlockType.Type0: return true; + case Bc7BlockType.Type1: return true; + case Bc7BlockType.Type2: return true; + case Bc7BlockType.Type3: return true; + case Bc7BlockType.Type7: return true; + default: return false; + } + } + } - public int PartitionSetId => Type switch + public int PartitionSetId { - Bc7BlockType.Type0 => ByteHelper.Extract4(lowBits, 1), - Bc7BlockType.Type1 => ByteHelper.Extract6(lowBits, 2), - Bc7BlockType.Type2 => ByteHelper.Extract6(lowBits, 3), - Bc7BlockType.Type3 => ByteHelper.Extract6(lowBits, 4), - Bc7BlockType.Type7 => ByteHelper.Extract6(lowBits, 8), - _ => -1 - }; + get + { + switch (Type) + { + case Bc7BlockType.Type0: return ByteHelper.Extract4(lowBits, 1); + case Bc7BlockType.Type1: return ByteHelper.Extract6(lowBits, 2); + case Bc7BlockType.Type2: return ByteHelper.Extract6(lowBits, 3); + case Bc7BlockType.Type3: return ByteHelper.Extract6(lowBits, 4); + case Bc7BlockType.Type7: return ByteHelper.Extract6(lowBits, 8); + default: return -1; + } + } + } - public byte RotationBits => Type switch + public byte RotationBits { - Bc7BlockType.Type4 => ByteHelper.Extract2(lowBits, 5), - Bc7BlockType.Type5 => ByteHelper.Extract2(lowBits, 6), - _ => 0 - }; + get + { + switch (Type) + { + case Bc7BlockType.Type4: return ByteHelper.Extract2(lowBits, 5); + case Bc7BlockType.Type5: return ByteHelper.Extract2(lowBits, 6); + default: return 0; + } + } + } /// /// Bitcount of color component including Pbit /// - public int ColorComponentPrecision => Type switch + public int ColorComponentPrecision { - Bc7BlockType.Type0 => 5, - Bc7BlockType.Type1 => 7, - Bc7BlockType.Type2 => 5, - Bc7BlockType.Type3 => 8, - Bc7BlockType.Type4 => 5, - Bc7BlockType.Type5 => 7, - Bc7BlockType.Type6 => 8, - Bc7BlockType.Type7 => 6, - _ => 0 - }; + get + { + switch (Type) + { + case Bc7BlockType.Type0: return 5; + case Bc7BlockType.Type1: return 7; + case Bc7BlockType.Type2: return 5; + case Bc7BlockType.Type3: return 8; + case Bc7BlockType.Type4: return 5; + case Bc7BlockType.Type5: return 7; + case Bc7BlockType.Type6: return 8; + case Bc7BlockType.Type7: return 6; + default: return 0; + } + } + } /// /// Bitcount of alpha component including Pbit /// - public int AlphaComponentPrecision => Type switch + public int AlphaComponentPrecision { + get + { + switch (Type) + { - Bc7BlockType.Type4 => 6, - Bc7BlockType.Type5 => 8, - Bc7BlockType.Type6 => 8, - Bc7BlockType.Type7 => 6, - _ => 0 - }; + case Bc7BlockType.Type4: return 6; + case Bc7BlockType.Type5: return 8; + case Bc7BlockType.Type6: return 8; + case Bc7BlockType.Type7: return 6; + default: return 0; + } + } + } - public bool HasRotationBits => Type switch + public bool HasRotationBits { - Bc7BlockType.Type4 => true, - Bc7BlockType.Type5 => true, - _ => false - }; + get + { + switch (Type) + { + case Bc7BlockType.Type4: return true; + case Bc7BlockType.Type5: return true; + default: return false; + } + } + } - public bool HasPBits => Type switch + public bool HasPBits { - Bc7BlockType.Type0 => true, - Bc7BlockType.Type1 => true, - Bc7BlockType.Type3 => true, - Bc7BlockType.Type6 => true, - Bc7BlockType.Type7 => true, - _ => false - }; + get + { + switch (Type) + { + case Bc7BlockType.Type0: return true; + case Bc7BlockType.Type1: return true; + case Bc7BlockType.Type3: return true; + case Bc7BlockType.Type6: return true; + case Bc7BlockType.Type7: return true; + default: return false; + } + } + } - public bool HasAlpha => Type switch + public bool HasAlpha { - Bc7BlockType.Type4 => true, - Bc7BlockType.Type5 => true, - Bc7BlockType.Type6 => true, - Bc7BlockType.Type7 => true, - _ => false - }; + get + { + switch (Type) + { + case Bc7BlockType.Type4: return true; + case Bc7BlockType.Type5: return true; + case Bc7BlockType.Type6: return true; + case Bc7BlockType.Type7: return true; + default: return false; + } + } + } /// - /// Type 4 has 2-bit and 3-bit indices. If index mode is 0, color components will use 2-bit indices and alpha will use 3-bit indices. - /// In index mode 1, color will use 3-bit indices and alpha will use 2-bit indices. + /// Type 4 has 2-bit and 3-bit indices. If index mode is 0; color components will use 2-bit indices and alpha will use 3-bit indices. + /// In index mode 1; color will use 3-bit indices and alpha will use 2-bit indices. /// - public int Type4IndexMode => Type switch + public int Type4IndexMode { - Bc7BlockType.Type4 => ByteHelper.Extract1(lowBits, 7), - _ => 0 - }; + get + { + switch (Type) + { + case Bc7BlockType.Type4: return ByteHelper.Extract1(lowBits, 7); + default: return 0; + } + } + } - public int ColorIndexBitCount => Type switch + public int ColorIndexBitCount { - Bc7BlockType.Type0 => 3, - Bc7BlockType.Type1 => 3, - Bc7BlockType.Type2 => 2, - Bc7BlockType.Type3 => 2, - Bc7BlockType.Type4 when Type4IndexMode == 0 => 2, - Bc7BlockType.Type4 when Type4IndexMode == 1 => 3, - Bc7BlockType.Type5 => 2, - Bc7BlockType.Type6 => 4, - Bc7BlockType.Type7 => 2, - _ => 0 - }; + get + { + switch (Type) + { + case Bc7BlockType.Type0: return 3; + case Bc7BlockType.Type1: return 3; + case Bc7BlockType.Type2: return 2; + case Bc7BlockType.Type3: return 2; + case Bc7BlockType.Type4 when Type4IndexMode == 0: return 2; + case Bc7BlockType.Type4 when Type4IndexMode == 1: return 3; + case Bc7BlockType.Type5: return 2; + case Bc7BlockType.Type6: return 4; + case Bc7BlockType.Type7: return 2; + default: return 0; + } + } + } - public int AlphaIndexBitCount => Type switch + public int AlphaIndexBitCount { - Bc7BlockType.Type4 when Type4IndexMode == 0 => 3, - Bc7BlockType.Type4 when Type4IndexMode == 1 => 2, - Bc7BlockType.Type5 => 2, - Bc7BlockType.Type6 => 4, - Bc7BlockType.Type7 => 2, - _ => 0 - }; + get + { + switch (Type) + { + case Bc7BlockType.Type4 when Type4IndexMode == 0: return 3; + case Bc7BlockType.Type4 when Type4IndexMode == 1: return 2; + case Bc7BlockType.Type5: return 2; + case Bc7BlockType.Type6: return 4; + case Bc7BlockType.Type7: return 2; + default: return 0; + } + } + } @@ -523,7 +595,7 @@ private byte[] ExtractPBitArray() ByteHelper.Extract1(highBits, 97 - 64) }; default: - return Array.Empty(); + return new byte[0]; } } @@ -714,7 +786,8 @@ private int GetIndexBegin(Bc7BlockType type, int bitCount, bool IsAlpha) } } - private int GetAlphaIndex(Bc7BlockType type, int numSubsets, int partitionIndex, int bitCount, int index) { + private int GetAlphaIndex(Bc7BlockType type, int numSubsets, int partitionIndex, int bitCount, int index) + { if (bitCount == 0) return 0; // No Alpha int indexOffset = GetIndexOffset(type, numSubsets, partitionIndex, bitCount, index); int indexBitCount = GetIndexBitCount(numSubsets, partitionIndex, bitCount, index); @@ -733,19 +806,20 @@ private int GetColorIndex(Bc7BlockType type, int numSubsets, int partitionIndex, private ColorRgba32 InterpolateColor(ColorRgba32 endPointStart, ColorRgba32 endPointEnd, int colorIndex, int alphaIndex, int colorBitCount, int alphaBitCount) { - - byte InterpolateByte(byte e0, byte e1, int index, int indexPrecision) { + + byte InterpolateByte(byte e0, byte e1, int index, int indexPrecision) + { if (indexPrecision == 0) return e0; - ReadOnlySpan aWeights2 = colorInterpolationWeights2; - ReadOnlySpan aWeights3 = colorInterpolationWeights3; - ReadOnlySpan aWeights4 = colorInterpolationWeights4; - - if(indexPrecision == 2) - return (byte) (((64 - aWeights2[index])* (e0) + aWeights2[index]*(e1) + 32) >> 6); - else if(indexPrecision == 3) - return (byte) (((64 - aWeights3[index])*(e0) + aWeights3[index]*(e1) + 32) >> 6); + byte[] aWeights2 = colorInterpolationWeights2; + byte[] aWeights3 = colorInterpolationWeights3; + byte[] aWeights4 = colorInterpolationWeights4; + + if (indexPrecision == 2) + return (byte)(((64 - aWeights2[index]) * (e0) + aWeights2[index] * (e1) + 32) >> 6); + else if (indexPrecision == 3) + return (byte)(((64 - aWeights3[index]) * (e0) + aWeights3[index] * (e1) + 32) >> 6); else // indexprecision == 4 - return (byte) (((64 - aWeights4[index])*(e0) + aWeights4[index]*(e1) + 32) >> 6); + return (byte)(((64 - aWeights4[index]) * (e0) + aWeights4[index] * (e1) + 32) >> 6); } ColorRgba32 result = new ColorRgba32( @@ -763,8 +837,10 @@ byte InterpolateByte(byte e0, byte e1, int index, int indexPrecision) { /// 10 – swap A and G /// 11 - swap A and B /// - private static ColorRgba32 SwapChannels(ColorRgba32 source, int rotation) { - switch (rotation) { + private static ColorRgba32 SwapChannels(ColorRgba32 source, int rotation) + { + switch (rotation) + { case 0b00: return source; case 0b01: @@ -796,13 +872,11 @@ public RawBlock4X4Rgba32 Decode() //subset_index = get_partition_index(num_subsets, partition_set_id, x, y); } - var pixels = output.AsSpan; - bool hasRotationBits = HasRotationBits; int rotation = RotationBits; ColorRgba32[] endpoints = ExtractEndpoints(); - for (int i = 0; i < pixels.Length; i++) + for (int i = 0; i < 16; i++) { int subsetIndex = GetPartitionIndex(numSubsets, partitionIndex, i); @@ -817,7 +891,8 @@ public RawBlock4X4Rgba32 Decode() ColorRgba32 outputColor = InterpolateColor(endPointStart, endPointEnd, colorIndex, alphaIndex, colorBitCount, alphaBitCount); - if (hasRotationBits) { + if (hasRotationBits) + { //Decode the 2 color rotation bits as follows: // 00 – Block format is Scalar(A) Vector(RGB) - no swapping // 01 – Block format is Scalar(R) Vector(AGB) - swap A and R @@ -826,115 +901,128 @@ public RawBlock4X4Rgba32 Decode() outputColor = SwapChannels(outputColor, rotation); } - pixels[i] = outputColor.ToRgba32(); + output[i] = outputColor.ToRgba32(); } return output; } - public void PackType0(int partitionIndex4Bit, byte[][] subsetEndpoints, byte[] pBits, byte[] indices) { + public void PackType0(int partitionIndex4Bit, byte[][] subsetEndpoints, byte[] pBits, byte[] indices) + { Debug.Assert(partitionIndex4Bit < 16, "Mode 0 should have 4bit partition index"); Debug.Assert(subsetEndpoints.Length == 6, "Mode 0 should have 6 endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.Length == 3) , "Mode 0 should have RGB endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 4))) , "Mode 0 should have 4bit endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.Length == 3), "Mode 0 should have RGB endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 4))), "Mode 0 should have 4bit endpoints"); Debug.Assert(pBits.Length == 6, "Mode 0 should have 6 pBits"); Debug.Assert(indices.Length == 16, "Provide 16 indices"); - Debug.Assert(indices.All(x => x < (1 << 3)) , "Mode 0 should have 3bit indices"); + Debug.Assert(indices.All(x => x < (1 << 3)), "Mode 0 should have 3bit indices"); lowBits = 1; // Set Mode 0 highBits = 0; - lowBits = ByteHelper.Store4(lowBits, 1, (byte) partitionIndex4Bit); + lowBits = ByteHelper.Store4(lowBits, 1, (byte)partitionIndex4Bit); int nextIdx = 5; //Store endpoints - for (int i = 0; i < subsetEndpoints[0].Length; i++) { - for (int j = 0; j < subsetEndpoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 4, subsetEndpoints[j][i]); + for (int i = 0; i < subsetEndpoints[0].Length; i++) + { + for (int j = 0; j < subsetEndpoints.Length; j++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 4, subsetEndpoints[j][i]); nextIdx += 4; } } //Store pBits - for (int i = 0; i < pBits.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 1, pBits[i]); + for (int i = 0; i < pBits.Length; i++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 1, pBits[i]); nextIdx++; } Debug.Assert(nextIdx == 83); int colorBitCount = ColorIndexBitCount; int indexBegin = GetIndexBegin(Bc7BlockType.Type0, colorBitCount, false); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type0, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type0, NumSubsets, partitionIndex4Bit, colorBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex4Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + ByteHelper.StoreTo128(ref lowBits, ref highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } - public void PackType1(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] pBits, byte[] indices) { + public void PackType1(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] pBits, byte[] indices) + { Debug.Assert(partitionIndex6Bit < 64, "Mode 1 should have 6bit partition index"); Debug.Assert(subsetEndpoints.Length == 4, "Mode 1 should have 4 endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.Length == 3) , "Mode 1 should have RGB endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 6))) , "Mode 1 should have 6bit endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.Length == 3), "Mode 1 should have RGB endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 6))), "Mode 1 should have 6bit endpoints"); Debug.Assert(pBits.Length == 2, "Mode 1 should have 2 pBits"); Debug.Assert(indices.Length == 16, "Provide 16 indices"); - Debug.Assert(indices.All(x => x < (1 << 3)) , "Mode 1 should have 3bit indices"); + Debug.Assert(indices.All(x => x < (1 << 3)), "Mode 1 should have 3bit indices"); lowBits = 2; // Set Mode 1 highBits = 0; - lowBits = ByteHelper.Store6(lowBits, 2, (byte) partitionIndex6Bit); + lowBits = ByteHelper.Store6(lowBits, 2, (byte)partitionIndex6Bit); int nextIdx = 8; //Store endpoints - for (int i = 0; i < subsetEndpoints[0].Length; i++) { - for (int j = 0; j < subsetEndpoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 6, subsetEndpoints[j][i]); + for (int i = 0; i < subsetEndpoints[0].Length; i++) + { + for (int j = 0; j < subsetEndpoints.Length; j++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 6, subsetEndpoints[j][i]); nextIdx += 6; } } //Store pBits - for (int i = 0; i < pBits.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, 1, pBits[i]); + for (int i = 0; i < pBits.Length; i++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 1, pBits[i]); nextIdx++; } Debug.Assert(nextIdx == 82); int colorBitCount = ColorIndexBitCount; int indexBegin = GetIndexBegin(Bc7BlockType.Type1, colorBitCount, false); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type1, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type1, NumSubsets, partitionIndex6Bit, colorBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex6Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + ByteHelper.StoreTo128(ref lowBits, ref highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } - public void PackType2(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] indices) { + public void PackType2(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] indices) + { Debug.Assert(partitionIndex6Bit < 64, "Mode 2 should have 6bit partition index"); Debug.Assert(subsetEndpoints.Length == 6, "Mode 2 should have 6 endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.Length == 3) , "Mode 2 should have RGB endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 5))) , "Mode 2 should have 5bit endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.Length == 3), "Mode 2 should have RGB endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 5))), "Mode 2 should have 5bit endpoints"); Debug.Assert(indices.Length == 16, "Provide 16 indices"); - Debug.Assert(indices.All(x => x < (1 << 2)) , "Mode 2 should have 2bit indices"); + Debug.Assert(indices.All(x => x < (1 << 2)), "Mode 2 should have 2bit indices"); lowBits = 0b100; // Set Mode 2 highBits = 0; - lowBits = ByteHelper.Store6(lowBits, 3, (byte) partitionIndex6Bit); + lowBits = ByteHelper.Store6(lowBits, 3, (byte)partitionIndex6Bit); int nextIdx = 9; //Store endpoints - for (int i = 0; i < subsetEndpoints[0].Length; i++) { - for (int j = 0; j < subsetEndpoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + for (int i = 0; i < subsetEndpoints[0].Length; i++) + { + for (int j = 0; j < subsetEndpoints.Length; j++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 5, subsetEndpoints[j][i]); nextIdx += 5; } @@ -943,43 +1031,48 @@ public void PackType2(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] int colorBitCount = ColorIndexBitCount; int indexBegin = GetIndexBegin(Bc7BlockType.Type2, colorBitCount, false); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type2, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type2, NumSubsets, partitionIndex6Bit, colorBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex6Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + ByteHelper.StoreTo128(ref lowBits, ref highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } - public void PackType3(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] pBits, byte[] indices) { + public void PackType3(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] pBits, byte[] indices) + { Debug.Assert(partitionIndex6Bit < 64, "Mode 3 should have 6bit partition index"); Debug.Assert(subsetEndpoints.Length == 4, "Mode 3 should have 4 endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.Length == 3) , "Mode 3 should have RGB endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 7))) , "Mode 3 should have 7bit endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.Length == 3), "Mode 3 should have RGB endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 7))), "Mode 3 should have 7bit endpoints"); Debug.Assert(pBits.Length == 4, "Mode 3 should have 4 pBits"); Debug.Assert(indices.Length == 16, "Provide 16 indices"); - Debug.Assert(indices.All(x => x < (1 << 2)) , "Mode 3 should have 2bit indices"); + Debug.Assert(indices.All(x => x < (1 << 2)), "Mode 3 should have 2bit indices"); lowBits = 0b1000; // Set Mode 3 highBits = 0; - lowBits = ByteHelper.Store6(lowBits, 4, (byte) partitionIndex6Bit); + lowBits = ByteHelper.Store6(lowBits, 4, (byte)partitionIndex6Bit); int nextIdx = 10; //Store endpoints - for (int i = 0; i < subsetEndpoints[0].Length; i++) { - for (int j = 0; j < subsetEndpoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + for (int i = 0; i < subsetEndpoints[0].Length; i++) + { + for (int j = 0; j < subsetEndpoints.Length; j++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 7, subsetEndpoints[j][i]); nextIdx += 7; } } //Store pBits - for (int i = 0; i < pBits.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + for (int i = 0; i < pBits.Length; i++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 1, pBits[i]); nextIdx++; } @@ -987,179 +1080,200 @@ public void PackType3(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] p int colorBitCount = ColorIndexBitCount; int indexBegin = GetIndexBegin(Bc7BlockType.Type3, colorBitCount, false); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type3, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type3, NumSubsets, partitionIndex6Bit, colorBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex6Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + ByteHelper.StoreTo128(ref lowBits, ref highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } - public void PackType4(int rotation, byte idxMode, byte[][] colorEndPoints, byte[] alphaEndPoints, byte[] indices2Bit, byte[] indices3Bit) { + public void PackType4(int rotation, byte idxMode, byte[][] colorEndPoints, byte[] alphaEndPoints, byte[] indices2Bit, byte[] indices3Bit) + { Debug.Assert(rotation < 4, "Rotation can only be 0-3"); Debug.Assert(idxMode < 2, "IndexMode can only be 0 or 1"); Debug.Assert(colorEndPoints.Length == 2, "Mode 4 should have 2 endpoints"); - Debug.Assert(colorEndPoints.All(x => x.Length == 3) , "Mode 4 should have RGB color endpoints"); - Debug.Assert(colorEndPoints.All(x => x.All(y => y < (1 << 5))) , "Mode 4 should have 5bit color endpoints"); + Debug.Assert(colorEndPoints.All(x => x.Length == 3), "Mode 4 should have RGB color endpoints"); + Debug.Assert(colorEndPoints.All(x => x.All(y => y < (1 << 5))), "Mode 4 should have 5bit color endpoints"); Debug.Assert(alphaEndPoints.Length == 2, "Mode 4 should have 2 endpoints"); - Debug.Assert(alphaEndPoints.All(x => x < (1 << 6)) , "Mode 4 should have 6bit alpha endpoints"); + Debug.Assert(alphaEndPoints.All(x => x < (1 << 6)), "Mode 4 should have 6bit alpha endpoints"); Debug.Assert(indices2Bit.Length == 16, "Provide 16 indices"); - Debug.Assert(indices2Bit.All(x => x < (1 << 2)) , "Mode 4 should have 2bit indices"); + Debug.Assert(indices2Bit.All(x => x < (1 << 2)), "Mode 4 should have 2bit indices"); Debug.Assert(indices3Bit.Length == 16, "Provide 16 indices"); - Debug.Assert(indices3Bit.All(x => x < (1 << 3)) , "Mode 4 should have 3bit indices"); + Debug.Assert(indices3Bit.All(x => x < (1 << 3)), "Mode 4 should have 3bit indices"); lowBits = 0b10000; // Set Mode 4 highBits = 0; - lowBits = ByteHelper.Store2(lowBits, 5, (byte) rotation); - lowBits = ByteHelper.Store1(lowBits, 7, (byte) idxMode); + lowBits = ByteHelper.Store2(lowBits, 5, (byte)rotation); + lowBits = ByteHelper.Store1(lowBits, 7, (byte)idxMode); int nextIdx = 8; //Store color endpoints - for (int i = 0; i < colorEndPoints[0].Length; i++) { - for (int j = 0; j < colorEndPoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + for (int i = 0; i < colorEndPoints[0].Length; i++) + { + for (int j = 0; j < colorEndPoints.Length; j++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 5, colorEndPoints[j][i]); nextIdx += 5; } } //Store alpha endpoints - for (int i = 0; i < alphaEndPoints.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + for (int i = 0; i < alphaEndPoints.Length; i++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 6, alphaEndPoints[i]); - nextIdx+=6; + nextIdx += 6; } Debug.Assert(nextIdx == 50); int colorBitCount = ColorIndexBitCount; int colorIndexBegin = GetIndexBegin(Bc7BlockType.Type4, colorBitCount, false); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type4, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type4, NumSubsets, 0, colorBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, 0, colorBitCount, i); - if (idxMode == 0) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + if (idxMode == 0) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, colorIndexBegin + indexOffset, indexBitCount, indices2Bit[i]); } - else { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + else + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, colorIndexBegin + indexOffset, indexBitCount, indices3Bit[i]); } } int alphaBitCount = AlphaIndexBitCount; int alphaIndexBegin = GetIndexBegin(Bc7BlockType.Type4, alphaBitCount, true); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type4, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type4, NumSubsets, 0, alphaBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, 0, alphaBitCount, i); - if (idxMode == 0) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + if (idxMode == 0) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, alphaIndexBegin + indexOffset, indexBitCount, indices3Bit[i]); } - else { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + else + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, alphaIndexBegin + indexOffset, indexBitCount, indices2Bit[i]); } } } - public void PackType5(int rotation, byte[][] colorEndPoints, byte[] alphaEndPoints, byte[] colorIndices, byte[] alphaIndices) { + public void PackType5(int rotation, byte[][] colorEndPoints, byte[] alphaEndPoints, byte[] colorIndices, byte[] alphaIndices) + { Debug.Assert(rotation < 4, "Rotation can only be 0-3"); Debug.Assert(colorEndPoints.Length == 2, "Mode 5 should have 2 endpoints"); - Debug.Assert(colorEndPoints.All(x => x.Length == 3) , "Mode 5 should have RGB color endpoints"); - Debug.Assert(colorEndPoints.All(x => x.All(y => y < (1 << 7))) , "Mode 5 should have 7bit color endpoints"); + Debug.Assert(colorEndPoints.All(x => x.Length == 3), "Mode 5 should have RGB color endpoints"); + Debug.Assert(colorEndPoints.All(x => x.All(y => y < (1 << 7))), "Mode 5 should have 7bit color endpoints"); Debug.Assert(alphaEndPoints.Length == 2, "Mode 5 should have 2 endpoints"); Debug.Assert(colorIndices.Length == 16, "Provide 16 indices"); - Debug.Assert(colorIndices.All(x => x < (1 << 2)) , "Mode 5 should have 2bit color indices"); + Debug.Assert(colorIndices.All(x => x < (1 << 2)), "Mode 5 should have 2bit color indices"); Debug.Assert(alphaIndices.Length == 16, "Provide 16 indices"); - Debug.Assert(alphaIndices.All(x => x < (1 << 2)) , "Mode 5 should have 2bit alpha indices"); + Debug.Assert(alphaIndices.All(x => x < (1 << 2)), "Mode 5 should have 2bit alpha indices"); lowBits = 0b100000; // Set Mode 5 highBits = 0; - lowBits = ByteHelper.Store2(lowBits, 6, (byte) rotation); + lowBits = ByteHelper.Store2(lowBits, 6, (byte)rotation); int nextIdx = 8; //Store color endpoints - for (int i = 0; i < colorEndPoints[0].Length; i++) { - for (int j = 0; j < colorEndPoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + for (int i = 0; i < colorEndPoints[0].Length; i++) + { + for (int j = 0; j < colorEndPoints.Length; j++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 7, colorEndPoints[j][i]); nextIdx += 7; } } //Store alpha endpoints - for (int i = 0; i < alphaEndPoints.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + for (int i = 0; i < alphaEndPoints.Length; i++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 8, alphaEndPoints[i]); - nextIdx+=8; + nextIdx += 8; } Debug.Assert(nextIdx == 66); int colorBitCount = ColorIndexBitCount; int colorIndexBegin = GetIndexBegin(Bc7BlockType.Type5, colorBitCount, false); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type5, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type5, NumSubsets, 0, colorBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, 0, colorBitCount, i); - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, - colorIndexBegin + indexOffset, indexBitCount, colorIndices[i]); - + ByteHelper.StoreTo128(ref lowBits, ref highBits, + colorIndexBegin + indexOffset, indexBitCount, colorIndices[i]); + } int alphaBitCount = AlphaIndexBitCount; int alphaIndexBegin = GetIndexBegin(Bc7BlockType.Type5, alphaBitCount, true); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type5, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type5, NumSubsets, 0, alphaBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, 0, alphaBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + ByteHelper.StoreTo128(ref lowBits, ref highBits, alphaIndexBegin + indexOffset, indexBitCount, alphaIndices[i]); } } - public void PackType6(byte[][] colorAlphaEndPoints, byte[] pBits, byte[] indices) { - Debug.Assert(colorAlphaEndPoints.Length == 2, + public void PackType6(byte[][] colorAlphaEndPoints, byte[] pBits, byte[] indices) + { + Debug.Assert(colorAlphaEndPoints.Length == 2, "Mode 6 should have 2 endpoints"); - Debug.Assert(colorAlphaEndPoints.All(x => x.Length == 4) , + Debug.Assert(colorAlphaEndPoints.All(x => x.Length == 4), "Mode 6 should have RGBA color endpoints"); - Debug.Assert(colorAlphaEndPoints.All(x => x.All(y => y < (1 << 7))) , + Debug.Assert(colorAlphaEndPoints.All(x => x.All(y => y < (1 << 7))), "Mode 6 should have 7bit color and alpha endpoints"); Debug.Assert(pBits.Length == 2, "Mode 6 should have 2 pBits"); Debug.Assert(indices.Length == 16, "Provide 16 indices"); - Debug.Assert(indices.All(x => x < (1 << 4)) , "Mode 6 should have 4bit color indices"); - + Debug.Assert(indices.All(x => x < (1 << 4)), "Mode 6 should have 4bit color indices"); + lowBits = 0b1000000; // Set Mode 6 highBits = 0; int nextIdx = 7; //Store color and alpha endpoints - for (int i = 0; i < colorAlphaEndPoints[0].Length; i++) { - for (int j = 0; j < colorAlphaEndPoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + for (int i = 0; i < colorAlphaEndPoints[0].Length; i++) + { + for (int j = 0; j < colorAlphaEndPoints.Length; j++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 7, colorAlphaEndPoints[j][i]); nextIdx += 7; } } //Store pBits - for (int i = 0; i < pBits.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + for (int i = 0; i < pBits.Length; i++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 1, pBits[i]); nextIdx++; } @@ -1167,44 +1281,49 @@ public void PackType6(byte[][] colorAlphaEndPoints, byte[] pBits, byte[] indices int colorBitCount = ColorIndexBitCount; int colorIndexBegin = GetIndexBegin(Bc7BlockType.Type6, colorBitCount, false); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type6, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type6, NumSubsets, 0, colorBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, 0, colorBitCount, i); - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, - colorIndexBegin + indexOffset, indexBitCount, indices[i]); - + ByteHelper.StoreTo128(ref lowBits, ref highBits, + colorIndexBegin + indexOffset, indexBitCount, indices[i]); + } } - public void PackType7(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] pBits, byte[] indices) { + public void PackType7(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] pBits, byte[] indices) + { Debug.Assert(partitionIndex6Bit < 64, "Mode 7 should have 6bit partition index"); Debug.Assert(subsetEndpoints.Length == 4, "Mode 7 should have 4 endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.Length == 4) , "Mode 7 should have RGBA endpoints"); - Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 5))) , "Mode 7 should have 5bit endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.Length == 4), "Mode 7 should have RGBA endpoints"); + Debug.Assert(subsetEndpoints.All(x => x.All(y => y < (1 << 5))), "Mode 7 should have 5bit endpoints"); Debug.Assert(pBits.Length == 4, "Mode 7 should have 4 pBits"); Debug.Assert(indices.Length == 16, "Provide 16 indices"); - Debug.Assert(indices.All(x => x < (1 << 2)) , "Mode 3 should have 2bit indices"); + Debug.Assert(indices.All(x => x < (1 << 2)), "Mode 3 should have 2bit indices"); lowBits = 0b10000000; // Set Mode 7 highBits = 0; - lowBits = ByteHelper.Store6(lowBits, 8, (byte) partitionIndex6Bit); + lowBits = ByteHelper.Store6(lowBits, 8, (byte)partitionIndex6Bit); int nextIdx = 14; //Store endpoints - for (int i = 0; i < subsetEndpoints[0].Length; i++) { - for (int j = 0; j < subsetEndpoints.Length; j++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + for (int i = 0; i < subsetEndpoints[0].Length; i++) + { + for (int j = 0; j < subsetEndpoints.Length; j++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 5, subsetEndpoints[j][i]); nextIdx += 5; } } //Store pBits - for (int i = 0; i < pBits.Length; i++) { - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, nextIdx, + for (int i = 0; i < pBits.Length; i++) + { + ByteHelper.StoreTo128(ref lowBits, ref highBits, nextIdx, 1, pBits[i]); nextIdx++; } @@ -1212,13 +1331,14 @@ public void PackType7(int partitionIndex6Bit, byte[][] subsetEndpoints, byte[] p int colorBitCount = ColorIndexBitCount; int indexBegin = GetIndexBegin(Bc7BlockType.Type7, colorBitCount, false); - for (int i = 0; i < 16; i++) { - int indexOffset = GetIndexOffset(Bc7BlockType.Type7, NumSubsets, + for (int i = 0; i < 16; i++) + { + int indexOffset = GetIndexOffset(Bc7BlockType.Type7, NumSubsets, partitionIndex6Bit, colorBitCount, i); - int indexBitCount = GetIndexBitCount(NumSubsets, + int indexBitCount = GetIndexBitCount(NumSubsets, partitionIndex6Bit, colorBitCount, i); - - (lowBits, highBits) = ByteHelper.StoreTo128(lowBits, highBits, + + ByteHelper.StoreTo128(ref lowBits, ref highBits, indexBegin + indexOffset, indexBitCount, indices[i]); } } diff --git a/BCnEnc.Net/Shared/BinaryReaderWriterExtensions.cs b/BCnEnc.Net/Shared/BinaryReaderWriterExtensions.cs index ebdf410..0e5817c 100644 --- a/BCnEnc.Net/Shared/BinaryReaderWriterExtensions.cs +++ b/BCnEnc.Net/Shared/BinaryReaderWriterExtensions.cs @@ -9,20 +9,42 @@ internal static class BinaryReaderWriterExtensions { public static unsafe void WriteStruct(this BinaryWriter bw, T t) where T : unmanaged { - int size = Unsafe.SizeOf(); + int size = sizeof(T); byte* bytes = stackalloc byte[size]; - Unsafe.Write(bytes, t); - Span bSpan = new Span(bytes, size); - bw.Write(bSpan); + ((T*) bytes)[0] = t; + for (int b = 0; b < size; b++) + { + bw.Write(bytes[b]); + } } public static unsafe T ReadStruct(this BinaryReader br) where T : unmanaged { - int size = Unsafe.SizeOf(); + int size = sizeof(T); byte* bytes = stackalloc byte[size]; - Span bSpan = new Span(bytes, size); - br.Read(bSpan); - return Unsafe.Read(bytes); + for (int b = 0; b < size; b++) + { + bytes[b] = br.ReadByte(); + } + return ((T*)bytes)[0]; + } + + public static void Read(this BinaryReader br, byte[] data) + { + for (int b = 0; b < data.Length; b++) + { + data[b] = br.ReadByte(); + } + } + + public static byte[] Slice(this byte[] bytes, int start, int length) + { + byte[] output = new byte[length]; + for (int i = 0; i < length; i++) + { + output[i] = bytes[i + start]; + } + return output; } public static void AddPadding(this BinaryWriter bw, uint padding) diff --git a/BCnEnc.Net/Shared/ByteHelper.cs b/BCnEnc.Net/Shared/ByteHelper.cs index 4359cf2..f52e20d 100644 --- a/BCnEnc.Net/Shared/ByteHelper.cs +++ b/BCnEnc.Net/Shared/ByteHelper.cs @@ -168,15 +168,15 @@ public static ulong ExtractFrom128(ulong low, ulong high, int index, int bitCoun } } - public static (ulong, ulong) StoreTo128(ulong low, ulong high, int index, int bitCount, ulong value) + public static void StoreTo128(ref ulong low, ref ulong high, int index, int bitCount, ulong value) { if (index + bitCount <= 64) { // Store to low - return (Store(low, index, bitCount, value), high); + low = Store(low, index, bitCount, value); } else if (index >= 64) { // Store to high - return (low, Store(high, index - 64, bitCount, value)); + high = Store(high, index - 64, bitCount, value); } else { //handle boundary case @@ -188,7 +188,8 @@ public static (ulong, ulong) StoreTo128(ulong low, ulong high, int index, int bi var l = Store(low, lowIndex, lowBitCount, value); value >>= lowBitCount; var h = Store(high, highIndex, highBitCount, value); - return (l, h); + low = l; + high = h; } } } diff --git a/BCnEnc.Net/Shared/Colors.cs b/BCnEnc.Net/Shared/Colors.cs index 620cff7..e923991 100644 --- a/BCnEnc.Net/Shared/Colors.cs +++ b/BCnEnc.Net/Shared/Colors.cs @@ -1,6 +1,5 @@ using System; using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Shared { @@ -40,7 +39,7 @@ public override int GetHashCode() public ushort data; public byte R { - readonly get { + get { int r5 = ((data & RedMask) >> RedShift); return (byte)((r5 << 3) | (r5 >> 2)); } @@ -52,7 +51,7 @@ readonly get { } public byte G { - readonly get { + get { int g6 = ((data & GreenMask) >> GreenShift); return (byte)((g6 << 2) | (g6 >> 4)); } @@ -64,7 +63,7 @@ readonly get { } public byte B { - readonly get { + get { int b5 = (data & BlueMask); return (byte)((b5 << 3) | (b5 >> 2)); } @@ -76,7 +75,7 @@ readonly get { } public int RawR { - readonly get { return ((data & RedMask) >> RedShift); } + get { return ((data & RedMask) >> RedShift); } set { if (value > 31) value = 31; if (value < 0) value = 0; @@ -86,7 +85,7 @@ public int RawR { } public int RawG { - readonly get { return ((data & GreenMask) >> GreenShift); } + get { return ((data & GreenMask) >> GreenShift); } set { if (value > 63) value = 63; if (value < 0) value = 0; @@ -96,7 +95,7 @@ public int RawG { } public int RawB { - readonly get { return (data & BlueMask); } + get { return (data & BlueMask); } set { if (value > 31) value = 31; if (value < 0) value = 0; @@ -127,7 +126,7 @@ public ColorRgb565(ColorRgb24 color) { B = color.b; } - public readonly ColorRgb24 ToColorRgb24() + public ColorRgb24 ToColorRgb24() { return new ColorRgb24(R, G, B); } @@ -503,7 +502,7 @@ public float CalcDistWeighted(ColorYCbCr other, float yWeight = 4) { float dcb = (cb - other.cb) * (cb - other.cb); float dcr = (cr - other.cr) * (cr - other.cr); - return MathF.Sqrt(dy + dcb + dcr); + return (float)Math.Sqrt(dy + dcb + dcr); } public static ColorYCbCr operator+(ColorYCbCr left, ColorYCbCr right) @@ -617,7 +616,7 @@ public float CalcDistWeighted(ColorYCbCrAlpha other, float yWeight = 4, float aW float dcr = (cr - other.cr) * (cr - other.cr); float da = (alpha - other.alpha) * (alpha - other.alpha) * aWeight; - return MathF.Sqrt(dy + dcb + dcr + da); + return (float)Math.Sqrt(dy + dcb + dcr + da); } public static ColorYCbCrAlpha operator+(ColorYCbCrAlpha left, ColorYCbCrAlpha right) @@ -672,7 +671,7 @@ public static ColorXyz ColorToXyz(ColorRgb24 color) { } private static float PivotRgb(float n) { - return (n > 0.04045f ? MathF.Pow((n + 0.055f) / 1.055f, 2.4f) : n / 12.92f) * 100; + return (n > 0.04045f ? (float)Math.Pow((n + 0.055f) / 1.055f, 2.4f) : n / 12.92f) * 100; } } @@ -719,7 +718,7 @@ public static ColorLab XyzToLab(ColorXyz xyz) { } private static float PivotXyz(float n) { - float i = MathF.Cbrt(n); + float i = (float)Math.Pow(n, (1.0 / 3.0)); return n > 0.008856f ? i : 7.787f * n + 16 / 116f; } } diff --git a/BCnEnc.Net/Shared/CompressionFormat.cs b/BCnEnc.Net/Shared/CompressionFormat.cs index 04bf275..25f8d91 100644 --- a/BCnEnc.Net/Shared/CompressionFormat.cs +++ b/BCnEnc.Net/Shared/CompressionFormat.cs @@ -2,22 +2,6 @@ { 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, /// /// BC1 / DXT1 with no alpha. Very widely supported and good compression ratio. /// @@ -55,16 +39,7 @@ public enum CompressionFormat 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: - return false; - - default: - return true; - } + return true; } } } diff --git a/BCnEnc.Net/Shared/DdsFile.cs b/BCnEnc.Net/Shared/DdsFile.cs index 229c529..871f061 100644 --- a/BCnEnc.Net/Shared/DdsFile.cs +++ b/BCnEnc.Net/Shared/DdsFile.cs @@ -126,7 +126,10 @@ public static DdsFile Load(Stream s) } } byte[] data = new byte[sizeInBytes]; - br.Read(data); + for (int b = 0; b < data.Length; b++) + { + data[b] = br.ReadByte(); + } output.Faces[face].MipMaps[mip] = new DdsMipMap(data, mipWidth, mipHeight); } } @@ -218,7 +221,7 @@ public unsafe struct DdsHeader public uint dwCaps4; public uint dwReserved2; - public static (DdsHeader, DdsHeaderDxt10) InitializeCompressed(int width, int height, DXGI_FORMAT format) + public static DdsHeader InitializeCompressed(int width, int height, DXGI_FORMAT format, out DdsHeaderDxt10 dxt10) { DdsHeader header = new DdsHeader(); DdsHeaderDxt10 dxt10Header = new DdsHeaderDxt10(); @@ -271,7 +274,8 @@ public static (DdsHeader, DdsHeaderDxt10) InitializeCompressed(int width, int he dxt10Header.resourceDimension = D3D10_RESOURCE_DIMENSION.D3D10_RESOURCE_DIMENSION_TEXTURE2D; } - return (header, dxt10Header); + dxt10 = dxt10Header; + return header; } public static DdsHeader InitializeUncompressed(int width, int height, DXGI_FORMAT format) diff --git a/BCnEnc.Net/Shared/EncodedBlocks.cs b/BCnEnc.Net/Shared/EncodedBlocks.cs index bf41884..bbb4d40 100644 --- a/BCnEnc.Net/Shared/EncodedBlocks.cs +++ b/BCnEnc.Net/Shared/EncodedBlocks.cs @@ -1,11 +1,10 @@ using System; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Shared { [StructLayout(LayoutKind.Sequential)] - internal unsafe struct Bc1Block + internal struct Bc1Block { public ColorRgb565 color0; public ColorRgb565 color1; @@ -13,7 +12,7 @@ internal unsafe struct Bc1Block public int this[int index] { - readonly get => (int)(colorIndices >> (index * 2)) & 0b11; + get => (int)(colorIndices >> (index * 2)) & 0b11; set { colorIndices = (uint)(colorIndices & (~(0b11 << (index * 2)))); @@ -22,42 +21,41 @@ public int this[int index] } } - public readonly bool HasAlphaOrBlack => color0.data <= color1.data; + public bool HasAlphaOrBlack => color0.data <= color1.data; - public readonly RawBlock4X4Rgba32 Decode(bool useAlpha) + public RawBlock4X4Rgba32 Decode(bool useAlpha) { RawBlock4X4Rgba32 output = new RawBlock4X4Rgba32(); - var pixels = output.AsSpan; var color0 = this.color0.ToColorRgb24(); var color1 = this.color1.ToColorRgb24(); useAlpha = useAlpha && HasAlphaOrBlack; - Span colors = HasAlphaOrBlack ? - stackalloc ColorRgb24[] { + ColorRgb24[] colors = HasAlphaOrBlack ? + new ColorRgb24[] { color0, color1, color0 * (1.0 / 2.0) + color1 * (1.0 / 2.0), new ColorRgb24(0, 0, 0) - } : stackalloc ColorRgb24[] { + } : new ColorRgb24[] { color0, color1, color0 * (2.0 / 3.0) + color1 * (1.0 / 3.0), color0 * (1.0 / 3.0) + color1 * (2.0 / 3.0) }; - for (int i = 0; i < pixels.Length; i++) + for (int i = 0; i < 16; i++) { int colorIndex = (int)((colorIndices >> (i * 2)) & 0b11); var color = colors[colorIndex]; if (useAlpha && colorIndex == 3) { - pixels[i] = new Rgba32(0, 0, 0, 0); + output[i] = new Rgba32(0, 0, 0, 0); } else { - pixels[i] = new Rgba32(color.r, color.g, color.b, 255); + output[i] = new Rgba32(color.r, color.g, color.b, 255); } } return output; @@ -75,7 +73,7 @@ internal unsafe struct Bc2Block public int this[int index] { - readonly get => (int)(colorIndices >> (index * 2)) & 0b11; + get => (int)(colorIndices >> (index * 2)) & 0b11; set { colorIndices = (uint)(colorIndices & (~(0b11 << (index * 2)))); @@ -84,7 +82,7 @@ public int this[int index] } } - public readonly byte GetAlpha(int index) + public byte GetAlpha(int index) { ulong mask = 0xFUL << (index * 4); int shift = (index * 4); @@ -101,27 +99,26 @@ public void SetAlpha(int index, byte alpha) alphaColors |= (ulong)(a & 0xF) << shift; } - public readonly RawBlock4X4Rgba32 Decode() + public RawBlock4X4Rgba32 Decode() { RawBlock4X4Rgba32 output = new RawBlock4X4Rgba32(); - var pixels = output.AsSpan; var color0 = this.color0.ToColorRgb24(); var color1 = this.color1.ToColorRgb24(); - Span colors = stackalloc ColorRgb24[] { + ColorRgb24[] colors = new ColorRgb24[] { color0, color1, color0 * (2.0 / 3.0) + color1 * (1.0 / 3.0), color0 * (1.0 / 3.0) + color1 * (2.0 / 3.0) }; - for (int i = 0; i < pixels.Length; i++) + for (int i = 0; i < 16; i++) { int colorIndex = (int)((colorIndices >> (i * 2)) & 0b11); var color = colors[colorIndex]; - pixels[i] = new Rgba32(color.r, color.g, color.b, GetAlpha(i)); + output[i] = new Rgba32(color.r, color.g, color.b, GetAlpha(i)); } return output; } @@ -137,7 +134,7 @@ internal unsafe struct Bc3Block public int this[int index] { - readonly get => (int)(colorIndices >> (index * 2)) & 0b11; + get => (int)(colorIndices >> (index * 2)) & 0b11; set { colorIndices = (uint)(colorIndices & (~(0b11 << (index * 2)))); @@ -148,7 +145,7 @@ public int this[int index] public byte Alpha0 { - readonly get => (byte)(alphaBlock & 0xFFUL); + get => (byte)(alphaBlock & 0xFFUL); set { alphaBlock &= ~0xFFUL; @@ -158,7 +155,7 @@ public byte Alpha0 public byte Alpha1 { - readonly get => (byte)((alphaBlock >> 8) & 0xFFUL); + get => (byte)((alphaBlock >> 8) & 0xFFUL); set { alphaBlock &= ~0xFF00UL; @@ -166,7 +163,7 @@ public byte Alpha1 } } - public readonly byte GetAlphaIndex(int pixelIndex) + public byte GetAlphaIndex(int pixelIndex) { ulong mask = 0b0111UL << (pixelIndex * 3 + 16); int shift = (pixelIndex * 3 + 16); @@ -182,24 +179,23 @@ public void SetAlphaIndex(int pixelIndex, byte alphaIndex) alphaBlock |= (ulong)(alphaIndex & 0b111) << shift; } - public readonly RawBlock4X4Rgba32 Decode() + public RawBlock4X4Rgba32 Decode() { RawBlock4X4Rgba32 output = new RawBlock4X4Rgba32(); - var pixels = output.AsSpan; var color0 = this.color0.ToColorRgb24(); var color1 = this.color1.ToColorRgb24(); var a0 = Alpha0; var a1 = Alpha1; - Span colors = stackalloc ColorRgb24[] { + ColorRgb24[] colors = new ColorRgb24[] { color0, color1, color0 * (2.0 / 3.0) + color1 * (1.0 / 3.0), color0 * (1.0 / 3.0) + color1 * (2.0 / 3.0) }; - Span alphas = a0 > a1 ? stackalloc byte[] { + byte[] alphas = a0 > a1 ? new byte[] { a0, a1, (byte)(6 / 7.0 * a0 + 1 / 7.0 * a1), @@ -208,7 +204,7 @@ public readonly RawBlock4X4Rgba32 Decode() (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[] { + } : new byte[] { a0, a1, (byte)(4 / 5.0 * a0 + 1 / 5.0 * a1), @@ -219,12 +215,12 @@ public readonly RawBlock4X4Rgba32 Decode() 255 }; - for (int i = 0; i < pixels.Length; i++) + for (int i = 0; i < 16; i++) { int colorIndex = (int)((colorIndices >> (i * 2)) & 0b11); var color = colors[colorIndex]; - pixels[i] = new Rgba32(color.r, color.g, color.b, alphas[GetAlphaIndex(i)]); + output[i] = new Rgba32(color.r, color.g, color.b, alphas[GetAlphaIndex(i)]); } return output; } @@ -237,7 +233,7 @@ internal unsafe struct Bc4Block public byte Red0 { - readonly get => (byte)(redBlock & 0xFFUL); + get => (byte)(redBlock & 0xFFUL); set { redBlock &= ~0xFFUL; @@ -247,7 +243,7 @@ public byte Red0 public byte Red1 { - readonly get => (byte)((redBlock >> 8) & 0xFFUL); + get => (byte)((redBlock >> 8) & 0xFFUL); set { redBlock &= ~0xFF00UL; @@ -255,7 +251,7 @@ public byte Red1 } } - public readonly byte GetRedIndex(int pixelIndex) + public byte GetRedIndex(int pixelIndex) { ulong mask = 0b0111UL << (pixelIndex * 3 + 16); int shift = (pixelIndex * 3 + 16); @@ -271,15 +267,14 @@ public void SetRedIndex(int pixelIndex, byte redIndex) redBlock |= (ulong)(redIndex & 0b111) << shift; } - public readonly RawBlock4X4Rgba32 Decode(bool redAsLuminance) + public RawBlock4X4Rgba32 Decode(bool redAsLuminance) { RawBlock4X4Rgba32 output = new RawBlock4X4Rgba32(); - var pixels = output.AsSpan; var r0 = Red0; var r1 = Red1; - Span reds = r0 > r1 ? stackalloc byte[] { + byte[] reds = r0 > r1 ? new byte[] { r0, r1, (byte)(6 / 7.0 * r0 + 1 / 7.0 * r1), @@ -288,7 +283,7 @@ public readonly RawBlock4X4Rgba32 Decode(bool redAsLuminance) (byte)(3 / 7.0 * r0 + 4 / 7.0 * r1), (byte)(2 / 7.0 * r0 + 5 / 7.0 * r1), (byte)(1 / 7.0 * r0 + 6 / 7.0 * r1), - } : stackalloc byte[] { + } : new byte[] { r0, r1, (byte)(4 / 5.0 * r0 + 1 / 5.0 * r1), @@ -301,18 +296,18 @@ public readonly RawBlock4X4Rgba32 Decode(bool redAsLuminance) if (redAsLuminance) { - for (int i = 0; i < pixels.Length; i++) + for (int i = 0; i < 16; i++) { var index = GetRedIndex(i); - pixels[i] = new Rgba32(reds[index], reds[index], reds[index], 255); + output[i] = new Rgba32(reds[index], reds[index], reds[index], 255); } } else { - for (int i = 0; i < pixels.Length; i++) + for (int i = 0; i < 16; i++) { var index = GetRedIndex(i); - pixels[i] = new Rgba32(reds[index], 0, 0, 255); + output[i] = new Rgba32(reds[index], 0, 0, 255); } } @@ -328,7 +323,7 @@ internal unsafe struct Bc5Block public byte Red0 { - readonly get => (byte)(redBlock & 0xFFUL); + get => (byte)(redBlock & 0xFFUL); set { redBlock &= ~0xFFUL; @@ -338,7 +333,7 @@ public byte Red0 public byte Red1 { - readonly get => (byte)((redBlock >> 8) & 0xFFUL); + get => (byte)((redBlock >> 8) & 0xFFUL); set { redBlock &= ~0xFF00UL; @@ -348,7 +343,7 @@ public byte Red1 public byte Green0 { - readonly get => (byte)(greenBlock & 0xFFUL); + get => (byte)(greenBlock & 0xFFUL); set { greenBlock &= ~0xFFUL; @@ -358,7 +353,7 @@ public byte Green0 public byte Green1 { - readonly get => (byte)((greenBlock >> 8) & 0xFFUL); + get => (byte)((greenBlock >> 8) & 0xFFUL); set { greenBlock &= ~0xFF00UL; @@ -366,7 +361,7 @@ public byte Green1 } } - public readonly byte GetRedIndex(int pixelIndex) + public byte GetRedIndex(int pixelIndex) { ulong mask = 0b0111UL << (pixelIndex * 3 + 16); int shift = (pixelIndex * 3 + 16); @@ -382,7 +377,7 @@ public void SetRedIndex(int pixelIndex, byte redIndex) redBlock |= (ulong)(redIndex & 0b111) << shift; } - public readonly byte GetGreenIndex(int pixelIndex) + public byte GetGreenIndex(int pixelIndex) { ulong mask = 0b0111UL << (pixelIndex * 3 + 16); int shift = (pixelIndex * 3 + 16); @@ -398,15 +393,14 @@ public void SetGreenIndex(int pixelIndex, byte greenIndex) greenBlock |= (ulong)(greenIndex & 0b111) << shift; } - public readonly RawBlock4X4Rgba32 Decode() + public RawBlock4X4Rgba32 Decode() { RawBlock4X4Rgba32 output = new RawBlock4X4Rgba32(); - var pixels = output.AsSpan; var r0 = Red0; var r1 = Red1; - Span reds = r0 > r1 ? stackalloc byte[] { + byte[] reds = r0 > r1 ? new byte[] { r0, r1, (byte)(6 / 7.0 * r0 + 1 / 7.0 * r1), @@ -415,7 +409,7 @@ public readonly RawBlock4X4Rgba32 Decode() (byte)(3 / 7.0 * r0 + 4 / 7.0 * r1), (byte)(2 / 7.0 * r0 + 5 / 7.0 * r1), (byte)(1 / 7.0 * r0 + 6 / 7.0 * r1), - } : stackalloc byte[] { + } : new byte[] { r0, r1, (byte)(4 / 5.0 * r0 + 1 / 5.0 * r1), @@ -429,7 +423,7 @@ public readonly RawBlock4X4Rgba32 Decode() var g0 = Green0; var g1 = Green1; - Span greens = g0 > g1 ? stackalloc byte[] { + byte[] greens = g0 > g1 ? new byte[] { g0, g1, (byte)(6 / 7.0 * g0 + 1 / 7.0 * g1), @@ -438,7 +432,7 @@ public readonly RawBlock4X4Rgba32 Decode() (byte)(3 / 7.0 * g0 + 4 / 7.0 * g1), (byte)(2 / 7.0 * g0 + 5 / 7.0 * g1), (byte)(1 / 7.0 * g0 + 6 / 7.0 * g1), - } : stackalloc byte[] { + } : new byte[] { g0, g1, (byte)(4 / 5.0 * g0 + 1 / 5.0 * g1), @@ -449,11 +443,11 @@ public readonly RawBlock4X4Rgba32 Decode() 255 }; - for (int i = 0; i < pixels.Length; i++) + for (int i = 0; i < 16; i++) { var redIndex = GetRedIndex(i); var greenIndex = GetGreenIndex(i); - pixels[i] = new Rgba32(reds[redIndex], greens[greenIndex], 0, 255); + output[i] = new Rgba32(reds[redIndex], greens[greenIndex], 0, 255); } return output; diff --git a/BCnEnc.Net/Shared/ImageToBlocks.cs b/BCnEnc.Net/Shared/ImageToBlocks.cs index fb8b7bb..50c62f5 100644 --- a/BCnEnc.Net/Shared/ImageToBlocks.cs +++ b/BCnEnc.Net/Shared/ImageToBlocks.cs @@ -1,30 +1,28 @@ using System; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Shared { internal static class ImageToBlocks { - internal static RawBlock4X4Rgba32[] ImageTo4X4(ImageFrame image, out int blocksWidth, out int blocksHeight) + internal static RawBlock4X4Rgba32[] ImageTo4X4(byte[] imageRgba, int imageWidth, int imageHeight, out int blocksWidth, out int blocksHeight) { - blocksWidth = (int)MathF.Ceiling(image.Width / 4.0f); - blocksHeight = (int)MathF.Ceiling(image.Height / 4.0f); + blocksWidth = (int)Math.Ceiling(imageWidth / 4.0f); + blocksHeight = (int)Math.Ceiling(imageHeight / 4.0f); RawBlock4X4Rgba32[] output = new RawBlock4X4Rgba32[blocksWidth * blocksHeight]; - if (!image.TryGetSinglePixelSpan(out var pixels)) { - throw new Exception("Cannot get pixel span."); - } - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageHeight; y++) { - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < imageWidth; x++) { - Rgba32 color = pixels[x + y * image.Width]; - int blockIndexX = (int)MathF.Floor(x / 4.0f); - int blockIndexY = (int)MathF.Floor(y / 4.0f); + byte r = imageRgba[(x + y * imageWidth) * 4 + 0]; + byte g = imageRgba[(x + y * imageWidth) * 4 + 1]; + byte b = imageRgba[(x + y * imageWidth) * 4 + 2]; + byte a = imageRgba[(x + y * imageWidth) * 4 + 3]; + Rgba32 color = new Rgba32(r, g, b, a); + int blockIndexX = (int)Math.Floor(x / 4.0f); + int blockIndexY = (int)Math.Floor(y / 4.0f); int blockInternalIndexX = x % 4; int blockInternalIndexY = y % 4; @@ -33,9 +31,9 @@ internal static RawBlock4X4Rgba32[] ImageTo4X4(ImageFrame image, out int } //Fill in block y with edge color - if (image.Height % 4 != 0) + if (imageHeight % 4 != 0) { - int yPaddingStart = image.Height % 4; + int yPaddingStart = imageHeight % 4; for (int i = 0; i < blocksWidth; i++) { var lastBlock = output[i + blocksWidth * (blocksHeight - 1)]; @@ -51,9 +49,9 @@ internal static RawBlock4X4Rgba32[] ImageTo4X4(ImageFrame image, out int } //Fill in block x with edge color - if (image.Width % 4 != 0) + if (imageWidth % 4 != 0) { - int xPaddingStart = image.Width % 4; + int xPaddingStart = imageWidth % 4; for (int i = 0; i < blocksHeight; i++) { var lastBlock = output[blocksWidth - 1 + i * blocksWidth]; @@ -71,60 +69,55 @@ internal static RawBlock4X4Rgba32[] ImageTo4X4(ImageFrame image, out int return output; } - internal static Image ImageFromRawBlocks(RawBlock4X4Rgba32[,] blocks, int blocksWidth, int blocksHeight) + internal static byte[] ImageFromRawBlocks(RawBlock4X4Rgba32[,] blocks, int blocksWidth, int blocksHeight) => ImageFromRawBlocks(blocks, blocksWidth, blocksHeight, blocksWidth * 4, blocksHeight * 4); - internal static Image ImageFromRawBlocks(RawBlock4X4Rgba32[,] blocks, int blocksWidth, int blocksHeight, int pixelWidth, int pixelHeight) + internal static byte[] ImageFromRawBlocks(RawBlock4X4Rgba32[,] blocks, int blocksWidth, int blocksHeight, int pixelWidth, int pixelHeight) { - Image output = new Image(pixelWidth, pixelHeight); - - if (!output.TryGetSinglePixelSpan(out var pixels)) { - throw new Exception("Cannot get pixel span."); - } + byte[] output = new byte[pixelWidth * pixelHeight * 4]; - for (int y = 0; y < output.Height; y++) + for (int y = 0; y < pixelHeight; y++) { - for (int x = 0; x < output.Width; x++) + for (int x = 0; x < pixelWidth; x++) { - int blockIndexX = (int)MathF.Floor(x / 4.0f); - int blockIndexY = (int)MathF.Floor(y / 4.0f); + int blockIndexX = (int)Math.Floor(x / 4.0f); + int blockIndexY = (int)Math.Floor(y / 4.0f); int blockInternalIndexX = x % 4; int blockInternalIndexY = y % 4; - pixels[x + y * output.Width] = - blocks[blockIndexX, blockIndexY] - [blockInternalIndexX, blockInternalIndexY]; + output[(x + y * pixelWidth) * 4 + 0] = blocks[blockIndexX, blockIndexY][blockInternalIndexX, blockInternalIndexY].R; + output[(x + y * pixelWidth) * 4 + 1] = blocks[blockIndexX, blockIndexY][blockInternalIndexX, blockInternalIndexY].G; + output[(x + y * pixelWidth) * 4 + 2] = blocks[blockIndexX, blockIndexY][blockInternalIndexX, blockInternalIndexY].B; + output[(x + y * pixelWidth) * 4 + 3] = blocks[blockIndexX, blockIndexY][blockInternalIndexX, blockInternalIndexY].A; } } return output; } - internal static Image ImageFromRawBlocks(RawBlock4X4Rgba32[] blocks, int blocksWidth, int blocksHeight) + internal static byte[] ImageFromRawBlocks(RawBlock4X4Rgba32[] blocks, int blocksWidth, int blocksHeight) => ImageFromRawBlocks(blocks, blocksWidth, blocksHeight, blocksWidth * 4, blocksHeight * 4); - internal static Image ImageFromRawBlocks(RawBlock4X4Rgba32[] blocks, int blocksWidth, int blocksHeight, int pixelWidth, int pixelHeight) + internal static byte[] ImageFromRawBlocks(RawBlock4X4Rgba32[] blocks, int blocksWidth, int blocksHeight, int pixelWidth, int pixelHeight) { - Image output = new Image(pixelWidth, pixelHeight); - - if (!output.TryGetSinglePixelSpan(out var pixels)) { - throw new Exception("Cannot get pixel span."); - } + byte[] output = new byte[pixelWidth * pixelHeight * 4]; - for (int y = 0; y < output.Height; y++) + + for (int y = 0; y < pixelHeight; y++) { - for (int x = 0; x < output.Width; x++) + for (int x = 0; x < pixelWidth; x++) { - int blockIndexX = (int)MathF.Floor(x / 4.0f); - int blockIndexY = (int)MathF.Floor(y / 4.0f); + int blockIndexX = (int)Math.Floor(x / 4.0f); + int blockIndexY = (int)Math.Floor(y / 4.0f); int blockInternalIndexX = x % 4; int blockInternalIndexY = y % 4; - - pixels[x + y * output.Width] = - blocks[blockIndexX + blockIndexY * blocksWidth] - [blockInternalIndexX, blockInternalIndexY]; + + output[(x + y * pixelWidth) * 4 + 0] = blocks[blockIndexX + blockIndexY * blocksWidth][blockInternalIndexX, blockInternalIndexY].R; + output[(x + y * pixelWidth) * 4 + 1] = blocks[blockIndexX + blockIndexY * blocksWidth][blockInternalIndexX, blockInternalIndexY].G; + output[(x + y * pixelWidth) * 4 + 2] = blocks[blockIndexX + blockIndexY * blocksWidth][blockInternalIndexX, blockInternalIndexY].B; + output[(x + y * pixelWidth) * 4 + 3] = blocks[blockIndexX + blockIndexY * blocksWidth][blockInternalIndexX, blockInternalIndexY].A; } } diff --git a/BCnEnc.Net/Shared/KtxFile.cs b/BCnEnc.Net/Shared/KtxFile.cs index 27ce05f..276be55 100644 --- a/BCnEnc.Net/Shared/KtxFile.cs +++ b/BCnEnc.Net/Shared/KtxFile.cs @@ -217,7 +217,7 @@ public uint GetSizeWithPadding() public static KtxKeyValuePair ReadKeyValuePair(BinaryReader br, out int bytesRead) { uint totalSize = br.ReadUInt32(); - Span keyValueBytes = stackalloc byte[(int)totalSize]; + byte[] keyValueBytes = new byte[(int)totalSize]; br.Read(keyValueBytes); // Find the key's null terminator @@ -237,12 +237,12 @@ public static KtxKeyValuePair ReadKeyValuePair(BinaryReader br, out int bytesRea int keySize = i; - string key = UTF8.GetString(keyValueBytes.Slice(0, keySize)); + string key = UTF8.GetString(keyValueBytes, 0, keySize); int valueSize = (int)(totalSize - keySize - 1); - Span valueBytes = keyValueBytes.Slice(i + 1, valueSize); + byte[] valueBytes = keyValueBytes.Slice(i + 1, valueSize); byte[] value = new byte[valueSize]; - valueBytes.CopyTo(value); + valueBytes.CopyTo(value, 0); int paddingBytes = (int)(3 - ((totalSize + 3) % 4)); br.SkipPadding(paddingBytes); @@ -254,8 +254,8 @@ public static KtxKeyValuePair ReadKeyValuePair(BinaryReader br, out int bytesRea public static uint WriteKeyValuePair(BinaryWriter bw, KtxKeyValuePair pair) { int keySpanLength = UTF8.GetByteCount(pair.Key); - Span keySpan = stackalloc byte[keySpanLength]; - Span valueSpan = pair.Value; + byte[] keySpan = new byte[keySpanLength]; + byte[] valueSpan = pair.Value; uint totalSize = (uint)(keySpan.Length + 1 + valueSpan.Length); int paddingBytes = (int)(3 - ((totalSize + 3) % 4)); @@ -290,7 +290,7 @@ public unsafe struct KtxHeader public bool VerifyHeader() { - Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + byte[] id = new byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; for (int i = 0; i < id.Length; i++) { if (Identifier[i] != id[i]) return false; @@ -301,7 +301,7 @@ public bool VerifyHeader() public static KtxHeader InitializeCompressed(int width, int height, GlInternalFormat internalFormat, GLFormat baseInternalFormat) { KtxHeader header = new KtxHeader(); - Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + byte[] id = new byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; for (int i = 0; i < id.Length; i++) { header.Identifier[i] = id[i]; @@ -321,7 +321,7 @@ public static KtxHeader InitializeCompressed(int width, int height, GlInternalFo public static KtxHeader InitializeUncompressed(int width, int height, GLType type, GLFormat format, uint glTypeSize, GlInternalFormat internalFormat, GLFormat baseInternalFormat) { KtxHeader header = new KtxHeader(); - Span id = stackalloc byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + byte[] id = new byte[] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; for (int i = 0; i < id.Length; i++) { header.Identifier[i] = id[i]; diff --git a/BCnEnc.Net/Shared/LinearClustering.cs b/BCnEnc.Net/Shared/LinearClustering.cs index 5df2b97..f41d2db 100644 --- a/BCnEnc.Net/Shared/LinearClustering.cs +++ b/BCnEnc.Net/Shared/LinearClustering.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Shared { @@ -63,27 +62,27 @@ public ClusterCenter(LabXy labxy) count = 0; } - public readonly float Distance(LabXy other, float m, float s) + public float Distance(LabXy other, float m, float s) { - float dLab = MathF.Sqrt( - MathF.Pow(l - other.l, 2) + - MathF.Pow(a - other.a, 2) + - MathF.Pow(b - other.b, 2)); - - float dXy = MathF.Sqrt( - MathF.Pow(x - other.x, 2) + - MathF.Pow(y - other.y, 2)); + float dLab = (float)Math.Sqrt( + (float)Math.Pow(l - other.l, 2) + + (float)Math.Pow(a - other.a, 2) + + (float)Math.Pow(b - other.b, 2)); + + float dXy = (float)Math.Sqrt( + (float)Math.Pow(x - other.x, 2) + + (float)Math.Pow(y - other.y, 2)); return dLab + (m / s) * dXy; } - public readonly float Distance(ClusterCenter other, float m, float s) + public float Distance(ClusterCenter other, float m, float s) { - float dLab = MathF.Sqrt( + float dLab = (float)Math.Sqrt( (l - other.l) * (l - other.l) + (a - other.a) * (a - other.a) + (b - other.b) * (b - other.b)); - float dXy = MathF.Sqrt( + float dXy = (float)Math.Sqrt( (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y)); return dLab + m / s * dXy; @@ -121,7 +120,7 @@ public readonly float Distance(ClusterCenter other, float m, float s) /// the more spatial proximity is emphasized and the more compact the cluster, /// M should be in range of 1 to 20. /// - public static int[] ClusterPixels(ReadOnlySpan pixels, int width, int height, + public static int[] ClusterPixels(Rgba32[] pixels, int width, int height, int clusters, float m = 10, int maxIterations = 10, bool enforceConnectivity = true) { @@ -131,14 +130,13 @@ public static int[] ClusterPixels(ReadOnlySpan pixels, int width, int he } //Grid interval S - float S = MathF.Sqrt(pixels.Length / (float)clusters); + float S = (float)Math.Sqrt(pixels.Length / (float)clusters); int[] clusterIndices = new int[pixels.Length]; var labXys = ConvertToLabXy(pixels, width, height); - - Span clusterCenters = InitialClusterCenters(width, height, clusters, S, labXys); - Span previousCenters = new ClusterCenter[clusters]; + ClusterCenter[] clusterCenters = InitialClusterCenters(width, height, clusters, S, labXys); + ClusterCenter[] previousCenters = new ClusterCenter[clusters]; float Error = 999; const float threshold = 0.1f; @@ -151,9 +149,9 @@ public static int[] ClusterPixels(ReadOnlySpan pixels, int width, int he } iter++; - clusterCenters.CopyTo(previousCenters); + clusterCenters.CopyTo(previousCenters, 0); - Array.Fill(clusterIndices, -1); + clusterIndices.Fill(-1); // Find closest cluster for pixels for (int j = 0; j < clusters; j++) @@ -189,15 +187,16 @@ public static int[] ClusterPixels(ReadOnlySpan pixels, int width, int he Error = RecalculateCenters(clusters, m, labXys, clusterIndices, previousCenters, S, ref clusterCenters); } - if (enforceConnectivity) { + if (enforceConnectivity) + { clusterIndices = EnforceConnectivity(clusterIndices, width, height, clusters); } - + return clusterIndices; } private static float RecalculateCenters(int clusters, float m, LabXy[] labXys, int[] clusterIndices, - Span previousCenters, float S, ref Span clusterCenters) + ClusterCenter[] previousCenters, float S, ref ClusterCenter[] clusterCenters) { clusterCenters.Clear(); for (int i = 0; i < labXys.Length; i++) @@ -209,9 +208,11 @@ private static float RecalculateCenters(int clusters, float m, LabXy[] labXys, i { int bestCluster = 0; float bestDistance = previousCenters[0].Distance(labXys[i], m, S); - for (int j = 1; j < clusters; j++) { + for (int j = 1; j < clusters; j++) + { float dist = previousCenters[j].Distance(labXys[i], m, S); - if (dist < bestDistance) { + if (dist < bestDistance) + { bestDistance = dist; bestCluster = j; } @@ -219,7 +220,8 @@ private static float RecalculateCenters(int clusters, float m, LabXy[] labXys, i clusterCenters[bestCluster] += labXys[i]; clusterIndices[i] = bestCluster; } - else { + else + { clusterCenters[clusterIndex] += labXys[i]; } } @@ -242,36 +244,39 @@ private static ClusterCenter[] InitialClusterCenters(int width, int height, int { ClusterCenter[] clusterCenters = new ClusterCenter[clusters]; - if (clusters == 2) { - int x0 = (int)MathF.Floor(width * 0.333f); - int y0 = (int)MathF.Floor(height * 0.333f); + if (clusters == 2) + { + int x0 = (int)(float)Math.Floor(width * 0.333f); + int y0 = (int)(float)Math.Floor(height * 0.333f); - int x1 = (int)MathF.Floor(width * 0.666f); - int y1 = (int)MathF.Floor(height * 0.666f); + int x1 = (int)(float)Math.Floor(width * 0.666f); + int y1 = (int)(float)Math.Floor(height * 0.666f); int i0 = x0 + y0 * width; clusterCenters[0] = new ClusterCenter(labXys[i0]); int i1 = x1 + y1 * width; clusterCenters[1] = new ClusterCenter(labXys[i1]); - }else if(clusters == 3) + } + else if (clusters == 3) { - int x0 = (int)MathF.Floor(width * 0.333f); - int y0 = (int)MathF.Floor(height * 0.333f); + int x0 = (int)(float)Math.Floor(width * 0.333f); + int y0 = (int)(float)Math.Floor(height * 0.333f); int i0 = x0 + y0 * width; clusterCenters[0] = new ClusterCenter(labXys[i0]); - int x1 = (int)MathF.Floor(width * 0.666f); - int y1 = (int)MathF.Floor(height * 0.333f); + int x1 = (int)(float)Math.Floor(width * 0.666f); + int y1 = (int)(float)Math.Floor(height * 0.333f); int i1 = x1 + y1 * width; clusterCenters[1] = new ClusterCenter(labXys[i1]); - int x2 = (int)MathF.Floor(width * 0.5f); - int y2 = (int)MathF.Floor(height * 0.666f); + int x2 = (int)(float)Math.Floor(width * 0.5f); + int y2 = (int)(float)Math.Floor(height * 0.666f); int i2 = x2 + y2 * width; clusterCenters[2] = new ClusterCenter(labXys[i2]); } - else { + else + { int cIdx = 0; //Choose initial centers for (float x = S / 2; x < width; x += S) @@ -292,7 +297,7 @@ private static ClusterCenter[] InitialClusterCenters(int width, int height, int return clusterCenters; } - private static LabXy[] ConvertToLabXy(ReadOnlySpan pixels, int width, int height) + private static LabXy[] ConvertToLabXy(Rgba32[] pixels, int width, int height) { LabXy[] labXys = new LabXy[pixels.Length]; //Convert pixels to LabXy @@ -318,9 +323,9 @@ private static LabXy[] ConvertToLabXy(ReadOnlySpan pixels, int width, in private static int[] EnforceConnectivity(int[] oldLabels, int width, int height, int clusters) { - ReadOnlySpan neighborX = new[] { -1, 0, 1, 0 }; - ReadOnlySpan neighborY = new[] { 0, -1, 0, 1 }; - + int[] neighborX = new[] { -1, 0, 1, 0 }; + int[] neighborY = new[] { 0, -1, 0, 1 }; + int sSquared = (width * height) / clusters; List clusterX = new List(sSquared); @@ -329,14 +334,15 @@ private static int[] EnforceConnectivity(int[] oldLabels, int width, int height, int adjacentLabel = 0; int[] newLabels = new int[oldLabels.Length]; bool[] usedLabels = new bool[clusters]; - Array.Fill(newLabels, -1); + newLabels.Fill(-1); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { int xyIndex = x + y * width; - if (newLabels[xyIndex] < 0) { + if (newLabels[xyIndex] < 0) + { int label = oldLabels[xyIndex]; newLabels[xyIndex] = label; @@ -389,7 +395,8 @@ private static int[] EnforceConnectivity(int[] oldLabels, int width, int height, newLabels[(clusterY[i] * width + clusterX[i])] = adjacentLabel; } } - else { + else + { usedLabels[label] = true; } diff --git a/BCnEnc.Net/Shared/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs deleted file mode 100644 index 23329e2..0000000 --- a/BCnEnc.Net/Shared/MipMapper.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace BCnEncoder.Shared -{ - public static class MipMapper - { - - public static uint CalculateMipChainLength(int width, int height, uint maxNumMipMaps) { - if (maxNumMipMaps == 1) { - return 1; - } - if (maxNumMipMaps == 0) { - maxNumMipMaps = 999; - } - uint output = 0; - for (uint mipLevel = 1; mipLevel < maxNumMipMaps; mipLevel++) { - int mipWidth = Math.Max(1, width / (int)(Math.Pow(2, mipLevel))); - int mipHeight = Math.Max(1, height / (int)(Math.Pow(2, mipLevel))); - if (mipWidth == 1 && mipHeight == 1) { - output = mipLevel + 1; - break; - } - } - return output; - } - - public static List> GenerateMipChain(Image sourceImage, ref uint numMipMaps) { - List> result = new List>(); - result.Add(sourceImage.Clone()); - - if (numMipMaps == 1) { - return result; - } - - if (numMipMaps == 0) { - numMipMaps = 999; - } - - for (uint mipLevel = 1; mipLevel < numMipMaps; mipLevel++) { - int mipWidth = Math.Max(1, sourceImage.Width / (int)(Math.Pow(2, mipLevel))); - int mipHeight = Math.Max(1, sourceImage.Height / (int)(Math.Pow(2, mipLevel))); - - var newImage = sourceImage.Clone(x => x.Resize(mipWidth, mipHeight)); - result.Add(newImage); - - if (mipWidth == 1 && mipHeight == 1) { - numMipMaps = mipLevel + 1; - break; - } - } - - return result; - } - } -} diff --git a/BCnEnc.Net/Shared/PcaVectors.cs b/BCnEnc.Net/Shared/PcaVectors.cs index 999e5c8..acac61e 100644 --- a/BCnEnc.Net/Shared/PcaVectors.cs +++ b/BCnEnc.Net/Shared/PcaVectors.cs @@ -1,5 +1,4 @@ using System; -using SixLabors.ImageSharp.PixelFormats; using Vector3 = System.Numerics.Vector3; using Vector4 = System.Numerics.Vector4; using Matrix4x4 = System.Numerics.Matrix4x4; @@ -11,7 +10,7 @@ internal static class PcaVectors private const int c565_5_mask = 0xF8; private const int c565_6_mask = 0xFC; - private static void ConvertToVector4(ReadOnlySpan colors, Span vectors) + private static void ConvertToVector4(Rgba32[] colors, Vector4[] vectors) { for (int i = 0; i < colors.Length; i++) { @@ -22,7 +21,7 @@ private static void ConvertToVector4(ReadOnlySpan colors, Span } } - private static Vector4 CalculateMean(Span colors) + private static Vector4 CalculateMean(Vector4[] colors) { float r = 0; @@ -46,7 +45,7 @@ private static Vector4 CalculateMean(Span colors) ); } - internal static Matrix4x4 CalculateCovariance(Span values, out Vector4 mean) { + internal static Matrix4x4 CalculateCovariance(Vector4[] values, out Vector4 mean) { mean = CalculateMean(values); for (int i = 0; i < values.Length; i++) { @@ -112,9 +111,9 @@ internal static Vector4 CalculatePrincipalAxis(Matrix4x4 covarianceMatrix) { return lastDa; } - public static void Create(Span colors, out Vector3 mean, out Vector3 principalAxis) + public static void Create(Rgba32[] colors, out Vector3 mean, out Vector3 principalAxis) { - Span vectors = stackalloc Vector4[colors.Length]; + Vector4[] vectors = new Vector4[colors.Length]; ConvertToVector4(colors, vectors); @@ -132,9 +131,9 @@ public static void Create(Span colors, out Vector3 mean, out Vector3 pri } - public static void CreateWithAlpha(Span colors, out Vector4 mean, out Vector4 principalAxis) + public static void CreateWithAlpha(Rgba32[] colors, out Vector4 mean, out Vector4 principalAxis) { - Span vectors = stackalloc Vector4[colors.Length]; + Vector4[] vectors = new Vector4[colors.Length]; ConvertToVector4(colors, vectors); var cov = CalculateCovariance(vectors, out mean); @@ -142,7 +141,7 @@ public static void CreateWithAlpha(Span colors, out Vector4 mean, out Ve } - public static void GetExtremePoints(Span colors, Vector3 mean, Vector3 principalAxis, out ColorRgb24 min, + public static void GetExtremePoints(Rgba32[] colors, Vector3 mean, Vector3 principalAxis, out ColorRgb24 min, out ColorRgb24 max) { @@ -182,7 +181,7 @@ public static void GetExtremePoints(Span colors, Vector3 mean, Vector3 p max = new ColorRgb24((byte)maxR, (byte)maxG, (byte)maxB); } - public static void GetMinMaxColor565(Span colors, Vector3 mean, Vector3 principalAxis, + public static void GetMinMaxColor565(Rgba32[] colors, Vector3 mean, Vector3 principalAxis, out ColorRgb565 min, out ColorRgb565 max) { @@ -236,7 +235,7 @@ public static void GetMinMaxColor565(Span colors, Vector3 mean, Vector3 } - public static void GetExtremePointsWithAlpha(Span colors, Vector4 mean, Vector4 principalAxis, out Vector4 min, + public static void GetExtremePointsWithAlpha(Rgba32[] colors, Vector4 mean, Vector4 principalAxis, out Vector4 min, out Vector4 max) { @@ -257,7 +256,7 @@ public static void GetExtremePointsWithAlpha(Span colors, Vector4 mean, max = mean + (principalAxis * maxD); } - public static void GetOptimizedEndpoints565(Span colors, Vector3 mean, Vector3 principalAxis, out ColorRgb565 min, out ColorRgb565 max, + public static void GetOptimizedEndpoints565(Rgba32[] colors, Vector3 mean, Vector3 principalAxis, out ColorRgb565 min, out ColorRgb565 max, float rWeight = 0.3f, float gWeight = 0.6f, float bWeight = 0.1f) { int length = colors.Length; @@ -278,7 +277,7 @@ Vector3 Clamp565(Vector3 vec) if (vec.Y > 63) vec.Y = 63; if (vec.Z < 0) vec.Z = 0; if (vec.Z > 31) vec.Z = 31; - return new Vector3(MathF.Round(vec.X), MathF.Round(vec.Y), MathF.Round(vec.Z)); + return new Vector3((float)Math.Round(vec.X), (float)Math.Round(vec.Y), (float)Math.Round(vec.Z)); } float Distance(Vector3 v, Vector3 p) @@ -332,8 +331,8 @@ double calculateError() endPoint0 = mean + (principalAxis * minD); endPoint1 = mean + (principalAxis * maxD); - endPoint0 = new Vector3(MathF.Round(endPoint0.X * 31), MathF.Round(endPoint0.Y * 63), MathF.Round(endPoint0.Z * 31)); - endPoint1 = new Vector3(MathF.Round(endPoint1.X * 31), MathF.Round(endPoint1.Y * 63), MathF.Round(endPoint1.Z * 31)); + endPoint0 = new Vector3((float)Math.Round(endPoint0.X * 31), (float)Math.Round(endPoint0.Y * 63), (float)Math.Round(endPoint0.Z * 31)); + endPoint1 = new Vector3((float)Math.Round(endPoint1.X * 31), (float)Math.Round(endPoint1.Y * 63), (float)Math.Round(endPoint1.Z * 31)); endPoint0 = Clamp565(endPoint0); endPoint1 = Clamp565(endPoint1); diff --git a/BCnEnc.Net/Shared/RawBlocks.cs b/BCnEnc.Net/Shared/RawBlocks.cs index 551e330..5ff181f 100644 --- a/BCnEnc.Net/Shared/RawBlocks.cs +++ b/BCnEnc.Net/Shared/RawBlocks.cs @@ -1,32 +1,132 @@ using System; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Shared { - internal struct RawBlock4X4Rgba32 { + internal struct RawBlock4X4Rgba32 + { public Rgba32 p00, p10, p20, p30; public Rgba32 p01, p11, p21, p31; public Rgba32 p02, p12, p22, p32; public Rgba32 p03, p13, p23, p33; - public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); - public Rgba32 this[int x, int y] { - get => AsSpan[x + y * 4]; - set => AsSpan[x + y * 4] = value; + public Rgba32[] AsArray + { + get + { + return new[] + { + p00, p10, p20, p30, + p01, p11, p21, p31, + p02, p12, p22, p32, + p03, p13, p23, p33, + }; + } + } + + public Rgba32 this[int x, int y] + { + get + { + switch (x) + { + case 0: + switch (y) + { + case 0: return p00; + case 1: return p01; + case 2: return p02; + case 3: return p03; + } + break; + case 1: + switch (y) + { + case 0: return p10; + case 1: return p11; + case 2: return p12; + case 3: return p13; + } + break; + case 2: + switch (y) + { + case 0: return p20; + case 1: return p21; + case 2: return p22; + case 3: return p23; + } + break; + case 3: + switch (y) + { + case 0: return p30; + case 1: return p31; + case 2: return p32; + case 3: return p33; + } + break; + } + return default; + } + set + { + switch (x) + { + case 0: + switch (y) + { + case 0: p00 = value; break; + case 1: p01 = value; break; + case 2: p02 = value; break; + case 3: p03 = value; break; + } + break; + case 1: + switch (y) + { + case 0: p10 = value; break; + case 1: p11 = value; break; + case 2: p12 = value; break; + case 3: p13 = value; break; + } + break; + case 2: + switch (y) + { + case 0: p20 = value; break; + case 1: p21 = value; break; + case 2: p22 = value; break; + case 3: p23 = value; break; + } + break; + case 3: + switch (y) + { + case 0: p30 = value; break; + case 1: p31 = value; break; + case 2: p32 = value; break; + case 3: p33 = value; break; + } + break; + } + } } - public Rgba32 this[int index] { - get => AsSpan[index]; - set => AsSpan[index] = value; + public Rgba32 this[int index] + { + get => this[index % 4, (int)Math.Floor(index / 4.0)]; + set => this[index % 4, (int)Math.Floor(index / 4.0)] = value; } - public int CalculateError(RawBlock4X4Rgba32 other, bool useAlpha = false) { + public int CalculateError(RawBlock4X4Rgba32 other, bool useAlpha = false) + { float error = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (int i = 0; i < pix1.Length; i++) { + var pix1 = AsArray; + var pix2 = other.AsArray; + for (int i = 0; i < pix1.Length; i++) + { var col1 = pix1[i]; var col2 = pix2[i]; @@ -38,25 +138,28 @@ public int CalculateError(RawBlock4X4Rgba32 other, bool useAlpha = false) { error += ge * ge; error += be * be; - if (useAlpha) { + if (useAlpha) + { var ae = col1.A - col2.A; error += ae * ae * 4; } } error /= pix1.Length; - error = MathF.Sqrt(error); + error = (float)Math.Sqrt(error); return (int)error; } - public float CalculateYCbCrError(RawBlock4X4Rgba32 other) { + public float CalculateYCbCrError(RawBlock4X4Rgba32 other) + { float yError = 0; float cbError = 0; float crError = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (int i = 0; i < pix1.Length; i++) { + var pix1 = AsArray; + var pix2 = other.AsArray; + for (int i = 0; i < pix1.Length; i++) + { var col1 = new ColorYCbCr(pix1[i]); var col2 = new ColorYCbCr(pix2[i]); @@ -74,14 +177,16 @@ public float CalculateYCbCrError(RawBlock4X4Rgba32 other) { return error; } - public float CalculateYCbCrAlphaError(RawBlock4X4Rgba32 other, float yMultiplier = 2, float alphaMultiplier = 1) { + public float CalculateYCbCrAlphaError(RawBlock4X4Rgba32 other, float yMultiplier = 2, float alphaMultiplier = 1) + { float yError = 0; float cbError = 0; float crError = 0; float alphaError = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (int i = 0; i < pix1.Length; i++) { + var pix1 = AsArray; + var pix2 = other.AsArray; + for (int i = 0; i < pix1.Length; i++) + { var col1 = new ColorYCbCrAlpha(pix1[i]); var col2 = new ColorYCbCrAlpha(pix2[i]); @@ -100,19 +205,23 @@ public float CalculateYCbCrAlphaError(RawBlock4X4Rgba32 other, float yMultiplier return error; } - public RawBlock4X4Ycbcr ToRawBlockYcbcr() { + public RawBlock4X4Ycbcr ToRawBlockYcbcr() + { RawBlock4X4Ycbcr rawYcbcr = new RawBlock4X4Ycbcr(); - var pixels = AsSpan; - var ycbcrPs = rawYcbcr.AsSpan; - for (int i = 0; i < pixels.Length; i++) { + var pixels = AsArray; + var ycbcrPs = rawYcbcr.AsArray; + for (int i = 0; i < pixels.Length; i++) + { ycbcrPs[i] = new ColorYCbCr(pixels[i]); } return rawYcbcr; } - public bool HasTransparentPixels() { - var pixels = AsSpan; - for (int i = 0; i < pixels.Length; i++) { + public bool HasTransparentPixels() + { + var pixels = AsArray; + for (int i = 0; i < pixels.Length; i++) + { if (pixels[i].A < 255) return true; } return false; @@ -120,25 +229,131 @@ public bool HasTransparentPixels() { } - internal struct RawBlock4X4Ycbcr { + internal struct RawBlock4X4Ycbcr + { public ColorYCbCr p00, p10, p20, p30; public ColorYCbCr p01, p11, p21, p31; public ColorYCbCr p02, p12, p22, p32; public ColorYCbCr p03, p13, p23, p33; - public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16); - public ColorYCbCr this[int x, int y] { - get => AsSpan[x + y * 4]; - set => AsSpan[x + y * 4] = value; + public ColorYCbCr[] AsArray + { + get + { + return new[] + { + p00, p10, p20, p30, + p01, p11, p21, p31, + p02, p12, p22, p32, + p03, p13, p23, p33, + }; + } + } + + public ColorYCbCr this[int x, int y] + { + get + { + switch (x) + { + case 0: + switch (y) + { + case 0: return p00; + case 1: return p01; + case 2: return p02; + case 3: return p03; + } + break; + case 1: + switch (y) + { + case 0: return p10; + case 1: return p11; + case 2: return p12; + case 3: return p13; + } + break; + case 2: + switch (y) + { + case 0: return p20; + case 1: return p21; + case 2: return p22; + case 3: return p23; + } + break; + case 3: + switch (y) + { + case 0: return p30; + case 1: return p31; + case 2: return p32; + case 3: return p33; + } + break; + } + return default; + } + set + { + switch (x) + { + case 0: + switch (y) + { + case 0: p00 = value; break; + case 1: p01 = value; break; + case 2: p02 = value; break; + case 3: p03 = value; break; + } + break; + case 1: + switch (y) + { + case 0: p10 = value; break; + case 1: p11 = value; break; + case 2: p12 = value; break; + case 3: p13 = value; break; + } + break; + case 2: + switch (y) + { + case 0: p20 = value; break; + case 1: p21 = value; break; + case 2: p22 = value; break; + case 3: p23 = value; break; + } + break; + case 3: + switch (y) + { + case 0: p30 = value; break; + case 1: p31 = value; break; + case 2: p32 = value; break; + case 3: p33 = value; break; + } + break; + } + } + } + + public ColorYCbCr this[int index] + { + get => this[index % 4, (int)Math.Floor(index / 4.0)]; + set => this[index % 4, (int)Math.Floor(index / 4.0)] = value; } - public float CalculateError(RawBlock4X4Rgba32 other) { + public float CalculateError(RawBlock4X4Rgba32 other) + { float yError = 0; float cbError = 0; float crError = 0; - var pix1 = AsSpan; - var pix2 = other.AsSpan; - for (int i = 0; i < pix1.Length; i++) { + var pix1 = AsArray; + var pix2 = other.AsArray; + for (int i = 0; i < pix1.Length; i++) + { var col1 = pix1[i]; var col2 = new ColorYCbCr(pix2[i]); diff --git a/BCnEnc.Net/Shared/RgbBoundingBox.cs b/BCnEnc.Net/Shared/RgbBoundingBox.cs index 3e72628..cd2b99c 100644 --- a/BCnEnc.Net/Shared/RgbBoundingBox.cs +++ b/BCnEnc.Net/Shared/RgbBoundingBox.cs @@ -1,5 +1,4 @@ using System; -using SixLabors.ImageSharp.PixelFormats; namespace BCnEncoder.Shared { @@ -11,7 +10,7 @@ namespace BCnEncoder.Shared internal static class RgbBoundingBox { - public static void Create565(ReadOnlySpan colors, out ColorRgb565 min, out ColorRgb565 max) + public static void Create565(Rgba32[] colors, out ColorRgb565 min, out ColorRgb565 max) { const int colorInsetShift = 4; const int c565_5_mask = 0xF8; @@ -71,7 +70,7 @@ public static void Create565(ReadOnlySpan colors, out ColorRgb565 min, o max = new ColorRgb565((byte)maxR, (byte)maxG, (byte)maxB); } - public static void Create565AlphaCutoff(ReadOnlySpan colors, out ColorRgb565 min, out ColorRgb565 max, int alphaCutoff = 128) + public static void Create565AlphaCutoff(Rgba32[] colors, out ColorRgb565 min, out ColorRgb565 max, int alphaCutoff = 128) { const int colorInsetShift = 4; const int c565_5_mask = 0xF8; @@ -129,7 +128,7 @@ public static void Create565AlphaCutoff(ReadOnlySpan colors, out ColorRg max = new ColorRgb565((byte)maxR, (byte)maxG, (byte)maxB); } - public static void Create565a(ReadOnlySpan colors, out ColorRgb565 min, out ColorRgb565 max, out byte minAlpha, out byte maxAlpha) + public static void Create565a(Rgba32[] colors, out ColorRgb565 min, out ColorRgb565 max, out byte minAlpha, out byte maxAlpha) { const int colorInsetShift = 4; const int alphaInsetShift = 5; diff --git a/BCnEnc.Net/Shared/Rgba32.cs b/BCnEnc.Net/Shared/Rgba32.cs new file mode 100644 index 0000000..ebd3840 --- /dev/null +++ b/BCnEnc.Net/Shared/Rgba32.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BCnEncoder.Shared +{ + public struct Rgba32 + { + public byte R; + public byte G; + public byte B; + public byte A; + + public Rgba32(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + public Rgba32(byte r, byte g, byte b) + { + this.R = r; + this.G = g; + this.B = b; + this.A = 255; + } + + public bool Equals(Rgba32 other) + { + return R == other.R && G == other.G && B == other.B && A == other.A; + } + + public override bool Equals(object obj) + { + return obj is Rgba32 other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = R.GetHashCode(); + hashCode = (hashCode * 397) ^ G.GetHashCode(); + hashCode = (hashCode * 397) ^ B.GetHashCode(); + hashCode = (hashCode * 397) ^ A.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(Rgba32 left, Rgba32 right) + { + return left.Equals(right); + } + + public static bool operator !=(Rgba32 left, Rgba32 right) + { + return !left.Equals(right); + } + + public override string ToString() + { + return $"{nameof(R)}: {R}, {nameof(G)}: {G}, {nameof(B)}: {B}, {nameof(A)}: {A}"; + } + } +} diff --git a/BCnEncTests/BC1Tests.cs b/BCnEncTests/BC1Tests.cs index 77492f4..48f0801 100644 --- a/BCnEncTests/BC1Tests.cs +++ b/BCnEncTests/BC1Tests.cs @@ -1,6 +1,7 @@ using BCnEncoder.Shared; using SixLabors.ImageSharp.PixelFormats; using Xunit; +using Rgba32 = BCnEncoder.Shared.Rgba32; namespace BCnEncTests { diff --git a/BCnEncTests/BCnEncTests.csproj b/BCnEncTests/BCnEncTests.csproj index 9770be3..0ddd93d 100644 --- a/BCnEncTests/BCnEncTests.csproj +++ b/BCnEncTests/BCnEncTests.csproj @@ -8,6 +8,7 @@ + diff --git a/BCnEncTests/BlockTests.cs b/BCnEncTests/BlockTests.cs index 2213ed6..c94dd3b 100644 --- a/BCnEncTests/BlockTests.cs +++ b/BCnEncTests/BlockTests.cs @@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using Xunit; +using Rgba32 = SixLabors.ImageSharp.PixelFormats.Rgba32; namespace BCnEncTests { @@ -14,7 +15,8 @@ public void CreateBlocksExact() { using Image testImage = new Image(16, 16); - var blocks = ImageToBlocks.ImageTo4X4(testImage.Frames[0], out var blocksWidth, out var blocksHeight); + testImage.Frames[0].TryGetSinglePixelSpan(out var span); + var blocks = ImageToBlocks.ImageTo4X4(span.ToArray().ToBytes(), testImage.Frames[0].Width, testImage.Frames[0].Height, out var blocksWidth, out var blocksHeight); Assert.Equal(16, blocks.Length); Assert.Equal(4, blocksWidth); @@ -26,7 +28,8 @@ public void CreateBlocksPadding() { using Image testImage = new Image(11, 15); - var blocks = ImageToBlocks.ImageTo4X4(testImage.Frames[0], out var blocksWidth, out var blocksHeight); + testImage.Frames[0].TryGetSinglePixelSpan(out var span); + var blocks = ImageToBlocks.ImageTo4X4(span.ToArray().ToBytes(), testImage.Frames[0].Width, testImage.Frames[0].Height, out var blocksWidth, out var blocksHeight); Assert.Equal(12, blocks.Length); Assert.Equal(3, blocksWidth); @@ -45,7 +48,8 @@ public void PaddingColor() pixels[i] = new Rgba32(0, 125, 125); } - var blocks = ImageToBlocks.ImageTo4X4(testImage.Frames[0], out var blocksWidth, out var blocksHeight); + testImage.Frames[0].TryGetSinglePixelSpan(out var span); + var blocks = ImageToBlocks.ImageTo4X4(span.ToArray().ToBytes(), testImage.Frames[0].Width, testImage.Frames[0].Height, out var blocksWidth, out var blocksHeight); Assert.Equal(16, blocks.Length); Assert.Equal(4, blocksWidth); @@ -53,8 +57,8 @@ public void PaddingColor() for (int x = 0; x < blocksWidth; x++) { for (int y = 0; y < blocksHeight; y++) { - foreach (var color in blocks[x + y * blocksWidth].AsSpan) { - Assert.Equal(new Rgba32(0, 125, 125), color); + foreach (var color in blocks[x + y * blocksWidth].AsArray) { + Assert.Equal(new BCnEncoder.Shared.Rgba32(0, 125, 125), color); } } } @@ -77,21 +81,20 @@ public void BlocksToImage() (byte)r.Next(255)); } - var blocks = ImageToBlocks.ImageTo4X4(testImage.Frames[0], out var blocksWidth, out var blocksHeight); + testImage.Frames[0].TryGetSinglePixelSpan(out var span); + var blocks = ImageToBlocks.ImageTo4X4(span.ToArray().ToBytes(), testImage.Frames[0].Width, testImage.Frames[0].Height, out var blocksWidth, out var blocksHeight); Assert.Equal(16, blocks.Length); Assert.Equal(4, blocksWidth); Assert.Equal(4, blocksHeight); - using var output = ImageToBlocks.ImageFromRawBlocks(blocks, blocksWidth, blocksHeight); - - if (!output.TryGetSinglePixelSpan(out var pixels2)) { - throw new Exception("Cannot get pixel span."); - } + var output = ImageToBlocks.ImageFromRawBlocks(blocks, blocksWidth, blocksHeight); - Assert.Equal(pixels.Length, pixels2.Length); - for (int i = 0; i < pixels.Length; i++) { - Assert.Equal(pixels[i], pixels2[i]); + Assert.Equal(pixels.Length * 4, output.Length); + for (int i = 0; i < pixels.Length; i++) + { + Rgba32 o = new Rgba32(output[i * 4 + 0], output[i * 4 + 1], output[i * 4 + 2], output[i * 4 + 3]); + Assert.Equal(pixels[i], o); } } @@ -100,20 +103,30 @@ public void BlockError() { using Image testImage = new Image(16, 16); - var blocks = ImageToBlocks.ImageTo4X4(testImage.Frames[0], out var blocksWidth, out var blocksHeight); + testImage.Frames[0].TryGetSinglePixelSpan(out var span); + var blocks = ImageToBlocks.ImageTo4X4(span.ToArray().ToBytes(), testImage.Frames[0].Width, testImage.Frames[0].Height, out var blocksWidth, out var blocksHeight); var block1 = blocks[2 + 2 * blocksWidth]; var block2 = blocks[2 + 2 * blocksWidth]; Assert.Equal(0, block1.CalculateError(block2)); - for (int i = 0; i < block2.AsSpan.Length; i++) { - block2.AsSpan[i].R = (byte) (block2.AsSpan[i].R + 2); + for (int i = 0; i < block2.AsArray.Length; i++) + { + block2[i] = new BCnEncoder.Shared.Rgba32( + (byte)(block1.AsArray[i].R + 2), + block1.AsArray[i].G, + block1.AsArray[i].B, + block1.AsArray[i].A); } Assert.Equal(2, block1.CalculateError(block2)); - for (int i = 0; i < block2.AsSpan.Length; i++) { - block2.AsSpan[i].G = (byte) (block2.AsSpan[i].R + 20); + for (int i = 0; i < block2.AsArray.Length; i++) { + block2[i] = new BCnEncoder.Shared.Rgba32( + block2.AsArray[i].R, + (byte)(block2.AsArray[i].R + 20), + block2.AsArray[i].B, + block2.AsArray[i].A); } Assert.Equal(22, block1.CalculateError(block2)); } diff --git a/BCnEncTests/ClusterTests.cs b/BCnEncTests/ClusterTests.cs index 26e613c..411aa0c 100644 --- a/BCnEncTests/ClusterTests.cs +++ b/BCnEncTests/ClusterTests.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using Xunit; +using Rgba32 = SixLabors.ImageSharp.PixelFormats.Rgba32; namespace BCnEncTests { @@ -23,19 +24,26 @@ public void Clusterize() { int numClusters = (testImage.Width / 32) * (testImage.Height / 32); - var clusters = LinearClustering.ClusterPixels(pixels, testImage.Width, testImage.Height, numClusters, 10, 10); + var clusters = LinearClustering.ClusterPixels(pixels.ToBcn(), testImage.Width, testImage.Height, numClusters, 10, 10); ColorYCbCr[] pixC = new ColorYCbCr[numClusters]; int[] counts = new int[numClusters]; for (int i = 0; i < pixels.Length; i++) { - pixC[clusters[i]] += new ColorYCbCr(pixels[i]); + pixC[clusters[i]] += new ColorYCbCr(pixels[i].ToBcn()); counts[clusters[i]]++; } for (int i = 0; i < numClusters; i++) { pixC[i] /= counts[i]; } - for (int i = 0; i < pixels.Length; i++) { - pixels[i] = pixC[clusters[i]].ToRgba32(); + for (int i = 0; i < pixels.Length; i++) + { + Rgba32 rgba = new Rgba32( + pixC[clusters[i]].ToRgba32().R, + pixC[clusters[i]].ToRgba32().G, + pixC[clusters[i]].ToRgba32().B, + pixC[clusters[i]].ToRgba32().A + ); + pixels[i] = rgba; } using var fs = File.OpenWrite("test_cluster.png"); diff --git a/BCnEncTests/DdsReadTests.cs b/BCnEncTests/DdsReadTests.cs index 3e94ff2..49119aa 100644 --- a/BCnEncTests/DdsReadTests.cs +++ b/BCnEncTests/DdsReadTests.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using Xunit; +using Rgba32 = SixLabors.ImageSharp.PixelFormats.Rgba32; namespace BCnEncTests { @@ -27,10 +28,12 @@ public void ReadRgba() { Assert.Equal((uint)images[0].Width, file.Header.dwWidth); Assert.Equal((uint)images[0].Height, file.Header.dwHeight); - for (int i = 0; i < images.Length; i++) { + for (int i = 0; i < images.Length; i++) + { + using var img = + Image.LoadPixelData(images[i].data, images[i].Width, images[i].Height); using FileStream outFs = File.OpenWrite($"decoding_test_dds_rgba_mip{i}.png"); - images[i].SaveAsPng(outFs); - images[i].Dispose(); + img.SaveAsPng(outFs); } } @@ -49,9 +52,10 @@ public void ReadBc1() { Assert.Equal((uint)images[0].Height, file.Header.dwHeight); for (int i = 0; i < images.Length; i++) { + using var img = + Image.LoadPixelData(images[i].data, images[i].Width, images[i].Height); using FileStream outFs = File.OpenWrite($"decoding_test_dds_bc1_mip{i}.png"); - images[i].SaveAsPng(outFs); - images[i].Dispose(); + img.SaveAsPng(outFs); } } @@ -70,14 +74,10 @@ public void ReadBc1a() { Assert.Equal((uint)image.Width, file.Header.dwWidth); Assert.Equal((uint)image.Height, file.Header.dwHeight); - if (!image.TryGetSinglePixelSpan(out var pixels)) { - throw new Exception("Cannot get pixel span."); - } - Assert.Contains(pixels.ToArray(), x => x.A == 0); - + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); using FileStream outFs = File.OpenWrite($"decoding_test_dds_bc1a.png"); - image.SaveAsPng(outFs); - image.Dispose(); + img.SaveAsPng(outFs); } [Fact] @@ -87,9 +87,10 @@ public void ReadBc7() { var images = decoder.DecodeAllMipMaps(fs); for (int i = 0; i < images.Length; i++) { + using var img = + Image.LoadPixelData(images[i].data, images[i].Width, images[i].Height); using FileStream outFs = File.OpenWrite($"decoding_test_dds_bc7_mip{i}.png"); - images[i].SaveAsPng(outFs); - images[i].Dispose(); + img.SaveAsPng(outFs); } } @@ -101,9 +102,10 @@ public void ReadFromStream() { var images = decoder.DecodeAllMipMaps(fs); for (int i = 0; i < images.Length; i++) { + using var img = + Image.LoadPixelData(images[i].data, images[i].Width, images[i].Height); using FileStream outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png"); - images[i].SaveAsPng(outFs); - images[i].Dispose(); + img.SaveAsPng(outFs); } } } diff --git a/BCnEncTests/DdsWritingTests.cs b/BCnEncTests/DdsWritingTests.cs index 0037918..628fd32 100644 --- a/BCnEncTests/DdsWritingTests.cs +++ b/BCnEncTests/DdsWritingTests.cs @@ -10,20 +10,6 @@ namespace BCnEncTests { public class DdsWritingTests { - [Fact] - public void DdsWriteRgba() { - var image = ImageLoader.testLenna; - - BcEncoder encoder = new BcEncoder(); - encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; - encoder.OutputOptions.format = CompressionFormat.RGBA; - encoder.OutputOptions.fileFormat = OutputFileFormat.Dds; - - using FileStream fs = File.OpenWrite("encoding_dds_rgba.dds"); - encoder.Encode(image, fs); - fs.Close(); - } [Fact] public void DdsWriteBc1() { @@ -31,12 +17,12 @@ public void DdsWriteBc1() { BcEncoder encoder = new BcEncoder(); encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; encoder.OutputOptions.format = CompressionFormat.BC1; encoder.OutputOptions.fileFormat = OutputFileFormat.Dds; using FileStream fs = File.OpenWrite("encoding_dds_bc1.dds"); - encoder.Encode(image, fs); + image.TryGetSinglePixelSpan(out var span); + encoder.Encode(span.ToBytes(), image.Width, image.Height, fs); fs.Close(); } @@ -46,12 +32,12 @@ public void DdsWriteBc2() { BcEncoder encoder = new BcEncoder(); encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; encoder.OutputOptions.format = CompressionFormat.BC2; encoder.OutputOptions.fileFormat = OutputFileFormat.Dds; using FileStream fs = File.OpenWrite("encoding_dds_bc2.dds"); - encoder.Encode(image, fs); + image.TryGetSinglePixelSpan(out var span); + encoder.Encode(span.ToBytes(), image.Width, image.Height, fs); fs.Close(); } @@ -61,12 +47,12 @@ public void DdsWriteBc3() { BcEncoder encoder = new BcEncoder(); encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; encoder.OutputOptions.format = CompressionFormat.BC3; encoder.OutputOptions.fileFormat = OutputFileFormat.Dds; using FileStream fs = File.OpenWrite("encoding_dds_bc3.dds"); - encoder.Encode(image, fs); + image.TryGetSinglePixelSpan(out var span); + encoder.Encode(span.ToBytes(), image.Width, image.Height, fs); fs.Close(); } @@ -76,12 +62,12 @@ public void DdsWriteBc4() { BcEncoder encoder = new BcEncoder(); encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; encoder.OutputOptions.format = CompressionFormat.BC4; encoder.OutputOptions.fileFormat = OutputFileFormat.Dds; using FileStream fs = File.OpenWrite("encoding_dds_bc4.dds"); - encoder.Encode(image, fs); + image.TryGetSinglePixelSpan(out var span); + encoder.Encode(span.ToBytes(), image.Width, image.Height, fs); fs.Close(); } @@ -91,12 +77,12 @@ public void DdsWriteBc5() { BcEncoder encoder = new BcEncoder(); encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; encoder.OutputOptions.format = CompressionFormat.BC5; encoder.OutputOptions.fileFormat = OutputFileFormat.Dds; using FileStream fs = File.OpenWrite("encoding_dds_bc5.dds"); - encoder.Encode(image, fs); + image.TryGetSinglePixelSpan(out var span); + encoder.Encode(span.ToBytes(), image.Width, image.Height, fs); fs.Close(); } @@ -106,27 +92,12 @@ public void DdsWriteBc7() { BcEncoder encoder = new BcEncoder(); encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; encoder.OutputOptions.format = CompressionFormat.BC7; encoder.OutputOptions.fileFormat = OutputFileFormat.Dds; using FileStream fs = File.OpenWrite("encoding_dds_bc7.dds"); - encoder.Encode(image, fs); - fs.Close(); - } - - [Fact] - public void DdsWriteCubemap() { - var images = ImageLoader.testCubemap; - - BcEncoder encoder = new BcEncoder(); - encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; - encoder.OutputOptions.format = CompressionFormat.BC1; - encoder.OutputOptions.fileFormat = OutputFileFormat.Dds; - - using FileStream fs = File.OpenWrite("encoding_dds_cubemap_bc1.dds"); - encoder.EncodeCubeMap(images[0],images[1],images[2],images[3],images[4],images[5], fs); + image.TryGetSinglePixelSpan(out var span); + encoder.Encode(span.ToBytes(), image.Width, image.Height, fs); fs.Close(); } } diff --git a/BCnEncTests/DecodingTests.cs b/BCnEncTests/DecodingTests.cs index 3cf65e7..e262acb 100644 --- a/BCnEncTests/DecodingTests.cs +++ b/BCnEncTests/DecodingTests.cs @@ -6,161 +6,190 @@ using SixLabors.ImageSharp; using Xunit; +using Rgba32 = SixLabors.ImageSharp.PixelFormats.Rgba32; + namespace BCnEncTests { public class DecodingTests { [Fact] - public void Bc1Decode() { + public void Bc1Decode() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc1.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc1.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } [Fact] - public void Bc1AlphaDecode() { + public void Bc1AlphaDecode() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc1a.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc1a.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } [Fact] - public void Bc2Decode() { + public void Bc2Decode() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc2.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc2.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } [Fact] - public void Bc3Decode() { + public void Bc3Decode() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc3.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc3.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } [Fact] - public void Bc4Decode() { + public void Bc4Decode() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc4_unorm.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc4.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } [Fact] - public void Bc5Decode() { + public void Bc5Decode() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc5_unorm.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc5.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } [Fact] - public void Bc7DecodeRgb() { + public void Bc7DecodeRgb() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc7_rgb.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc7_rgb.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } [Fact] - public void Bc7DecodeUnorm() { + public void Bc7DecodeUnorm() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc7_unorm.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc7_unorm.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } [Fact] - public void Bc7DecodeEveryBlockType() { + public void Bc7DecodeEveryBlockType() + { using FileStream fs = File.OpenRead(@"../../../testImages/test_decompress_bc7_types.ktx"); KtxFile file = KtxFile.Load(fs); Assert.True(file.Header.VerifyHeader()); Assert.Equal((uint)1, file.Header.NumberOfFaces); BcDecoder decoder = new BcDecoder(); - using var image = decoder.Decode(file); + var image = decoder.Decode(file); Assert.Equal((uint)image.Width, file.Header.PixelWidth); Assert.Equal((uint)image.Height, file.Header.PixelHeight); using FileStream outFs = File.OpenWrite("decoding_test_bc7_types.png"); - image.SaveAsPng(outFs); + using var img = + Image.LoadPixelData(image.data, image.Width, image.Height); + img.SaveAsPng(outFs); } } } diff --git a/BCnEncTests/EncodingTest.cs b/BCnEncTests/EncodingTest.cs index 1cc3240..aa00e51 100644 --- a/BCnEncTests/EncodingTest.cs +++ b/BCnEncTests/EncodingTest.cs @@ -506,24 +506,4 @@ public void Bc7RgbaFast() } } - public class CubemapTest - { - - [Fact] - public void WriteCubeMapFile() - { - var images = ImageLoader.testCubemap; - - string filename = "encoding_bc1_cubemap.ktx"; - - BcEncoder encoder = new BcEncoder(); - encoder.OutputOptions.quality = CompressionQuality.Fast; - encoder.OutputOptions.generateMipMaps = true; - encoder.OutputOptions.format = CompressionFormat.BC1; - - using FileStream fs = File.OpenWrite(filename); - encoder.EncodeCubeMap(images[0],images[1],images[2],images[3],images[4],images[5], fs); - fs.Close(); - } - } } diff --git a/BCnEnc.Net/Shared/ImageQuality.cs b/BCnEncTests/ImageQuality.cs similarity index 59% rename from BCnEnc.Net/Shared/ImageQuality.cs rename to BCnEncTests/ImageQuality.cs index 1def66f..cc290ca 100644 --- a/BCnEnc.Net/Shared/ImageQuality.cs +++ b/BCnEncTests/ImageQuality.cs @@ -1,30 +1,39 @@ using System; -using SixLabors.ImageSharp.PixelFormats; +using System.Collections.Generic; +using System.Text; +using BCnEncoder.Shared; +using Rgba32 = SixLabors.ImageSharp.PixelFormats.Rgba32; -namespace BCnEncoder.Shared +namespace BCnEncTests { public class ImageQuality { - public static float PeakSignalToNoiseRatio(ReadOnlySpan original, ReadOnlySpan other, bool countAlpha = true) { - if (original.Length != other.Length) { + public static float PeakSignalToNoiseRatio(ReadOnlySpan original, ReadOnlySpan other, bool countAlpha = true) + { + if (original.Length != other.Length) + { throw new ArgumentException("Both spans should be the same length"); } float error = 0; - for (int i = 0; i < original.Length; i++) { - var o = new ColorYCbCr(original[i]); - var c = new ColorYCbCr(other[i]); + for (int i = 0; i < original.Length; i++) + { + var o = new ColorYCbCr(original[i].ToBcn()); + var c = new ColorYCbCr(other[i].ToBcn()); error += (o.y - c.y) * (o.y - c.y); error += (o.cb - c.cb) * (o.cb - c.cb); error += (o.cr - c.cr) * (o.cr - c.cr); - if (countAlpha) { + if (countAlpha) + { error += ((original[i].A - other[i].A) / 255.0f) * ((original[i].A - other[i].A) / 255.0f); } - + } - if (error < float.Epsilon) { + if (error < float.Epsilon) + { return 100; } - if (countAlpha) { + if (countAlpha) + { error /= original.Length * 4; } else @@ -35,24 +44,30 @@ public static float PeakSignalToNoiseRatio(ReadOnlySpan original, ReadOn return 20 * MathF.Log10(1 / MathF.Sqrt(error)); } - public static float PeakSignalToNoiseRatioLuminance(ReadOnlySpan original, ReadOnlySpan other, bool countAlpha = true) { - if (original.Length != other.Length) { + public static float PeakSignalToNoiseRatioLuminance(ReadOnlySpan original, ReadOnlySpan other, bool countAlpha = true) + { + if (original.Length != other.Length) + { throw new ArgumentException("Both spans should be the same length"); } float error = 0; - for (int i = 0; i < original.Length; i++) { - var o = new ColorYCbCr(original[i]); - var c = new ColorYCbCr(other[i]); + for (int i = 0; i < original.Length; i++) + { + var o = new ColorYCbCr(original[i].ToBcn()); + var c = new ColorYCbCr(other[i].ToBcn()); error += (o.y - c.y) * (o.y - c.y); - if (countAlpha) { + if (countAlpha) + { error += ((original[i].A - other[i].A) / 255.0f) * ((original[i].A - other[i].A) / 255.0f); } - + } - if (error < float.Epsilon) { + if (error < float.Epsilon) + { return 100; } - if (countAlpha) { + if (countAlpha) + { error /= original.Length * 2; } else diff --git a/BCnEncTests/TestHelper.cs b/BCnEncTests/TestHelper.cs index e99bbd2..65626a1 100644 --- a/BCnEncTests/TestHelper.cs +++ b/BCnEncTests/TestHelper.cs @@ -10,35 +10,91 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Abstractions; +using Rgba32 = SixLabors.ImageSharp.PixelFormats.Rgba32; namespace BCnEncTests { public static class TestHelper { + + public static byte[] ToBytes(this Rgba32[] rgba) + { + byte[] output = new byte[rgba.Length * 4]; + for (int i = 0; i < rgba.Length; i++) + { + output[i * 4 + 0] = rgba[i].R; + output[i * 4 + 1] = rgba[i].G; + output[i * 4 + 2] = rgba[i].B; + output[i * 4 + 3] = rgba[i].A; + } + return output; + } + + public static BCnEncoder.Shared.Rgba32 ToBcn(this Rgba32 rgba) + { + return new BCnEncoder.Shared.Rgba32(rgba.R, rgba.G, rgba.B, rgba.A); + } + + public static BCnEncoder.Shared.Rgba32[] ToBcn(this Span rgba) + { + BCnEncoder.Shared.Rgba32[] output = new BCnEncoder.Shared.Rgba32[rgba.Length]; + for (int i = 0; i < rgba.Length; i++) + { + output[i] = rgba[i].ToBcn(); + } + return output; + } + + public static byte[] ToBytes(this Span rgba) + { + byte[] output = new byte[rgba.Length * 4]; + for (int i = 0; i < rgba.Length; i++) + { + output[i * 4 + 0] = rgba[i].R; + output[i * 4 + 1] = rgba[i].G; + output[i * 4 + 2] = rgba[i].B; + output[i * 4 + 3] = rgba[i].A; + } + return output; + } + + public static Rgba32[] ToRgba(this byte[] rgba) + { + Rgba32[] output = new Rgba32[rgba.Length / 4]; + for (int i = 0; i < output.Length; i++) + { + output[i] = new Rgba32 + ( + rgba[i * 4 + 0], + rgba[i * 4 + 1], + rgba[i * 4 + 2], + rgba[i * 4 + 3] + ); + } + return output; + } + public static float DecodeCheckPSNR(string filename, Image original) { using FileStream fs = File.OpenRead(filename); var ktx = KtxFile.Load(fs); var decoder = new BcDecoder(); - using var img = decoder.Decode(ktx); + var img = decoder.Decode(ktx); if (!original.TryGetSinglePixelSpan(out var pixels)) { throw new Exception("Cannot get pixel span."); } - if (!img.TryGetSinglePixelSpan(out var pixels2)) { - throw new Exception("Cannot get pixel span."); - } - return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, true); + return ImageQuality.PeakSignalToNoiseRatio(pixels, img.data.ToRgba(), true); } public static void ExecuteEncodingTest(Image image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output) { BcEncoder encoder = new BcEncoder(); encoder.OutputOptions.quality = quality; - encoder.OutputOptions.generateMipMaps = true; encoder.OutputOptions.format = format; using FileStream fs = File.OpenWrite(filename); - encoder.Encode(image, fs); + image.TryGetSinglePixelSpan(out var span); + encoder.Encode(span.ToBytes(), image.Width, image.Height, fs); fs.Close(); var psnr = TestHelper.DecodeCheckPSNR(filename, image); output.WriteLine("RGBA PSNR: " + psnr + "db");