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; - } - } -}