diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 30cb6c851b..70c4402e86 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -112,6 +112,28 @@ namespace ImageSharp.Formats.Jpg /// /// Reads the blocks from the -s stream, and processes them into the corresponding instances. + /// The blocks are traversed one MCU at a time. For 4:2:0 chroma + /// subsampling, there are four Y 8x8 blocks in every 16x16 MCU. + /// For a baseline 32x16 pixel image, the Y blocks visiting order is: + /// 0 1 4 5 + /// 2 3 6 7 + /// For progressive images, the interleaved scans (those with component count > 1) + /// are traversed as above, but non-interleaved scans are traversed left + /// to right, top to bottom: + /// 0 1 2 3 + /// 4 5 6 7 + /// Only DC scans (zigStart == 0) can be interleave AC scans must have + /// only one component. + /// To further complicate matters, for non-interleaved scans, there is no + /// data for any blocks that are inside the image at the MCU level but + /// outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 + /// progressive image consists of two 16x16 MCUs. The interleaved scans + /// will process 8 Y blocks: + /// 0 1 4 5 + /// 2 3 6 7 + /// The non-interleaved scans will process only 6 Y blocks: + /// 0 1 2 + /// 3 4 5 /// /// The instance public void ProcessBlocks(JpegDecoderCore decoder) @@ -132,28 +154,6 @@ namespace ImageSharp.Formats.Jpg for (int j = 0; j < this.hi * vi; j++) { - // The blocks are traversed one MCU at a time. For 4:2:0 chroma - // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. - // For a baseline 32x16 pixel image, the Y blocks visiting order is: - // 0 1 4 5 - // 2 3 6 7 - // For progressive images, the interleaved scans (those with component count > 1) - // are traversed as above, but non-interleaved scans are traversed left - // to right, top to bottom: - // 0 1 2 3 - // 4 5 6 7 - // Only DC scans (zigStart == 0) can be interleave AC scans must have - // only one component. - // To further complicate matters, for non-interleaved scans, there is no - // data for any blocks that are inside the image at the MCU level but - // outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 - // progressive image consists of two 16x16 MCUs. The interleaved scans - // will process 8 Y blocks: - // 0 1 4 5 - // 2 3 6 7 - // The non-interleaved scans will process only 6 Y blocks: - // 0 1 2 - // 3 4 5 if (this.componentScanCount != 1) { this.bx = (this.hi * mx) + (j % this.hi); @@ -171,23 +171,8 @@ namespace ImageSharp.Formats.Jpg } } - int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; - - // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - - // Load the previous partially decoded coefficients, if applicable. - if (decoder.IsProgressive) - { - int blockIndex = this.GetBlockIndex(decoder); - this.data.Block = decoder.DecodedBlocks[this.componentIndex][blockIndex]; - } - else - { - this.data.Block.Clear(); - } - - this.ProcessBlockImpl(decoder, scanIndex); + this.ReadBlock(decoder, scanIndex); + this.ProcessBlock(decoder); } // for j @@ -301,12 +286,15 @@ namespace ImageSharp.Formats.Jpg } /// - /// Process the current block at (, ) + /// Read the current the current block at (, ) from the decoders stream /// /// The decoder /// The index of the scan - private void ProcessBlockImpl(JpegDecoderCore decoder, int scanIndex) + private void ReadBlock(JpegDecoderCore decoder, int scanIndex) { + int blockIndex = this.GetBlockIndex(decoder); + this.data.Block = decoder.DecodedBlocks[this.componentIndex][blockIndex]; + var b = this.pointers.Block; DecoderErrorCode errorCode; int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; @@ -391,23 +379,23 @@ namespace ImageSharp.Formats.Jpg } } - if (decoder.IsProgressive) - { - if (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0) - { - // We haven't completely decoded this 8x8 block. Save the coefficients. - decoder.DecodedBlocks[this.componentIndex][this.GetBlockIndex(decoder)] = *b; - - // At this point, we could execute the rest of the loop body to dequantize and - // perform the inverse DCT, to save early stages of a progressive image to the - // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, - // the jpeg.Decode function does not return until the entire image is decoded, - // so we "continue" here to avoid wasted computation. - return; - } - } + decoder.DecodedBlocks[this.componentIndex][blockIndex] = this.data.Block; + } + + private bool IsProgressiveBlockFinished(JpegDecoderCore decoder) + => decoder.IsProgressive && (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0); + + /// + /// Dequantize, perform the inverse DCT and store the block to the into the corresponding instances. + /// + /// The instance + private void ProcessBlock(JpegDecoderCore decoder) + { + int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; + this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + Block8x8F* b = this.pointers.Block; - // Dequantize, perform the inverse DCT and store the block to the image. Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); @@ -437,7 +425,7 @@ namespace ImageSharp.Formats.Jpg /// The index private int GetBlockIndex(JpegDecoderCore decoder) { - return ((this.@by * decoder.MCUCountX) * this.hi) + this.bx; + return ((this.by * decoder.MCUCountX) * this.hi) + this.bx; } private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref ComponentScan currentComponentScan, ref int totalHv)