Browse Source

Decode CMYK and YCCK images Fix #40

We can decode the images but there is a loss of accuracy due to a lack
of ICC support.
af/merge-core
James Jackson-South 10 years ago
parent
commit
1fd9afcba6
  1. 153
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  2. 1
      tests/ImageSharp.Tests/FileTestBase.cs
  3. 1
      tests/ImageSharp.Tests/TestImages.cs
  4. 4
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/cmyk.jpg
  5. 3
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/ycck.jpg

153
src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs

@ -408,7 +408,24 @@ namespace ImageSharp.Formats
{ {
if (this.componentCount == 4) 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; return;
} }
@ -1167,60 +1184,87 @@ namespace ImageSharp.Formats
} }
/// <summary> /// <summary>
/// Converts the image from the original CMYK image pixels. /// Converts the image from the original YCCK image pixels.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="height">The image height.</param> /// <param name="height">The image height.</param>
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
private void ConvertFromCmyk<TColor, TPacked>(int width, int height, Image<TColor, TPacked> image) private void ConvertFromYcck<TColor, TPacked>(int width, int height, Image<TColor, TPacked> image)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked> where TPacked : struct, IEquatable<TPacked>
{ {
if (!this.adobeTransformValid) int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
image.InitPixels(width, height);
using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{ {
throw new ImageFormatException( Parallel.For(
"Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); 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<TColor, TPacked>(ref packed, yy, cb, cr, x, y);
pixels[x, y] = packed;
}
});
} }
// If the 4-component JPEG image isn't explicitly marked as "Unknown (RGB this.AssignResolution(image);
// 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;
image.InitPixels(width, height); /// <summary>
/// Converts the image from the original CMYK image pixels.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <param name="image">The image.</param>
private void ConvertFromCmyk<TColor, TPacked>(int width, int height, Image<TColor, TPacked> image)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
using (PixelAccessor<TColor, TPacked> pixels = image.Lock()) image.InitPixels(width, height);
{
// 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);
for (int x = 0; x < width; x++) using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{ {
byte yy = this.ycbcrImage.YChannel[yo + x]; Parallel.For(
byte cb = this.ycbcrImage.CbChannel[co + (x / scale)]; 0,
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; height,
y =>
{
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
TColor packed = default(TColor); for (int x = 0; x < width; x++)
this.PackCmyk<TColor, TPacked>(ref packed, yy, cb, cr, x, y); {
pixels[x, y] = packed; 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<TColor, TPacked>(ref packed, cyan, magenta, yellow, x, y);
pixels[x, y] = packed;
}
});
} }
this.AssignResolution(image);
} }
/// <summary> /// <summary>
@ -2101,7 +2145,7 @@ namespace ImageSharp.Formats
} }
/// <summary> /// <summary>
/// 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. /// This is faster than implicit casting as it avoids double packing.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
@ -2112,10 +2156,14 @@ namespace ImageSharp.Formats
/// <param name="cr">The cr chroma component.</param> /// <param name="cr">The cr chroma component.</param>
/// <param name="xx">The x-position within the image.</param> /// <param name="xx">The x-position within the image.</param>
/// <param name="yy">The y-position within the image.</param> /// <param name="yy">The y-position within the image.</param>
private void PackCmyk<TColor, TPacked>(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy) private void PackYcck<TColor, TPacked>(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked> where TPacked : struct, IEquatable<TPacked>
{ {
// 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 // TODO: We can speed this up further with Vector4
int ccb = cb - 128; int ccb = cb - 128;
int ccr = cr - 128; int ccr = cr - 128;
@ -2136,6 +2184,33 @@ namespace ImageSharp.Formats
packed.PackFromBytes(r, g, b, 255); packed.PackFromBytes(r, g, b, 255);
} }
/// <summary>
/// Optimized method to pack bytes to the image from the CMYK color space.
/// This is faster than implicit casting as it avoids double packing.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="packed">The packed pixel.</param>
/// <param name="c">The cyan component.</param>
/// <param name="m">The magenta component.</param>
/// <param name="y">The yellow component.</param>
/// <param name="xx">The x-position within the image.</param>
/// <param name="yy">The y-position within the image.</param>
private void PackCmyk<TColor, TPacked>(ref TColor packed, byte c, byte m, byte y, int xx, int yy)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
// 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);
}
/// <summary> /// <summary>
/// Represents a component scan /// Represents a component scan
/// </summary> /// </summary>

1
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.Png.Pd), // Perf: Enable for local testing only
// new TestFile(TestImages.Jpeg.Floorplan), // 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.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.Cmyk), // Perf: Enable for local testing only
new TestFile(TestImages.Jpeg.Turtle), new TestFile(TestImages.Jpeg.Turtle),
// new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only // new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only

1
tests/ImageSharp.Tests/TestImages.cs

@ -41,6 +41,7 @@ namespace ImageSharp.Tests
public static string Exif => folder + "exif.jpg"; public static string Exif => folder + "exif.jpg";
public static string Floorplan => folder + "Floorplan.jpg"; public static string Floorplan => folder + "Floorplan.jpg";
public static string Calliphora => folder + "Calliphora.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 Turtle => folder + "turtle.jpg";
public static string Fb => folder + "fb.jpg"; public static string Fb => folder + "fb.jpg";
public static string Progress => folder + "progress.jpg"; public static string Progress => folder + "progress.jpg";

4
tests/ImageSharp.Tests/TestImages/Formats/Jpg/cmyk.jpg

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0 oid sha256:013c1673f618abec6afff4d9b9a69b95e0e8cc1f61aa34993208601c346b5677
size 611572 size 2531270

3
tests/ImageSharp.Tests/TestImages/Formats/Jpg/ycck.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0
size 611572
Loading…
Cancel
Save