diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
index d3a5ea15b0..15fdb3dc4b 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
@@ -247,7 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.scanBuffer = new JpegBitReader(this.stream);
- bool fullScan = this.frame.Progressive || this.frame.MultiScan;
+ bool fullScan = this.frame.Progressive || !this.frame.Interleaved;
this.frame.AllocateComponents(fullScan);
if (this.frame.Progressive)
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
index da2d5da65a..308c52dbac 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
@@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.scanBuffer = new JpegBitReader(this.stream);
- bool fullScan = this.frame.Progressive || this.frame.MultiScan;
+ bool fullScan = this.frame.Progressive || !this.frame.Interleaved;
this.frame.AllocateComponents(fullScan);
if (!this.frame.Progressive)
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
index db1febd399..200096e193 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount)
{
- this.Extended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9;
+ this.IsExtended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9;
this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10;
this.Precision = precision;
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Gets a value indicating whether the frame uses the extended specification.
///
- public bool Extended { get; private set; }
+ public bool IsExtended { get; private set; }
///
/// Gets a value indicating whether the frame uses the progressive specification.
@@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// This is true for progressive and baseline non-interleaved images.
///
- public bool MultiScan { get; set; }
+ public bool Interleaved { get; set; }
///
/// Gets the precision.
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
index d271287bca..583b7225de 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
@@ -135,7 +135,65 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table);
}
- public void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken)
+ public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ switch (color)
+ {
+ case JpegEncodingColor.YCbCrRatio444:
+ case JpegEncodingColor.Rgb:
+ this.EncodeScanBaselineInterleaved444(frame, converter, cancellationToken);
+ break;
+ case JpegEncodingColor.YCbCrRatio420:
+ this.EncodeScanBaselineInterleaved420(frame, converter, cancellationToken);
+ break;
+ default:
+ this.EncodeScanBaselineInterleavedArbitrarySampling(frame, converter, cancellationToken);
+ break;
+ }
+
+ this.FlushRemainingBytes();
+ }
+
+ public void EncodeScanBaselineSingleComponent(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ JpegComponent component = frame.Components[0];
+
+ int mcuLines = frame.McusPerColumn;
+ int w = component.WidthInBlocks;
+
+ ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
+ ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
+
+ for (int i = 0; i < mcuLines; i++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Convert from pixels to spectral via given converter
+ converter.ConvertStrideBaseline();
+
+ // Encode spectral to binary
+ Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int k = 0; k < w; k++)
+ {
+ this.WriteBlock(
+ component,
+ ref Unsafe.Add(ref blockRef, k),
+ ref dcHuffmanTable,
+ ref acHuffmanTable);
+
+ if (this.IsStreamFlushNeeded)
+ {
+ this.FlushToStream();
+ }
+ }
+ }
+ }
+
+ private void EncodeScanBaselineInterleavedArbitrarySampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
int mcu = 0;
@@ -154,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
// Scan an interleaved mcu... process components in order
int mcuCol = mcu % mcusPerLine;
- for (int k = 0; k < frame.ComponentCount; k++)
+ for (int k = 0; k < frame.Components.Length; k++)
{
JpegComponent component = frame.Components[k];
@@ -192,24 +250,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
}
-
- this.FlushRemainingBytes();
}
- public void EncodeScanBaselineSingleComponent(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken)
+ private void EncodeScanBaselineInterleaved444(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- // DEBUG INITIALIZATION SETUP
- frame.AllocateComponents(fullScan: false);
+ int mcusPerColumn = frame.McusPerColumn;
+ int mcusPerLine = frame.McusPerLine;
- JpegComponent component = frame.Components[0];
- int mcuLines = frame.McusPerColumn;
- int w = component.WidthInBlocks;
- int h = component.SamplingFactors.Height;
- ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
- ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
+ JpegComponent c2 = frame.Components[2];
+ JpegComponent c1 = frame.Components[1];
+ JpegComponent c0 = frame.Components[0];
- for (int i = 0; i < mcuLines; i++)
+ ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId];
+ ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId];
+ ref HuffmanLut c1dcHuffmanTable = ref this.dcHuffmanTables[c1.DcTableId];
+ ref HuffmanLut c1acHuffmanTable = ref this.acHuffmanTables[c1.AcTableId];
+ ref HuffmanLut c2dcHuffmanTable = ref this.dcHuffmanTables[c2.DcTableId];
+ ref HuffmanLut c2acHuffmanTable = ref this.acHuffmanTables[c2.AcTableId];
+
+ ref Block8x8 c0BlockRef = ref MemoryMarshal.GetReference(c0.SpectralBlocks.DangerousGetRowSpan(y: 0));
+ ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0));
+ ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0));
+
+ for (int j = 0; j < mcusPerColumn; j++)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -217,28 +281,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
converter.ConvertStrideBaseline();
// Encode spectral to binary
- for (int j = 0; j < h; j++)
+ for (int i = 0; i < mcusPerLine; i++)
{
- Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
- ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+ this.WriteBlock(
+ c0,
+ ref Unsafe.Add(ref c0BlockRef, i),
+ ref c0dcHuffmanTable,
+ ref c0acHuffmanTable);
+
+ this.WriteBlock(
+ c1,
+ ref Unsafe.Add(ref c1BlockRef, i),
+ ref c1dcHuffmanTable,
+ ref c1acHuffmanTable);
+
+ this.WriteBlock(
+ c2,
+ ref Unsafe.Add(ref c2BlockRef, i),
+ ref c2dcHuffmanTable,
+ ref c2acHuffmanTable);
- for (int k = 0; k < w; k++)
+ if (this.IsStreamFlushNeeded)
{
- this.WriteBlock(
- component,
- ref Unsafe.Add(ref blockRef, k),
- ref dcHuffmanTable,
- ref acHuffmanTable);
-
- if (this.IsStreamFlushNeeded)
- {
- this.FlushToStream();
- }
+ this.FlushToStream();
}
}
}
}
+ private void EncodeScanBaselineInterleaved420(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ throw new NotImplementedException();
+ }
+
private void WriteBlock(
JpegComponent component,
ref Block8x8 block,
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
index c20e3d8635..a6a9bcb0a9 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
@@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8);
this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8);
- for (int i = 0; i < this.ComponentCount; i++)
+ for (int i = 0; i < this.Components.Length; i++)
{
JpegComponent component = this.Components[i];
component.Init(this, maxSubFactorH, maxSubFactorV);
@@ -50,8 +50,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public int PixelWidth { get; private set; }
- public int ComponentCount => this.Components.Length;
-
public JpegComponent[] Components { get; }
public int McusPerLine { get; }
@@ -70,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public void AllocateComponents(bool fullScan)
{
- for (int i = 0; i < this.ComponentCount; i++)
+ for (int i = 0; i < this.Components.Length; i++)
{
JpegComponent component = this.Components[i];
component.AllocateSpectral(fullScan);
diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
index ff87d88eb5..de55904212 100644
--- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
@@ -14,5 +14,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Defaults to 75.
///
public int? Quality { get; set; }
+
+ ///
+ /// Gets or sets the component encoding mode.
+ ///
+ ///
+ /// Interleaved encoding mode encodes all color components in a single scan.
+ /// Non-interleaved encoding mode encodes each color component in a separate scan.
+ ///
+ public bool? Interleaved { get; set; }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 10e52066d1..25e31a2362 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -488,6 +488,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Read on.
fileMarker = FindNextFileMarker(this.markerBuffer, stream);
}
+
+ this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved;
}
///
@@ -1178,6 +1180,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
+ this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive;
remaining -= length;
@@ -1386,7 +1389,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// selectorsCount*2 bytes: component index + huffman tables indices
stream.Read(this.temp, 0, selectorsBytes);
- this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount;
+ this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount;
for (int i = 0; i < selectorsBytes; i += 2)
{
// 1 byte: Component id
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
index 5d0e964a1e..c2a50b37e2 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
@@ -24,6 +24,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
public int? Quality { get; set; }
+ ///
+ public bool? Interleaved { get; set; }
+
///
/// Sets jpeg color for encoding.
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 4318f9e09c..fe3fbb4d94 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -38,9 +38,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
private readonly int? quality;
- ///
- /// Gets or sets the colorspace to use.
- ///
+ private readonly bool? interleaved;
+
private JpegEncodingColor? colorType;
private JpegFrameConfig frameConfig;
@@ -60,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig)
{
this.quality = options.Quality;
+ this.interleaved = options.Interleaved;
this.frameConfig = frameConfig;
this.colorType = frameConfig.EncodingColor;
@@ -121,20 +121,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the quantization tables.
this.WriteDefineQuantizationTables(this.frameConfig.QuantizationTables, jpegMetadata);
- // Write the scan header.
- this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components);
-
var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables, Configuration.Default);
- // TODO: change this for non-interleaved scans
- frame.AllocateComponents(fullScan: false);
- if (frame.ComponentCount > 1)
+ if (frame.Components.Length == 1)
+ {
+ frame.AllocateComponents(fullScan: false);
+
+ this.WriteStartOfScan(this.frameConfig.Components);
+ this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken);
+ }
+ else if (this.interleaved ?? jpegMetadata.Interleaved ?? true)
{
- this.scanEncoder.EncodeScanBaselineInterleaved(frame, spectralConverter, cancellationToken);
+ frame.AllocateComponents(fullScan: false);
+
+ this.WriteStartOfScan(this.frameConfig.Components);
+ this.scanEncoder.EncodeScanBaselineInterleaved(this.frameConfig.EncodingColor, frame, spectralConverter, cancellationToken);
}
else
{
- this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken);
+ throw new NotImplementedException();
}
// Write the End Of Image marker.
@@ -143,50 +148,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
stream.Flush();
}
- ///
- /// If color type was not set, set it based on the given image.
- /// Note, if there is no metadata and the image has multiple components this method
- /// returns defering the field assignment
- /// to .
- ///
- private static JpegEncodingColor? GetFallbackColorType(Image image)
- where TPixel : unmanaged, IPixel
- {
- // First inspect the image metadata.
- JpegEncodingColor? colorType = null;
- JpegMetadata metadata = image.Metadata.GetJpegMetadata();
- if (IsSupportedColorType(metadata.ColorType))
- {
- return metadata.ColorType;
- }
-
- // Secondly, inspect the pixel type.
- // TODO: PixelTypeInfo should contain a component count!
- bool isGrayscale =
- typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
- typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
-
- // We don't set multi-component color types here since we can set it based upon
- // the quality in InitQuantizationTables.
- if (isGrayscale)
- {
- colorType = JpegEncodingColor.Luminance;
- }
-
- return colorType;
- }
-
- ///
- /// Returns true, if the color type is supported by the encoder.
- ///
- /// The color type.
- /// true, if color type is supported.
- private static bool IsSupportedColorType(JpegEncodingColor? colorType)
- => colorType == JpegEncodingColor.YCbCrRatio444
- || colorType == JpegEncodingColor.YCbCrRatio420
- || colorType == JpegEncodingColor.Luminance
- || colorType == JpegEncodingColor.Rgb;
-
///
/// Write the start of image marker.
///
@@ -611,7 +572,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
/// Writes the StartOfScan marker.
///
- private void WriteStartOfScan(int componentCount, JpegComponentConfig[] components)
+ private void WriteStartOfScan(Span components)
{
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
// - the marker length "\x00\x0c",
@@ -626,13 +587,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[1] = JpegConstants.Markers.SOS;
// Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
- int sosSize = 6 + (2 * componentCount);
+ int sosSize = 6 + (2 * components.Length);
this.buffer[2] = 0x00;
this.buffer[3] = (byte)sosSize;
- this.buffer[4] = (byte)componentCount; // Number of components in a scan
+ this.buffer[4] = (byte)components.Length; // Number of components in a scan
// Components data
- for (int i = 0; i < componentCount; i++)
+ for (int i = 0; i < components.Length; i++)
{
int i2 = 2 * i;
diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
index 6c34036793..b878d26fb8 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
@@ -99,9 +99,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
///
- /// Gets or sets the color type.
+ /// Gets the color type.
///
- public JpegEncodingColor? ColorType { get; set; }
+ public JpegEncodingColor? ColorType { get; internal set; }
+
+ ///
+ /// Gets the component encoding mode.
+ ///
+ ///
+ /// Interleaved encoding mode encodes all color components in a single scan.
+ /// Non-interleaved encoding mode encodes each color component in a separate scan.
+ ///
+ public bool? Interleaved { get; internal set; }
+
+ ///
+ /// Gets the scan encoding mode.
+ ///
+ ///
+ /// Progressive jpeg images encode component data across multiple scans.
+ ///
+ public bool? Progressive { get; internal set; }
///
public IDeepCloneable DeepClone() => new JpegMetadata(this);
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
index c4a448ff8e..c5b3b3da50 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
@@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
// Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing
// Progressive and multi-scan images must be loaded manually
- if (this.frame.Progressive || this.frame.MultiScan)
+ if (this.frame.Progressive || !this.frame.Interleaved)
{
LibJpegTools.ComponentData[] components = this.spectralData.Components;
for (int i = 0; i < components.Length; i++)