diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs
deleted file mode 100644
index e2416d9273..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- ///
- /// Enumerates the Huffman tables
- ///
- internal enum HuffIndex
- {
- ///
- /// The DC luminance huffman table index
- ///
- LuminanceDC = 0,
-
- // ReSharper disable UnusedMember.Local
-
- ///
- /// The AC luminance huffman table index
- ///
- LuminanceAC = 1,
-
- ///
- /// The DC chrominance huffman table index
- ///
- ChrominanceDC = 2,
-
- ///
- /// The AC chrominance huffman table index
- ///
- ChrominanceAC = 3,
-
- // ReSharper restore UnusedMember.Local
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
index 3c606d6e06..6e81e3a9a6 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
@@ -7,7 +7,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
@@ -98,8 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
private int emitWriteIndex;
- private Block8x8 tempBlock;
-
///
/// The output stream. All attempted writes after the first error become no-ops.
///
@@ -246,321 +243,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
- private HuffmanLut[] huffmanTables;
-
- ///
- /// Encodes the image with no subsampling.
- ///
- /// The pixel format.
- /// The pixel accessor providing access to the image pixels.
- /// Luminance quantization table provided by the callee.
- /// Chrominance quantization table provided by the callee.
- /// The token to monitor for cancellation.
- public void Encode444(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
- FastFloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable);
-
- this.huffmanTables = HuffmanLut.TheHuffmanLut;
-
- // ReSharper disable once InconsistentNaming
- int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
-
- ImageFrame frame = pixels.Frames.RootFrame;
- Buffer2D pixelBuffer = frame.PixelBuffer;
- RowOctet currentRows = default;
-
- var pixelConverter = new YCbCrForwardConverter444(frame);
-
- for (int y = 0; y < pixels.Height; y += 8)
- {
- cancellationToken.ThrowIfCancellationRequested();
- currentRows.Update(pixelBuffer, y);
-
- for (int x = 0; x < pixels.Width; x += 8)
- {
- pixelConverter.Convert(x, y, ref currentRows);
-
- prevDCY = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCY,
- ref pixelConverter.Y,
- ref luminanceQuantTable);
-
- prevDCCb = this.WriteBlock(
- QuantIndex.Chrominance,
- prevDCCb,
- ref pixelConverter.Cb,
- ref chrominanceQuantTable);
-
- prevDCCr = this.WriteBlock(
- QuantIndex.Chrominance,
- prevDCCr,
- ref pixelConverter.Cr,
- ref chrominanceQuantTable);
-
- if (this.IsStreamFlushNeeded)
- {
- this.FlushToStream();
- }
- }
- }
-
- this.FlushRemainingBytes();
- }
-
- ///
- /// Encodes the image with subsampling. The Cb and Cr components are each subsampled
- /// at a factor of 2 both horizontally and vertically.
- ///
- /// The pixel format.
- /// The pixel accessor providing access to the image pixels.
- /// Luminance quantization table provided by the callee.
- /// Chrominance quantization table provided by the callee.
- /// The token to monitor for cancellation.
- public void Encode420(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
- FastFloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable);
-
- this.huffmanTables = HuffmanLut.TheHuffmanLut;
-
- // ReSharper disable once InconsistentNaming
- int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
- ImageFrame frame = pixels.Frames.RootFrame;
- Buffer2D pixelBuffer = frame.PixelBuffer;
- RowOctet currentRows = default;
-
- var pixelConverter = new YCbCrForwardConverter420(frame);
-
- for (int y = 0; y < pixels.Height; y += 16)
- {
- cancellationToken.ThrowIfCancellationRequested();
- for (int x = 0; x < pixels.Width; x += 16)
- {
- for (int i = 0; i < 2; i++)
- {
- int yOff = i * 8;
- currentRows.Update(pixelBuffer, y + yOff);
- pixelConverter.Convert(x, y, ref currentRows, i);
-
- prevDCY = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCY,
- ref pixelConverter.YLeft,
- ref luminanceQuantTable);
-
- prevDCY = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCY,
- ref pixelConverter.YRight,
- ref luminanceQuantTable);
- }
-
- prevDCCb = this.WriteBlock(
- QuantIndex.Chrominance,
- prevDCCb,
- ref pixelConverter.Cb,
- ref chrominanceQuantTable);
-
- prevDCCr = this.WriteBlock(
- QuantIndex.Chrominance,
- prevDCCr,
- ref pixelConverter.Cr,
- ref chrominanceQuantTable);
-
- if (this.IsStreamFlushNeeded)
- {
- this.FlushToStream();
- }
- }
- }
-
- this.FlushRemainingBytes();
- }
-
- ///
- /// Encodes the image with no chroma, just luminance.
- ///
- /// The pixel format.
- /// The pixel accessor providing access to the image pixels.
- /// Luminance quantization table provided by the callee.
- /// The token to monitor for cancellation.
- public void EncodeGrayscale(Image pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
-
- this.huffmanTables = HuffmanLut.TheHuffmanLut;
-
- // ReSharper disable once InconsistentNaming
- int prevDCY = 0;
-
- ImageFrame frame = pixels.Frames.RootFrame;
- Buffer2D pixelBuffer = frame.PixelBuffer;
- RowOctet currentRows = default;
-
- var pixelConverter = new LuminanceForwardConverter(frame);
-
- for (int y = 0; y < pixels.Height; y += 8)
- {
- cancellationToken.ThrowIfCancellationRequested();
- currentRows.Update(pixelBuffer, y);
-
- for (int x = 0; x < pixels.Width; x += 8)
- {
- pixelConverter.Convert(x, y, ref currentRows);
-
- prevDCY = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCY,
- ref pixelConverter.Y,
- ref luminanceQuantTable);
-
- if (this.IsStreamFlushNeeded)
- {
- this.FlushToStream();
- }
- }
- }
-
- this.FlushRemainingBytes();
- }
-
- ///
- /// Encodes the image with no subsampling and keeps the pixel data as Rgb24.
- ///
- /// The pixel format.
- /// The pixel accessor providing access to the image pixels.
- /// Quantization table provided by the callee.
- /// The token to monitor for cancellation.
- public void EncodeRgb(Image pixels, ref Block8x8F quantTable, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
- {
- FastFloatingPointDCT.AdjustToFDCT(ref quantTable);
-
- this.huffmanTables = HuffmanLut.TheHuffmanLut;
-
- // ReSharper disable once InconsistentNaming
- int prevDCR = 0, prevDCG = 0, prevDCB = 0;
-
- ImageFrame frame = pixels.Frames.RootFrame;
- Buffer2D pixelBuffer = frame.PixelBuffer;
- RowOctet currentRows = default;
-
- var pixelConverter = new RgbForwardConverter(frame);
-
- for (int y = 0; y < pixels.Height; y += 8)
- {
- cancellationToken.ThrowIfCancellationRequested();
- currentRows.Update(pixelBuffer, y);
-
- for (int x = 0; x < pixels.Width; x += 8)
- {
- pixelConverter.Convert(x, y, ref currentRows);
-
- prevDCR = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCR,
- ref pixelConverter.R,
- ref quantTable);
-
- prevDCG = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCG,
- ref pixelConverter.G,
- ref quantTable);
-
- prevDCB = this.WriteBlock(
- QuantIndex.Luminance,
- prevDCB,
- ref pixelConverter.B,
- ref quantTable);
-
- if (this.IsStreamFlushNeeded)
- {
- this.FlushToStream();
- }
- }
- }
-
- this.FlushRemainingBytes();
- }
-
- ///
- /// Writes a block of pixel data using the given quantization table,
- /// returning the post-quantized DC value of the DCT-transformed block.
- /// The block is in natural (not zig-zag) order.
- ///
- /// The quantization table index.
- /// The previous DC value.
- /// Source block.
- /// Quantization table.
- /// The .
- private int WriteBlock(
- QuantIndex index,
- int prevDC,
- ref Block8x8F block,
- ref Block8x8F quant)
- {
- ref Block8x8 spectralBlock = ref this.tempBlock;
-
- // Shifting level from 0..255 to -128..127
- block.AddInPlace(-128f);
-
- // Discrete cosine transform
- FastFloatingPointDCT.TransformFDCT(ref block);
-
- // Quantization
- Block8x8F.Quantize(ref block, ref spectralBlock, ref quant);
-
- // Emit the DC delta.
- int dc = spectralBlock[0];
- this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC);
-
- // Emit the AC components.
- int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values;
-
- nint lastValuableIndex = spectralBlock.GetLastNonZeroIndex();
-
- int runLength = 0;
- ref short blockRef = ref Unsafe.As(ref spectralBlock);
- for (nint zig = 1; zig <= lastValuableIndex; zig++)
- {
- const int zeroRun1 = 1 << 4;
- const int zeroRun16 = 16 << 4;
-
- int ac = Unsafe.Add(ref blockRef, zig);
- if (ac == 0)
- {
- runLength += zeroRun1;
- }
- else
- {
- while (runLength >= zeroRun16)
- {
- this.EmitHuff(acHuffTable, 0xf0);
- runLength -= zeroRun16;
- }
-
- this.EmitHuffRLE(acHuffTable, runLength, ac);
- runLength = 0;
- }
- }
-
- // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over
- // this can be done for any number of trailing zeros, even when all 63 ac values are zero
- // (Block8x8F.Size - 1) == 63 - last index of the mcu elements
- if (lastValuableIndex != Block8x8F.Size - 1)
- {
- this.EmitHuff(acHuffTable, 0x00);
- }
-
- return dc;
- }
-
private void WriteBlock(
JpegComponent component,
ref Block8x8 block,
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
deleted file mode 100644
index e87f2fc573..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-#if SUPPORTS_RUNTIME_INTRINSICS
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.X86;
-#endif
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- ///
- /// On-stack worker struct to efficiently encapsulate the TPixel -> L8 -> Y conversion chain of 8x8 pixel blocks.
- ///
- /// The pixel type to work on
- internal ref struct LuminanceForwardConverter
- where TPixel : unmanaged, IPixel
- {
- ///
- /// Number of pixels processed per single call
- ///
- private const int PixelsPerSample = 8 * 8;
-
- ///
- /// The Y component
- ///
- public Block8x8F Y;
-
- ///
- /// Temporal 64-pixel span to hold unconverted TPixel data.
- ///
- private readonly Span pixelSpan;
-
- ///
- /// Temporal 64-byte span to hold converted data.
- ///
- private readonly Span l8Span;
-
- ///
- /// Sampled pixel buffer size.
- ///
- private readonly Size samplingAreaSize;
-
- ///
- /// for internal operations.
- ///
- private readonly Configuration config;
-
- public LuminanceForwardConverter(ImageFrame frame)
- {
- this.Y = default;
-
- this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
- this.l8Span = new L8[PixelsPerSample].AsSpan();
-
- this.samplingAreaSize = new Size(frame.Width, frame.Height);
- this.config = frame.GetConfiguration();
- }
-
- ///
- /// Gets size of sampling area from given frame pixel buffer.
- ///
- private static Size SampleSize => new(8, 8);
-
- ///
- /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure ()
- ///
- public void Convert(int x, int y, ref RowOctet currentRows)
- {
- YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
-
- PixelOperations.Instance.ToL8(this.config, this.pixelSpan, this.l8Span);
-
- ref Block8x8F yBlock = ref this.Y;
- ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span);
-
- if (RgbToYCbCrConverterVectorized.IsSupported)
- {
- ConvertAvx(ref l8Start, ref yBlock);
- }
- else
- {
- ConvertScalar(ref l8Start, ref yBlock);
- }
- }
-
- ///
- /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics.
- ///
- /// Start of span of L8 pixels with size of 64
- /// 8x8 destination matrix of Luminance(Y) converted data
- private static void ConvertAvx(ref L8 l8Start, ref Block8x8F yBlock)
- {
- Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter");
-
-#if SUPPORTS_RUNTIME_INTRINSICS
- ref Vector128 l8ByteSpan = ref Unsafe.As>(ref l8Start);
- ref Vector256 destRef = ref yBlock.V0;
-
- const int bytesPerL8Stride = 8;
- for (nint i = 0; i < 8; i++)
- {
- Unsafe.Add(ref destRef, i) = Avx2.ConvertToVector256Single(Avx2.ConvertToVector256Int32(Unsafe.AddByteOffset(ref l8ByteSpan, bytesPerL8Stride * i)));
- }
-#endif
- }
-
- ///
- /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats.
- ///
- /// Start of span of L8 pixels with size of 64
- /// 8x8 destination matrix of Luminance(Y) converted data
- private static void ConvertScalar(ref L8 l8Start, ref Block8x8F yBlock)
- {
- for (int i = 0; i < Block8x8F.Size; i++)
- {
- ref L8 c = ref Unsafe.Add(ref l8Start, i);
- yBlock[i] = c.PackedValue;
- }
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
deleted file mode 100644
index e2d12916c0..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-#if SUPPORTS_RUNTIME_INTRINSICS
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.X86;
-#endif
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- ///
- /// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks.
- ///
- /// The pixel type to work on.
- internal ref struct RgbForwardConverter
- where TPixel : unmanaged, IPixel
- {
- ///
- /// Number of pixels processed per single call
- ///
- private const int PixelsPerSample = 8 * 8;
-
- ///
- /// Total byte size of processed pixels converted from TPixel to
- ///
- private const int RgbSpanByteSize = PixelsPerSample * 3;
-
- ///
- /// The Red component.
- ///
- public Block8x8F R;
-
- ///
- /// The Green component.
- ///
- public Block8x8F G;
-
- ///
- /// The Blue component.
- ///
- public Block8x8F B;
-
- ///
- /// Temporal 64-byte span to hold unconverted TPixel data.
- ///
- private readonly Span pixelSpan;
-
- ///
- /// Temporal 64-byte span to hold converted Rgb24 data.
- ///
- private readonly Span rgbSpan;
-
- ///
- /// Sampled pixel buffer size.
- ///
- private readonly Size samplingAreaSize;
-
- ///
- /// for internal operations.
- ///
- private readonly Configuration config;
-
- public RgbForwardConverter(ImageFrame frame)
- {
- this.R = default;
- this.G = default;
- this.B = default;
-
- // temporal pixel buffers
- this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
- this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
-
- // frame data
- this.samplingAreaSize = new Size(frame.Width, frame.Height);
- this.config = frame.GetConfiguration();
- }
-
- ///
- /// Gets size of sampling area from given frame pixel buffer.
- ///
- private static Size SampleSize => new(8, 8);
-
- ///
- /// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
- ///
- public void Convert(int x, int y, ref RowOctet currentRows)
- {
- YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
-
- PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
-
- ref Block8x8F redBlock = ref this.R;
- ref Block8x8F greenBlock = ref this.G;
- ref Block8x8F blueBlock = ref this.B;
-
- if (RgbToYCbCrConverterVectorized.IsSupported)
- {
- ConvertAvx(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
- }
- else
- {
- ConvertScalar(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
- }
- }
-
- ///
- /// Converts 8x8 RGB24 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics.
- ///
- /// Span of Rgb24 pixels with size of 64
- /// 8x8 destination matrix of Red converted data
- /// 8x8 destination matrix of Blue converted data
- /// 8x8 destination matrix of Green converted data
- private static void ConvertAvx(Span rgbSpan, ref Block8x8F rBlock, ref Block8x8F gBlock, ref Block8x8F bBlock)
- {
- Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter");
-
-#if SUPPORTS_RUNTIME_INTRINSICS
- ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan));
- ref Vector256 redRef = ref rBlock.V0;
- ref Vector256 greenRef = ref gBlock.V0;
- ref Vector256 blueRef = ref bBlock.V0;
- var zero = Vector256.Create(0).AsByte();
-
- var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.MoveFirst24BytesToSeparateLanes));
- var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.ExtractRgb));
- Vector256 rgb, rg, bx;
-
- const int bytesPerRgbStride = 24;
- for (nint i = 0; i < 8; i++)
- {
- rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, bytesPerRgbStride * i).AsUInt32(), extractToLanesMask).AsByte();
-
- rgb = Avx2.Shuffle(rgb, extractRgbMask);
-
- rg = Avx2.UnpackLow(rgb, zero);
- bx = Avx2.UnpackHigh(rgb, zero);
-
- Unsafe.Add(ref redRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
- Unsafe.Add(ref greenRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
- Unsafe.Add(ref blueRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
- }
-#endif
- }
-
- private static void ConvertScalar(Span rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
- {
- ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan);
-
- for (int i = 0; i < Block8x8F.Size; i++)
- {
- Rgb24 c = Unsafe.Add(ref rgbStart, (nint)(uint)i);
-
- redBlock[i] = c.R;
- greenBlock[i] = c.G;
- blueBlock[i] = c.B;
- }
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs
deleted file mode 100644
index 15574a32a2..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- ///
- /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace.
- /// Methods to build the tables are based on libjpeg implementation.
- ///
- internal unsafe struct RgbToYCbCrConverterLut
- {
- ///
- /// The red luminance table
- ///
- public fixed int YRTable[256];
-
- ///
- /// The green luminance table
- ///
- public fixed int YGTable[256];
-
- ///
- /// The blue luminance table
- ///
- public fixed int YBTable[256];
-
- ///
- /// The red blue-chrominance table
- ///
- public fixed int CbRTable[256];
-
- ///
- /// The green blue-chrominance table
- ///
- public fixed int CbGTable[256];
-
- ///
- /// The blue blue-chrominance table
- /// B=>Cb and R=>Cr are the same
- ///
- public fixed int CbBTable[256];
-
- ///
- /// The green red-chrominance table
- ///
- public fixed int CrGTable[256];
-
- ///
- /// The blue red-chrominance table
- ///
- public fixed int CrBTable[256];
-
- // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
- private const int ScaleBits = 16;
-
- private const int CBCrOffset = 128 << ScaleBits;
-
- private const int Half = 1 << (ScaleBits - 1);
-
- ///
- /// Initializes the YCbCr tables
- ///
- /// The initialized
- public static RgbToYCbCrConverterLut Create()
- {
- RgbToYCbCrConverterLut tables = default;
-
- for (int i = 0; i <= 255; i++)
- {
- // The values for the calculations are left scaled up since we must add them together before rounding.
- tables.YRTable[i] = Fix(0.299F) * i;
- tables.YGTable[i] = Fix(0.587F) * i;
- tables.YBTable[i] = (Fix(0.114F) * i) + Half;
- tables.CbRTable[i] = (-Fix(0.168735892F)) * i;
- tables.CbGTable[i] = (-Fix(0.331264108F)) * i;
-
- // We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr.
- // This ensures that the maximum output will round to 255
- // not 256, and thus that we don't have to range-limit.
- //
- // B=>Cb and R=>Cr tables are the same
- tables.CbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1;
-
- tables.CrGTable[i] = (-Fix(0.418687589F)) * i;
- tables.CrBTable[i] = (-Fix(0.081312411F)) * i;
- }
-
- return tables;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private float CalculateY(byte r, byte g, byte b)
- {
- // float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
- return (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private float CalculateCb(byte r, byte g, byte b)
- {
- // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
- return (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private float CalculateCr(byte r, byte g, byte b)
- {
- // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
- return (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits;
- }
-
- ///
- /// Converts Rgb24 pixels into YCbCr color space with 4:4:4 subsampling sampling of luminance and chroma.
- ///
- /// Span of Rgb24 pixel data
- /// Resulting Y values block
- /// Resulting Cb values block
- /// Resulting Cr values block
- public void Convert444(Span rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
- {
- ref Rgb24 rgbStart = ref rgbSpan[0];
-
- for (int i = 0; i < Block8x8F.Size; i++)
- {
- Rgb24 c = Unsafe.Add(ref rgbStart, i);
-
- yBlock[i] = this.CalculateY(c.R, c.G, c.B);
- cbBlock[i] = this.CalculateCb(c.R, c.G, c.B);
- crBlock[i] = this.CalculateCr(c.R, c.G, c.B);
- }
- }
-
- ///
- /// Converts Rgb24 pixels into YCbCr color space with 4:2:0 subsampling of luminance and chroma.
- ///
- /// Calculates 2 out of 4 luminance blocks and half of chroma blocks. This method must be called twice per 4x 8x8 DCT blocks with different row param.
- /// Span of Rgb24 pixel data
- /// First or "left" resulting Y block
- /// Second or "right" resulting Y block
- /// Resulting Cb values block
- /// Resulting Cr values block
- /// Row index of the 16x16 block, 0 or 1
- public void Convert420(Span rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row)
- {
- DebugGuard.MustBeBetweenOrEqualTo(row, 0, 1, nameof(row));
-
- ref float yBlockLeftRef = ref Unsafe.As(ref yBlockLeft);
- ref float yBlockRightRef = ref Unsafe.As(ref yBlockRight);
-
- // 0-31 or 32-63
- // upper or lower part
- int chromaWriteOffset = row * (Block8x8F.Size / 2);
- ref float cbBlockRef = ref Unsafe.Add(ref Unsafe.As(ref cbBlock), chromaWriteOffset);
- ref float crBlockRef = ref Unsafe.Add(ref Unsafe.As(ref crBlock), chromaWriteOffset);
-
- ref Rgb24 rgbStart = ref rgbSpan[0];
-
- for (int i = 0; i < 8; i += 2)
- {
- int yBlockWriteOffset = i * 8;
- ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, i * 16);
-
- int chromaOffset = 8 * (i / 2);
-
- // left
- this.ConvertChunk420(
- ref stride,
- ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset),
- ref Unsafe.Add(ref cbBlockRef, chromaOffset),
- ref Unsafe.Add(ref crBlockRef, chromaOffset));
-
- // right
- this.ConvertChunk420(
- ref Unsafe.Add(ref stride, 8),
- ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset),
- ref Unsafe.Add(ref cbBlockRef, chromaOffset + 4),
- ref Unsafe.Add(ref crBlockRef, chromaOffset + 4));
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, ref float cbBlock, ref float crBlock)
- {
- // jpeg 8x8 blocks are processed as 16x16 blocks with 16x8 subpasses (this is done for performance reasons)
- // each row is 16 pixels wide thus +16 stride reference offset
- // resulting luminance (Y`) are sampled at original resolution thus +8 reference offset
- for (int k = 0; k < 8; k += 2)
- {
- ref float yBlockRef = ref Unsafe.Add(ref yBlock, k);
-
- // top row
- Rgb24 px0 = Unsafe.Add(ref stride, k);
- Rgb24 px1 = Unsafe.Add(ref stride, k + 1);
- yBlockRef = this.CalculateY(px0.R, px0.G, px0.B);
- Unsafe.Add(ref yBlockRef, 1) = this.CalculateY(px1.R, px1.G, px1.B);
-
- // bottom row
- Rgb24 px2 = Unsafe.Add(ref stride, k + 16);
- Rgb24 px3 = Unsafe.Add(ref stride, k + 17);
- Unsafe.Add(ref yBlockRef, 8) = this.CalculateY(px2.R, px2.G, px2.B);
- Unsafe.Add(ref yBlockRef, 9) = this.CalculateY(px3.R, px3.G, px3.B);
-
- // chroma average for 2x2 pixel block
- Unsafe.Add(ref cbBlock, k / 2) = this.CalculateAverageCb(px0, px1, px2, px3);
- Unsafe.Add(ref crBlock, k / 2) = this.CalculateAverageCr(px0, px1, px2, px3);
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private float CalculateAverageCb(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3)
- {
- return 0.25f
- * (this.CalculateCb(px0.R, px0.G, px0.B)
- + this.CalculateCb(px1.R, px1.G, px1.B)
- + this.CalculateCb(px2.R, px2.G, px2.B)
- + this.CalculateCb(px3.R, px3.G, px3.B));
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private float CalculateAverageCr(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3)
- {
- return 0.25f
- * (this.CalculateCr(px0.R, px0.G, px0.B)
- + this.CalculateCr(px1.R, px1.G, px1.B)
- + this.CalculateCr(px2.R, px2.G, px2.B)
- + this.CalculateCr(px3.R, px3.G, px3.B));
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int Fix(float x)
- => (int)((x * (1L << ScaleBits)) + 0.5F);
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
deleted file mode 100644
index d7542d7a59..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Diagnostics;
-#if SUPPORTS_RUNTIME_INTRINSICS
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.X86;
-#endif
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- internal static class RgbToYCbCrConverterVectorized
- {
- public static bool IsSupported
- {
- get
- {
-#if SUPPORTS_RUNTIME_INTRINSICS
- return Avx2.IsSupported;
-#else
- return false;
-#endif
- }
- }
-
- public static int AvxCompatibilityPadding
- {
- // rgb byte matrices contain 8 strides by 8 pixels each, thus 64 pixels total
- // Strides are stored sequentially - one big span of 64 * 3 = 192 bytes
- // Each stride has exactly 3 * 8 = 24 bytes or 3 * 8 * 8 = 192 bits
- // Avx registers are 256 bits so rgb span will be loaded with extra 64 bits from the next stride:
- // stride 0 0 - 192 -(+64bits)-> 256
- // stride 1 192 - 384 -(+64bits)-> 448
- // stride 2 384 - 576 -(+64bits)-> 640
- // stride 3 576 - 768 -(+64bits)-> 832
- // stride 4 768 - 960 -(+64bits)-> 1024
- // stride 5 960 - 1152 -(+64bits)-> 1216
- // stride 6 1152 - 1344 -(+64bits)-> 1408
- // stride 7 1344 - 1536 -(+64bits)-> 1600 <-- READ ACCESS VIOLATION
- //
- // Total size of the 64 pixel rgb span: 64 * 3 * 8 = 1536 bits, avx operations require 1600 bits
- // This is not permitted - we are reading foreign memory
- //
- // 8 byte padding to rgb byte span will solve this problem without extra code in converters
- get
- {
-#if SUPPORTS_RUNTIME_INTRINSICS
- if (IsSupported)
- {
- return 8;
- }
-#endif
- return 0;
- }
- }
-
-#if SUPPORTS_RUNTIME_INTRINSICS
-
- internal static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[]
- {
- 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0,
- 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0
- };
-
- internal static ReadOnlySpan ExtractRgb => new byte[]
- {
- 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF,
- 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF
- };
-#endif
-
- ///
- /// Converts 8x8 Rgb24 pixel matrix to YCbCr pixel matrices with 4:4:4 subsampling
- ///
- /// Total size of rgb span must be 200 bytes
- /// Span of rgb pixels with size of 64
- /// 8x8 destination matrix of Luminance(Y) converted data
- /// 8x8 destination matrix of Chrominance(Cb) converted data
- /// 8x8 destination matrix of Chrominance(Cr) converted data
- public static void Convert444(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
- {
- Debug.Assert(IsSupported, "AVX2 is required to run this converter");
-
-#if SUPPORTS_RUNTIME_INTRINSICS
- 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 f128 = Vector256.Create(128f);
- var fn0418688 = Vector256.Create(-0.418688f);
- var fn0081312F = Vector256.Create(-0.081312F);
- var f05 = Vector256.Create(0.5f);
- var zero = Vector256.Create(0).AsByte();
-
- ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan));
- ref Vector256 destYRef = ref yBlock.V0;
- ref Vector256 destCbRef = ref cbBlock.V0;
- ref Vector256 destCrRef = ref crBlock.V0;
-
- var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
- var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb));
- Vector256 rgb, rg, bx;
- Vector256 r, g, b;
-
- const int bytesPerRgbStride = 24;
- for (int i = 0; i < 8; i++)
- {
- rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte();
-
- rgb = Avx2.Shuffle(rgb, extractRgbMask);
-
- rg = Avx2.UnpackLow(rgb, zero);
- bx = Avx2.UnpackHigh(rgb, zero);
-
- r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
- g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
- b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
-
- // (0.299F * r) + (0.587F * g) + (0.114F * b);
- Unsafe.Add(ref destYRef, i) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
-
- // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
- Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
-
- // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
- Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
- }
-#endif
- }
-
- ///
- /// Converts 16x8 Rgb24 pixels matrix to 2 Y 8x8 matrices with 4:2:0 subsampling
- ///
- public static void Convert420(ReadOnlySpan rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row)
- {
- Debug.Assert(IsSupported, "AVX2 is required to run this converter");
-
-#if SUPPORTS_RUNTIME_INTRINSICS
- 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 f128 = Vector256.Create(128f);
- var fn0418688 = Vector256.Create(-0.418688f);
- var fn0081312F = Vector256.Create(-0.081312F);
- var f05 = Vector256.Create(0.5f);
- var zero = Vector256.Create(0).AsByte();
-
- ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan));
-
- int destOffset = row * 4;
-
- ref Vector256 destCbRef = ref Unsafe.Add(ref Unsafe.As>(ref cbBlock), destOffset);
- ref Vector256 destCrRef = ref Unsafe.Add(ref Unsafe.As>(ref crBlock), destOffset);
-
- var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
- var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb));
- Vector256 rgb, rg, bx;
- Vector256 r, g, b;
-
- Span> rDataLanes = stackalloc Vector256[4];
- Span> gDataLanes = stackalloc Vector256[4];
- Span> bDataLanes = stackalloc Vector256[4];
-
- const int bytesPerRgbStride = 24;
- for (int i = 0; i < 4; i++)
- {
- // 16x2 => 8x1
- // left 8x8 column conversions
- for (int j = 0; j < 4; j += 2)
- {
- rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte();
-
- rgb = Avx2.Shuffle(rgb, extractRgbMask);
-
- rg = Avx2.UnpackLow(rgb, zero);
- bx = Avx2.UnpackHigh(rgb, zero);
-
- r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
- g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
- b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
-
- int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
-
- // (0.299F * r) + (0.587F * g) + (0.114F * b);
- Unsafe.Add(ref yBlockLeft.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
-
- rDataLanes[j] = r;
- gDataLanes[j] = g;
- bDataLanes[j] = b;
- }
-
- // 16x2 => 8x1
- // right 8x8 column conversions
- for (int j = 1; j < 4; j += 2)
- {
- rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte();
-
- rgb = Avx2.Shuffle(rgb, extractRgbMask);
-
- rg = Avx2.UnpackLow(rgb, zero);
- bx = Avx2.UnpackHigh(rgb, zero);
-
- r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
- g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
- b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
-
- int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
-
- // (0.299F * r) + (0.587F * g) + (0.114F * b);
- Unsafe.Add(ref yBlockRight.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
-
- rDataLanes[j] = r;
- gDataLanes[j] = g;
- bDataLanes[j] = b;
- }
-
- r = Scale16x2_8x1(rDataLanes);
- g = Scale16x2_8x1(gDataLanes);
- b = Scale16x2_8x1(bDataLanes);
-
- // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
- Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
-
- // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
- Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
- }
-#endif
- }
-
-#if SUPPORTS_RUNTIME_INTRINSICS
- ///
- /// Scales 16x2 matrix to 8x1 using 2x2 average
- ///
- /// Input matrix consisting of 4 256bit vectors
- /// 256bit vector containing upper and lower scaled parts of the input matrix
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static Vector256 Scale16x2_8x1(ReadOnlySpan> v)
- {
- Debug.Assert(Avx2.IsSupported, "AVX2 is required to run this converter");
- DebugGuard.IsTrue(v.Length == 4, "Input span must consist of 4 elements");
-
- var f025 = Vector256.Create(0.25f);
-
- Vector256 left = Avx.Add(v[0], v[2]);
- Vector256 right = Avx.Add(v[1], v[3]);
- Vector256 avg2x2 = Avx.Multiply(Avx.HorizontalAdd(left, right), f025);
-
- return Avx2.Permute4x64(avg2x2.AsDouble(), 0b11_01_10_00).AsSingle();
- }
-#endif
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
deleted file mode 100644
index 3a878f3c63..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- ///
- /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
- ///
- /// The pixel type to work on
- internal ref struct YCbCrForwardConverter420
- where TPixel : unmanaged, IPixel
- {
- ///
- /// Number of pixels processed per single call
- ///
- private const int PixelsPerSample = 16 * 8;
-
- ///
- /// Total byte size of processed pixels converted from TPixel to
- ///
- private const int RgbSpanByteSize = PixelsPerSample * 3;
-
- ///
- /// The left Y component
- ///
- public Block8x8F YLeft;
-
- ///
- /// The left Y component
- ///
- public Block8x8F YRight;
-
- ///
- /// The Cb component
- ///
- public Block8x8F Cb;
-
- ///
- /// The Cr component
- ///
- public Block8x8F Cr;
-
- ///
- /// The color conversion tables
- ///
- private RgbToYCbCrConverterLut colorTables;
-
- ///
- /// Temporal 16x8 block to hold TPixel data
- ///
- private readonly Span pixelSpan;
-
- ///
- /// Temporal RGB block
- ///
- private readonly Span rgbSpan;
-
- ///
- /// Sampled pixel buffer size
- ///
- private readonly Size samplingAreaSize;
-
- ///
- /// for internal operations
- ///
- private readonly Configuration config;
-
- public YCbCrForwardConverter420(ImageFrame frame)
- {
- // matrices would be filled during convert calls
- this.YLeft = default;
- this.YRight = default;
- this.Cb = default;
- this.Cr = default;
-
- // temporal pixel buffers
- this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
- this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
-
- // frame data
- this.samplingAreaSize = new Size(frame.Width, frame.Height);
- this.config = frame.GetConfiguration();
-
- // conversion vector fallback data
- if (!RgbToYCbCrConverterVectorized.IsSupported)
- {
- this.colorTables = RgbToYCbCrConverterLut.Create();
- }
- else
- {
- this.colorTables = default;
- }
- }
-
- ///
- /// Gets size of sampling area from given frame pixel buffer.
- ///
- private static Size SampleSize => new(16, 8);
-
- public void Convert(int x, int y, ref RowOctet currentRows, int idx)
- {
- YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
-
- PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
-
- if (RgbToYCbCrConverterVectorized.IsSupported)
- {
- RgbToYCbCrConverterVectorized.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx);
- }
- else
- {
- this.colorTables.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx);
- }
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
deleted file mode 100644
index 5f7725bddb..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- ///
- /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
- ///
- /// The pixel type to work on
- internal ref struct YCbCrForwardConverter444
- where TPixel : unmanaged, IPixel
- {
- ///
- /// Number of pixels processed per single call
- ///
- private const int PixelsPerSample = 8 * 8;
-
- ///
- /// Total byte size of processed pixels converted from TPixel to
- ///
- private const int RgbSpanByteSize = PixelsPerSample * 3;
-
- ///
- /// The Y component
- ///
- public Block8x8F Y;
-
- ///
- /// The Cb component
- ///
- public Block8x8F Cb;
-
- ///
- /// The Cr component
- ///
- public Block8x8F Cr;
-
- ///
- /// The color conversion tables
- ///
- private RgbToYCbCrConverterLut colorTables;
-
- ///
- /// Temporal 64-byte span to hold unconverted TPixel data
- ///
- private readonly Span pixelSpan;
-
- ///
- /// Temporal 64-byte span to hold converted Rgb24 data
- ///
- private readonly Span rgbSpan;
-
- ///
- /// Sampled pixel buffer size
- ///
- private readonly Size samplingAreaSize;
-
- ///
- /// for internal operations
- ///
- private readonly Configuration config;
-
- public YCbCrForwardConverter444(ImageFrame frame)
- {
- // matrices would be filled during convert calls
- this.Y = default;
- this.Cb = default;
- this.Cr = default;
-
- // temporal pixel buffers
- this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
- this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
-
- // frame data
- this.samplingAreaSize = new Size(frame.Width, frame.Height);
- this.config = frame.GetConfiguration();
-
- // conversion vector fallback data
- if (!RgbToYCbCrConverterVectorized.IsSupported)
- {
- this.colorTables = RgbToYCbCrConverterLut.Create();
- }
- else
- {
- this.colorTables = default;
- }
- }
-
- ///
- /// Gets size of sampling area from given frame pixel buffer.
- ///
- private static Size SampleSize => new(8, 8);
-
- ///
- /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , )
- ///
- public void Convert(int x, int y, ref RowOctet currentRows)
- {
- YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
-
- PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
-
- ref Block8x8F yBlock = ref this.Y;
- ref Block8x8F cbBlock = ref this.Cb;
- ref Block8x8F crBlock = ref this.Cr;
-
- if (RgbToYCbCrConverterVectorized.IsSupported)
- {
- RgbToYCbCrConverterVectorized.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
- }
- else
- {
- this.colorTables.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
- }
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
deleted file mode 100644
index 6d3620c622..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
-{
- internal static class YCbCrForwardConverter
- where TPixel : unmanaged, IPixel
- {
- public static void LoadAndStretchEdges(RowOctet source, Span dest, Point start, Size sampleSize, Size totalSize)
- {
- DebugGuard.MustBeBetweenOrEqualTo(start.X, 0, totalSize.Width - 1, nameof(start.X));
- DebugGuard.MustBeBetweenOrEqualTo(start.Y, 0, totalSize.Height - 1, nameof(start.Y));
-
- int width = Math.Min(sampleSize.Width, totalSize.Width - start.X);
- int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y);
-
- uint byteWidth = (uint)(width * Unsafe.SizeOf());
- int remainderXCount = sampleSize.Width - width;
-
- ref byte blockStart = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(dest));
- int rowSizeInBytes = sampleSize.Width * Unsafe.SizeOf();
-
- for (int y = 0; y < height; y++)
- {
- Span row = source[y];
-
- ref byte s = ref Unsafe.As(ref row[start.X]);
- ref byte d = ref Unsafe.Add(ref blockStart, y * rowSizeInBytes);
-
- Unsafe.CopyBlock(ref d, ref s, byteWidth);
-
- ref TPixel last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1);
-
- for (int x = 1; x <= remainderXCount; x++)
- {
- Unsafe.Add(ref last, x) = last;
- }
- }
-
- int remainderYCount = sampleSize.Height - height;
-
- if (remainderYCount == 0)
- {
- return;
- }
-
- ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * rowSizeInBytes);
-
- for (int y = 1; y <= remainderYCount; y++)
- {
- ref byte remStart = ref Unsafe.Add(ref lastRowStart, rowSizeInBytes * y);
- Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)rowSizeInBytes);
- }
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index a8e4dc2a17..cbcaae2fc3 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
cancellationToken.ThrowIfCancellationRequested();
- var frame = new JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.EncodingColor));
+ var frame = new JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, this.frameConfig.ColorType);
this.scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream);
this.outputStream = stream;
@@ -142,27 +142,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteEndOfImageMarker();
stream.Flush();
-
- static JpegColorSpace GetTargetColorSpace(JpegEncodingColor colorType)
- {
- switch (colorType)
- {
- case JpegEncodingColor.YCbCrRatio444:
- case JpegEncodingColor.YCbCrRatio422:
- case JpegEncodingColor.YCbCrRatio420:
- case JpegEncodingColor.YCbCrRatio411:
- case JpegEncodingColor.YCbCrRatio410:
- return JpegColorSpace.YCbCr;
- case JpegEncodingColor.Rgb:
- return JpegColorSpace.RGB;
- case JpegEncodingColor.Cmyk:
- return JpegColorSpace.Cmyk;
- case JpegEncodingColor.Luminance:
- return JpegColorSpace.Grayscale;
- default:
- throw new NotImplementedException($"Unknown output color space: {colorType}");
- }
- }
}
///
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs
deleted file mode 100644
index 24a8195217..0000000000
--- a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-#if SUPPORTS_RUNTIME_INTRINSICS
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.X86;
-#endif
-using SixLabors.ImageSharp.ColorSpaces;
-using SixLabors.ImageSharp.Formats.Jpeg.Components;
-using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Tests.Colorspaces.Conversion;
-using Xunit;
-using Xunit.Abstractions;
-
-// ReSharper disable InconsistentNaming
-namespace SixLabors.ImageSharp.Tests.Formats.Jpg
-{
- public class RgbToYCbCrConverterTests
- {
- public RgbToYCbCrConverterTests(ITestOutputHelper output)
- {
- this.Output = output;
- }
-
- private ITestOutputHelper Output { get; }
-
- [Fact]
- public void TestConverterLut444()
- {
- int dataSize = 8 * 8;
- Rgb24[] data = CreateTestData(dataSize);
- var target = RgbToYCbCrConverterLut.Create();
-
- Block8x8F y = default;
- Block8x8F cb = default;
- Block8x8F cr = default;
-
- target.Convert444(data.AsSpan(), ref y, ref cb, ref cr);
-
- Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F));
- }
-
- [Fact]
- public void TestConverterVectorized444()
- {
- if (!RgbToYCbCrConverterVectorized.IsSupported)
- {
- this.Output.WriteLine("No AVX and/or FMA present, skipping test!");
- return;
- }
-
- int dataSize = 8 * 8;
- Rgb24[] data = CreateTestData(dataSize);
-
- Block8x8F y = default;
- Block8x8F cb = default;
- Block8x8F cr = default;
-
- RgbToYCbCrConverterVectorized.Convert444(data.AsSpan(), ref y, ref cb, ref cr);
-
- Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F));
- }
-
- [Fact]
- public void TestConverterLut420()
- {
- int dataSize = 16 * 16;
- Span data = CreateTestData(dataSize).AsSpan();
- var target = RgbToYCbCrConverterLut.Create();
-
- var yBlocks = new Block8x8F[4];
- var cb = default(Block8x8F);
- var cr = default(Block8x8F);
-
- target.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0);
- target.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1);
-
- Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F));
- }
-
- [Fact]
- public void TestConverterVectorized420()
- {
- if (!RgbToYCbCrConverterVectorized.IsSupported)
- {
- this.Output.WriteLine("No AVX and/or FMA present, skipping test!");
- return;
- }
-
- int dataSize = 16 * 16;
- Span data = CreateTestData(dataSize).AsSpan();
-
- var yBlocks = new Block8x8F[4];
- var cb = default(Block8x8F);
- var cr = default(Block8x8F);
-
- RgbToYCbCrConverterVectorized.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0);
- RgbToYCbCrConverterVectorized.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1);
-
- Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F));
- }
-
-#if SUPPORTS_RUNTIME_INTRINSICS
- [Theory]
- [InlineData(1)]
- [InlineData(2)]
- [InlineData(3)]
- public void Scale16x2_8x1(int seed)
- {
- if (!Avx2.IsSupported)
- {
- return;
- }
-
- Span data = new Random(seed).GenerateRandomFloatArray(Vector256.Count * 4, -1000, 1000);
-
- // Act:
- Vector256 resultVector = RgbToYCbCrConverterVectorized.Scale16x2_8x1(MemoryMarshal.Cast>(data));
- ref float result = ref Unsafe.As, float>(ref resultVector);
-
- // Assert:
- // Comparison epsilon is tricky but 10^(-4) is good enough (?)
- var comparer = new ApproximateFloatComparer(0.0001f);
- for (int i = 0; i < Vector256.Count; i++)
- {
- float actual = Unsafe.Add(ref result, i);
- float expected = CalculateAverage16x2_8x1(data, i);
-
- Assert.True(comparer.Equals(actual, expected), $"Pos {i}, Expected: {expected}, Actual: {actual}");
- }
-
- static float CalculateAverage16x2_8x1(Span data, int index)
- {
- int upIdx = index * 2;
- int lowIdx = (index + 8) * 2;
- return 0.25f * (data[upIdx] + data[upIdx + 1] + data[lowIdx] + data[lowIdx + 1]);
- }
- }
-#endif
-
- private static void Verify444(
- ReadOnlySpan data,
- ref Block8x8F yResult,
- ref Block8x8F cbResult,
- ref Block8x8F crResult,
- ApproximateColorSpaceComparer comparer)
- {
- Block8x8F y = default;
- Block8x8F cb = default;
- Block8x8F cr = default;
-
- RgbToYCbCr(data, ref y, ref cb, ref cr);
-
- for (int i = 0; i < Block8x8F.Size; i++)
- {
- Assert.True(comparer.Equals(new YCbCr(y[i], cb[i], cr[i]), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y[i]} == {yResult[i]}, {cb[i]} == {cbResult[i]}, {cr[i]} == {crResult[i]}");
- }
- }
-
- private static void Verify420(
- ReadOnlySpan data,
- Block8x8F[] yResult,
- ref Block8x8F cbResult,
- ref Block8x8F crResult,
- ApproximateFloatComparer comparer)
- {
- var trueBlock = default(Block8x8F);
- var cbTrue = new Block8x8F[4];
- var crTrue = new Block8x8F[4];
-
- Span tempData = new Rgb24[8 * 8].AsSpan();
-
- // top left
- Copy8x8(data, tempData);
- RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[0], ref crTrue[0]);
- VerifyBlock(ref yResult[0], ref trueBlock, comparer);
-
- // top right
- Copy8x8(data.Slice(8), tempData);
- RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[1], ref crTrue[1]);
- VerifyBlock(ref yResult[1], ref trueBlock, comparer);
-
- // bottom left
- Copy8x8(data.Slice(8 * 16), tempData);
- RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[2], ref crTrue[2]);
- VerifyBlock(ref yResult[2], ref trueBlock, comparer);
-
- // bottom right
- Copy8x8(data.Slice((8 * 16) + 8), tempData);
- RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[3], ref crTrue[3]);
- VerifyBlock(ref yResult[3], ref trueBlock, comparer);
-
- // verify Cb
- Scale16X16To8X8(ref trueBlock, cbTrue);
- VerifyBlock(ref cbResult, ref trueBlock, comparer);
-
- // verify Cr
- Scale16X16To8X8(ref trueBlock, crTrue);
- VerifyBlock(ref crResult, ref trueBlock, comparer);
-
- // extracts 8x8 blocks from 16x8 memory region
- static void Copy8x8(ReadOnlySpan source, Span dest)
- {
- for (int i = 0; i < 8; i++)
- {
- source.Slice(i * 16, 8).CopyTo(dest.Slice(i * 8));
- }
- }
-
- // scales 16x16 to 8x8, used in chroma subsampling tests
- static void Scale16X16To8X8(ref Block8x8F dest, ReadOnlySpan source)
- {
- for (int i = 0; i < 4; i++)
- {
- int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
- Block8x8F iSource = source[i];
-
- for (int y = 0; y < 4; y++)
- {
- for (int x = 0; x < 4; x++)
- {
- int j = (16 * y) + (2 * x);
- float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9];
- dest[(8 * y) + x + dstOff] = (sum + 2) * .25F;
- }
- }
- }
- }
- }
-
- private static void RgbToYCbCr(ReadOnlySpan data, ref Block8x8F y, ref Block8x8F cb, ref Block8x8F cr)
- {
- for (int i = 0; i < data.Length; i++)
- {
- int r = data[i].R;
- int g = data[i].G;
- int b = data[i].B;
-
- y[i] = (0.299F * r) + (0.587F * g) + (0.114F * b);
- cb[i] = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
- cr[i] = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
- }
- }
-
- private static void VerifyBlock(ref Block8x8F res, ref Block8x8F target, ApproximateFloatComparer comparer)
- {
- for (int i = 0; i < Block8x8F.Size; i++)
- {
- Assert.True(comparer.Equals(res[i], target[i]), $"Pos {i}, Expected: {target[i]}, Got: {res[i]}");
- }
- }
-
- private static Rgb24[] CreateTestData(int size)
- {
- var data = new Rgb24[size];
- var r = new Random();
-
- var random = new byte[3];
- for (int i = 0; i < data.Length; i++)
- {
- r.NextBytes(random);
- data[i] = new Rgb24(random[0], random[1], random[2]);
- }
-
- return data;
- }
- }
-}