diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
index 331da275c..5468d93c4 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
@@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
private int emitLen = 0;
///
- /// Emmited bits 'micro buffer' before being transfered to the .
+ /// Emitted bits 'micro buffer' before being transferred to the .
///
private int accumulatedBits;
@@ -58,18 +58,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
private readonly Stream target;
- public HuffmanScanEncoder(Stream outputStream)
- {
- this.target = outputStream;
- }
+ public HuffmanScanEncoder(Stream outputStream) => this.target = outputStream;
///
/// 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
+ /// 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
@@ -128,8 +125,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// 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
+ /// 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
@@ -196,7 +193,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// The pixel format.
/// The pixel accessor providing access to the image pixels.
- /// Luminance quantization table provided by the callee
+ /// 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
@@ -234,6 +231,65 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.FlushInternalBuffer();
}
+ ///
+ /// 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.
+ /// Luminance quantization table provided by the callee.
+ /// Chrominance quantization table provided by the callee.
+ /// The token to monitor for cancellation.
+ public void EncodeRgb(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ this.huffmanTables = HuffmanLut.TheHuffmanLut;
+
+ var unzig = ZigZag.CreateUnzigTable();
+
+ // 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 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);
+
+ prevDCY = this.WriteBlock(
+ QuantIndex.Luminance,
+ prevDCY,
+ ref pixelConverter.R,
+ ref luminanceQuantTable,
+ ref unzig);
+
+ prevDCCb = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCb,
+ ref pixelConverter.G,
+ ref chrominanceQuantTable,
+ ref unzig);
+
+ prevDCCr = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCr,
+ ref pixelConverter.B,
+ ref chrominanceQuantTable,
+ ref unzig);
+ }
+ }
+
+ this.FlushInternalBuffer();
+ }
+
///
/// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block.
@@ -437,7 +493,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max");
#if SUPPORTS_BITOPERATIONS
// This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation
- // But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value))
+ // But internal log2 is implemented like this: (31 - (int)Lzcnt.LeadingZeroCount(value))
// BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0
// Lzcnt would return 32 for input value of 0 - no need to check that with branching
@@ -449,7 +505,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// if 0 - return 0 in this case
// else - return log2(value) + 1
//
- // Hack based on input value constaint:
+ // Hack based on input value constraint:
// We know that input values are guaranteed to be maximum 16 bit large for huffman encoding
// We can safely shift input value for one bit -> log2(value << 1)
// Because of the 16 bit value constraint it won't overflow
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
new file mode 100644
index 000000000..e23cf348a
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
@@ -0,0 +1,114 @@
+// 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.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;
+
+ ///
+ /// of sampling area from given frame pixel buffer.
+ ///
+ private static readonly Size SampleSize = new Size(8, 8);
+
+ ///
+ /// 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();
+ }
+
+ ///
+ /// 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;
+
+ CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
+ }
+
+ private static void CopyToBlock(Span rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
+ {
+ ref Rgb24 rgbStart = ref rgbSpan[0];
+
+ for (int i = 0; i < Block8x8F.Size; i++)
+ {
+ Rgb24 c = Unsafe.Add(ref rgbStart, i);
+
+ redBlock[i] = c.R;
+ greenBlock[i] = c.G;
+ blueBlock[i] = c.B;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
index a4abd532b..bfeafcbb3 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
@@ -58,22 +58,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// Temporal 16x8 block to hold TPixel data
///
- private Span pixelSpan;
+ private readonly Span pixelSpan;
///
/// Temporal RGB block
///
- private Span rgbSpan;
+ private readonly Span rgbSpan;
///
/// Sampled pixel buffer size
///
- private Size samplingAreaSize;
+ private readonly Size samplingAreaSize;
///
/// for internal operations
///
- private Configuration config;
+ private readonly Configuration config;
public YCbCrForwardConverter420(ImageFrame frame)
{
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
index ef589272b..2dbd1a2dc 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
@@ -53,22 +53,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// Temporal 64-byte span to hold unconverted TPixel data
///
- private Span pixelSpan;
+ private readonly Span pixelSpan;
///
/// Temporal 64-byte span to hold converted Rgb24 data
///
- private Span rgbSpan;
+ private readonly Span rgbSpan;
///
/// Sampled pixel buffer size
///
- private Size samplingAreaSize;
+ private readonly Size samplingAreaSize;
///
/// for internal operations
///
- private Configuration config;
+ private readonly Configuration config;
public YCbCrForwardConverter444(ImageFrame frame)
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 88d96f554..d49e40d2f 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -90,6 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Compute number of components based on color type in options.
int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3;
+ byte[] componentIds = this.GetComponentIds();
// TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that
// Initialize the quantization tables.
@@ -105,13 +106,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write the image dimensions.
- this.WriteStartOfFrame(image.Width, image.Height, componentCount);
+ this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds);
// Write the Huffman tables.
this.WriteDefineHuffmanTables(componentCount);
// Write the scan header.
- this.WriteStartOfScan(image, componentCount, cancellationToken);
+ this.WriteStartOfScan(componentCount, componentIds);
// Write the scan compressed data.
var scanEncoder = new HuffmanScanEncoder(stream);
@@ -131,6 +132,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegSubsample.Ratio420:
scanEncoder.Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
+ case JpegSubsample.Rgb:
+ scanEncoder.EncodeRgb(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
+ break;
}
}
@@ -141,12 +145,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
///
- /// Writes data to "Define Quantization Tables" block for QuantIndex
+ /// Gets the component ids.
+ /// For color space RGB this will be RGB as ASCII, otherwise 1, 2, 3.
///
- /// The "Define Quantization Tables" block
- /// Offset in "Define Quantization Tables" block
- /// The quantization index
- /// The quantization table to copy data from
+ /// The component Ids.
+ private byte[] GetComponentIds()
+ {
+ if (this.subsample == JpegSubsample.Rgb)
+ {
+ return new byte[] { 82, 71, 66 };
+ }
+
+ return new byte[] { 1, 2, 3 };
+ }
+
+ ///
+ /// Writes data to "Define Quantization Tables" block for QuantIndex.
+ ///
+ /// The "Define Quantization Tables" block.
+ /// Offset in "Define Quantization Tables" block.
+ /// The quantization index.
+ /// The quantization table to copy data from.
private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F quant)
{
dqt[offset++] = (byte)i;
@@ -343,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes");
}
- var app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length +
+ int app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length +
ProfileResolver.AdobeImageResourceBlockMarker.Length +
ProfileResolver.AdobeIptcMarker.Length +
2 + 4 + data.Length;
@@ -478,12 +497,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
///
- /// Writes the Start Of Frame (Baseline) marker
+ /// Writes the Start Of Frame (Baseline) marker.
///
- /// The width of the image
- /// The height of the image
- /// The number of components in a pixel
- private void WriteStartOfFrame(int width, int height, int componentCount)
+ /// The width of the image.
+ /// The height of the image.
+ /// The number of components in a pixel.
+ /// The component Id's.
+ private void WriteStartOfFrame(int width, int height, int componentCount, byte[] componentIds)
{
// "default" to 4:2:0
Span subsamples = stackalloc byte[]
@@ -513,6 +533,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
switch (this.subsample)
{
+ case JpegSubsample.Rgb:
case JpegSubsample.Ratio444:
subsamples = stackalloc byte[]
{
@@ -545,8 +566,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int i = 0; i < componentCount; i++)
{
int i3 = 3 * i;
- this.buffer[i3 + 6] = (byte)(i + 1);
+ // Component ID.
+ this.buffer[i3 + 6] = componentIds[i];
this.buffer[i3 + 7] = subsamples[i];
this.buffer[i3 + 8] = chroma[i];
}
@@ -557,19 +579,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
/// Writes the StartOfScan marker.
///
- /// The pixel format.
- /// 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(Image image, int componentCount, CancellationToken cancellationToken)
- where TPixel : unmanaged, IPixel
+ /// The componentId's.
+ private void WriteStartOfScan(int componentCount, byte[] componentIds)
{
- Span componentId = stackalloc byte[]
- {
- 0x01,
- 0x02,
- 0x03
- };
Span huffmanId = stackalloc byte[]
{
0x00,
@@ -597,7 +610,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int i = 0; i < componentCount; i++)
{
int i2 = 2 * i;
- this.buffer[i2 + 5] = componentId[i]; // Component Id
+ this.buffer[i2 + 5] = componentIds[i]; // Component Id
this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table
}
@@ -633,7 +646,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
///
- /// Initializes quntization tables.
+ /// Initializes quantization tables.
///
///
/// We take quality values in a hierarchical order:
diff --git a/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs b/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs
index 16488f6d2..760ba3a96 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegSubsample.cs
@@ -18,6 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only
/// sampled on each alternate line.
///
- Ratio420
+ Ratio420,
+
+ ///
+ /// The pixel data will be preserved as RGB without any sub sampling.
+ ///
+ Rgb,
}
}