mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
22 changed files with 2027 additions and 1079 deletions
@ -1 +1 @@ |
|||
Subproject commit 48e73f455f15eafefbe3175efc7433e5f277e506 |
|||
Subproject commit 1f7ee702812f3a1713ab7f749c0faae0ef139ed7 |
|||
@ -0,0 +1,392 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder |
|||
{ |
|||
internal class HuffmanScanEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Number of bytes cached before being written to target stream via Stream.Write(byte[], offest, count).
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is subject to change, 1024 seems to be the best value in terms of performance.
|
|||
/// <see cref="Emit(int, int)"/> expects it to be at least 8 (see comments in method body).
|
|||
/// </remarks>
|
|||
private const int EmitBufferSizeInBytes = 1024; |
|||
|
|||
/// <summary>
|
|||
/// A buffer for reducing the number of stream writes when emitting Huffman tables.
|
|||
/// </summary>
|
|||
private readonly byte[] emitBuffer = new byte[EmitBufferSizeInBytes]; |
|||
|
|||
/// <summary>
|
|||
/// Number of filled bytes in <see cref="emitBuffer"/> buffer
|
|||
/// </summary>
|
|||
private int emitLen = 0; |
|||
|
|||
/// <summary>
|
|||
/// Emmited bits 'micro buffer' before being transfered to the <see cref="emitBuffer"/>.
|
|||
/// </summary>
|
|||
private int accumulatedBits; |
|||
|
|||
/// <summary>
|
|||
/// Number of jagged bits stored in <see cref="accumulatedBits"/>
|
|||
/// </summary>
|
|||
private int bitCount; |
|||
|
|||
private Block8x8F temporalBlock1; |
|||
private Block8x8F temporalBlock2; |
|||
|
|||
/// <summary>
|
|||
/// The output stream. All attempted writes after the first error become no-ops.
|
|||
/// </summary>
|
|||
private readonly Stream target; |
|||
|
|||
public HuffmanScanEncoder(Stream outputStream) |
|||
{ |
|||
this.target = outputStream; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image with no subsampling.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
|
|||
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
|
|||
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
|
|||
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
|
|||
public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var unzig = ZigZag.CreateUnzigTable(); |
|||
|
|||
// ReSharper disable once InconsistentNaming
|
|||
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|||
|
|||
ImageFrame<TPixel> frame = pixels.Frames.RootFrame; |
|||
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer; |
|||
RowOctet<TPixel> currentRows = default; |
|||
|
|||
var pixelConverter = new YCbCrForwardConverter444<TPixel>(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, |
|||
ref unzig); |
|||
|
|||
prevDCCb = this.WriteBlock( |
|||
QuantIndex.Chrominance, |
|||
prevDCCb, |
|||
ref pixelConverter.Cb, |
|||
ref chrominanceQuantTable, |
|||
ref unzig); |
|||
|
|||
prevDCCr = this.WriteBlock( |
|||
QuantIndex.Chrominance, |
|||
prevDCCr, |
|||
ref pixelConverter.Cr, |
|||
ref chrominanceQuantTable, |
|||
ref unzig); |
|||
} |
|||
} |
|||
|
|||
this.FlushInternalBuffer(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image with subsampling. The Cb and Cr components are each subsampled
|
|||
/// at a factor of 2 both horizontally and vertically.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
|
|||
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
|
|||
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
|
|||
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
|
|||
public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var unzig = ZigZag.CreateUnzigTable(); |
|||
|
|||
// ReSharper disable once InconsistentNaming
|
|||
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|||
ImageFrame<TPixel> frame = pixels.Frames.RootFrame; |
|||
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer; |
|||
RowOctet<TPixel> currentRows = default; |
|||
|
|||
var pixelConverter = new YCbCrForwardConverter420<TPixel>(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, |
|||
ref unzig); |
|||
|
|||
prevDCY = this.WriteBlock( |
|||
QuantIndex.Luminance, |
|||
prevDCY, |
|||
ref pixelConverter.YRight, |
|||
ref luminanceQuantTable, |
|||
ref unzig); |
|||
} |
|||
|
|||
prevDCCb = this.WriteBlock( |
|||
QuantIndex.Chrominance, |
|||
prevDCCb, |
|||
ref pixelConverter.Cb, |
|||
ref chrominanceQuantTable, |
|||
ref unzig); |
|||
|
|||
prevDCCr = this.WriteBlock( |
|||
QuantIndex.Chrominance, |
|||
prevDCCr, |
|||
ref pixelConverter.Cr, |
|||
ref chrominanceQuantTable, |
|||
ref unzig); |
|||
} |
|||
} |
|||
|
|||
this.FlushInternalBuffer(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image with no chroma, just luminance.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
|
|||
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
|
|||
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
|
|||
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
var unzig = ZigZag.CreateUnzigTable(); |
|||
|
|||
// ReSharper disable once InconsistentNaming
|
|||
int prevDCY = 0; |
|||
|
|||
var pixelConverter = LuminanceForwardConverter<TPixel>.Create(); |
|||
ImageFrame<TPixel> frame = pixels.Frames.RootFrame; |
|||
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer; |
|||
RowOctet<TPixel> currentRows = default; |
|||
|
|||
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(frame, x, y, ref currentRows); |
|||
|
|||
prevDCY = this.WriteBlock( |
|||
QuantIndex.Luminance, |
|||
prevDCY, |
|||
ref pixelConverter.Y, |
|||
ref luminanceQuantTable, |
|||
ref unzig); |
|||
} |
|||
} |
|||
|
|||
this.FlushInternalBuffer(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <param name="index">The quantization table index.</param>
|
|||
/// <param name="prevDC">The previous DC value.</param>
|
|||
/// <param name="src">Source block</param>
|
|||
/// <param name="quant">Quantization table</param>
|
|||
/// <param name="unZig">The 8x8 Unzig block.</param>
|
|||
/// <returns>The <see cref="int"/>.</returns>
|
|||
private int WriteBlock( |
|||
QuantIndex index, |
|||
int prevDC, |
|||
ref Block8x8F src, |
|||
ref Block8x8F quant, |
|||
ref ZigZag unZig) |
|||
{ |
|||
ref Block8x8F refTemp1 = ref this.temporalBlock1; |
|||
ref Block8x8F refTemp2 = ref this.temporalBlock2; |
|||
|
|||
FastFloatingPointDCT.TransformFDCT(ref src, ref refTemp1, ref refTemp2); |
|||
|
|||
Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig); |
|||
|
|||
int dc = (int)refTemp2[0]; |
|||
|
|||
// Emit the DC delta.
|
|||
this.EmitHuffRLE((2 * (int)index) + 0, 0, dc - prevDC); |
|||
|
|||
// Emit the AC components.
|
|||
int h = (2 * (int)index) + 1; |
|||
int runLength = 0; |
|||
|
|||
for (int zig = 1; zig < Block8x8F.Size; zig++) |
|||
{ |
|||
int ac = (int)refTemp2[zig]; |
|||
|
|||
if (ac == 0) |
|||
{ |
|||
runLength++; |
|||
} |
|||
else |
|||
{ |
|||
while (runLength > 15) |
|||
{ |
|||
this.EmitHuff(h, 0xf0); |
|||
runLength -= 16; |
|||
} |
|||
|
|||
this.EmitHuffRLE(h, runLength, ac); |
|||
runLength = 0; |
|||
} |
|||
} |
|||
|
|||
if (runLength > 0) |
|||
{ |
|||
this.EmitHuff(h, 0x00); |
|||
} |
|||
|
|||
return dc; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Emits the least significant count of bits to the stream write buffer.
|
|||
/// The precondition is bits
|
|||
/// <example>
|
|||
/// < 1<<nBits && nBits <= 16
|
|||
/// </example>
|
|||
/// .
|
|||
/// </summary>
|
|||
/// <param name="bits">The packed bits.</param>
|
|||
/// <param name="count">The number of bits</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private void Emit(int bits, int count) |
|||
{ |
|||
count += this.bitCount; |
|||
bits <<= 32 - count; |
|||
bits |= this.accumulatedBits; |
|||
|
|||
// Only write if more than 8 bits.
|
|||
if (count >= 8) |
|||
{ |
|||
// Track length
|
|||
while (count >= 8) |
|||
{ |
|||
byte b = (byte)(bits >> 24); |
|||
this.emitBuffer[this.emitLen++] = b; |
|||
if (b == byte.MaxValue) |
|||
{ |
|||
this.emitBuffer[this.emitLen++] = byte.MinValue; |
|||
} |
|||
|
|||
bits <<= 8; |
|||
count -= 8; |
|||
} |
|||
|
|||
// This can emit 4 times of:
|
|||
// 1 byte guaranteed
|
|||
// 1 extra byte.MinValue byte if previous one was byte.MaxValue
|
|||
// Thus writing (1 + 1) * 4 = 8 bytes max
|
|||
// So we must check if emit buffer has extra 8 bytes, if not - call stream.Write
|
|||
if (this.emitLen > EmitBufferSizeInBytes - 8) |
|||
{ |
|||
this.target.Write(this.emitBuffer, 0, this.emitLen); |
|||
this.emitLen = 0; |
|||
} |
|||
} |
|||
|
|||
this.accumulatedBits = bits; |
|||
this.bitCount = count; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Emits the given value with the given Huffman encoder.
|
|||
/// </summary>
|
|||
/// <param name="index">The index of the Huffman encoder</param>
|
|||
/// <param name="value">The value to encode.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private void EmitHuff(int index, int value) |
|||
{ |
|||
int x = HuffmanLut.TheHuffmanLut[index].Values[value]; |
|||
this.Emit(x & ((1 << 24) - 1), x >> 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Emits a run of runLength copies of value encoded with the given Huffman encoder.
|
|||
/// </summary>
|
|||
/// <param name="index">The index of the Huffman encoder</param>
|
|||
/// <param name="runLength">The number of copies to encode.</param>
|
|||
/// <param name="value">The value to encode.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private void EmitHuffRLE(int index, int runLength, int value) |
|||
{ |
|||
int a = value; |
|||
int b = value; |
|||
if (a < 0) |
|||
{ |
|||
a = -value; |
|||
b = value - 1; |
|||
} |
|||
|
|||
int bt = Numerics.MinimumBitsToStore16((uint)a); |
|||
|
|||
this.EmitHuff(index, (runLength << 4) | bt); |
|||
if (bt > 0) |
|||
{ |
|||
this.Emit(b & ((1 << bt) - 1), bt); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes remaining bytes from internal buffer to the target stream.
|
|||
/// </summary>
|
|||
/// <remarks>Pads last byte with 1's if necessary</remarks>
|
|||
private void FlushInternalBuffer() |
|||
{ |
|||
// pad last byte with 1's
|
|||
int padBitsCount = 8 - (this.bitCount % 8); |
|||
if (padBitsCount != 0) |
|||
{ |
|||
this.Emit((1 << padBitsCount) - 1, padBitsCount); |
|||
} |
|||
|
|||
// flush remaining bytes
|
|||
if (this.emitLen != 0) |
|||
{ |
|||
this.target.Write(this.emitBuffer, 0, this.emitLen); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
// 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 |
|||
{ |
|||
/// <summary>
|
|||
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
|
|||
internal ref struct YCbCrForwardConverter420<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel}, int)"/> call
|
|||
/// </summary>
|
|||
private const int PixelsPerSample = 16 * 8; |
|||
|
|||
/// <summary>
|
|||
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
|
|||
/// </summary>
|
|||
private const int RgbSpanByteSize = PixelsPerSample * 3; |
|||
|
|||
/// <summary>
|
|||
/// <see cref="Size"/> of sampling area from given frame pixel buffer
|
|||
/// </summary>
|
|||
private static readonly Size SampleSize = new Size(16, 8); |
|||
|
|||
/// <summary>
|
|||
/// The left Y component
|
|||
/// </summary>
|
|||
public Block8x8F YLeft; |
|||
|
|||
/// <summary>
|
|||
/// The left Y component
|
|||
/// </summary>
|
|||
public Block8x8F YRight; |
|||
|
|||
/// <summary>
|
|||
/// The Cb component
|
|||
/// </summary>
|
|||
public Block8x8F Cb; |
|||
|
|||
/// <summary>
|
|||
/// The Cr component
|
|||
/// </summary>
|
|||
public Block8x8F Cr; |
|||
|
|||
/// <summary>
|
|||
/// The color conversion tables
|
|||
/// </summary>
|
|||
private RgbToYCbCrConverterLut colorTables; |
|||
|
|||
/// <summary>
|
|||
/// Temporal 16x8 block to hold TPixel data
|
|||
/// </summary>
|
|||
private Span<TPixel> pixelSpan; |
|||
|
|||
/// <summary>
|
|||
/// Temporal RGB block
|
|||
/// </summary>
|
|||
private Span<Rgb24> rgbSpan; |
|||
|
|||
/// <summary>
|
|||
/// Sampled pixel buffer size
|
|||
/// </summary>
|
|||
private Size samplingAreaSize; |
|||
|
|||
/// <summary>
|
|||
/// <see cref="Configuration"/> for internal operations
|
|||
/// </summary>
|
|||
private Configuration config; |
|||
|
|||
public YCbCrForwardConverter420(ImageFrame<TPixel> 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<byte, Rgb24>(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; |
|||
} |
|||
} |
|||
|
|||
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows, int idx) |
|||
{ |
|||
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); |
|||
|
|||
PixelOperations<TPixel>.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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
// 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 |
|||
{ |
|||
/// <summary>
|
|||
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
|
|||
internal ref struct YCbCrForwardConverter444<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
|
|||
/// </summary>
|
|||
private const int PixelsPerSample = 8 * 8; |
|||
|
|||
/// <summary>
|
|||
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
|
|||
/// </summary>
|
|||
private const int RgbSpanByteSize = PixelsPerSample * 3; |
|||
|
|||
/// <summary>
|
|||
/// <see cref="Size"/> of sampling area from given frame pixel buffer
|
|||
/// </summary>
|
|||
private static readonly Size SampleSize = new Size(8, 8); |
|||
|
|||
/// <summary>
|
|||
/// The Y component
|
|||
/// </summary>
|
|||
public Block8x8F Y; |
|||
|
|||
/// <summary>
|
|||
/// The Cb component
|
|||
/// </summary>
|
|||
public Block8x8F Cb; |
|||
|
|||
/// <summary>
|
|||
/// The Cr component
|
|||
/// </summary>
|
|||
public Block8x8F Cr; |
|||
|
|||
/// <summary>
|
|||
/// The color conversion tables
|
|||
/// </summary>
|
|||
private RgbToYCbCrConverterLut colorTables; |
|||
|
|||
/// <summary>
|
|||
/// Temporal 64-byte span to hold unconverted TPixel data
|
|||
/// </summary>
|
|||
private Span<TPixel> pixelSpan; |
|||
|
|||
/// <summary>
|
|||
/// Temporal 64-byte span to hold converted Rgb24 data
|
|||
/// </summary>
|
|||
private Span<Rgb24> rgbSpan; |
|||
|
|||
/// <summary>
|
|||
/// Sampled pixel buffer size
|
|||
/// </summary>
|
|||
private Size samplingAreaSize; |
|||
|
|||
/// <summary>
|
|||
/// <see cref="Configuration"/> for internal operations
|
|||
/// </summary>
|
|||
private Configuration config; |
|||
|
|||
public YCbCrForwardConverter444(ImageFrame<TPixel> 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<byte, Rgb24>(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; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
|
|||
/// </summary>
|
|||
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows) |
|||
{ |
|||
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); |
|||
|
|||
PixelOperations<TPixel>.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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components |
|||
{ |
|||
[Config(typeof(Config.HwIntrinsics_SSE_AVX))] |
|||
public class Block8x8F_Scale16X16To8X8 |
|||
{ |
|||
private Block8x8F source; |
|||
private readonly Block8x8F[] target = new Block8x8F[4]; |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
var random = new Random(); |
|||
|
|||
float[] f = new float[8 * 8]; |
|||
for (int i = 0; i < f.Length; i++) |
|||
{ |
|||
f[i] = (float)random.NextDouble(); |
|||
} |
|||
|
|||
for (int i = 0; i < 4; i++) |
|||
{ |
|||
this.target[i] = Block8x8F.Load(f); |
|||
} |
|||
|
|||
this.source = Block8x8F.Load(f); |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void Scale16X16To8X8() => Block8x8F.Scale16X16To8X8(ref this.source, this.target); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue