From 0604a403855c76eb0923dd5135c9eb56f69ca677 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 7 May 2022 23:52:16 +0300 Subject: [PATCH] Implemented ycbcr/rgb/grayscale color converters --- .../JpegColorConverter.FromCmykAvx.cs | 2 + .../JpegColorConverter.FromCmykScalar.cs | 2 + .../JpegColorConverter.FromCmykVector.cs | 6 ++- .../JpegColorConverter.FromGrayScaleAvx.cs | 16 ++++++ .../JpegColorConverter.FromGrayScaleScalar.cs | 20 +++++-- .../JpegColorConverter.FromGrayScaleVector.cs | 24 +++++++-- .../JpegColorConverter.FromRgbAvx.cs | 23 ++++++++ .../JpegColorConverter.FromRgbScalar.cs | 22 +++++--- .../JpegColorConverter.FromRgbVector.cs | 32 +++++++++-- .../JpegColorConverter.FromYCbCrAvx.cs | 42 ++++++++++++++- .../JpegColorConverter.FromYCbCrScalar.cs | 30 ++++++++++- .../JpegColorConverter.FromYCbCrVector.cs | 54 +++++++++++++++++-- .../JpegColorConverter.FromYccKAvx.cs | 2 + .../JpegColorConverter.FromYccKScalar.cs | 2 + .../JpegColorConverter.FromYccKVector.cs | 6 ++- .../ColorConverters/JpegColorConverterBase.cs | 2 +- .../JpegColorConverterVector.cs | 35 ++++++++++-- .../Components/Encoder/HuffmanScanEncoder.cs | 3 +- .../Jpeg/Components/Encoder/JpegComponent.cs | 4 +- .../Encoder/JpegComponentPostProcessor.cs | 17 ++---- .../Jpeg/Components/Encoder/JpegFrame.cs | 51 +++++++----------- .../Encoder/SpectralConverter{TPixel}.cs | 20 +++---- .../Formats/Jpeg/JpegEncoderCore.cs | 24 ++++++++- 23 files changed, 349 insertions(+), 90 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 7366ee30a9..6429b0ea85 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -18,6 +18,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } + public override void ConvertFromRgbInplace(in ComponentValues values) => throw new System.NotImplementedException(); + public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs index 68dfa9bfba..17459a5851 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -38,6 +38,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c2[i] = y * k; } } + + public override void ConvertFromRgbInplace(in ComponentValues values) => throw new NotImplementedException(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs index 6b7ed169e3..50228c09a1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector cBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -44,8 +44,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - protected override void ConvertCoreInplace(in ComponentValues values) => + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index 963543ad44..bcb366298d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -33,6 +33,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c0 = Avx.Multiply(c0, scale); } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + // Used for the color conversion + var scale = Vector256.Create(this.MaximumValue); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + c0 = Avx.Multiply(c0, scale); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs index 3f6a6caa45..a0db2e801c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs @@ -16,10 +16,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values.Component0, this.MaximumValue); + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); - internal static void ConvertCoreInplace(Span values, float maxValue) + public override void ConvertFromRgbInplace(in ComponentValues values) + => ConvertCoreInplaceFromRgb(values.Component0, this.MaximumValue); + + internal static void ConvertCoreInplaceToRgb(Span values, float maxValue) { ref float valuesRef = ref MemoryMarshal.GetReference(values); float scale = 1 / maxValue; @@ -29,6 +32,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters Unsafe.Add(ref valuesRef, i) *= scale; } } + + internal static void ConvertCoreInplaceFromRgb(Span values, float maxValue) + { + ref float valuesRef = ref MemoryMarshal.GetReference(values); + float scale = maxValue; + + for (nint i = 0; i < values.Length; i++) + { + Unsafe.Add(ref valuesRef, i) *= scale; + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs index c484aac28d..ca7781f1be 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector cBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -31,8 +31,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - protected override void ConvertCoreInplace(in ComponentValues values) => - FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue); + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) + => FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); + + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + var scale = new Vector(this.MaximumValue); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c0 = ref Unsafe.Add(ref cBase, i); + c0 *= scale; + } + } + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + => FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component0, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs index f017716e3f..8453aa9ac5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -40,6 +40,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters b = Avx.Multiply(b, scale); } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + { + ref Vector256 rBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 gBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 bBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + // Used for the color conversion + var scale = Vector256.Create(this.MaximumValue); + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 r = ref Unsafe.Add(ref rBase, i); + ref Vector256 g = ref Unsafe.Add(ref gBase, i); + ref Vector256 b = ref Unsafe.Add(ref bBase, i); + r = Avx.Multiply(r, scale); + g = Avx.Multiply(g, scale); + b = Avx.Multiply(b, scale); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs index 24c59206d8..c4c8523bfc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs @@ -12,14 +12,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values, this.MaximumValue); + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertCoreInplaceFromRgb(values, this.MaximumValue); - internal static void ConvertCoreInplace(ComponentValues values, float maxValue) + public override void ConvertFromRgbInplace(in ComponentValues values) + => ConvertCoreInplaceFromRgb(values, this.MaximumValue); + + internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxValue) + { + FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue); + } + + internal static void ConvertCoreInplaceFromRgb(ComponentValues values, float maxValue) { - FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue); - FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue); - FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component0, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component1, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component2, maxValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs index ff3a2bee19..ac789ec8c4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -39,8 +39,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - protected override void ConvertCoreInplace(in ComponentValues values) => - FromRgbScalar.ConvertCoreInplace(values, this.MaximumValue); + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) + => FromRgbScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue); + + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + { + ref Vector rBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector gBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector bBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + var scale = new Vector(this.MaximumValue); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector r = ref Unsafe.Add(ref rBase, i); + ref Vector g = ref Unsafe.Add(ref gBase, i); + ref Vector b = ref Unsafe.Add(ref bBase, i); + r *= scale; + g *= scale; + b *= scale; + } + } + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + => FromRgbScalar.ConvertCoreInplaceFromRgb(values, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 53fa176480..653a2f482b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -71,7 +71,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters public override void ConvertFromRgbInplace(in ComponentValues values) { - return; + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + // Used for the color conversion + var chromaOffset = Vector256.Create(this.HalfValue); + var scale = Vector256.Create(this.MaximumValue); + 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++) + { + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); + + Vector256 r = Avx.Multiply(c0, scale); + Vector256 g = Avx.Multiply(c1, scale); + Vector256 b = Avx.Multiply(c2, scale); + + // 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)); + + c0 = y; + c1 = cb; + c2 = cr; + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs index 4b6d88f725..4c426d856b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -21,9 +21,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + => ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) + public override void ConvertFromRgbInplace(in ComponentValues values) + => ConvertCoreInplaceFromRgb(values, this.MaximumValue, this.HalfValue); + + public static void ConvertCoreInplaceToRgb(in ComponentValues values, float maxValue, float halfValue) { Span c0 = values.Component0; Span c1 = values.Component1; @@ -45,6 +48,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; } } + + public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float maxValue, float halfValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + float scale = maxValue; + + for (int i = 0; i < c0.Length; i++) + { + float r = c0[i] * scale; + float g = c1[i] * scale; + float b = c2[i] * scale; + + // 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) + c0[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + c1[i] = 128 - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + c2[i] = 128 + (0.5f * r) - (0.418688f * g) - (0.081312f * b); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs index 48e311d995..a26e09ceda 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -67,8 +67,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - protected override void ConvertCoreInplace(in ComponentValues values) => - FromYCbCrScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) + => FromYCbCrScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); + + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + { + ref Vector c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + var chromaOffset = new Vector(this.HalfValue); + + var scale = new Vector(this.MaximumValue); + + 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++) + { + ref Vector c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector c2 = ref Unsafe.Add(ref c2Base, i); + + Vector r = c0 * scale; + Vector g = c1 * scale; + Vector b = c2 * scale; + + // 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) + c0 = (rYMult * r) + (gYMult * g) + (bYMult * b); + c1 = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + c2 = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); + } + } + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + => FromYCbCrScalar.ConvertCoreInplaceFromRgb(values, this.MaximumValue, this.HalfValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs index 1f18d5324d..bd672d6b84 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } + public override void ConvertFromRgbInplace(in ComponentValues values) => throw new System.NotImplementedException(); + public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs index d6387ae714..345da654e5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -38,6 +38,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK; } } + + public override void ConvertFromRgbInplace(in ComponentValues values) => throw new NotImplementedException(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs index 66c79ae7c8..e90ff9438c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -68,8 +68,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - protected override void ConvertCoreInplace(in ComponentValues values) => + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index 464358d0e8..18dc0fd15c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// The input/ouptut as a stack-only struct public abstract void ConvertToRgbInplace(in ComponentValues values); - public virtual void ConvertFromRgbInplace(in ComponentValues values) => throw new NotImplementedException("This is a test exception"); + public abstract void ConvertFromRgbInplace(in ComponentValues values); /// /// Returns the s for all supported colorspaces and precisions. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs index ca482d78df..e07b87db45 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide // Thus there's no need to check whether simdCount is greater than zero int simdCount = length - remainder; - this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); + this.ConvertCoreVectorizedInplaceToRgb(values.Slice(0, simdCount)); // Jpeg images width is always divisible by 8 without a remainder // so it's safe to say SSE/AVX implementations would never have @@ -47,13 +47,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // remainder pixels if (remainder > 0) { - this.ConvertCoreInplace(values.Slice(simdCount, remainder)); + this.ConvertCoreInplaceToRgb(values.Slice(simdCount, remainder)); } } - protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); + public override void ConvertFromRgbInplace(in ComponentValues values) + { + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); + + int length = values.Component0.Length; + int remainder = (int)((uint)length % (uint)Vector.Count); + + // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide + // Thus there's no need to check whether simdCount is greater than zero + int simdCount = length - remainder; + this.ConvertCoreVectorizedInplaceFromRgb(values.Slice(0, simdCount)); + + // Jpeg images width is always divisible by 8 without a remainder + // so it's safe to say SSE/AVX implementations would never have + // 'remainder' pixels + // But some exotic simd implementations e.g. AVX-512 can have + // remainder pixels + if (remainder > 0) + { + this.ConvertCoreInplaceFromRgb(values.Slice(simdCount, remainder)); + } + } + + protected abstract void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values); + + protected abstract void ConvertCoreInplaceToRgb(in ComponentValues values); + + protected abstract void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values); - protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); + protected abstract void ConvertCoreInplaceFromRgb(in ComponentValues values); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 03f503ddee..8d404a3fd2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -128,13 +128,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } - public void Encode(Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + public void EncodeScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP this.huffmanTables = HuffmanLut.TheHuffmanLut; - var frame = new JpegFrame(configuration.MemoryAllocator, image, componentCount: 3); frame.Init(1, 1); frame.AllocateComponents(fullScan: false); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs index f50f9e8891..1c071c3352 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -62,12 +62,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Gets or sets the index for the DC Huffman table. /// - public int DcTableId { get; set; } = 0; // TODO: DEBUG!!! + public int DcTableId { get; set; } /// /// Gets or sets the index for the AC Huffman table. /// - public int AcTableId { get; set; } = 1; // TODO: DEBUG!!! + public int AcTableId { get; set; } /// public void Dispose() diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs index 8c6008620b..ad7bb5f0ff 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs @@ -12,8 +12,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private readonly JpegComponent component; - private readonly int blockRowsPerStep; - private Block8x8F quantTable; public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegComponent component, Size postProcessorBufferSize, Block8x8F quantTable) @@ -27,9 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( postProcessorBufferSize.Width, postProcessorBufferSize.Height, - this.blockAreaSize.Height); - - this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height; + this.blockAreaSize.Height, + AllocationOptions.Clean); } /// @@ -42,21 +39,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder Buffer2D spectralBuffer = this.component.SpectralBlocks; // should be this.frame.MaxColorChannelValue - // but currently 12-bit jpegs are not supported - float maximumValue = 255f; + // but 12-bit jpegs are not supported currently float normalizationValue = -128f; int blocksRowsPerStep = this.component.SamplingFactors.Height; int destAreaStride = this.ColorBuffer.Width; - int yBlockStart = spectralStep * this.blockRowsPerStep; + int yBlockStart = spectralStep * blocksRowsPerStep; Size subSamplingDivisors = this.component.SubSamplingDivisors; Block8x8F workspaceBlock = default; - for (int y = 0; y < this.blockRowsPerStep; y++) + for (int y = 0; y < blocksRowsPerStep; y++) { int yBuffer = y * this.blockAreaSize.Height; @@ -73,9 +69,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder subSamplingDivisors.Width, subSamplingDivisors.Height); - // multiply by maximum (for debug only, should be done in color converter) - workspaceBlock.MultiplyInPlace(maximumValue); - // level shift via -128f workspaceBlock.AddInPlace(normalizationValue); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 3343ae02ba..31e59acf8a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -11,55 +11,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// internal sealed class JpegFrame : IDisposable { - public JpegFrame(MemoryAllocator allocator, Image image, byte componentCount) + public JpegFrame(MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) { + this.ColorSpace = colorSpace; + this.PixelWidth = image.Width; this.PixelHeight = image.Height; - if (componentCount != 3) - { - throw new ArgumentException("This is YCbCr debug path only."); - } - + // int componentCount = 3; this.Components = new JpegComponent[] { - new JpegComponent(allocator, 1, 1, 0), - new JpegComponent(allocator, 1, 1, 0), - new JpegComponent(allocator, 1, 1, 0), + // RGB + new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, + new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, + new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, + + // YCbCr + //new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, + //new JpegComponent(allocator, 1, 1, 1) { DcTableId = 2, AcTableId = 3 }, + //new JpegComponent(allocator, 1, 1, 1) { DcTableId = 2, AcTableId = 3 }, + + // Luminance + //new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 } }; } - /// - /// Gets the number of pixel per row. - /// + public Decoder.JpegColorSpace ColorSpace { get; } + public int PixelHeight { get; private set; } - /// - /// Gets the number of pixels per line. - /// public int PixelWidth { get; private set; } - /// - /// Gets the number of components within a frame. - /// public int ComponentCount => this.Components.Length; - /// - /// Gets the frame component collection. - /// public JpegComponent[] Components { get; } - /// - /// Gets or sets the number of MCU's per line. - /// public int McusPerLine { get; set; } - /// - /// Gets or sets the number of MCU's per column. - /// public int McusPerColumn { get; set; } - /// public void Dispose() { for (int i = 0; i < this.Components.Length; i++) @@ -68,11 +58,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - /// - /// Allocates the frame component blocks. - /// - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. public void Init(int maxSubFactorH, int maxSubFactorV) { this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index e3f659b721..7164a49f80 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -25,6 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private Buffer2D pixelBuffer; + private int alignedPixelWidth; + private Decoder.ColorConverters.JpegColorConverterBase colorConverter; public SpectralConverter(Configuration configuration) => @@ -47,7 +49,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // component processors from spectral to Rgba32 const int blockPixelWidth = 8; - var postProcessorBufferSize = new Size(majorBlockWidth * blockPixelWidth, this.pixelRowsPerStep); + this.alignedPixelWidth = majorBlockWidth * blockPixelWidth; + var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep); this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { @@ -56,10 +59,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } // single 'stride' rgba32 buffer for conversion between spectral and TPixel - this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3); + this.rgbBuffer = allocator.Allocate(this.alignedPixelWidth * 3); // color converter from Rgb24 to YCbCr - this.colorConverter = Decoder.ColorConverters.JpegColorConverterBase.GetConverter(colorSpace: Decoder.JpegColorSpace.YCbCr, precision: 8); + this.colorConverter = Decoder.ColorConverters.JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); } public void ConvertStrideBaseline() @@ -80,18 +83,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder int width = this.pixelBuffer.Width; - // unpack TPixel to r/g/b planes - Span r = this.rgbBuffer.Slice(0, width); - Span g = this.rgbBuffer.Slice(width, width); - Span b = this.rgbBuffer.Slice(width * 2, width); + Span r = this.rgbBuffer.Slice(0, this.alignedPixelWidth); + Span g = this.rgbBuffer.Slice(this.alignedPixelWidth, this.alignedPixelWidth); + Span b = this.rgbBuffer.Slice(this.alignedPixelWidth * 2, this.alignedPixelWidth); for (int yy = this.pixelRowCounter; yy < maxY; yy++) { int y = yy - this.pixelRowCounter; - // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. - // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, - // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. + // unpack TPixel to r/g/b planes Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, r, g, b, sourceRow); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 910a723fb7..79106856d7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -131,13 +131,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); + var frame = new Components.Encoder.JpegFrame(Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.colorType.Value)); var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); + new HuffmanScanEncoder(3, stream).EncodeScan(frame, image, quantTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); stream.Flush(); + + static JpegColorSpace GetTargetColorSpace(JpegColorType colorType) + { + switch (colorType) + { + case JpegColorType.YCbCrRatio444: + case JpegColorType.YCbCrRatio422: + case JpegColorType.YCbCrRatio420: + case JpegColorType.YCbCrRatio411: + case JpegColorType.YCbCrRatio410: + return JpegColorSpace.YCbCr; + case JpegColorType.Rgb: + return JpegColorSpace.RGB; + case JpegColorType.Cmyk: + return JpegColorSpace.Cmyk; + case JpegColorType.Luminance: + return JpegColorSpace.Grayscale; + default: + throw new NotImplementedException($"Unknown output color space: {colorType}"); + } + } } ///