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