Use SkSwizzler to convert from CMYK

Move convert_CMYK_to_RGBA into two functions in SkSwizzler: one for 565
and one for 8888.

For simplicity, when converting to 8888, we no longer convert in place.

BUG=skia:4476

Committed: https://skia.googlesource.com/skia/+/450ee8f26d39f975cf6af37a27de658ae5a9fa10

Review URL: https://codereview.chromium.org/1411083009
This commit is contained in:
scroggo 2015-10-23 09:29:22 -07:00 committed by Commit bot
parent 19e82e3b9f
commit ef27d89b07
4 changed files with 145 additions and 100 deletions

View File

@ -23,69 +23,6 @@ extern "C" {
#include "jpeglib.h" #include "jpeglib.h"
} }
/*
* Convert a row of CMYK samples to RGBA in place.
* Note that this method moves the row pointer.
* @param width the number of pixels in the row that is being converted
* CMYK is stored as four bytes per pixel
*/
static void convert_CMYK_to_RGBA(uint8_t* row, uint32_t width) {
// We will implement a crude conversion from CMYK -> RGB using formulas
// from easyrgb.com.
//
// CMYK -> CMY
// C = C * (1 - K) + K
// M = M * (1 - K) + K
// Y = Y * (1 - K) + K
//
// libjpeg actually gives us inverted CMYK, so we must subtract the
// original terms from 1.
// CMYK -> CMY
// C = (1 - C) * (1 - (1 - K)) + (1 - K)
// M = (1 - M) * (1 - (1 - K)) + (1 - K)
// Y = (1 - Y) * (1 - (1 - K)) + (1 - K)
//
// Simplifying the above expression.
// CMYK -> CMY
// C = 1 - CK
// M = 1 - MK
// Y = 1 - YK
//
// CMY -> RGB
// R = (1 - C) * 255
// G = (1 - M) * 255
// B = (1 - Y) * 255
//
// Therefore the full conversion is below. This can be verified at
// www.rapidtables.com (assuming inverted CMYK).
// CMYK -> RGB
// R = C * K * 255
// G = M * K * 255
// B = Y * K * 255
//
// As a final note, we have treated the CMYK values as if they were on
// a scale from 0-1, when in fact they are 8-bit ints scaling from 0-255.
// We must divide each CMYK component by 255 to obtain the true conversion
// we should perform.
// CMYK -> RGB
// R = C * K / 255
// G = M * K / 255
// B = Y * K / 255
for (uint32_t x = 0; x < width; x++, row += 4) {
#if defined(SK_PMCOLOR_IS_RGBA)
row[0] = SkMulDiv255Round(row[0], row[3]);
row[1] = SkMulDiv255Round(row[1], row[3]);
row[2] = SkMulDiv255Round(row[2], row[3]);
#else
uint8_t tmp = row[0];
row[0] = SkMulDiv255Round(row[2], row[3]);
row[1] = SkMulDiv255Round(row[1], row[3]);
row[2] = SkMulDiv255Round(tmp, row[3]);
#endif
row[3] = 0xFF;
}
}
bool SkJpegCodec::IsJpeg(SkStream* stream) { bool SkJpegCodec::IsJpeg(SkStream* stream) {
static const uint8_t jpegSig[] = { 0xFF, 0xD8, 0xFF }; static const uint8_t jpegSig[] = { 0xFF, 0xD8, 0xFF };
char buffer[sizeof(jpegSig)]; char buffer[sizeof(jpegSig)];
@ -263,10 +200,7 @@ bool SkJpegCodec::setOutputColorSpace(const SkImageInfo& dst) {
return true; return true;
case kRGB_565_SkColorType: case kRGB_565_SkColorType:
if (isCMYK) { if (isCMYK) {
// FIXME (msarett): We need to support 565 here. It's not hard to do, considering fDecoderMgr->dinfo()->out_color_space = JCS_CMYK;
// we already convert CMYK to RGBA, I just need to do it. I think it might be
// best to do this in SkSwizzler and also move convert_CMYK_to_RGBA into SkSwizzler.
return false;
} else { } else {
#if defined(GOOGLE3) #if defined(GOOGLE3)
return false; return false;
@ -365,9 +299,22 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
// If it's not, we want to know because it means our strategy is not optimal. // If it's not, we want to know because it means our strategy is not optimal.
SkASSERT(1 == dinfo->rec_outbuf_height); SkASSERT(1 == dinfo->rec_outbuf_height);
if (JCS_CMYK == dinfo->out_color_space) {
this->initializeSwizzler(dstInfo, options);
}
// Perform the decode a single row at a time // Perform the decode a single row at a time
uint32_t dstHeight = dstInfo.height(); uint32_t dstHeight = dstInfo.height();
JSAMPLE* dstRow = (JSAMPLE*) dst;
JSAMPLE* dstRow;
if (fSwizzler) {
// write data to storage row, then sample using swizzler
dstRow = fSrcRow;
} else {
// write data directly to dst
dstRow = (JSAMPLE*) dst;
}
for (uint32_t y = 0; y < dstHeight; y++) { for (uint32_t y = 0; y < dstHeight; y++) {
// Read rows of the image // Read rows of the image
uint32_t lines = jpeg_read_scanlines(dinfo, &dstRow, 1); uint32_t lines = jpeg_read_scanlines(dinfo, &dstRow, 1);
@ -379,13 +326,13 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
return fDecoderMgr->returnFailure("Incomplete image data", kIncompleteInput); return fDecoderMgr->returnFailure("Incomplete image data", kIncompleteInput);
} }
// Convert to RGBA if necessary if (fSwizzler) {
if (JCS_CMYK == dinfo->out_color_space) { // use swizzler to sample row
convert_CMYK_to_RGBA(dstRow, dstInfo.width()); fSwizzler->swizzle(dst, dstRow);
dst = SkTAddOffset<JSAMPLE>(dst, dstRowBytes);
} else {
dstRow = SkTAddOffset<JSAMPLE>(dstRow, dstRowBytes);
} }
// Move to the next row
dstRow = SkTAddOffset<JSAMPLE>(dstRow, dstRowBytes);
} }
return kSuccess; return kSuccess;
@ -393,26 +340,30 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options) { void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options) {
SkSwizzler::SrcConfig srcConfig = SkSwizzler::kUnknown; SkSwizzler::SrcConfig srcConfig = SkSwizzler::kUnknown;
switch (dstInfo.colorType()) { if (JCS_CMYK == fDecoderMgr->dinfo()->out_color_space) {
case kGray_8_SkColorType: srcConfig = SkSwizzler::kCMYK;
srcConfig = SkSwizzler::kGray; } else {
break; switch (dstInfo.colorType()) {
case kRGBA_8888_SkColorType: case kGray_8_SkColorType:
srcConfig = SkSwizzler::kRGBX; srcConfig = SkSwizzler::kGray;
break; break;
case kBGRA_8888_SkColorType: case kRGBA_8888_SkColorType:
srcConfig = SkSwizzler::kBGRX; srcConfig = SkSwizzler::kRGBX;
break; break;
case kRGB_565_SkColorType: case kBGRA_8888_SkColorType:
srcConfig = SkSwizzler::kRGB_565; srcConfig = SkSwizzler::kBGRX;
break; break;
default: case kRGB_565_SkColorType:
// This function should only be called if the colorType is supported by jpeg srcConfig = SkSwizzler::kRGB_565;
break;
default:
// This function should only be called if the colorType is supported by jpeg
#if defined(GOOGLE3) #if defined(GOOGLE3)
SK_CRASH(); SK_CRASH();
#else #else
SkASSERT(false); SkASSERT(false);
#endif #endif
}
} }
fSwizzler.reset(SkSwizzler::CreateSwizzler(srcConfig, nullptr, dstInfo, options)); fSwizzler.reset(SkSwizzler::CreateSwizzler(srcConfig, nullptr, dstInfo, options));
@ -454,8 +405,9 @@ SkCodec::Result SkJpegCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
return kInvalidInput; return kInvalidInput;
} }
// We will need a swizzler if we are performing a subset decode // We will need a swizzler if we are performing a subset decode or
if (options.fSubset) { // converting from CMYK.
if (options.fSubset || JCS_CMYK == fDecoderMgr->dinfo()->out_color_space) {
this->initializeSwizzler(dstInfo, options); this->initializeSwizzler(dstInfo, options);
} }
@ -485,12 +437,7 @@ int SkJpegCodec::onGetScanlines(void* dst, int count, size_t rowBytes) {
return y; return y;
} }
// Convert to RGBA if necessary if (fSwizzler) {
if (JCS_CMYK == fDecoderMgr->dinfo()->out_color_space) {
convert_CMYK_to_RGBA(dstRow, fDecoderMgr->dinfo()->output_width);
}
if(fSwizzler) {
// use swizzler to sample row // use swizzler to sample row
fSwizzler->swizzle(dst, dstRow); fSwizzler->swizzle(dst, dstRow);
dst = SkTAddOffset<JSAMPLE>(dst, rowBytes); dst = SkTAddOffset<JSAMPLE>(dst, rowBytes);

View File

@ -480,6 +480,89 @@ static SkSwizzler::ResultAlpha swizzle_rgba_to_n32_premul_skipZ(
return COMPUTE_RESULT_ALPHA; return COMPUTE_RESULT_ALPHA;
} }
// kCMYK
//
// CMYK is stored as four bytes per pixel.
//
// We will implement a crude conversion from CMYK -> RGB using formulas
// from easyrgb.com.
//
// CMYK -> CMY
// C = C * (1 - K) + K
// M = M * (1 - K) + K
// Y = Y * (1 - K) + K
//
// libjpeg actually gives us inverted CMYK, so we must subtract the
// original terms from 1.
// CMYK -> CMY
// C = (1 - C) * (1 - (1 - K)) + (1 - K)
// M = (1 - M) * (1 - (1 - K)) + (1 - K)
// Y = (1 - Y) * (1 - (1 - K)) + (1 - K)
//
// Simplifying the above expression.
// CMYK -> CMY
// C = 1 - CK
// M = 1 - MK
// Y = 1 - YK
//
// CMY -> RGB
// R = (1 - C) * 255
// G = (1 - M) * 255
// B = (1 - Y) * 255
//
// Therefore the full conversion is below. This can be verified at
// www.rapidtables.com (assuming inverted CMYK).
// CMYK -> RGB
// R = C * K * 255
// G = M * K * 255
// B = Y * K * 255
//
// As a final note, we have treated the CMYK values as if they were on
// a scale from 0-1, when in fact they are 8-bit ints scaling from 0-255.
// We must divide each CMYK component by 255 to obtain the true conversion
// we should perform.
// CMYK -> RGB
// R = C * K / 255
// G = M * K / 255
// B = Y * K / 255
static SkSwizzler::ResultAlpha swizzle_cmyk_to_n32(
void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth,
int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) {
src += offset;
SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
for (int x = 0; x < dstWidth; x++) {
const uint8_t r = SkMulDiv255Round(src[0], src[3]);
const uint8_t g = SkMulDiv255Round(src[1], src[3]);
const uint8_t b = SkMulDiv255Round(src[2], src[3]);
dst[x] = SkPackARGB32NoCheck(0xFF, r, g, b);
src += deltaSrc;
}
// CMYK is always opaque
return SkSwizzler::kOpaque_ResultAlpha;
}
static SkSwizzler::ResultAlpha swizzle_cmyk_to_565(
void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth,
int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) {
src += offset;
uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
for (int x = 0; x < dstWidth; x++) {
const uint8_t r = SkMulDiv255Round(src[0], src[3]);
const uint8_t g = SkMulDiv255Round(src[1], src[3]);
const uint8_t b = SkMulDiv255Round(src[2], src[3]);
dst[x] = SkPack888ToRGB16(r, g, b);
src += deltaSrc;
}
// CMYK is always opaque
return SkSwizzler::kOpaque_ResultAlpha;
}
/** /**
FIXME: This was my idea to cheat in order to continue taking advantage of skipping zeroes. FIXME: This was my idea to cheat in order to continue taking advantage of skipping zeroes.
This would be fine for drawing normally, but not for drawing with transfer modes. Being This would be fine for drawing normally, but not for drawing with transfer modes. Being
@ -672,6 +755,19 @@ SkSwizzler* SkSwizzler::CreateSwizzler(SkSwizzler::SrcConfig sc,
default: default:
break; break;
} }
break;
case kCMYK:
switch (dstInfo.colorType()) {
case kN32_SkColorType:
proc = &swizzle_cmyk_to_n32;
break;
case kRGB_565_SkColorType:
proc = &swizzle_cmyk_to_565;
break;
default:
break;
}
break;
default: default:
break; break;
} }

View File

@ -33,6 +33,7 @@ public:
kRGBA, kRGBA,
kBGRA, kBGRA,
kRGB_565, kRGB_565,
kCMYK,
}; };
/* /*
@ -97,6 +98,7 @@ public:
case kRGBA: case kRGBA:
case kBGRX: case kBGRX:
case kBGRA: case kBGRA:
case kCMYK:
return 32; return 32;
default: default:
SkASSERT(false); SkASSERT(false);

View File

@ -408,7 +408,7 @@ DEF_TEST(Codec, r) {
check(r, "randPixels.gif", SkISize::Make(8, 8), true, false, true, false); check(r, "randPixels.gif", SkISize::Make(8, 8), true, false, true, false);
// JPG // JPG
check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false, false); check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false, true);
check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false); check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false);
// grayscale.jpg is too small to test incomplete // grayscale.jpg is too small to test incomplete
check(r, "grayscale.jpg", SkISize::Make(128, 128), true, false, true, false); check(r, "grayscale.jpg", SkISize::Make(128, 128), true, false, true, false);