diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4f54f5078..5c8059939 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -407,10 +407,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// Returns the correct colorspace based on the image component count. + /// Returns the correct colorspace based on the image component count and the jpeg frame component id's. /// /// The - private JpegColorSpace DeduceJpegColorSpace(byte componentCount) + private JpegColorSpace DeduceJpegColorSpace(byte componentCount, JpegComponent[] components) { if (componentCount == 1) { @@ -424,6 +424,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return JpegColorSpace.RGB; } + // If the component Id's are R, G, B in ASCII the colorspace is RGB and not YCbCr. + if (components[2].Id == 66 && components[1].Id == 71 && components[0].Id == 82) + { + return JpegColorSpace.RGB; + } + // Some images are poorly encoded and contain incorrect colorspace transform metadata. // We ignore that and always fall back to the default colorspace. return JpegColorSpace.YCbCr; @@ -898,60 +904,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 1 byte: Number of components byte componentCount = this.temp[5]; - this.ColorSpace = this.DeduceJpegColorSpace(componentCount); - - this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); - if (!metadataOnly) + remaining -= length; + + // Validate: remaining part must be equal to components * 3 + const int componentBytes = 3; + if (remaining != componentCount * componentBytes) { - remaining -= length; + JpegThrowHelper.ThrowBadMarker("SOFn", remaining); + } - // Validate: remaining part must be equal to components * 3 - const int componentBytes = 3; - if (remaining != componentCount * componentBytes) - { - JpegThrowHelper.ThrowBadMarker("SOFn", remaining); - } + // components*3 bytes: component data + stream.Read(this.temp, 0, remaining); + + // No need to pool this. They max out at 4 + this.Frame.ComponentIds = new byte[componentCount]; + this.Frame.ComponentOrder = new byte[componentCount]; + this.Frame.Components = new JpegComponent[componentCount]; - // components*3 bytes: component data - stream.Read(this.temp, 0, remaining); + int maxH = 0; + int maxV = 0; + int index = 0; + for (int i = 0; i < componentCount; i++) + { + byte hv = this.temp[index + 1]; + int h = (hv >> 4) & 15; + int v = hv & 15; - // No need to pool this. They max out at 4 - this.Frame.ComponentIds = new byte[componentCount]; - this.Frame.ComponentOrder = new byte[componentCount]; - this.Frame.Components = new JpegComponent[componentCount]; + if (maxH < h) + { + maxH = h; + } - int maxH = 0; - int maxV = 0; - int index = 0; - for (int i = 0; i < componentCount; i++) + if (maxV < v) { - byte hv = this.temp[index + 1]; - int h = (hv >> 4) & 15; - int v = hv & 15; + maxV = v; + } - if (maxH < h) - { - maxH = h; - } + var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i); - if (maxV < v) - { - maxV = v; - } + this.Frame.Components[i] = component; + this.Frame.ComponentIds[i] = component.Id; - var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i); + index += componentBytes; + } - this.Frame.Components[i] = component; - this.Frame.ComponentIds[i] = component.Id; + this.ColorSpace = this.DeduceJpegColorSpace(componentCount, this.Frame.Components); - index += componentBytes; - } + this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; + if (!metadataOnly) + { this.Frame.Init(maxH, maxV); - this.scanDecoder.InjectFrameData(this.Frame, this); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 674aa6d8f..e8d307f90 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -174,6 +174,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg await Assert.ThrowsAsync(async () => await Image.IdentifyAsync(config, "someFakeFile", cts.Token)); } + // https://github.com/SixLabors/ImageSharp/pull/1732 + [Theory] + [WithFile(TestImages.Jpeg.Issues.WrongColorSpace, PixelTypes.Rgba32)] + public void Issue1732_DecodesWithRgbColorSpace(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new JpegDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + // DEBUG ONLY! // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" // into "\tests\Images\ActualOutput\JpegDecoderTests\" diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index b0b962624..8a7ea9d92 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -237,6 +237,7 @@ namespace SixLabors.ImageSharp.Tests public const string ExifResize1049 = "Jpg/issues/issue1049-exif-resize.jpg"; public const string BadSubSampling1076 = "Jpg/issues/issue-1076-invalid-subsampling.jpg"; public const string IdentifyMultiFrame1211 = "Jpg/issues/issue-1221-identify-multi-frame.jpg"; + public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/Issue1732-WrongColorSpace.jpg b/tests/Images/Input/Jpg/issues/Issue1732-WrongColorSpace.jpg new file mode 100644 index 000000000..e3ba85ae8 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue1732-WrongColorSpace.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c72235954cdfb9d0cc7f09c537704e617313dc77708b4dca27b47c94c5e67a6 +size 2852