diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrEncoder{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrEncoder{TPixel}.cs
index 051acf0e84..db2a3c354f 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrEncoder{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrEncoder{TPixel}.cs
@@ -41,16 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
private uint bitCount;
- ///
- /// The scaled chrominance table, in zig-zag order.
- ///
- private Block8x8F chrominanceQuantTable;
-
- ///
- /// The scaled luminance table, in zig-zag order.
- ///
- private Block8x8F luminanceQuantTable;
-
private Block8x8F temporalBlock1;
private Block8x8F temporalBlock2;
@@ -82,71 +72,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
8, 8, 8,
};
- ///
- /// 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.
- ///
- // The C# compiler emits this as a compile-time constant embedded in the PE file.
- // This is effectively compiled down to: return new ReadOnlySpan(&data, length)
- // More details can be found: https://github.com/dotnet/roslyn/pull/24621
- private static ReadOnlySpan 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,
- };
-
- ///
- /// 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.
- ///
- // The C# compiler emits this as a compile-time constant embedded in the PE file.
- // This is effectively compiled down to: return new ReadOnlySpan(&data, length)
- // More details can be found: https://github.com/dotnet/roslyn/pull/24621
- private static ReadOnlySpan 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,
- };
-
-
- public ref Block8x8F ChrominanceQuantizationTable => ref this.chrominanceQuantTable;
-
- public ref Block8x8F LuminanceQuantizationTable => ref this.luminanceQuantTable;
-
-
- public YCbCrEncoder(Stream outputStream, int componentCount, int quality)
+ public YCbCrEncoder(Stream outputStream)
{
this.target = outputStream;
-
- // Convert from a quality rating to a scaling factor.
- int scale;
- if (quality < 50)
- {
- scale = 5000 / quality;
- }
- else
- {
- scale = 200 - (quality * 2);
- }
-
- // Initialize the quantization tables.
- InitQuantizationTable(0, scale, ref this.luminanceQuantTable);
- if (componentCount > 1)
- {
- InitQuantizationTable(1, scale, ref this.chrominanceQuantTable);
- }
}
///
@@ -155,12 +83,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// The pixel format.
/// The pixel accessor providing access to the image pixels.
/// The token to monitor for cancellation.
- private void Encode444(Image pixels, CancellationToken cancellationToken)
+ private void Encode444(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
- Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
-
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
@@ -184,21 +109,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
- ref onStackLuminanceQuantTable,
+ ref luminanceQuantTable,
ref unzig);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref pixelConverter.Cb,
- ref onStackChrominanceQuantTable,
+ ref chrominanceQuantTable,
ref unzig);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref pixelConverter.Cr,
- ref onStackChrominanceQuantTable,
+ ref chrominanceQuantTable,
ref unzig);
}
}
@@ -211,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// The pixel format.
/// The pixel accessor providing access to the image pixels.
/// The token to monitor for cancellation.
- private void Encode420(Image pixels, CancellationToken cancellationToken)
+ private void Encode420(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
// TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@@ -219,9 +144,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
Span cb = stackalloc Block8x8F[4];
Span cr = stackalloc Block8x8F[4];
- Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
- Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
-
var unzig = ZigZag.CreateUnzigTable();
var pixelConverter = YCbCrForwardConverter.Create();
@@ -252,7 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
- ref onStackLuminanceQuantTable,
+ ref luminanceQuantTable,
ref unzig);
}
@@ -261,7 +183,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
QuantIndex.Chrominance,
prevDCCb,
ref b,
- ref onStackChrominanceQuantTable,
+ ref chrominanceQuantTable,
ref unzig);
Block8x8F.Scale16X16To8X8(ref b, cr);
@@ -269,7 +191,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
QuantIndex.Chrominance,
prevDCCr,
ref b,
- ref onStackChrominanceQuantTable,
+ ref chrominanceQuantTable,
ref unzig);
}
}
@@ -282,11 +204,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// The pixel format.
/// The pixel accessor providing access to the image pixels.
/// The token to monitor for cancellation.
- private void EncodeGrayscale(Image pixels, CancellationToken cancellationToken)
+ private void EncodeGrayscale(Image pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
-
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
@@ -310,28 +230,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
- ref onStackLuminanceQuantTable,
+ ref luminanceQuantTable,
ref unzig);
}
}
}
- public void WriteStartOfScan(Image image, JpegColorType? colorType, JpegSubsample? subsample, CancellationToken cancellationToken)
+ public void WriteStartOfScan(
+ Image image,
+ JpegColorType? colorType,
+ JpegSubsample? subsample,
+ ref Block8x8F luminanceQuantTable,
+ ref Block8x8F chrominanceTable,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
if (colorType == JpegColorType.Luminance)
{
- this.EncodeGrayscale(image, cancellationToken);
+ this.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
}
else
{
switch (subsample)
{
case JpegSubsample.Ratio444:
- this.Encode444(image, cancellationToken);
+ this.Encode444(image, ref luminanceQuantTable, ref chrominanceTable, cancellationToken);
break;
case JpegSubsample.Ratio420:
- this.Encode420(image, cancellationToken);
+ this.Encode420(image, ref luminanceQuantTable, ref chrominanceTable, cancellationToken);
break;
}
}
@@ -499,35 +425,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt);
}
}
-
-
- ///
- /// Initializes quantization table.
- ///
- /// The quantization index.
- /// The scaling factor.
- /// The quantization table.
- private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
- {
- DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i));
- ReadOnlySpan 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;
- }
- }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 2625d490c0..6b58ef483c 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -31,6 +31,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
private const int QuantizationTableCount = 2;
+ ///
+ /// 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.
+ ///
+ // The C# compiler emits this as a compile-time constant embedded in the PE file.
+ // This is effectively compiled down to: return new ReadOnlySpan(&data, length)
+ // More details can be found: https://github.com/dotnet/roslyn/pull/24621
+ private static ReadOnlySpan 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,
+ };
+
+ ///
+ /// 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.
+ ///
+ // The C# compiler emits this as a compile-time constant embedded in the PE file.
+ // This is effectively compiled down to: return new ReadOnlySpan(&data, length)
+ // More details can be found: https://github.com/dotnet/roslyn/pull/24621
+ private static ReadOnlySpan 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,
+ };
+
+
///
/// A scratch buffer to reduce allocations.
///
@@ -97,7 +136,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int qlty = Numerics.Clamp(this.quality ?? metadata.GetJpegMetadata().Quality, 1, 100);
this.subsample ??= qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420;
- YCbCrEncoder scanEncoder = new YCbCrEncoder(stream, componentCount, qlty);
+ // Convert from a quality rating to a scaling factor.
+ int scale;
+ if (qlty < 50)
+ {
+ scale = 5000 / qlty;
+ }
+ else
+ {
+ scale = 200 - (qlty * 2);
+ }
+
+ // Initialize the quantization tables.
+ // TODO: This looks ugly, should we write chrominance table for luminance-only images?
+ // If not - this can code can be simplified
+ Block8x8F luminanceQuantTable = default;
+ Block8x8F chrominanceQuantTable = default;
+ InitQuantizationTable(0, scale, ref luminanceQuantTable);
+ if (componentCount > 1)
+ {
+ InitQuantizationTable(1, scale, ref chrominanceQuantTable);
+ }
// Write the Start Of Image marker.
this.WriteApplicationHeader(metadata);
@@ -106,7 +165,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteProfiles(metadata);
// Write the quantization tables.
- this.WriteDefineQuantizationTables(ref scanEncoder.LuminanceQuantizationTable, ref scanEncoder.ChrominanceQuantizationTable);
+ this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, componentCount);
@@ -114,8 +173,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Huffman tables.
this.WriteDefineHuffmanTables(componentCount);
- // Write the image data.
- this.WriteStartOfScan(scanEncoder, image, componentCount, cancellationToken);
+ // Write the scan header.
+ this.WriteStartOfScan(image, componentCount, cancellationToken);
+
+ // Write the scan compressed data.
+ new YCbCrEncoder(stream).WriteStartOfScan(
+ image,
+ this.colorType,
+ this.subsample,
+ ref luminanceQuantTable,
+ ref chrominanceQuantTable,
+ cancellationToken);
// Write the End Of Image marker.
this.buffer[0] = JpegConstants.Markers.XFF;
@@ -573,7 +641,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The pixel accessor providing access to the image pixels.
/// The number of components in a pixel.
/// The token to monitor for cancellation.
- private void WriteStartOfScan(YCbCrEncoder scanEncoder, Image image, int componentCount, CancellationToken cancellationToken)
+ private void WriteStartOfScan(Image image, int componentCount, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
// TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@@ -618,9 +686,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[sosSize] = 0x3f; // Se - End of spectral selection.
this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low)
this.outputStream.Write(this.buffer, 0, sosSize + 2);
-
-
- scanEncoder.WriteStartOfScan(image, this.colorType, this.subsample, cancellationToken);
}
///
@@ -637,5 +702,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[3] = (byte)(length & 0xff);
this.outputStream.Write(this.buffer, 0, 4);
}
+
+ ///
+ /// Initializes quantization table.
+ ///
+ /// The quantization index.
+ /// The scaling factor.
+ /// The quantization table.
+ private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
+ {
+ DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i));
+ ReadOnlySpan 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;
+ }
+ }
}
}