diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 2ae3ae86bc..cf2fd02908 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -148,11 +148,16 @@ public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) private void ParseBaselineData() { - if (this.componentsCount == this.frame.ComponentCount) + if (this.componentsCount != 1) { this.ParseBaselineDataInterleaved(); this.spectralConverter.CommitConversion(); } + else if (this.frame.ComponentCount == 1) + { + this.ParseBaselineDataSingleComponent(); + this.spectralConverter.CommitConversion(); + } else { this.ParseBaselineDataNonInterleaved(); @@ -161,7 +166,6 @@ private void ParseBaselineData() private void ParseBaselineDataInterleaved() { - // Interleaved int mcu = 0; int mcusPerColumn = this.frame.McusPerColumn; int mcusPerLine = this.frame.McusPerLine; @@ -198,7 +202,7 @@ private void ParseBaselineDataInterleaved() { if (buffer.NoData) { - // It is very likely that some spectral data was decoded before we encountered EOI marker + // It is very likely that some spectral data was decoded before we've encountered 'end of scan' // so we need to decode what's left and return (or maybe throw?) this.spectralConverter.ConvertStrideBaseline(); return; @@ -221,9 +225,12 @@ ref Unsafe.Add(ref blockRef, blockCol), this.HandleRestart(); } - // convert from spectral to actual pixels via given converter + // Convert from spectral to actual pixels via given converter this.spectralConverter.ConvertStrideBaseline(); } + + // Stride conversion must be sealed for stride conversion approach + this.spectralConverter.CommitConversion(); } private void ParseBaselineDataNonInterleaved() @@ -261,6 +268,52 @@ ref Unsafe.Add(ref blockRef, i), } } + private void ParseBaselineDataSingleComponent() + { + JpegComponent component = this.frame.Components[0]; + int mcuLines = this.frame.McusPerColumn; + int w = component.WidthInBlocks; + int h = component.SamplingFactors.Height; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; + + ref HuffmanScanBuffer buffer = ref this.scanBuffer; + + for (int i = 0; i < mcuLines; i++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + + // decode from binary to spectral + for (int j = 0; j < h; j++) + { + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int k = 0; k < w; k++) + { + if (buffer.NoData) + { + // It is very likely that some spectral data was decoded before we've encountered 'end of scan' + // so we need to decode what's left and return (or maybe throw?) + this.spectralConverter.ConvertStrideBaseline(); + return; + } + + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); + + this.HandleRestart(); + } + } + + // Convert from spectral to actual pixels via given converter + this.spectralConverter.ConvertStrideBaseline(); + } + } + private void CheckProgressiveData() { // Validate successive scan parameters. diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index db5169a045..70cbc3af72 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -42,6 +42,9 @@ public partial class JpegDecoderTests // High depth images TestImages.Jpeg.Baseline.Testorig12bit, + + // Grayscale jpeg with 2x2 sampling factors (not a usual thing to encounter in the wild) + TestImages.Jpeg.Baseline.GrayscaleSampling2x2, }; public static string[] ProgressiveTestJpegs = diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 5ff71ba396..b0537817bc 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -223,6 +223,7 @@ public static class Bad public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg"; public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg"; public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg"; + public const string GrayscaleSampling2x2 = "Jpg/baseline/grayscale_sampling22.jpg"; public static readonly string[] All = { diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png new file mode 100644 index 0000000000..b2c3effdda --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_grayscale_sampling22.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad22f28d20ea0ceda983a138b3bf9503ed836d779ed75a313f668329c910665e +size 168405 diff --git a/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg b/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg new file mode 100644 index 0000000000..b861c68ab5 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/grayscale_sampling22.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5cc8572082a54944d48b3e4f49e6c441871f6eb2b616fbbbfb025f20e0aeff5 +size 45066