Skip to content

Commit bbe396e

Browse files
Merge pull request #2126 from SixLabors/backport/issue-2123
Backport - Issue 2123
2 parents 3139a8a + 4a42893 commit bbe396e

19 files changed

+125
-12
lines changed

.github/workflows/build-and-test.yml

+2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ on:
44
push:
55
branches:
66
- main
7+
- "release/*"
78
tags:
89
- "v*"
910
pull_request:
1011
branches:
1112
- main
13+
- "release/*"
1214
jobs:
1315
Build:
1416
strategy:

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

+60-5
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
8989
/// </summary>
9090
private JFifMarker jFif;
9191

92+
/// <summary>
93+
/// Whether the image has a JFIF marker. This is needed to determine, if the colorspace is YCbCr.
94+
/// </summary>
95+
private bool hasJFif;
96+
9297
/// <summary>
9398
/// Contains information about the Adobe marker.
9499
/// </summary>
@@ -488,27 +493,75 @@ private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
488493

489494
if (componentCount == 3)
490495
{
491-
if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
496+
// We prioritize adobe marker over jfif marker, if somebody really encoded this image with redundant adobe marker,
497+
// then it's most likely an adobe jfif image.
498+
if (!this.adobe.Equals(default))
492499
{
493-
return JpegColorSpace.RGB;
500+
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr)
501+
{
502+
return JpegColorSpace.YCbCr;
503+
}
504+
505+
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
506+
{
507+
return JpegColorSpace.RGB;
508+
}
509+
510+
// Fallback to the id color deduction: If these values are 1-3 for a 3-channel image, then the image is assumed to be YCbCr.
511+
if (this.Components[2].Id == 3 && this.Components[1].Id == 2 && this.Components[0].Id == 1)
512+
{
513+
return JpegColorSpace.YCbCr;
514+
}
515+
516+
JpegThrowHelper.ThrowNotSupportedColorSpace();
517+
}
518+
519+
if (this.hasJFif)
520+
{
521+
// JFIF implies YCbCr.
522+
return JpegColorSpace.YCbCr;
494523
}
495524

525+
// Fallback to the id color deduction.
496526
// If the component Id's are R, G, B in ASCII the colorspace is RGB and not YCbCr.
527+
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#color
497528
if (this.Components[2].Id == 66 && this.Components[1].Id == 71 && this.Components[0].Id == 82)
498529
{
499530
return JpegColorSpace.RGB;
500531
}
501532

533+
// 3-channel non-subsampled images are assumed to be RGB.
534+
if (this.Components[2].VerticalSamplingFactor == 1 && this.Components[1].VerticalSamplingFactor == 1 && this.Components[0].VerticalSamplingFactor == 1 &&
535+
this.Components[2].HorizontalSamplingFactor == 1 && this.Components[1].HorizontalSamplingFactor == 1 && this.Components[0].HorizontalSamplingFactor == 1)
536+
{
537+
return JpegColorSpace.RGB;
538+
}
539+
502540
// Some images are poorly encoded and contain incorrect colorspace transform metadata.
503541
// We ignore that and always fall back to the default colorspace.
504542
return JpegColorSpace.YCbCr;
505543
}
506544

507545
if (componentCount == 4)
508546
{
509-
return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck
510-
? JpegColorSpace.Ycck
511-
: JpegColorSpace.Cmyk;
547+
// jfif images doesn't not support 4 component images, so we only check adobe.
548+
if (!this.adobe.Equals(default))
549+
{
550+
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck)
551+
{
552+
return JpegColorSpace.Ycck;
553+
}
554+
555+
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
556+
{
557+
return JpegColorSpace.Cmyk;
558+
}
559+
560+
JpegThrowHelper.ThrowNotSupportedColorSpace();
561+
}
562+
563+
// Fallback to cmyk as neither of cmyk nor ycck have 'special' component ids.
564+
return JpegColorSpace.Cmyk;
512565
}
513566

514567
JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount);
@@ -675,6 +728,8 @@ private void ExtendProfile(ref byte[] profile, byte[] extension)
675728
/// <param name="remaining">The remaining bytes in the segment block.</param>
676729
private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining)
677730
{
731+
this.hasJFif = true;
732+
678733
// We can only decode JFif identifiers.
679734
// Some images contain multiple JFIF markers (Issue 1932) so we check to see
680735
// if it's already been read.

src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs

+3
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,8 @@ internal static class JpegThrowHelper
5151

5252
[MethodImpl(InliningOptions.ColdPath)]
5353
public static void ThrowNotSupportedComponentCount(int componentCount) => throw new NotSupportedException($"Images with {componentCount} components are not supported.");
54+
55+
[MethodImpl(InliningOptions.ColdPath)]
56+
public static void ThrowNotSupportedColorSpace() => throw new NotSupportedException("Image color space could not be deduced.");
5457
}
5558
}

src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs

+7
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,15 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3939
Span<byte> tmpBufferSpan = tmpBuffer.GetSpan();
4040
ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], data, tmpBufferSpan);
4141
ycbcrData = tmpBufferSpan;
42+
this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData);
43+
return;
4244
}
4345

46+
this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData);
47+
}
48+
49+
private void DecodeYCbCrData(Buffer2D<TPixel> pixels, int left, int top, int width, int height, ReadOnlySpan<byte> ycbcrData)
50+
{
4451
var color = default(TPixel);
4552
int offset = 0;
4653
int widthPadding = 0;

src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

+8
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,14 @@ private static void ParseCompression(this TiffDecoderCore options, TiffCompressi
459459
case TiffCompression.Jpeg:
460460
{
461461
options.CompressionType = TiffDecoderCompressionType.Jpeg;
462+
463+
if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null)
464+
{
465+
// Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data.
466+
options.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
467+
options.ColorType = TiffColorType.Rgb;
468+
}
469+
462470
break;
463471
}
464472

tests/Directory.Build.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<PackageReference Update="BenchmarkDotNet" Version="0.13.0" />
2222
<PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.0" Condition="'$(IsWindows)'=='true'" />
2323
<PackageReference Update="Colourful" Version="3.0.0" />
24-
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="8.0.1" />
24+
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="11.1.2" />
2525
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="6.0.0-beta.21311.3" />
2626
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="6.0.0-beta.21311.3" />
2727
<PackageReference Update="Moq" Version="4.14.6" />

tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,6 @@ public void TiffDecoder_CanDecode_24Bit_Gray<TPixel>(TestImageProvider<TPixel> p
303303
[Theory]
304304
[WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)]
305305
[WithFile(FlowerYCbCr888Planar, PixelTypes.Rgba32)]
306-
[WithFile(RgbYCbCr888Contiguoush1v1, PixelTypes.Rgba32)]
307-
[WithFile(RgbYCbCr888Contiguoush2v1, PixelTypes.Rgba32)]
308306
[WithFile(RgbYCbCr888Contiguoush2v2, PixelTypes.Rgba32)]
309307
[WithFile(RgbYCbCr888Contiguoush4v4, PixelTypes.Rgba32)]
310308
[WithFile(FlowerYCbCr888Contiguoush2v1, PixelTypes.Rgba32)]
@@ -317,6 +315,12 @@ public void TiffDecoder_CanDecode_YCbCr_24Bit<TPixel>(TestImageProvider<TPixel>
317315
// converting the pixel data from Magick.NET to our format with YCbCr?
318316
using Image<TPixel> image = provider.GetImage();
319317
image.DebugSave(provider);
318+
319+
#if NETCOREAPP
320+
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
321+
#else
322+
image.CompareToReferenceOutput(TolerantImageComparer.TolerantPercentage(0.0002F), provider);
323+
#endif
320324
}
321325

322326
[Theory]
@@ -642,10 +646,13 @@ public void CanDecodeJustOneFrame<TPixel>(TestImageProvider<TPixel> provider)
642646

643647
[Theory]
644648
[WithFile(RgbJpegCompressed, PixelTypes.Rgba32)]
649+
[WithFile(RgbJpegCompressed2, PixelTypes.Rgba32)]
645650
[WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)]
646651
[WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)]
652+
[WithFile(YCbCrJpegCompressed2, PixelTypes.Rgba32)]
647653
[WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)]
648654
[WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)]
655+
[WithFile(Issues2123, PixelTypes.Rgba32)]
649656
public void TiffDecoder_CanDecode_JpegCompressed<TPixel>(TestImageProvider<TPixel> provider)
650657
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider, useExactComparer: false);
651658

tests/ImageSharp.Tests/TestImages.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ public static class Tiff
778778
public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff";
779779
public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff";
780780
public const string RgbJpegCompressed = "Tiff/rgb_jpegcompression.tiff";
781+
public const string RgbJpegCompressed2 = "Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff";
781782
public const string RgbWithStripsJpegCompressed = "Tiff/rgb_jpegcompressed_stripped.tiff";
782783
public const string RgbJpegCompressedNoJpegTable = "Tiff/rgb_jpegcompressed_nojpegtable.tiff";
783784
public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.tiff";
@@ -823,11 +824,10 @@ public static class Tiff
823824
public const string FlowerYCbCr888Contiguoush2v1 = "Tiff/flower-ycbcr-contig-08_h2v1.tiff";
824825
public const string FlowerYCbCr888Contiguoush2v2 = "Tiff/flower-ycbcr-contig-08_h2v2.tiff";
825826
public const string FlowerYCbCr888Contiguoush4v4 = "Tiff/flower-ycbcr-contig-08_h4v4.tiff";
826-
public const string RgbYCbCr888Contiguoush1v1 = "Tiff/rgb-ycbcr-contig-08_h1v1.tiff";
827-
public const string RgbYCbCr888Contiguoush2v1 = "Tiff/rgb-ycbcr-contig-08_h2v1.tiff";
828827
public const string RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff";
829828
public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff";
830829
public const string YCbCrJpegCompressed = "Tiff/ycbcr_jpegcompressed.tiff";
830+
public const string YCbCrJpegCompressed2 = "Tiff/ycbcr_jpegcompressed2.tiff";
831831
public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff";
832832
public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff";
833833
public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff";
@@ -910,6 +910,7 @@ public static class Tiff
910910

911911
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
912912
public const string Issues1891 = "Tiff/Issues/Issue1891.tiff";
913+
public const string Issues2123 = "Tiff/Issues/Issue2123.tiff";
913914

914915
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
915916
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:5663288720203035c454c61c529222c2170df21dcde1a89f1f30e3b668020d6f
3+
size 3805
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:6b81013d7b0a29ed1ac9c33e175e0c0e69494b93b2b65b692f16d9ea042b9d5d
3-
size 7759
2+
oid sha256:3e889209fc31702aaa7c966c1b5370cc0904cbfbcfd17718977045049cc1bfd9
3+
size 5904
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:d7a559d36e3852265ab4f82e43d28cc0bfc310813a5ced08e51c1366d8e323f9
3+
size 146853
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:533f92b6a45c2de4dc0f3cdb8debf45dcfe84790cfa652404b2f44e15f06e44f
3+
size 38816

0 commit comments

Comments
 (0)