diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
index b9cdad39d..c209cde3f 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
@@ -408,7 +408,24 @@ namespace ImageSharp.Formats
{
if (this.componentCount == 4)
{
- this.ConvertFromCmyk(this.imageWidth, this.imageHeight, image);
+ if (!this.adobeTransformValid)
+ {
+ throw new ImageFormatException("Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
+ }
+
+ // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
+ // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
+ // TODO: YCbCrA?
+ if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck)
+ {
+ this.ConvertFromYcck(this.imageWidth, this.imageHeight, image);
+ }
+ else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown)
+ {
+ // Assume CMYK
+ this.ConvertFromCmyk(this.imageWidth, this.imageHeight, image);
+ }
+
return;
}
@@ -1167,60 +1184,87 @@ namespace ImageSharp.Formats
}
///
- /// Converts the image from the original CMYK image pixels.
+ /// Converts the image from the original YCCK image pixels.
///
/// The pixel format.
/// The packed format. uint, long, float.
/// The image width.
/// The image height.
/// The image.
- private void ConvertFromCmyk(int width, int height, Image image)
+ private void ConvertFromYcck(int width, int height, Image image)
where TColor : struct, IPackedPixel
where TPacked : struct, IEquatable
{
- if (!this.adobeTransformValid)
+ int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
+
+ image.InitPixels(width, height);
+
+ using (PixelAccessor pixels = image.Lock())
{
- throw new ImageFormatException(
- "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
+ Parallel.For(
+ 0,
+ height,
+ y =>
+ {
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ byte yy = this.ycbcrImage.YChannel[yo + x];
+ byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
+ byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
+
+ TColor packed = default(TColor);
+ this.PackYcck(ref packed, yy, cb, cr, x, y);
+ pixels[x, y] = packed;
+ }
+ });
}
- // If the 4-component JPEG image isn't explicitly marked as "Unknown (RGB
- // or CMYK)" as per http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
- if (this.adobeTransform != JpegConstants.Adobe.ColorTransformUnknown)
- {
- int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
+ this.AssignResolution(image);
+ }
- image.InitPixels(width, height);
+ ///
+ /// Converts the image from the original CMYK image pixels.
+ ///
+ /// The pixel format.
+ /// The packed format. uint, long, float.
+ /// The image width.
+ /// The image height.
+ /// The image.
+ private void ConvertFromCmyk(int width, int height, Image image)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
- using (PixelAccessor pixels = image.Lock())
- {
- // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get
- // CMY, and patch in the original K. The RGB to CMY inversion cancels
- // out the 'Adobe inversion' described in the applyBlack doc comment
- // above, so in practice, only the fourth channel (black) is inverted.
- Parallel.For(
- 0,
- height,
- y =>
- {
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
+ image.InitPixels(width, height);
- for (int x = 0; x < width; x++)
- {
- byte yy = this.ycbcrImage.YChannel[yo + x];
- byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
+ using (PixelAccessor pixels = image.Lock())
+ {
+ Parallel.For(
+ 0,
+ height,
+ y =>
+ {
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
- TColor packed = default(TColor);
- this.PackCmyk(ref packed, yy, cb, cr, x, y);
- pixels[x, y] = packed;
- }
- });
- }
+ for (int x = 0; x < width; x++)
+ {
+ byte cyan = this.ycbcrImage.YChannel[yo + x];
+ byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)];
+ byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)];
- this.AssignResolution(image);
+ TColor packed = default(TColor);
+ this.PackCmyk(ref packed, cyan, magenta, yellow, x, y);
+ pixels[x, y] = packed;
+ }
+ });
}
+
+ this.AssignResolution(image);
}
///
@@ -2101,7 +2145,7 @@ namespace ImageSharp.Formats
}
///
- /// Optimized method to pack bytes to the image from the CMYK color space.
+ /// Optimized method to pack bytes to the image from the YCCK color space.
/// This is faster than implicit casting as it avoids double packing.
///
/// The pixel format.
@@ -2112,10 +2156,14 @@ namespace ImageSharp.Formats
/// The cr chroma component.
/// The x-position within the image.
/// The y-position within the image.
- private void PackCmyk(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy)
+ private void PackYcck(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy)
where TColor : struct, IPackedPixel
where TPacked : struct, IEquatable
{
+ // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get
+ // CMY, and patch in the original K. The RGB to CMY inversion cancels
+ // out the 'Adobe inversion' described in the applyBlack doc comment
+ // above, so in practice, only the fourth channel (black) is inverted.
// TODO: We can speed this up further with Vector4
int ccb = cb - 128;
int ccr = cr - 128;
@@ -2136,6 +2184,33 @@ namespace ImageSharp.Formats
packed.PackFromBytes(r, g, b, 255);
}
+ ///
+ /// Optimized method to pack bytes to the image from the CMYK color space.
+ /// This is faster than implicit casting as it avoids double packing.
+ ///
+ /// The pixel format.
+ /// The packed format. uint, long, float.
+ /// The packed pixel.
+ /// The cyan component.
+ /// The magenta component.
+ /// The yellow component.
+ /// The x-position within the image.
+ /// The y-position within the image.
+ private void PackCmyk(ref TColor packed, byte c, byte m, byte y, int xx, int yy)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ // Get keyline
+ float keyline = (255 - this.blackPixels[(yy * this.blackStride) + xx]) / 255F;
+
+ // Convert back to RGB. CMY are not inverted
+ byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
+ byte g = (byte)(((m / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
+ byte b = (byte)(((y / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
+
+ packed.PackFromBytes(r, g, b, 255);
+ }
+
///
/// Represents a component scan
///
diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs
index 18b3863cb..80db710cd 100644
--- a/tests/ImageSharp.Tests/FileTestBase.cs
+++ b/tests/ImageSharp.Tests/FileTestBase.cs
@@ -22,6 +22,7 @@ namespace ImageSharp.Tests
// new TestFile(TestImages.Png.Pd), // Perf: Enable for local testing only
// new TestFile(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only
new TestFile(TestImages.Jpeg.Calliphora),
+ // new TestFile(TestImages.Jpeg.Ycck), // Perf: Enable for local testing only
// new TestFile(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only
new TestFile(TestImages.Jpeg.Turtle),
// new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 9c0a9d66c..941aa376e 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -41,6 +41,7 @@ namespace ImageSharp.Tests
public static string Exif => folder + "exif.jpg";
public static string Floorplan => folder + "Floorplan.jpg";
public static string Calliphora => folder + "Calliphora.jpg";
+ public static string Ycck => folder + "ycck.jpg";
public static string Turtle => folder + "turtle.jpg";
public static string Fb => folder + "fb.jpg";
public static string Progress => folder + "progress.jpg";
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/cmyk.jpg b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/cmyk.jpg
index 30e88773e..8837cfed3 100644
Binary files a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/cmyk.jpg and b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/cmyk.jpg differ
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/ycck.jpg b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/ycck.jpg
new file mode 100644
index 000000000..30e88773e
Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/ycck.jpg differ