Skip to content

Commit b506e92

Browse files
committed
Add additional YCbCr subsample rates
1 parent 5834e4f commit b506e92

File tree

8 files changed

+94
-10
lines changed

8 files changed

+94
-10
lines changed

src/ImageSharp/Formats/Jpeg/JpegColorType.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,45 @@ public enum JpegColorType : byte
2222
/// </summary>
2323
YCbCrRatio444 = 1,
2424

25+
/// <summary>
26+
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
27+
/// The two chroma components are sampled at half the horizontal sample rate of luma while vertically it has full resolution.
28+
///
29+
/// Note: Not supported by the encoder.
30+
/// </summary>
31+
YCbCrRatio422 = 2,
32+
33+
/// <summary>
34+
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
35+
/// In 4:1:1 chroma subsampling, the horizontal color resolution is quartered.
36+
///
37+
/// Note: Not supported by the encoder.
38+
/// </summary>
39+
YCbCrRatio411 = 3,
40+
41+
/// <summary>
42+
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
43+
/// This ratio uses half of the vertical and one-fourth the horizontal color resolutions.
44+
///
45+
/// Note: Not supported by the encoder.
46+
/// </summary>
47+
YCbCrRatio410 = 4,
48+
2549
/// <summary>
2650
/// Single channel, luminance.
2751
/// </summary>
28-
Luminance = 2,
52+
Luminance = 5,
2953

3054
/// <summary>
3155
/// The pixel data will be preserved as RGB without any sub sampling.
3256
/// </summary>
33-
Rgb = 3,
57+
Rgb = 6,
58+
59+
/// <summary>
60+
/// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing.
61+
///
62+
/// Note: Not supported by the encoder.
63+
/// </summary>
64+
Cmyk = 7,
3465
}
3566
}

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,20 +457,49 @@ private JpegColorType DeduceJpegColorType()
457457
{
458458
case JpegColorSpace.Grayscale:
459459
return JpegColorType.Luminance;
460+
460461
case JpegColorSpace.RGB:
461462
return JpegColorType.Rgb;
463+
462464
case JpegColorSpace.YCbCr:
463465
if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
464466
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
465467
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
466468
{
467469
return JpegColorType.YCbCrRatio444;
468470
}
471+
else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
472+
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
473+
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
474+
{
475+
return JpegColorType.YCbCrRatio420;
476+
}
477+
else if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
478+
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 2 &&
479+
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 2)
480+
{
481+
return JpegColorType.YCbCrRatio422;
482+
}
483+
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
484+
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
485+
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
486+
{
487+
return JpegColorType.YCbCrRatio411;
488+
}
489+
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
490+
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
491+
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
492+
{
493+
return JpegColorType.YCbCrRatio410;
494+
}
469495
else
470496
{
471497
return JpegColorType.YCbCrRatio420;
472498
}
473499

500+
case JpegColorSpace.Cmyk:
501+
return JpegColorType.Cmyk;
502+
474503
default:
475504
return JpegColorType.YCbCrRatio420;
476505
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ public void Decode_VerifyQuality(string imagePath, int quality)
143143
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegColorType.YCbCrRatio420)]
144144
[InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegColorType.YCbCrRatio444)]
145145
[InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegColorType.Rgb)]
146+
[InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorType.Cmyk)]
147+
[InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegColorType.YCbCrRatio410)]
148+
[InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegColorType.YCbCrRatio422)]
149+
[InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegColorType.YCbCrRatio411)]
146150
public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType expectedColorType)
147151
{
148152
var testFile = TestFile.Create(imagePath);
@@ -159,6 +163,7 @@ public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType exp
159163
[WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegColorType.YCbCrRatio420)]
160164
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
161165
[WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegColorType.Rgb)]
166+
[WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgba32, JpegColorType.Cmyk)]
162167
public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType expectedColorType)
163168
where TPixel : unmanaged, IPixel<TPixel>
164169
{

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,18 @@ public void Encode_PreservesColorType<TPixel>(TestImageProvider<TPixel> provider
9292
[WithFile(TestImages.Jpeg.Baseline.Jpeg410, PixelTypes.Rgba32)]
9393
[WithFile(TestImages.Jpeg.Baseline.Jpeg411, PixelTypes.Rgba32)]
9494
[WithFile(TestImages.Jpeg.Baseline.Jpeg422, PixelTypes.Rgba32)]
95-
public void Encode_WithUnsupportedColorType_FromInput_DefaultsToYCbCr420<TPixel>(TestImageProvider<TPixel> provider)
95+
public void Encode_WithUnsupportedColorType_FromInputImage_DefaultsToYCbCr420<TPixel>(TestImageProvider<TPixel> provider)
9696
where TPixel : unmanaged, IPixel<TPixel>
9797
{
9898
// arrange
9999
using Image<TPixel> input = provider.GetImage(JpegDecoder);
100100
using var memoryStream = new MemoryStream();
101101

102102
// act
103-
input.Save(memoryStream, JpegEncoder);
103+
input.Save(memoryStream, new JpegEncoder()
104+
{
105+
Quality = 75
106+
});
104107

105108
// assert
106109
memoryStream.Position = 0;
@@ -110,12 +113,11 @@ public void Encode_WithUnsupportedColorType_FromInput_DefaultsToYCbCr420<TPixel>
110113
}
111114

112115
[Theory]
113-
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.Cmyk)]
114-
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio410)]
115-
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio411)]
116-
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio422)]
117-
public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType)
118-
where TPixel : unmanaged, IPixel<TPixel>
116+
[InlineData(JpegColorType.Cmyk)]
117+
[InlineData(JpegColorType.YCbCrRatio410)]
118+
[InlineData(JpegColorType.YCbCrRatio411)]
119+
[InlineData(JpegColorType.YCbCrRatio422)]
120+
public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegColorType colorType)
119121
{
120122
// arrange
121123
var jpegEncoder = new JpegEncoder() { ColorType = colorType };
@@ -153,6 +155,11 @@ public void Encode_PreservesQuality(string imagePath, int quality)
153155
}
154156
}
155157

158+
[Theory]
159+
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
160+
public void EncodeBaseline_CalliphoraPartial<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
161+
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
162+
156163
[Theory]
157164
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
158165
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)]

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ public static class Bad
191191
public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg";
192192
public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg";
193193
public const string JpegRgb = "Jpg/baseline/jpeg-rgb.jpg";
194+
public const string Jpeg410 = "Jpg/baseline/jpeg410.jpg";
195+
public const string Jpeg411 = "Jpg/baseline/jpeg411.jpg";
196+
public const string Jpeg422 = "Jpg/baseline/jpeg422.jpg";
194197
public const string Testorig420 = "Jpg/baseline/testorig.jpg";
195198
public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg";
196199
public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg";
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)