Skip to content

Commit 9b09775

Browse files
authored
Merge pull request #1732 from SixLabors/bp/deduceColorSpaceFix
If component id's are R, G, B in ASCII the color space should be RGB
2 parents d105ab4 + 7e7dbbb commit 9b09775

File tree

4 files changed

+63
-40
lines changed

4 files changed

+63
-40
lines changed

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,10 @@ public void Dispose()
345345
}
346346

347347
/// <summary>
348-
/// Returns the correct colorspace based on the image component count
348+
/// Returns the correct colorspace based on the image component count and the jpeg frame components.
349349
/// </summary>
350350
/// <returns>The <see cref="JpegColorSpace"/></returns>
351-
private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
351+
private JpegColorSpace DeduceJpegColorSpace(byte componentCount, JpegComponent[] components)
352352
{
353353
if (componentCount == 1)
354354
{
@@ -362,6 +362,12 @@ private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
362362
return JpegColorSpace.RGB;
363363
}
364364

365+
// If the component Id's are R, G, B in ASCII the colorspace is RGB and not YCbCr.
366+
if (components[2].Id == 66 && components[1].Id == 71 && components[0].Id == 82)
367+
{
368+
return JpegColorSpace.RGB;
369+
}
370+
365371
// Some images are poorly encoded and contain incorrect colorspace transform metadata.
366372
// We ignore that and always fall back to the default colorspace.
367373
return JpegColorSpace.YCbCr;
@@ -836,60 +842,60 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
836842

837843
// 1 byte: Number of components
838844
byte componentCount = this.temp[5];
839-
this.ColorSpace = this.DeduceJpegColorSpace(componentCount);
840-
841-
this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
842845

843846
this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
844847

845-
if (!metadataOnly)
848+
remaining -= length;
849+
850+
// Validate: remaining part must be equal to components * 3
851+
const int componentBytes = 3;
852+
if (remaining != componentCount * componentBytes)
846853
{
847-
remaining -= length;
854+
JpegThrowHelper.ThrowBadMarker("SOFn", remaining);
855+
}
848856

849-
// Validate: remaining part must be equal to components * 3
850-
const int componentBytes = 3;
851-
if (remaining != componentCount * componentBytes)
852-
{
853-
JpegThrowHelper.ThrowBadMarker("SOFn", remaining);
854-
}
857+
// components*3 bytes: component data
858+
stream.Read(this.temp, 0, remaining);
859+
860+
// No need to pool this. They max out at 4
861+
this.Frame.ComponentIds = new byte[componentCount];
862+
this.Frame.ComponentOrder = new byte[componentCount];
863+
this.Frame.Components = new JpegComponent[componentCount];
855864

856-
// components*3 bytes: component data
857-
stream.Read(this.temp, 0, remaining);
865+
int maxH = 0;
866+
int maxV = 0;
867+
int index = 0;
868+
for (int i = 0; i < componentCount; i++)
869+
{
870+
byte hv = this.temp[index + 1];
871+
int h = (hv >> 4) & 15;
872+
int v = hv & 15;
858873

859-
// No need to pool this. They max out at 4
860-
this.Frame.ComponentIds = new byte[componentCount];
861-
this.Frame.ComponentOrder = new byte[componentCount];
862-
this.Frame.Components = new JpegComponent[componentCount];
874+
if (maxH < h)
875+
{
876+
maxH = h;
877+
}
863878

864-
int maxH = 0;
865-
int maxV = 0;
866-
int index = 0;
867-
for (int i = 0; i < componentCount; i++)
879+
if (maxV < v)
868880
{
869-
byte hv = this.temp[index + 1];
870-
int h = (hv >> 4) & 15;
871-
int v = hv & 15;
881+
maxV = v;
882+
}
872883

873-
if (maxH < h)
874-
{
875-
maxH = h;
876-
}
884+
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
877885

878-
if (maxV < v)
879-
{
880-
maxV = v;
881-
}
886+
this.Frame.Components[i] = component;
887+
this.Frame.ComponentIds[i] = component.Id;
882888

883-
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
889+
index += componentBytes;
890+
}
884891

885-
this.Frame.Components[i] = component;
886-
this.Frame.ComponentIds[i] = component.Id;
892+
this.ColorSpace = this.DeduceJpegColorSpace(componentCount, this.Frame.Components);
887893

888-
index += componentBytes;
889-
}
894+
this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
890895

896+
if (!metadataOnly)
897+
{
891898
this.Frame.Init(maxH, maxV);
892-
893899
this.scanDecoder.InjectFrameData(this.Frame, this);
894900
}
895901
}

tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,19 @@ public async Task Identify_IsCancellable()
174174
await Assert.ThrowsAsync<TaskCanceledException>(async () => await Image.IdentifyAsync(config, "someFakeFile", cts.Token));
175175
}
176176

177+
// https://github.com/SixLabors/ImageSharp/pull/1732
178+
[Theory]
179+
[WithFile(TestImages.Jpeg.Issues.WrongColorSpace, PixelTypes.Rgba32)]
180+
public void Issue1732_DecodesWithRgbColorSpace<TPixel>(TestImageProvider<TPixel> provider)
181+
where TPixel : unmanaged, IPixel<TPixel>
182+
{
183+
using (Image<TPixel> image = provider.GetImage(new JpegDecoder()))
184+
{
185+
image.DebugSave(provider);
186+
image.CompareToOriginal(provider);
187+
}
188+
}
189+
177190
// DEBUG ONLY!
178191
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
179192
// into "\tests\Images\ActualOutput\JpegDecoderTests\"

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ public static class Issues
237237
public const string ExifResize1049 = "Jpg/issues/issue1049-exif-resize.jpg";
238238
public const string BadSubSampling1076 = "Jpg/issues/issue-1076-invalid-subsampling.jpg";
239239
public const string IdentifyMultiFrame1211 = "Jpg/issues/issue-1221-identify-multi-frame.jpg";
240+
public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg";
240241

241242
public static class Fuzz
242243
{
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)