From a7bea836809c74a71089a03332f40500631c6a66 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 22 May 2022 16:21:37 +0300 Subject: [PATCH] Implemented YccK encoding --- .../JpegColorConverter.CmykAvx.cs | 5 +- .../JpegColorConverter.CmykVector.cs | 12 +++- .../JpegColorConverter.YCbCrScalar.cs | 2 +- .../JpegColorConverter.YccKAvx.cs | 52 ++++++++++++++++- .../JpegColorConverter.YccKScalar.cs | 42 ++++++++++++-- .../JpegColorConverter.YccKVector.cs | 58 ++++++++++++++++++- .../EncodingConfigs/JpegFrameConfig.cs | 2 + .../Formats/Jpeg/JpegDecoderCore.cs | 7 +-- .../Formats/Jpeg/JpegEncoderCore.cs | 48 ++++++++++++--- .../Formats/Jpeg/JpegEncodingColor.cs | 5 ++ 10 files changed, 203 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs index 66173b0a5a..158886b054 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs @@ -49,6 +49,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) + => ConvertFromRgbInplace(in values, this.MaximumValue, rLane, gLane, bLane); + + public static void ConvertFromRgbInplace(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) { ref Vector256 destC = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -67,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); // Used for the color conversion - var scale = Vector256.Create(this.MaximumValue); + var scale = Vector256.Create(maxValue); nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs index 196f64ead3..e75d61e4c2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs @@ -49,6 +49,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components => CmykScalar.ConvertToRgbInplace(values, this.MaximumValue); protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, r, g, b); + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplaceRemainder(values, this.MaximumValue, r, g, b); + + public static void ConvertFromRgbInplaceVectorized(in ComponentValues values, float maxValue, Span r, Span g, Span b) { ref Vector destC = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -67,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ref Unsafe.As>(ref MemoryMarshal.GetReference(b)); // Used for the color conversion - var scale = new Vector(this.MaximumValue); + var scale = new Vector(maxValue); nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) @@ -89,8 +95,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => CmykScalar.ConvertFromRgbInplace(values, this.MaximumValue, r, g, b); + public static void ConvertFromRgbInplaceRemainder(in ComponentValues values, float maxValue, Span r, Span g, Span b) + => CmykScalar.ConvertFromRgbInplace(values, maxValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs index e5f76dadfd..418fa02073 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal sealed class YCbCrScalar : JpegColorConverterScalar { - // TODO: comments, derived from ITU-T Rec. T.871 + // derived from ITU-T Rec. T.871 internal const float RCrMult = 1.402f; internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs index 58d024e376..b558f94cb9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs @@ -78,8 +78,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) - => throw new NotImplementedException(); + public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykAvx.ConvertFromRgbInplace(in values, this.MaximumValue, rLane, gLane, bLane); + + // cmyk -> ycck + ref Vector256 destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector256 srcR = ref destY; + ref Vector256 srcG = ref destCb; + ref Vector256 srcB = ref destCr; + + // Used for the color conversion + var maxSampleValue = Vector256.Create(this.MaximumValue); + + var chromaOffset = Vector256.Create(this.HalfValue); + + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + Vector256 r = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i)); + Vector256 g = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i)); + Vector256 b = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcB, i)); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + + Unsafe.Add(ref destY, i) = y; + Unsafe.Add(ref destCb, i) = cb; + Unsafe.Add(ref destCr, i) = cr; + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs index f49e819b9e..2868ffc993 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs @@ -9,6 +9,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal sealed class YccKScalar : JpegColorConverterScalar { + // derived from ITU-T Rec. T.871 + internal const float RCrMult = 1.402f; + internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); + internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); + internal const float BCbMult = 1.772f; + public YccKScalar(int precision) : base(JpegColorSpace.Ycck, precision) { @@ -17,6 +23,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public override void ConvertToRgbInplace(in ComponentValues values) => ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + => ConvertCoreInplaceFromRgb(values, this.HalfValue, this.MaximumValue, r, g, b); + public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue) { Span c0 = values.Component0; @@ -33,14 +42,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components float cr = c2[i] - halfValue; float scaledK = c3[i] * scale; - c0[i] = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1[i] = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK; + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scaledK; + c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scaledK; + c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scaledK; } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) - => throw new NotImplementedException(); + public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykScalar.ConvertFromRgbInplace(in values, maxValue, rLane, gLane, bLane); + + // cmyk -> ycck + Span c = values.Component0; + Span m = values.Component1; + Span y = values.Component2; + + for (int i = 0; i < y.Length; i++) + { + float r = maxValue - c[i]; + float g = maxValue - m[i]; + float b = maxValue - y[i]; + + // k value is passed untouched from rgb -> cmyk conversion + c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs index 7a5597c463..1bae94c71c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs @@ -72,11 +72,63 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => throw new System.NotImplementedException(); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykVector.ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, rLane, gLane, bLane); + + // cmyk -> ycck + ref Vector destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector srcR = ref destY; + ref Vector srcG = ref destCb; + ref Vector srcB = ref destCr; + + var maxSampleValue = new Vector(this.MaximumValue); + + var chromaOffset = new Vector(this.HalfValue); + + var rYMult = new Vector(0.299f); + var gYMult = new Vector(0.587f); + var bYMult = new Vector(0.114f); + + var rCbMult = new Vector(0.168736f); + var gCbMult = new Vector(0.331264f); + var bCbMult = new Vector(0.5f); + + var rCrMult = new Vector(0.5f); + var gCrMult = new Vector(0.418688f); + var bCrMult = new Vector(0.081312f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + Vector r = maxSampleValue - Unsafe.Add(ref srcR, i); + Vector g = maxSampleValue - Unsafe.Add(ref srcG, i); + Vector b = maxSampleValue - Unsafe.Add(ref srcB, i); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); + Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); + } + } protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => throw new System.NotImplementedException(); + { + // rgb -> cmyk + CmykScalar.ConvertFromRgbInplace(in values, this.MaximumValue, r, g, b); + + // cmyk -> ycck + YccKScalar.ConvertCoreInplaceFromRgb(in values, this.HalfValue, this.MaximumValue, r, g, b); + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs index 0bb0f17d13..efff92b2bb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs @@ -38,5 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public int MaxHorizontalSamplingFactor { get; } public int MaxVerticalSamplingFactor { get; } + + public byte? AdobeColorTransformMarkerFlag { get; set; } = null; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 0efbedff1d..3c2aa6d981 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -596,12 +596,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegColorSpace.Cmyk: return JpegEncodingColor.Cmyk; case JpegColorSpace.Ycck: - - // TODO: change this after YccK encoding is implemented - // We are deliberately mapping YccK color space to Cmyk color space at metadata - // level so encoder can fallback to cmyk color space from it. - // YccK -> Cmyk is the closest conversion logically wise - return JpegEncodingColor.Cmyk; + return JpegEncodingColor.Ycck; default: return JpegEncodingColor.YCbCrRatio420; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index fc13876213..c8dd763e71 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -81,16 +81,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the Start Of Image marker. this.WriteStartOfImage(); - // Write APP0 marker for any non-RGB colorspace image. - if (frameConfig.EncodingColor != JpegEncodingColor.Rgb) + // Write APP0 marker + if (frameConfig.AdobeColorTransformMarkerFlag is null) { this.WriteJfifApplicationHeader(metadata); } - // Else write App14 marker to indicate RGB color space. + // Write APP14 marker with adobe color extension else { - this.WriteApp14Marker(); + this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value); } // Write Exif, XMP, ICC and IPTC profiles @@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Writes the APP14 marker to indicate the image is in RGB color space. /// - private void WriteApp14Marker() + private void WriteApp14Marker(byte colorTransform) { this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length); @@ -227,8 +227,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Flags1 BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(9, 2), 0); - // Transform byte, 0 in combination with three components means the image is in RGB colorspace. - this.buffer[11] = 0; + // Color transform byte + this.buffer[11] = colorTransform; this.outputStream.Write(this.buffer.AsSpan(0, 12)); } @@ -840,7 +840,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg new JpegQuantizationTableConfig[] { defaultLuminanceQuantTable - }), + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown + }, // Cmyk new JpegFrameConfig( @@ -861,7 +864,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg new JpegQuantizationTableConfig[] { defaultLuminanceQuantTable - }), + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown, + }, + + // YccK + new JpegFrameConfig( + JpegColorSpace.Ycck, + JpegEncodingColor.Ycck, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck, + }, }; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs index 995b6036c8..f803c830e5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs @@ -54,5 +54,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. /// Cmyk = 7, + + /// + /// YCCK colorspace (Y, Cb, Cr, and key black). + /// + Ycck = 8, } }