|
|
|
@ -92,67 +92,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
this.colorType = options.ColorType; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the counts the number of bits needed to hold an integer.
|
|
|
|
/// </summary>
|
|
|
|
// The C# compiler emits this as a compile-time constant embedded in the PE file.
|
|
|
|
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
|
|
|
|
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
|
|
|
|
private static ReadOnlySpan<byte> BitCountLut => new byte[] |
|
|
|
{ |
|
|
|
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, |
|
|
|
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, |
|
|
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, |
|
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
|
|
|
7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, |
|
|
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, |
|
|
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, |
|
|
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, |
|
|
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, |
|
|
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, |
|
|
|
8, 8, 8, |
|
|
|
}; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the unscaled quantization tables in zig-zag order. Each
|
|
|
|
/// encoder copies and scales the tables according to its quality parameter.
|
|
|
|
/// The values are derived from section K.1 after converting from natural to
|
|
|
|
/// zig-zag order.
|
|
|
|
/// </summary>
|
|
|
|
// The C# compiler emits this as a compile-time constant embedded in the PE file.
|
|
|
|
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
|
|
|
|
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
|
|
|
|
private static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[] |
|
|
|
{ |
|
|
|
// Luminance.
|
|
|
|
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, |
|
|
|
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, |
|
|
|
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, |
|
|
|
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, |
|
|
|
100, 120, 92, 101, 103, 99, |
|
|
|
}; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the unscaled quantization tables in zig-zag order. Each
|
|
|
|
/// encoder copies and scales the tables according to its quality parameter.
|
|
|
|
/// The values are derived from section K.1 after converting from natural to
|
|
|
|
/// zig-zag order.
|
|
|
|
/// </summary>
|
|
|
|
// The C# compiler emits this as a compile-time constant embedded in the PE file.
|
|
|
|
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
|
|
|
|
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
|
|
|
|
private static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[] |
|
|
|
{ |
|
|
|
// Chrominance.
|
|
|
|
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, |
|
|
|
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, |
|
|
|
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, |
|
|
|
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, |
|
|
|
99, 99, 99, 99, 99, 99, 99, 99, |
|
|
|
}; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Encode writes the image to the jpeg baseline format with the given options.
|
|
|
|
/// </summary>
|
|
|
|
@ -228,248 +167,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes quantization table.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="i">The quantization index.</param>
|
|
|
|
/// <param name="scale">The scaling factor.</param>
|
|
|
|
/// <param name="quant">The quantization table.</param>
|
|
|
|
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) |
|
|
|
{ |
|
|
|
DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i)); |
|
|
|
ReadOnlySpan<byte> unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance; |
|
|
|
|
|
|
|
for (int j = 0; j < Block8x8F.Size; j++) |
|
|
|
{ |
|
|
|
int x = unscaledQuant[j]; |
|
|
|
x = ((x * scale) + 50) / 100; |
|
|
|
if (x < 1) |
|
|
|
{ |
|
|
|
x = 1; |
|
|
|
} |
|
|
|
|
|
|
|
if (x > 255) |
|
|
|
{ |
|
|
|
x = 255; |
|
|
|
} |
|
|
|
|
|
|
|
quant[j] = x; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Emits the least significant count of bits of bits to the bit-stream.
|
|
|
|
/// 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>
|
|
|
|
/// <param name="emitBufferBase">The reference to the emitBuffer.</param>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
private void Emit(uint bits, uint count, ref byte emitBufferBase) |
|
|
|
{ |
|
|
|
count += this.bitCount; |
|
|
|
bits <<= (int)(32 - count); |
|
|
|
bits |= this.accumulatedBits; |
|
|
|
|
|
|
|
// Only write if more than 8 bits.
|
|
|
|
if (count >= 8) |
|
|
|
{ |
|
|
|
// Track length
|
|
|
|
int len = 0; |
|
|
|
while (count >= 8) |
|
|
|
{ |
|
|
|
byte b = (byte)(bits >> 24); |
|
|
|
Unsafe.Add(ref emitBufferBase, len++) = b; |
|
|
|
if (b == byte.MaxValue) |
|
|
|
{ |
|
|
|
Unsafe.Add(ref emitBufferBase, len++) = byte.MinValue; |
|
|
|
} |
|
|
|
|
|
|
|
bits <<= 8; |
|
|
|
count -= 8; |
|
|
|
} |
|
|
|
|
|
|
|
if (len > 0) |
|
|
|
{ |
|
|
|
this.outputStream.Write(this.emitBuffer, 0, len); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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>
|
|
|
|
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
private void EmitHuff(HuffIndex index, int value, ref byte emitBufferBase) |
|
|
|
{ |
|
|
|
uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value]; |
|
|
|
this.Emit(x & ((1 << 24) - 1), x >> 24, ref emitBufferBase); |
|
|
|
} |
|
|
|
|
|
|
|
/// <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>
|
|
|
|
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
private void EmitHuffRLE(HuffIndex index, int runLength, int value, ref byte emitBufferBase) |
|
|
|
{ |
|
|
|
int a = value; |
|
|
|
int b = value; |
|
|
|
if (a < 0) |
|
|
|
{ |
|
|
|
a = -value; |
|
|
|
b = value - 1; |
|
|
|
} |
|
|
|
|
|
|
|
uint bt; |
|
|
|
if (a < 0x100) |
|
|
|
{ |
|
|
|
bt = BitCountLut[a]; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
bt = 8 + (uint)BitCountLut[a >> 8]; |
|
|
|
} |
|
|
|
|
|
|
|
this.EmitHuff(index, (int)((uint)(runLength << 4) | bt), ref emitBufferBase); |
|
|
|
if (bt > 0) |
|
|
|
{ |
|
|
|
this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt, ref emitBufferBase); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <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="cancellationToken">The token to monitor for cancellation.</param>
|
|
|
|
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
|
|
|
|
private void Encode444<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
|
|
|
|
// (Partially done with YCbCrForwardConverter<TPixel>)
|
|
|
|
Block8x8F temp1 = default; |
|
|
|
Block8x8F temp2 = default; |
|
|
|
|
|
|
|
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; |
|
|
|
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; |
|
|
|
|
|
|
|
var unzig = ZigZag.CreateUnzigTable(); |
|
|
|
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
|
|
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|
|
|
|
|
|
|
var pixelConverter = YCbCrForwardConverter<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 temp1, |
|
|
|
ref temp2, |
|
|
|
ref onStackLuminanceQuantTable, |
|
|
|
ref unzig, |
|
|
|
ref emitBufferBase); |
|
|
|
|
|
|
|
prevDCCb = this.WriteBlock( |
|
|
|
QuantIndex.Chrominance, |
|
|
|
prevDCCb, |
|
|
|
ref pixelConverter.Cb, |
|
|
|
ref temp1, |
|
|
|
ref temp2, |
|
|
|
ref onStackChrominanceQuantTable, |
|
|
|
ref unzig, |
|
|
|
ref emitBufferBase); |
|
|
|
|
|
|
|
prevDCCr = this.WriteBlock( |
|
|
|
QuantIndex.Chrominance, |
|
|
|
prevDCCr, |
|
|
|
ref pixelConverter.Cr, |
|
|
|
ref temp1, |
|
|
|
ref temp2, |
|
|
|
ref onStackChrominanceQuantTable, |
|
|
|
ref unzig, |
|
|
|
ref emitBufferBase); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <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="cancellationToken">The token to monitor for cancellation.</param>
|
|
|
|
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
|
|
|
|
private void EncodeGrayscale<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
|
|
|
|
// (Partially done with YCbCrForwardConverter<TPixel>)
|
|
|
|
Block8x8F temp1 = default; |
|
|
|
Block8x8F temp2 = default; |
|
|
|
|
|
|
|
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; |
|
|
|
|
|
|
|
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 temp1, |
|
|
|
ref temp2, |
|
|
|
ref onStackLuminanceQuantTable, |
|
|
|
ref unzig, |
|
|
|
ref emitBufferBase); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Writes the application header containing the JFIF identifier plus extra data.
|
|
|
|
/// </summary>
|
|
|
|
@ -519,72 +216,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
this.outputStream.Write(this.buffer, 0, 20); |
|
|
|
} |
|
|
|
|
|
|
|
/// <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="tempDest1">Temporal block to be used as FDCT Destination</param>
|
|
|
|
/// <param name="tempDest2">Temporal block 2</param>
|
|
|
|
/// <param name="quant">Quantization table</param>
|
|
|
|
/// <param name="unZig">The 8x8 Unzig block.</param>
|
|
|
|
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
|
|
|
|
/// <returns>The <see cref="int"/>.</returns>
|
|
|
|
private int WriteBlock( |
|
|
|
QuantIndex index, |
|
|
|
int prevDC, |
|
|
|
ref Block8x8F src, |
|
|
|
ref Block8x8F tempDest1, |
|
|
|
ref Block8x8F tempDest2, |
|
|
|
ref Block8x8F quant, |
|
|
|
ref ZigZag unZig, |
|
|
|
ref byte emitBufferBase) |
|
|
|
{ |
|
|
|
FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2); |
|
|
|
|
|
|
|
Block8x8F.Quantize(ref tempDest1, ref tempDest2, ref quant, ref unZig); |
|
|
|
|
|
|
|
int dc = (int)tempDest2[0]; |
|
|
|
|
|
|
|
// Emit the DC delta.
|
|
|
|
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC, ref emitBufferBase); |
|
|
|
|
|
|
|
// Emit the AC components.
|
|
|
|
var h = (HuffIndex)((2 * (int)index) + 1); |
|
|
|
int runLength = 0; |
|
|
|
|
|
|
|
for (int zig = 1; zig < Block8x8F.Size; zig++) |
|
|
|
{ |
|
|
|
int ac = (int)tempDest2[zig]; |
|
|
|
|
|
|
|
if (ac == 0) |
|
|
|
{ |
|
|
|
runLength++; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
while (runLength > 15) |
|
|
|
{ |
|
|
|
this.EmitHuff(h, 0xf0, ref emitBufferBase); |
|
|
|
runLength -= 16; |
|
|
|
} |
|
|
|
|
|
|
|
this.EmitHuffRLE(h, runLength, ac, ref emitBufferBase); |
|
|
|
runLength = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (runLength > 0) |
|
|
|
{ |
|
|
|
this.EmitHuff(h, 0x00, ref emitBufferBase); |
|
|
|
} |
|
|
|
|
|
|
|
return dc; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Writes the Define Huffman Table marker and tables.
|
|
|
|
/// </summary>
|
|
|
|
@ -1017,110 +648,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
|
|
|
|
|
|
|
|
scanEncoder.WriteStartOfScan(image, this.colorType, this.subsample, cancellationToken); |
|
|
|
//ref byte emitBufferBase = ref MemoryMarshal.GetReference<byte>(this.emitBuffer);
|
|
|
|
//if (this.colorType == JpegColorType.Luminance)
|
|
|
|
//{
|
|
|
|
// scanEncoder.EncodeGrayscale(image, cancellationToken);
|
|
|
|
//}
|
|
|
|
//else
|
|
|
|
//{
|
|
|
|
// switch (this.subsample)
|
|
|
|
// {
|
|
|
|
// case JpegSubsample.Ratio444:
|
|
|
|
// scanEncoder.Encode444(image, cancellationToken);
|
|
|
|
// break;
|
|
|
|
// case JpegSubsample.Ratio420:
|
|
|
|
// scanEncoder.Encode420(image, cancellationToken);
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
|
|
|
//// Pad the last byte with 1's.
|
|
|
|
//this.Emit(0x7f, 7, ref emitBufferBase);
|
|
|
|
} |
|
|
|
|
|
|
|
/// <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="cancellationToken">The token to monitor for cancellation.</param>
|
|
|
|
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
|
|
|
|
private void Encode420<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
|
|
|
|
Block8x8F b = default; |
|
|
|
Span<Block8x8F> cb = stackalloc Block8x8F[4]; |
|
|
|
Span<Block8x8F> cr = stackalloc Block8x8F[4]; |
|
|
|
|
|
|
|
Block8x8F temp1 = default; |
|
|
|
Block8x8F temp2 = default; |
|
|
|
|
|
|
|
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; |
|
|
|
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; |
|
|
|
|
|
|
|
var unzig = ZigZag.CreateUnzigTable(); |
|
|
|
|
|
|
|
var pixelConverter = YCbCrForwardConverter<TPixel>.Create(); |
|
|
|
|
|
|
|
// 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; |
|
|
|
|
|
|
|
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 < 4; i++) |
|
|
|
{ |
|
|
|
int xOff = (i & 1) * 8; |
|
|
|
int yOff = (i & 2) * 4; |
|
|
|
|
|
|
|
currentRows.Update(pixelBuffer, y + yOff); |
|
|
|
pixelConverter.Convert(frame, x + xOff, y + yOff, ref currentRows); |
|
|
|
|
|
|
|
cb[i] = pixelConverter.Cb; |
|
|
|
cr[i] = pixelConverter.Cr; |
|
|
|
|
|
|
|
prevDCY = this.WriteBlock( |
|
|
|
QuantIndex.Luminance, |
|
|
|
prevDCY, |
|
|
|
ref pixelConverter.Y, |
|
|
|
ref temp1, |
|
|
|
ref temp2, |
|
|
|
ref onStackLuminanceQuantTable, |
|
|
|
ref unzig, |
|
|
|
ref emitBufferBase); |
|
|
|
} |
|
|
|
|
|
|
|
Block8x8F.Scale16X16To8X8(ref b, cb); |
|
|
|
prevDCCb = this.WriteBlock( |
|
|
|
QuantIndex.Chrominance, |
|
|
|
prevDCCb, |
|
|
|
ref b, |
|
|
|
ref temp1, |
|
|
|
ref temp2, |
|
|
|
ref onStackChrominanceQuantTable, |
|
|
|
ref unzig, |
|
|
|
ref emitBufferBase); |
|
|
|
|
|
|
|
Block8x8F.Scale16X16To8X8(ref b, cr); |
|
|
|
prevDCCr = this.WriteBlock( |
|
|
|
QuantIndex.Chrominance, |
|
|
|
prevDCCr, |
|
|
|
ref b, |
|
|
|
ref temp1, |
|
|
|
ref temp2, |
|
|
|
ref onStackChrominanceQuantTable, |
|
|
|
ref unzig, |
|
|
|
ref emitBufferBase); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|