diff --git a/dm/DM.cpp b/dm/DM.cpp index 8ee2ea739f..e17fd358d0 100644 --- a/dm/DM.cpp +++ b/dm/DM.cpp @@ -724,18 +724,15 @@ static bool gather_srcs() { return false; } - // Load the dstSpace. This particular dst is fairly similar to Adobe RGB. - SkAutoTUnref data(SkData::NewFromFileName( - GetResourcePath("monitor_profiles/HP_ZR30w.icc").c_str())); - sk_sp dstSpace = SkColorSpace::NewICC(data->data(), data->size()); - SkASSERT(dstSpace); - for (auto colorImage : colorImages) { - ColorCodecSrc* src = new ColorCodecSrc(colorImage, ColorCodecSrc::kBaseline_Mode, nullptr); + ColorCodecSrc* src = new ColorCodecSrc(colorImage, ColorCodecSrc::kBaseline_Mode); push_src("image", "color_codec_baseline", src); - src = new ColorCodecSrc(colorImage, ColorCodecSrc::kDst_HPZR30w_Mode, dstSpace); + src = new ColorCodecSrc(colorImage, ColorCodecSrc::kDst_HPZR30w_Mode); push_src("image", "color_codec_HPZR30w", src); + + src = new ColorCodecSrc(colorImage, ColorCodecSrc::kQCMS_HPZR30w_Mode); + push_src("image", "color_codec_QCMS_HPZR30w", src); } return true; diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index 755bae378e..a9851895bb 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -6,11 +6,13 @@ */ #include "DMSrcSink.h" +#include "Resources.h" #include "SkAndroidCodec.h" #include "SkCodec.h" #include "SkCodecImageGenerator.h" #include "SkColorSpace.h" #include "SkColorSpace_Base.h" +#include "SkColorSpaceXform.h" #include "SkCommonFlags.h" #include "SkData.h" #include "SkDocument.h" @@ -39,6 +41,8 @@ #include "SkAutoCoInitialize.h" #endif +#include "qcms.h" + DEFINE_bool(multiPage, false, "For document-type backends, render the source" " into multiple pages"); DEFINE_bool(RAW_threading, true, "Allow RAW decodes to run on multiple threads?"); @@ -851,10 +855,9 @@ Name ImageGenSrc::name() const { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ -ColorCodecSrc::ColorCodecSrc(Path path, Mode mode, sk_sp dstSpace) +ColorCodecSrc::ColorCodecSrc(Path path, Mode mode) : fPath(path) , fMode(mode) - , fDstSpace(dstSpace) {} bool ColorCodecSrc::veto(SinkFlags flags) const { @@ -862,17 +865,6 @@ bool ColorCodecSrc::veto(SinkFlags flags) const { return flags.type != SinkFlags::kRaster || flags.approach != SinkFlags::kDirect; } -static uint8_t clampFloatToByte(float v) { - v = v * 255.0f; - if (v > 255.0f) { - return 255; - } else if (v < 0.0f) { - return 0; - } else { - return (uint8_t) (v + 0.5f); - } -} - Error ColorCodecSrc::draw(SkCanvas* canvas) const { if (kRGB_565_SkColorType == canvas->imageInfo().colorType()) { return Error::Nonfatal("No need to test color correction to 565 backend."); @@ -905,62 +897,69 @@ Error ColorCodecSrc::draw(SkCanvas* canvas) const { return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r); } + // Load the dst ICC profile. This particular dst is fairly similar to Adobe RGB. + sk_sp dstData = SkData::MakeFromFileName( + GetResourcePath("monitor_profiles/HP_ZR30w.icc").c_str()); + if (!dstData) { + return "Cannot read monitor profile. Is the resource path set correctly?"; + } + switch (fMode) { case kBaseline_Mode: canvas->drawBitmap(bitmap, 0, 0); break; case kDst_HPZR30w_Mode: { sk_sp srcSpace = sk_ref_sp(codec->getColorSpace()); - if (!srcSpace) { - return SkStringPrintf("Cannot test color correction without a src profile."); - } else if (!as_CSB(srcSpace)->gammas()->isValues()) { + sk_sp dstSpace = SkColorSpace::NewICC(dstData->data(), dstData->size()); + SkASSERT(dstSpace); + + std::unique_ptr xform = SkColorSpaceXform::New(srcSpace, dstSpace); + if (!xform) { // FIXME (msarett): - // The conversion here doesn't cover all of the images that I've uploaded for + // I haven't implemented conversions for all of the images that I've uploaded for // testing. Once we support all of them, this should be a fatal error. - return Error::Nonfatal("Unimplemented gamma conversion."); + return Error::Nonfatal("Unimplemented color conversion."); } - // Build a matrix to transform to dst gamut. - // srcToDst = inverse(dstToXYZ) * srcToXYZ - const SkMatrix44& srcToXYZ = srcSpace->xyz(); - const SkMatrix44& dstToXYZ = fDstSpace->xyz(); - SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); - dstToXYZ.invert(&srcToDst); - srcToDst.postConcat(srcToXYZ); - + uint32_t* row = (uint32_t*) bitmap.getPixels(); for (int y = 0; y < info.height(); y++) { - for (int x = 0; x < info.width(); x++) { - // Extract floats. - uint32_t* pixelPtr = (uint32_t*) bitmap.getAddr(x, y); - float src[3]; - src[0] = ((*pixelPtr >> 0) & 0xFF) / 255.0f; - src[1] = ((*pixelPtr >> 8) & 0xFF) / 255.0f; - src[2] = ((*pixelPtr >> 16) & 0xFF) / 255.0f; + xform->xform_RGBA_8888(row, row, info.width()); + row = SkTAddOffset(row, bitmap.rowBytes()); + } - // Convert to linear. - src[0] = pow(src[0], as_CSB(srcSpace)->gammas()->fRed.fValue); - src[1] = pow(src[1], as_CSB(srcSpace)->gammas()->fGreen.fValue); - src[2] = pow(src[2], as_CSB(srcSpace)->gammas()->fBlue.fValue); + canvas->drawBitmap(bitmap, 0, 0); + break; + } + case kQCMS_HPZR30w_Mode: { + sk_sp srcData = codec->getICCData(); + SkAutoTCallVProc + srcSpace(qcms_profile_from_memory(srcData->data(), srcData->size())); + if (!srcSpace) { + return Error::Nonfatal(SkStringPrintf("QCMS cannot create profile for %s.\n", + fPath.c_str())); + } - // Convert to dst gamut. - float dst[3]; - dst[0] = src[0]*srcToDst.getFloat(0, 0) + src[1]*srcToDst.getFloat(1, 0) + - src[2]*srcToDst.getFloat(2, 0) + srcToDst.getFloat(3, 0); - dst[1] = src[0]*srcToDst.getFloat(0, 1) + src[1]*srcToDst.getFloat(1, 1) + - src[2]*srcToDst.getFloat(2, 1) + srcToDst.getFloat(3, 1); - dst[2] = src[0]*srcToDst.getFloat(0, 2) + src[1]*srcToDst.getFloat(1, 2) + - src[2]*srcToDst.getFloat(2, 2) + srcToDst.getFloat(3, 2); + SkAutoTCallVProc + dstSpace(qcms_profile_from_memory(dstData->data(), dstData->size())); + SkASSERT(dstSpace); + SkAutoTCallVProc + transform (qcms_transform_create(srcSpace, QCMS_DATA_RGBA_8, dstSpace, + QCMS_DATA_RGBA_8, QCMS_INTENT_PERCEPTUAL)); + if (!transform) { + return SkStringPrintf("QCMS cannot create transform for %s.\n", fPath.c_str()); + } - // Convert to dst gamma. - dst[0] = pow(dst[0], 1.0f / as_CSB(fDstSpace)->gammas()->fRed.fValue); - dst[1] = pow(dst[1], 1.0f / as_CSB(fDstSpace)->gammas()->fGreen.fValue); - dst[2] = pow(dst[2], 1.0f / as_CSB(fDstSpace)->gammas()->fBlue.fValue); +#ifdef SK_PMCOLOR_IS_RGBA + qcms_output_type outType = QCMS_OUTPUT_RGBX; +#else + qcms_output_type outType = QCMS_OUTPUT_BGRX; +#endif - *pixelPtr = SkPackARGB32NoCheck(((*pixelPtr >> 24) & 0xFF), - clampFloatToByte(dst[0]), - clampFloatToByte(dst[1]), - clampFloatToByte(dst[2])); - } + // Perform color correction. + uint32_t* row = (uint32_t*) bitmap.getPixels(); + for (int y = 0; y < info.height(); y++) { + qcms_transform_data_type(transform, row, row, info.width(), outType); + row = SkTAddOffset(row, bitmap.rowBytes()); } canvas->drawBitmap(bitmap, 0, 0); diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h index 84096bf0fe..70d52f260a 100644 --- a/dm/DMSrcSink.h +++ b/dm/DMSrcSink.h @@ -212,9 +212,12 @@ public: // TODO (msarett): Should we add a new test with a new monitor and verify that outputs // look identical on two different dsts? kDst_HPZR30w_Mode, + + // Use QCMS for color correction. + kQCMS_HPZR30w_Mode, }; - ColorCodecSrc(Path, Mode, sk_sp); + ColorCodecSrc(Path, Mode); Error draw(SkCanvas*) const override; SkISize size() const override; @@ -223,7 +226,6 @@ public: private: Path fPath; Mode fMode; - sk_sp fDstSpace; }; class SKPSrc : public Src { diff --git a/gyp/core.gypi b/gyp/core.gypi index 3319e6311c..ff3e270534 100644 --- a/gyp/core.gypi +++ b/gyp/core.gypi @@ -76,6 +76,7 @@ '<(skia_src_path)/core/SkColorShader.cpp', '<(skia_src_path)/core/SkColorShader.h', '<(skia_src_path)/core/SkColorSpace.cpp', + '<(skia_src_path)/core/SkColorSpaceXform.cpp', '<(skia_src_path)/core/SkColorTable.cpp', '<(skia_src_path)/core/SkComposeShader.cpp', '<(skia_src_path)/core/SkConfig8888.cpp', diff --git a/gyp/ports.gyp b/gyp/ports.gyp index 6b29052144..536ae9ce49 100644 --- a/gyp/ports.gyp +++ b/gyp/ports.gyp @@ -14,6 +14,7 @@ 'core.gyp:*', 'qcms.gyp:qcms', ], + 'export_dependent_settings': [ 'qcms.gyp:qcms', ], 'include_dirs': [ '../include/effects', '../include/client/android', diff --git a/gyp/qcms.gyp b/gyp/qcms.gyp index ebd3fa4c84..fc007b3eee 100644 --- a/gyp/qcms.gyp +++ b/gyp/qcms.gyp @@ -19,7 +19,7 @@ 'direct_dependent_settings': { 'include_dirs': [ - './src', + '../third_party/qcms/src/', ], }, diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h index f66eb85377..31d6ea7cd5 100644 --- a/include/codec/SkCodec.h +++ b/include/codec/SkCodec.h @@ -25,6 +25,7 @@ class SkSampler; namespace DM { class CodecSrc; +class ColorCodecSrc; } /** @@ -695,6 +696,11 @@ protected: virtual int onOutputScanline(int inputScanline) const; + /** + * Used for testing with qcms. + * FIXME: Remove this when we are done comparing with qcms. + */ + virtual sk_sp getICCData() const { return nullptr; } private: const SkEncodedInfo fEncodedInfo; const SkImageInfo fSrcInfo; @@ -770,6 +776,11 @@ private: virtual SkSampler* getSampler(bool /*createIfNecessary*/) { return nullptr; } friend class DM::CodecSrc; // for fillIncompleteImage + + // For testing with qcms + // FIXME: Remove this when we are done comparing with qcms. + friend class DM::ColorCodecSrc; + friend class SkSampledCodec; friend class SkIcoCodec; }; diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp index 85de356118..0413f79a3c 100644 --- a/src/codec/SkJpegCodec.cpp +++ b/src/codec/SkJpegCodec.cpp @@ -120,7 +120,7 @@ static bool is_icc_marker(jpeg_marker_struct* marker) { * (1) Discover all ICC profile markers and verify that they are numbered properly. * (2) Copy the data from each marker into a contiguous ICC profile. */ -static sk_sp get_icc_profile(jpeg_decompress_struct* dinfo) { +static sk_sp get_icc_profile(jpeg_decompress_struct* dinfo) { // Note that 256 will be enough storage space since each markerIndex is stored in 8-bits. jpeg_marker_struct* markerSequence[256]; memset(markerSequence, 0, sizeof(markerSequence)); @@ -165,8 +165,8 @@ static sk_sp get_icc_profile(jpeg_decompress_struct* dinfo) { } // Combine the ICC marker data into a contiguous profile. - SkAutoMalloc iccData(totalBytes); - void* dst = iccData.get(); + sk_sp iccData = SkData::MakeUninitialized(totalBytes); + void* dst = iccData->writable_data(); for (uint32_t i = 1; i <= numMarkers; i++) { jpeg_marker_struct* marker = markerSequence[i]; if (!marker) { @@ -180,7 +180,7 @@ static sk_sp get_icc_profile(jpeg_decompress_struct* dinfo) { dst = SkTAddOffset(dst, bytes); } - return SkColorSpace::NewICC(iccData.get(), totalBytes); + return iccData; } bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, @@ -221,7 +221,14 @@ bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, SkEncodedInfo info = SkEncodedInfo::Make(color, SkEncodedInfo::kOpaque_Alpha, 8); Origin orientation = get_exif_orientation(decoderMgr->dinfo()); - sk_sp colorSpace = get_icc_profile(decoderMgr->dinfo()); + sk_sp iccData = get_icc_profile(decoderMgr->dinfo()); + sk_sp colorSpace = nullptr; + if (iccData) { + colorSpace = SkColorSpace::NewICC(iccData->data(), iccData->size()); + if (!colorSpace) { + SkCodecPrintf("Could not create SkColorSpace from ICC data.\n"); + } + } if (!colorSpace) { // Treat unmarked jpegs as sRGB. colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); @@ -230,7 +237,7 @@ bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, const int width = decoderMgr->dinfo()->image_width; const int height = decoderMgr->dinfo()->image_height; *codecOut = new SkJpegCodec(width, height, info, stream, decoderMgr.release(), - std::move(colorSpace), orientation); + std::move(colorSpace), orientation, std::move(iccData)); } else { SkASSERT(nullptr != decoderMgrOut); *decoderMgrOut = decoderMgr.release(); @@ -251,11 +258,13 @@ SkCodec* SkJpegCodec::NewFromStream(SkStream* stream) { } SkJpegCodec::SkJpegCodec(int width, int height, const SkEncodedInfo& info, SkStream* stream, - JpegDecoderMgr* decoderMgr, sk_sp colorSpace, Origin origin) + JpegDecoderMgr* decoderMgr, sk_sp colorSpace, Origin origin, + sk_sp iccData) : INHERITED(width, height, info, stream, std::move(colorSpace), origin) , fDecoderMgr(decoderMgr) , fReadyState(decoderMgr->dinfo()->global_state) , fSwizzlerSubset(SkIRect::MakeEmpty()) + , fICCData(std::move(iccData)) {} /* diff --git a/src/codec/SkJpegCodec.h b/src/codec/SkJpegCodec.h index f8cddd0221..7aa275ce4e 100644 --- a/src/codec/SkJpegCodec.h +++ b/src/codec/SkJpegCodec.h @@ -58,6 +58,8 @@ protected: bool onDimensionsSupported(const SkISize&) override; + sk_sp getICCData() const override { return fICCData; } + private: /* @@ -92,7 +94,8 @@ private: * takes ownership */ SkJpegCodec(int width, int height, const SkEncodedInfo& info, SkStream* stream, - JpegDecoderMgr* decoderMgr, sk_sp colorSpace, Origin origin); + JpegDecoderMgr* decoderMgr, sk_sp colorSpace, Origin origin, + sk_sp iccData); /* * Checks if the conversion between the input image and the requested output @@ -123,6 +126,8 @@ private: SkIRect fSwizzlerSubset; SkAutoTDelete fSwizzler; + sk_sp fICCData; + typedef SkCodec INHERITED; }; diff --git a/src/core/SkColorSpaceXform.cpp b/src/core/SkColorSpaceXform.cpp new file mode 100644 index 0000000000..473e715353 --- /dev/null +++ b/src/core/SkColorSpaceXform.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkColorPriv.h" +#include "SkColorSpace_Base.h" +#include "SkColorSpaceXform.h" + +bool compute_gamut_xform(SkMatrix44* srcToDst, const SkMatrix44& srcToXYZ, + const SkMatrix44& dstToXYZ) { + if (!dstToXYZ.invert(srcToDst)) { + return false; + } + + srcToDst->postConcat(srcToXYZ); + return true; +} + +std::unique_ptr SkColorSpaceXform::New(const sk_sp& srcSpace, + const sk_sp& dstSpace) { + if (!srcSpace || !dstSpace) { + return nullptr; + } + + if (as_CSB(srcSpace)->gammas()->isValues() && as_CSB(dstSpace)->gammas()->isValues()) { + SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor); + if (!compute_gamut_xform(&srcToDst, srcSpace->xyz(), dstSpace->xyz())) { + return nullptr; + } + + float srcGammas[3]; + float dstGammas[3]; + srcGammas[0] = as_CSB(srcSpace)->gammas()->fRed.fValue; + srcGammas[1] = as_CSB(srcSpace)->gammas()->fGreen.fValue; + srcGammas[2] = as_CSB(srcSpace)->gammas()->fBlue.fValue; + dstGammas[0] = 1.0f / as_CSB(dstSpace)->gammas()->fRed.fValue; + dstGammas[1] = 1.0f / as_CSB(dstSpace)->gammas()->fGreen.fValue; + dstGammas[2] = 1.0f / as_CSB(dstSpace)->gammas()->fBlue.fValue; + + return std::unique_ptr( + new SkGammaByValueXform(srcGammas, srcToDst, dstGammas)); + } + + // Unimplemeted + return nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +SkGammaByValueXform::SkGammaByValueXform(float srcGammas[3], const SkMatrix44& srcToDst, + float dstGammas[3]) + : fSrcToDst(srcToDst) +{ + memcpy(fSrcGammas, srcGammas, 3 * sizeof(float)); + memcpy(fDstGammas, dstGammas, 3 * sizeof(float)); +} + +static uint8_t clamp_float_to_byte(float v) { + v = v * 255.0f; + if (v > 255.0f) { + return 255; + } else if (v <= 0.0f) { + return 0; + } else { + return (uint8_t) (v + 0.5f); + } +} + +void SkGammaByValueXform::xform_RGBA_8888(uint32_t* dst, const uint32_t* src, uint32_t len) const { + while (len-- > 0) { + float srcFloats[3]; + srcFloats[0] = ((*src >> 0) & 0xFF) * (1.0f / 255.0f); + srcFloats[1] = ((*src >> 8) & 0xFF) * (1.0f / 255.0f); + srcFloats[2] = ((*src >> 16) & 0xFF) * (1.0f / 255.0f); + + // Convert to linear. + srcFloats[0] = pow(srcFloats[0], fSrcGammas[0]); + srcFloats[1] = pow(srcFloats[1], fSrcGammas[1]); + srcFloats[2] = pow(srcFloats[2], fSrcGammas[2]); + + // Convert to dst gamut. + float dstFloats[3]; + dstFloats[0] = srcFloats[0] * fSrcToDst.getFloat(0, 0) + + srcFloats[1] * fSrcToDst.getFloat(1, 0) + + srcFloats[2] * fSrcToDst.getFloat(2, 0) + fSrcToDst.getFloat(3, 0); + dstFloats[1] = srcFloats[0] * fSrcToDst.getFloat(0, 1) + + srcFloats[1] * fSrcToDst.getFloat(1, 1) + + srcFloats[2] * fSrcToDst.getFloat(2, 1) + fSrcToDst.getFloat(3, 1); + dstFloats[2] = srcFloats[0] * fSrcToDst.getFloat(0, 2) + + srcFloats[1] * fSrcToDst.getFloat(1, 2) + + srcFloats[2] * fSrcToDst.getFloat(2, 2) + fSrcToDst.getFloat(3, 2); + + // Convert to dst gamma. + dstFloats[0] = pow(dstFloats[0], fDstGammas[0]); + dstFloats[1] = pow(dstFloats[1], fDstGammas[1]); + dstFloats[2] = pow(dstFloats[2], fDstGammas[2]); + + *dst = SkPackARGB32NoCheck(((*src >> 24) & 0xFF), + clamp_float_to_byte(dstFloats[0]), + clamp_float_to_byte(dstFloats[1]), + clamp_float_to_byte(dstFloats[2])); + + dst++; + src++; + } +} diff --git a/src/core/SkColorSpaceXform.h b/src/core/SkColorSpaceXform.h new file mode 100644 index 0000000000..c3010f0cca --- /dev/null +++ b/src/core/SkColorSpaceXform.h @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorSpaceXform_DEFINED +#define SkColorSpaceXform_DEFINED + +#include "SkColorSpace.h" + +class SkColorSpaceXform { +public: + + /** + * Create an object to handle color space conversions. + * + * @param srcSpace The encoded color space. + * @param dstSpace The destination color space. + * + */ + static std::unique_ptr New(const sk_sp& srcSpace, + const sk_sp& dstSpace); + + /** + * Apply the color conversion to a src buffer, storing the output in the dst buffer. + * The src is stored in RGBA_8888 and the dst is stored in 8888 platform format. + * The output is not premultiplied. + */ + virtual void xform_RGBA_8888(uint32_t* dst, const uint32_t* src, uint32_t len) const = 0; + + virtual ~SkColorSpaceXform() {} +}; + +class SkGammaByValueXform : public SkColorSpaceXform { +public: + + void xform_RGBA_8888(uint32_t* dst, const uint32_t* src, uint32_t len) const override; + +private: + SkGammaByValueXform(float srcGammas[3], const SkMatrix44& srcToDst, float dstGammas[3]); + + float fSrcGammas[3]; + const SkMatrix44 fSrcToDst; + float fDstGammas[3]; + + friend class SkColorSpaceXform; +}; + +#endif