diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp index 9975748cf4..6850a77825 100644 --- a/src/codec/SkJpegCodec.cpp +++ b/src/codec/SkJpegCodec.cpp @@ -23,69 +23,6 @@ extern "C" { #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) { static const uint8_t jpegSig[] = { 0xFF, 0xD8, 0xFF }; char buffer[sizeof(jpegSig)]; @@ -263,10 +200,7 @@ bool SkJpegCodec::setOutputColorSpace(const SkImageInfo& dst) { return true; case kRGB_565_SkColorType: if (isCMYK) { - // FIXME (msarett): We need to support 565 here. It's not hard to do, considering - // 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; + fDecoderMgr->dinfo()->out_color_space = JCS_CMYK; } else { #if defined(GOOGLE3) 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. 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 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++) { // Read rows of the image 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); } - // Convert to RGBA if necessary - if (JCS_CMYK == dinfo->out_color_space) { - convert_CMYK_to_RGBA(dstRow, dstInfo.width()); + if (fSwizzler) { + // use swizzler to sample row + fSwizzler->swizzle(dst, dstRow); + dst = SkTAddOffset(dst, dstRowBytes); + } else { + dstRow = SkTAddOffset(dstRow, dstRowBytes); } - - // Move to the next row - dstRow = SkTAddOffset(dstRow, dstRowBytes); } return kSuccess; @@ -393,26 +340,30 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo, void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options) { SkSwizzler::SrcConfig srcConfig = SkSwizzler::kUnknown; - switch (dstInfo.colorType()) { - case kGray_8_SkColorType: - srcConfig = SkSwizzler::kGray; - break; - case kRGBA_8888_SkColorType: - srcConfig = SkSwizzler::kRGBX; - break; - case kBGRA_8888_SkColorType: - srcConfig = SkSwizzler::kBGRX; - break; - case kRGB_565_SkColorType: - srcConfig = SkSwizzler::kRGB_565; - break; - default: - // This function should only be called if the colorType is supported by jpeg + if (JCS_CMYK == fDecoderMgr->dinfo()->out_color_space) { + srcConfig = SkSwizzler::kCMYK; + } else { + switch (dstInfo.colorType()) { + case kGray_8_SkColorType: + srcConfig = SkSwizzler::kGray; + break; + case kRGBA_8888_SkColorType: + srcConfig = SkSwizzler::kRGBX; + break; + case kBGRA_8888_SkColorType: + srcConfig = SkSwizzler::kBGRX; + break; + case kRGB_565_SkColorType: + srcConfig = SkSwizzler::kRGB_565; + break; + default: + // This function should only be called if the colorType is supported by jpeg #if defined(GOOGLE3) - SK_CRASH(); + SK_CRASH(); #else - SkASSERT(false); + SkASSERT(false); #endif + } } fSwizzler.reset(SkSwizzler::CreateSwizzler(srcConfig, nullptr, dstInfo, options)); @@ -454,8 +405,9 @@ SkCodec::Result SkJpegCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, return kInvalidInput; } - // We will need a swizzler if we are performing a subset decode - if (options.fSubset) { + // We will need a swizzler if we are performing a subset decode or + // converting from CMYK. + if (options.fSubset || JCS_CMYK == fDecoderMgr->dinfo()->out_color_space) { this->initializeSwizzler(dstInfo, options); } @@ -485,12 +437,7 @@ int SkJpegCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { return y; } - // Convert to RGBA if necessary - if (JCS_CMYK == fDecoderMgr->dinfo()->out_color_space) { - convert_CMYK_to_RGBA(dstRow, fDecoderMgr->dinfo()->output_width); - } - - if(fSwizzler) { + if (fSwizzler) { // use swizzler to sample row fSwizzler->swizzle(dst, dstRow); dst = SkTAddOffset(dst, rowBytes); diff --git a/src/codec/SkSwizzler.cpp b/src/codec/SkSwizzler.cpp index 4eea8799c5..242866db44 100644 --- a/src/codec/SkSwizzler.cpp +++ b/src/codec/SkSwizzler.cpp @@ -480,6 +480,89 @@ static SkSwizzler::ResultAlpha swizzle_rgba_to_n32_premul_skipZ( 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. 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: 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: break; } diff --git a/src/codec/SkSwizzler.h b/src/codec/SkSwizzler.h index fd205880f8..691dc77aa6 100644 --- a/src/codec/SkSwizzler.h +++ b/src/codec/SkSwizzler.h @@ -33,6 +33,7 @@ public: kRGBA, kBGRA, kRGB_565, + kCMYK, }; /* @@ -97,6 +98,7 @@ public: case kRGBA: case kBGRX: case kBGRA: + case kCMYK: return 32; default: SkASSERT(false); diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp index b2743bfa1f..74047002c7 100644 --- a/tests/CodexTest.cpp +++ b/tests/CodexTest.cpp @@ -408,7 +408,7 @@ DEF_TEST(Codec, r) { check(r, "randPixels.gif", SkISize::Make(8, 8), true, false, true, false); // 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); // grayscale.jpg is too small to test incomplete check(r, "grayscale.jpg", SkISize::Make(128, 128), true, false, true, false);