diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 583b7225de..c989346fcb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -155,18 +155,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.FlushRemainingBytes(); } - public void EncodeScanBaselineSingleComponent(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + public void EncodeScanBaselineSingleComponent(JpegComponent component, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - JpegComponent component = frame.Components[0]; - - int mcuLines = frame.McusPerColumn; + int h = component.HeightInBlocks; 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++) + for (int i = 0; i < h; i++) { cancellationToken.ThrowIfCancellationRequested(); @@ -193,6 +191,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } + public void EncodeScanBaseline(JpegComponent component, CancellationToken cancellationToken) + { + int h = component.HeightInBlocks; + 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 < h; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Encode spectral to binary + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); + 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(); + } + } + } + + this.FlushRemainingBytes(); + } + private void EncodeScanBaselineInterleavedArbitrarySampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -608,6 +640,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // Flush cached bytes to the output stream with padding bits int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; this.FlushToStream(lastByteIndex); + + // Clean huffman register + // This is needed for for images with multiples scans + this.bitCount = 0; + this.accumulatedBits = 0; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index fe3fbb4d94..ae6c44f51f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg frame.AllocateComponents(fullScan: false); this.WriteStartOfScan(this.frameConfig.Components); - this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken); + this.scanEncoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken); } else if (this.interleaved ?? jpegMetadata.Interleaved ?? true) { @@ -139,7 +139,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - throw new NotImplementedException(); + frame.AllocateComponents(fullScan: true); + spectralConverter.ConvertFull(); + + Span components = this.frameConfig.Components; + for (int i = 0; i < frame.Components.Length; i++) + { + this.WriteStartOfScan(components.Slice(i, 1)); + this.scanEncoder.EncodeScanBaseline(frame.Components[i], cancellationToken); + } } // Write the End Of Image marker.