From eff7dfdfeae8dd054771824cecdcd8859e91e275 Mon Sep 17 00:00:00 2001 From: Julius Friedman Date: Tue, 22 Oct 2024 13:08:59 -0400 Subject: [PATCH] Png work, working on ImageFormat and Transformations. --- Codecs/Image/ImageFormat.cs | 40 +++- Codecs/Image/Png/Codec.Png/Chunk.cs | 7 + Codecs/Image/Png/Codec.Png/PngImage.cs | 7 +- .../Transformations/RgbToLabTransformation.cs | 210 ++++++++++++++++++ .../Transformations/RgbToXyzTransformation.cs | 181 +++++++++++++++ .../Image/Transformations/XyzLabConversion.cs | 142 ++++++++++++ 6 files changed, 582 insertions(+), 5 deletions(-) create mode 100644 Codecs/Image/Transformations/RgbToLabTransformation.cs create mode 100644 Codecs/Image/Transformations/RgbToXyzTransformation.cs create mode 100644 Codecs/Image/Transformations/XyzLabConversion.cs diff --git a/Codecs/Image/ImageFormat.cs b/Codecs/Image/ImageFormat.cs index 8900f60a..c4dffcf9 100644 --- a/Codecs/Image/ImageFormat.cs +++ b/Codecs/Image/ImageFormat.cs @@ -8,6 +8,7 @@ namespace Media.Codecs.Image { /// /// Represents an image format with various properties and methods for creating specific formats. + /// Can be used as a base for a ColorSpace implementation /// public class ImageFormat : Codec.MediaFormat { @@ -15,7 +16,7 @@ public class ImageFormat : Codec.MediaFormat public const byte AlphaChannelId = (byte)'a'; - public const byte PreMultipliedAlphaChannelId = (byte)'A'; + public const byte PreMultipliedAlphaChannelId = (byte)'p'; //Possibly a type which has multiplied and straight types... //public const byte MixedAlphaChannelId = (byte)'@'; @@ -44,12 +45,29 @@ public class ImageFormat : Codec.MediaFormat public const byte MagentaChannelId = (byte)'m'; - //Capital of Luma public const byte YellowChannelId = (byte)'y'; //Key public const byte KChannelId = (byte)'k'; + // + + //CIE + + public const byte LChannelId = (byte)'L'; + + public const byte AChannelId = (byte)'A'; + + public const byte BChannelId = (byte)'B'; + + // + + public const byte XChannelId = (byte)'X'; + + public const byte YChannelId = (byte)'Y'; + + public const byte ZChannelId = (byte)'Z'; + //Functions for reading lines are in the type which corresponds, e.g. Image. //Could have support here for this given a MediaBuffer and forced / known format... @@ -240,6 +258,24 @@ public static ImageFormat CMYKA(int bitsPerComponent, Common.Binary.ByteOrder by }); } + public static ImageFormat XYZ(int bitsPerComponent, Common.Binary.ByteOrder byteOrder = Common.Binary.ByteOrder.Little, Codec.DataLayout dataLayout = Codec.DataLayout.Planar) + { + return new ImageFormat(byteOrder, dataLayout, [ + new(XChannelId, bitsPerComponent), + new(YChannelId, bitsPerComponent), + new(ZChannelId, bitsPerComponent), + ]); + } + + public static ImageFormat LAB(int bitsPerComponent, Common.Binary.ByteOrder byteOrder = Common.Binary.ByteOrder.Little, Codec.DataLayout dataLayout = Codec.DataLayout.Planar) + { + return new ImageFormat(byteOrder, dataLayout, [ + new(LChannelId, bitsPerComponent), + new(AChannelId, bitsPerComponent), + new(BChannelId, bitsPerComponent), + ]); + } + //Supports 565 formats... etc. public static ImageFormat VariableYUV(int[] sizes, Common.Binary.ByteOrder byteOrder = Common.Binary.ByteOrder.Little, Codec.DataLayout dataLayout = Codec.DataLayout.Packed) diff --git a/Codecs/Image/Png/Codec.Png/Chunk.cs b/Codecs/Image/Png/Codec.Png/Chunk.cs index 6e1f7ecc..cc85e8f4 100644 --- a/Codecs/Image/Png/Codec.Png/Chunk.cs +++ b/Codecs/Image/Png/Codec.Png/Chunk.cs @@ -24,6 +24,13 @@ public Chunk(uint chunkType, int chunkSize) ChunkSize = chunkSize; } + public Chunk(ChunkName chunkName, int chunkSize) + : base(new MemorySegment(ChunkHeader.ChunkHeaderLength + Binary.BytesPerInteger + chunkSize)) + { + ChunkName = chunkName; + ChunkSize = chunkSize; + } + public ChunkHeader Header { get => new ChunkHeader(Array, Offset); diff --git a/Codecs/Image/Png/Codec.Png/PngImage.cs b/Codecs/Image/Png/Codec.Png/PngImage.cs index d64cabd1..eeb4fee9 100644 --- a/Codecs/Image/Png/Codec.Png/PngImage.cs +++ b/Codecs/Image/Png/Codec.Png/PngImage.cs @@ -136,7 +136,7 @@ public void Save(Stream stream) private void WriteIHDRChunk(Stream stream) { - using var ihdr = new Chunk("IHDR", 13); + using var ihdr = new Chunk(ChunkName.Header, 13); var offset = ihdr.DataOffset; Binary.Write32(ihdr.Array, ref offset, Binary.IsLittleEndian, Width); Binary.Write32(ihdr.Array, ref offset, Binary.IsLittleEndian, Height); @@ -159,15 +159,16 @@ private void WriteIDATChunk(Stream stream, CompressionLevel compressionLevel = C } ms.Seek(0, SeekOrigin.Begin); ms.TryGetBuffer(out var buffer); - idat = new Chunk("IDAT", buffer.Count); + idat = new Chunk(ChunkName.Data, buffer.Count); buffer.CopyTo(idat.Array, idat.DataOffset); } stream.Write(idat.Array, idat.Offset, idat.Count); + idat.Dispose(); } private void WriteIENDChunk(Stream stream) { - var iend = new Chunk("IEND", 0); + using var iend = new Chunk(ChunkName.End, 0); stream.Write(iend.Array, iend.Offset, iend.Count); } diff --git a/Codecs/Image/Transformations/RgbToLabTransformation.cs b/Codecs/Image/Transformations/RgbToLabTransformation.cs new file mode 100644 index 00000000..047b2cc5 --- /dev/null +++ b/Codecs/Image/Transformations/RgbToLabTransformation.cs @@ -0,0 +1,210 @@ +using Media.Common; +using System; +using System.Numerics; + +namespace Media.Codecs.Image.Transformations; + +//Todo Seperate into seperate assembly +public class RgbToLabTransformation : ImageTransformation +{ + // Constructor for the RGB to YUV transformation + public RgbToLabTransformation(Image source, Image dest, Codec.TransformationQuality quality = Codec.TransformationQuality.None, bool shouldDispose = true) + : base(source, dest, quality, shouldDispose) + { + // Check if the source and destination images have compatible formats + if (!IsRgbImage(source.ImageFormat) || !IsLabImage(dest.ImageFormat)) + { + throw new ArgumentException("Invalid image formats. Source must be RGB and destination must be YUV."); + } + } + + // Check if the image format is an RGB format + private static bool IsRgbImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.RedChannelId) != null && + format.GetComponentById(ImageFormat.GreenChannelId) != null && + format.GetComponentById(ImageFormat.BlueChannelId) != null; + } + + // Check if the image format is a YUV format + private static bool IsLabImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.LChannelId) != null && + format.GetComponentById(ImageFormat.AChannelId) != null && + format.GetComponentById(ImageFormat.BChannelId) != null; + } + + public override void Transform() + { + var lComponent = Destination.ImageFormat.GetComponentById(ImageFormat.LChannelId); + var lComponentIndex = Destination.GetComponentIndex(ImageFormat.LChannelId); + var lComponentData = new Common.MemorySegment(lComponent.Length); + + var aComponent = Destination.ImageFormat.GetComponentById(ImageFormat.AChannelId); + var aComponentIndex = Destination.GetComponentIndex(ImageFormat.AChannelId); + var aComponentData = new Common.MemorySegment(aComponent.Length); + + var bComponent = Destination.ImageFormat.GetComponentById(ImageFormat.BChannelId); + var bComponentIndex = Destination.GetComponentIndex(ImageFormat.BChannelId); + var bComponentData = new Common.MemorySegment(bComponent.Length); + + var redComponent = Source.ImageFormat.GetComponentById(ImageFormat.RedChannelId); + var greenComponent = Source.ImageFormat.GetComponentById(ImageFormat.GreenChannelId); + var blueComponent = Source.ImageFormat.GetComponentById(ImageFormat.BlueChannelId); + + for (int y = 0; y < Source.Height; y++) + { + for (int x = 0; x < Source.Width; x++) + { + var data = Source.GetComponentData(x, y, redComponent); + var r = Binary.ReadBits(data.Array, data.Offset, redComponent.Size, false); + + data = Source.GetComponentData(x, y, greenComponent); + var g = Binary.ReadBits(data.Array, data.Offset, greenComponent.Size, false); + + data = Source.GetComponentData(x, y, blueComponent); + var b = Binary.ReadBits(data.Array, data.Offset, bComponent.Size, false); + + // Convert RGB to XYZ + double X = r * 0.4124564 + g * 0.3575761 + b * 0.1804375; + double Y = r * 0.2126729 + g * 0.7151522 + b * 0.0721750; + double Z = r * 0.0193339 + g * 0.1191920 + b * 0.9503041; + + // Normalize for D65 white point + X /= 0.95047; + Y /= 1.00000; + Z /= 1.08883; + + // Convert XYZ to Lab + X = X > 0.008856 ? Math.Pow(X, 1.0 / 3.0) : (7.787 * X) + (16.0 / 116.0); + Y = Y > 0.008856 ? Math.Pow(Y, 1.0 / 3.0) : (7.787 * Y) + (16.0 / 116.0); + Z = Z > 0.008856 ? Math.Pow(Z, 1.0 / 3.0) : (7.787 * Z) + (16.0 / 116.0); + + double L = (116.0 * Y) - 16.0; + double A = 500.0 * (x - Y); + double B = 200.0 * (y - Z); + + // Store Lab components + var lByte = (byte)(L * 2.55); + var aByte = (byte)(A + 128); + var bByte = (byte)(B + 128); + + Binary.WriteBits(lComponentData.Array, lComponentData.Offset, lComponent.Size, lByte, false); + Destination.SetComponentData(x, y, lComponentIndex, lComponentData); + + Binary.WriteBits(aComponentData.Array, aComponentData.Offset, aComponent.Size, aByte, false); + Destination.SetComponentData(x, y, aComponentIndex, aComponentData); + + Binary.WriteBits(bComponentData.Array, bComponentData.Offset, bComponent.Size, bByte, false); + Destination.SetComponentData(x, y, bComponentIndex, bComponentData); + } + } + } +} + +/// +/// Not working. +/// +public class VectorizedRgbToLabTransformation : ImageTransformation +{ + // Constructor for the RGB to YUV transformation + public VectorizedRgbToLabTransformation(Image source, Image dest, Codec.TransformationQuality quality = Codec.TransformationQuality.None, bool shouldDispose = true) + : base(source, dest, quality, shouldDispose) + { + // Check if the source and destination images have compatible formats + if (!IsRgbImage(source.ImageFormat) || !IsLabImage(dest.ImageFormat)) + { + throw new ArgumentException("Invalid image formats. Source must be RGB and destination must be YUV."); + } + } + + // Check if the image format is an RGB format + private static bool IsRgbImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.RedChannelId) != null && + format.GetComponentById(ImageFormat.GreenChannelId) != null && + format.GetComponentById(ImageFormat.BlueChannelId) != null; + } + + // Check if the image format is a YUV format + private static bool IsLabImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.LChannelId) != null && + format.GetComponentById(ImageFormat.AChannelId) != null && + format.GetComponentById(ImageFormat.BChannelId) != null; + } + + //Could chain conversion using RbgToXyz transformation... + + public override void Transform() + { + int width = Source.Width; + int height = Source.Height; + + // Prepare Vector constants for conversion formulas + Vector vector0_4124564 = new(0.4124564f); + Vector vector0_3575761 = new(0.3575761f); + Vector vector0_1804375 = new(0.1804375f); + Vector vector0_2126729 = new(0.2126729f); + Vector vector0_7151522 = new(0.7151522f); + Vector vector0_0721750 = new(0.0721750f); + Vector vector0_0193339 = new(0.0193339f); + Vector vector0_1191920 = new(0.1191920f); + Vector vector0_9503041 = new(0.9503041f); + Vector vector0_95047 = new(0.95047f); + Vector vector1_00000 = new(1.00000f); + Vector vector1_08883 = new(1.08883f); + Vector vector7_787 = new(7.787f); + Vector vector16_116 = new(16.0f / 116.0f); + Vector vector116 = new(116.0f); + Vector vector500 = new(500.0f); + Vector vector200 = new(200.0f); + Vector vector2_55 = new(2.55f); + Vector vector128 = new(128f); + Vector vector0_008856 = new(0.008856f); + Vector vector1_0_3 = new(1.0f / 3.0f); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x += Vector.Count) + { + // Read RGB components + var r = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.RedChannelId)); + var g = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.GreenChannelId)); + var b = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.BlueChannelId)); + + // Convert RGB to XYZ + var X = r * vector0_4124564 + g * vector0_3575761 + b * vector0_1804375; + var Y = r * vector0_2126729 + g * vector0_7151522 + b * vector0_0721750; + var Z = r * vector0_0193339 + g * vector0_1191920 + b * vector0_9503041; + + // Normalize for D65 white point + X /= vector0_95047; + Y /= vector1_00000; + Z /= vector1_08883; + + // Convert XYZ to Lab + X = Vector.ConditionalSelect(Vector.GreaterThan(X, vector0_008856), Vector.SquareRoot(X), vector7_787 * X + vector16_116); + Y = Vector.ConditionalSelect(Vector.GreaterThan(Y, vector0_008856), Vector.SquareRoot(Y), vector7_787 * Y + vector16_116); + Z = Vector.ConditionalSelect(Vector.GreaterThan(Z, vector0_008856), Vector.SquareRoot(Z), vector7_787 * Z + vector16_116); + + var L = vector116 * Y - new Vector(16.0f); + var A = vector500 * (X - Y); + var B = vector200 * (Y - Z); + + // Store Lab components + var lByte = Vector.ConvertToInt32(L * vector2_55); + var aByte = Vector.ConvertToInt32(A + vector128); + var bByte = Vector.ConvertToInt32(B + vector128); + + Destination.SetComponentVector(x, y, ImageFormat.LChannelId, Vector.AsVectorByte(lByte)); + Destination.SetComponentVector(x, y, ImageFormat.AChannelId, Vector.AsVectorByte(aByte)); + Destination.SetComponentVector(x, y, ImageFormat.BChannelId, Vector.AsVectorByte(bByte)); + } + } + } +} diff --git a/Codecs/Image/Transformations/RgbToXyzTransformation.cs b/Codecs/Image/Transformations/RgbToXyzTransformation.cs new file mode 100644 index 00000000..69b3b986 --- /dev/null +++ b/Codecs/Image/Transformations/RgbToXyzTransformation.cs @@ -0,0 +1,181 @@ +using Media.Common; +using System; +using System.Numerics; + +namespace Media.Codecs.Image.Transformations; + +public class RgbToXyzTransformation : ImageTransformation +{ + // Constructor for the RGB to XYZ transformation + public RgbToXyzTransformation(Image source, Image dest, Codec.TransformationQuality quality = Codec.TransformationQuality.None, bool shouldDispose = true) + : base(source, dest, quality, shouldDispose) + { + // Check if the source and destination images have compatible formats + if (!IsRgbImage(source.ImageFormat) || !IsXyzImage(dest.ImageFormat)) + { + throw new ArgumentException("Invalid image formats. Source must be RGB and destination must be XYZ."); + } + } + + // Check if the image format is an RGB format + private static bool IsRgbImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.RedChannelId) != null && + format.GetComponentById(ImageFormat.GreenChannelId) != null && + format.GetComponentById(ImageFormat.BlueChannelId) != null; + } + + // Check if the image format is an XYZ format + private static bool IsXyzImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.XChannelId) != null && + format.GetComponentById(ImageFormat.YChannelId) != null && + format.GetComponentById(ImageFormat.ZChannelId) != null; + } + + public override void Transform() + { + var xComponent = Destination.ImageFormat.GetComponentById(ImageFormat.XChannelId); + var xComponentIndex = Destination.GetComponentIndex(ImageFormat.XChannelId); + var xComponentData = new Common.MemorySegment(xComponent.Length); + + var yComponent = Destination.ImageFormat.GetComponentById(ImageFormat.YChannelId); + var yComponentIndex = Destination.GetComponentIndex(ImageFormat.YChannelId); + var yComponentData = new Common.MemorySegment(yComponent.Length); + + var zComponent = Destination.ImageFormat.GetComponentById(ImageFormat.ZChannelId); + var zComponentIndex = Destination.GetComponentIndex(ImageFormat.ZChannelId); + var zComponentData = new Common.MemorySegment(zComponent.Length); + + var redComponent = Source.ImageFormat.GetComponentById(ImageFormat.RedChannelId); + var greenComponent = Source.ImageFormat.GetComponentById(ImageFormat.GreenChannelId); + var blueComponent = Source.ImageFormat.GetComponentById(ImageFormat.BlueChannelId); + + for (int y = 0; y < Source.Height; y++) + { + for (int x = 0; x < Source.Width; x++) + { + var data = Source.GetComponentData(x, y, redComponent); + var r = Binary.ReadBits(data.Array, data.Offset, redComponent.Size, false); + + data = Source.GetComponentData(x, y, greenComponent); + var g = Binary.ReadBits(data.Array, data.Offset, greenComponent.Size, false); + + data = Source.GetComponentData(x, y, blueComponent); + var b = Binary.ReadBits(data.Array, data.Offset, blueComponent.Size, false); + + // Convert RGB to XYZ + double X = r * 0.4124564 + g * 0.3575761 + b * 0.1804375; + double Y = r * 0.2126729 + g * 0.7151522 + b * 0.0721750; + double Z = r * 0.0193339 + g * 0.1191920 + b * 0.9503041; + + // Store XYZ components + var xByte = (byte)(X * 255.0 / 100.0); + var yByte = (byte)(Y * 255.0 / 100.0); + var zByte = (byte)(Z * 255.0 / 100.0); + + Binary.WriteBits(xComponentData.Array, xComponentData.Offset, xComponent.Size, xByte, false); + Destination.SetComponentData(x, y, xComponentIndex, xComponentData); + + Binary.WriteBits(yComponentData.Array, yComponentData.Offset, yComponent.Size, yByte, false); + Destination.SetComponentData(x, y, yComponentIndex, yComponentData); + + Binary.WriteBits(zComponentData.Array, zComponentData.Offset, zComponent.Size, zByte, false); + Destination.SetComponentData(x, y, zComponentIndex, zComponentData); + } + } + } +} + +public class VectorizedRgbToXyzTransformation : ImageTransformation +{ + // Constructor for the RGB to YUV transformation + public VectorizedRgbToXyzTransformation(Image source, Image dest, Codec.TransformationQuality quality = Codec.TransformationQuality.None, bool shouldDispose = true) + : base(source, dest, quality, shouldDispose) + { + // Check if the source and destination images have compatible formats + if (!IsRgbImage(source.ImageFormat) || !IsLabImage(dest.ImageFormat)) + { + throw new ArgumentException("Invalid image formats. Source must be RGB and destination must be YUV."); + } + } + + // Check if the image format is an RGB format + private static bool IsRgbImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.RedChannelId) != null && + format.GetComponentById(ImageFormat.GreenChannelId) != null && + format.GetComponentById(ImageFormat.BlueChannelId) != null; + } + + // Check if the image format is a YUV format + private static bool IsLabImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.LChannelId) != null && + format.GetComponentById(ImageFormat.AChannelId) != null && + format.GetComponentById(ImageFormat.BChannelId) != null; + } + + public override void Transform() + { + int width = Source.Width; + int height = Source.Height; + + // Prepare Vector constants for conversion formulas + Vector vector0_4124564 = new(0.4124564f); + Vector vector0_3575761 = new(0.3575761f); + Vector vector0_1804375 = new(0.1804375f); + Vector vector0_2126729 = new(0.2126729f); + Vector vector0_7151522 = new(0.7151522f); + Vector vector0_0721750 = new(0.0721750f); + Vector vector0_0193339 = new(0.0193339f); + Vector vector0_1191920 = new(0.1191920f); + Vector vector0_9503041 = new(0.9503041f); + Vector vector0_95047 = new(0.95047f); + Vector vector1_00000 = new(1.00000f); + Vector vector1_08883 = new(1.08883f); + Vector vector7_787 = new(7.787f); + Vector vector16_116 = new(16.0f / 116.0f); + Vector vector116 = new(116.0f); + Vector vector500 = new(500.0f); + Vector vector200 = new(200.0f); + Vector vector2_55 = new(2.55f); + Vector vector128 = new(128f); + Vector vector0_008856 = new(0.008856f); + Vector vector1_0_3 = new(1.0f / 3.0f); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x += Vector.Count) + { + // Read RGB components + var r = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.RedChannelId)); + var g = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.GreenChannelId)); + var b = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.BlueChannelId)); + + // Convert RGB to XYZ + var X = r * vector0_4124564 + g * vector0_3575761 + b * vector0_1804375; + var Y = r * vector0_2126729 + g * vector0_7151522 + b * vector0_0721750; + var Z = r * vector0_0193339 + g * vector0_1191920 + b * vector0_9503041; + + // Normalize for D65 white point + X /= vector0_95047; + Y /= vector1_00000; + Z /= vector1_08883; + + // Convert to byte and store XYZ components + var xByte = Vector.ConvertToInt32(X); + var yByte = Vector.ConvertToInt32(Y); + var zByte = Vector.ConvertToInt32(Z); + + Destination.SetComponentVector(x, y, ImageFormat.LChannelId, Vector.AsVectorByte(xByte)); + Destination.SetComponentVector(x, y, ImageFormat.AChannelId, Vector.AsVectorByte(yByte)); + Destination.SetComponentVector(x, y, ImageFormat.BChannelId, Vector.AsVectorByte(zByte)); + } + } + } +} diff --git a/Codecs/Image/Transformations/XyzLabConversion.cs b/Codecs/Image/Transformations/XyzLabConversion.cs new file mode 100644 index 00000000..bbf5dd49 --- /dev/null +++ b/Codecs/Image/Transformations/XyzLabConversion.cs @@ -0,0 +1,142 @@ +using System; +using System.Numerics; + +namespace Media.Codecs.Image.Transformations; + +public class XyzLabConversion + : ImageTransformation +{ + public XyzLabConversion(Image source, Image dest, Codec.TransformationQuality quality = Codec.TransformationQuality.None, bool shouldDispose = true) + : base(source, dest, quality, shouldDispose) + { + if (!IsXyzImage(source.ImageFormat) || !IsLabImage(dest.ImageFormat)) + { + throw new ArgumentException("Invalid image formats. Source must be XYZ and destination must be Lab."); + } + } + + private static bool IsXyzImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.XChannelId) != null && + format.GetComponentById(ImageFormat.YChannelId) != null && + format.GetComponentById(ImageFormat.ZChannelId) != null; + } + + private static bool IsLabImage(ImageFormat format) + { + return format.Components.Length >= 3 && + format.GetComponentById(ImageFormat.LChannelId) != null && + format.GetComponentById(ImageFormat.AChannelId) != null && + format.GetComponentById(ImageFormat.BChannelId) != null; + } + + /// + /// From Xyz to Lab + /// + public override void Transform() + { + int width = Source.Width; + int height = Source.Height; + + // Prepare Vector constants for conversion formulas + Vector vector0_008856 = new(0.008856f); + Vector vector7_787 = new(7.787f); + Vector vector16_116 = new(16.0f / 116.0f); + Vector vector116 = new(116.0f); + Vector vector500 = new(500.0f); + Vector vector200 = new(200.0f); + Vector vector128 = new(128f); + Vector vector2_55 = new(2.55f); + Vector vector0_95047 = new(0.95047f); + Vector vector1_00000 = new(1.00000f); + Vector vector1_08883 = new(1.08883f); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x += Vector.Count) + { + // Read XYZ components + var X = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.XChannelId)); + var Y = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.YChannelId)); + var Z = Vector.AsVectorSingle(Source.GetComponentVector(x, y, ImageFormat.ZChannelId)); + + // Normalize for D65 white point + X /= vector0_95047; + Y /= vector1_00000; + Z /= vector1_08883; + + // Convert XYZ to Lab + X = Vector.ConditionalSelect(Vector.GreaterThan(X, vector0_008856), Vector.SquareRoot(Vector.SquareRoot(X * X * X)), vector7_787 * X + vector16_116); + Y = Vector.ConditionalSelect(Vector.GreaterThan(Y, vector0_008856), Vector.SquareRoot(Vector.SquareRoot(Y * Y * Y)), vector7_787 * Y + vector16_116); + Z = Vector.ConditionalSelect(Vector.GreaterThan(Z, vector0_008856), Vector.SquareRoot(Vector.SquareRoot(Z * Z * Z)), vector7_787 * Z + vector16_116); + + var L = vector116 * Y - new Vector(16.0f); + var A = vector500 * (X - Y); + var B = vector200 * (Y - Z); + + // Store Lab components + var lByte = Vector.ConvertToInt32(L * vector2_55); + var aByte = Vector.ConvertToInt32(A + vector128); + var bByte = Vector.ConvertToInt32(B + vector128); + + Destination.SetComponentVector(x, y, ImageFormat.LChannelId, Vector.AsVectorByte(lByte)); + Destination.SetComponentVector(x, y, ImageFormat.AChannelId, Vector.AsVectorByte(aByte)); + Destination.SetComponentVector(x, y, ImageFormat.BChannelId, Vector.AsVectorByte(bByte)); + } + } + } + + /// + /// Todo add to interface... + /// From Lab to Xyz + /// + public void ReverseTransform() + { + int width = Destination.Width; + int height = Destination.Height; + + // Prepare Vector constants for reverse conversion formulas + Vector vector0_008856 = new(0.008856f); + Vector vector7_787 = new(7.787f); + Vector vector16_116 = new(16.0f / 116.0f); + Vector vector116 = new(116.0f); + Vector vector500 = new(500.0f); + Vector vector200 = new(200.0f); + Vector vector128 = new(128f); + Vector vector2_55 = new(2.55f); + Vector vector0_95047 = new(0.95047f); + Vector vector1_00000 = new(1.00000f); + Vector vector1_08883 = new(1.08883f); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x += Vector.Count) + { + // Read Lab components + var L = Vector.AsVectorSingle(Destination.GetComponentVector(x, y, ImageFormat.LChannelId)) / vector2_55; + var A = Vector.AsVectorSingle(Destination.GetComponentVector(x, y, ImageFormat.AChannelId)) - vector128; + var B = Vector.AsVectorSingle(Destination.GetComponentVector(x, y, ImageFormat.BChannelId)) - vector128; + + // Convert Lab to XYZ + var Y = (L + new Vector(16.0f)) / vector116; + var X = A / vector500 + Y; + var Z = Y - B / vector200; + + X = Vector.ConditionalSelect(Vector.GreaterThan(X, vector16_116), X * X * X, (X - vector16_116) / vector7_787); + Y = Vector.ConditionalSelect(Vector.GreaterThan(Y, vector16_116), Y * Y * Y, (Y - vector16_116) / vector7_787); + Z = Vector.ConditionalSelect(Vector.GreaterThan(Z, vector16_116), Z * Z * Z, (Z - vector16_116) / vector7_787); + + // Denormalize for D65 white point + X *= vector0_95047; + Y *= vector1_00000; + Z *= vector1_08883; + + // Store XYZ components + Destination.SetComponentVector(x, y, ImageFormat.XChannelId, Vector.AsVectorByte(Vector.ConvertToInt32(X))); + Destination.SetComponentVector(x, y, ImageFormat.YChannelId, Vector.AsVectorByte(Vector.ConvertToInt32(Y))); + Destination.SetComponentVector(x, y, ImageFormat.ZChannelId, Vector.AsVectorByte(Vector.ConvertToInt32(Z))); + } + } + } +} \ No newline at end of file