SkEncodeOptions options;
if (bitmap.colorSpace()) {
- options.fColorBehavior = SkEncodeOptions::ColorBehavior::kCorrect;
+ options.fUnpremulBehavior = SkTransferFunctionBehavior::kRespect;
}
switch (format) {
, fSubset(nullptr)
, fFrameIndex(0)
, fHasPriorFrame(false)
+ , fPremulBehavior(SkTransferFunctionBehavior::kRespect)
{}
- ZeroInitialized fZeroInitialized;
+ ZeroInitialized fZeroInitialized;
/**
* If not NULL, represents a subset of the original image to decode.
* Must be within the bounds returned by getInfo().
* subset left and subset width to decode partial scanlines on calls
* to getScanlines().
*/
- const SkIRect* fSubset;
+ const SkIRect* fSubset;
/**
* The frame to decode.
*
* Only meaningful for multi-frame images.
*/
- size_t fFrameIndex;
+ size_t fFrameIndex;
/**
* If true, the dst already contains the prior frame.
* codec needs to first decode the prior frame (which in turn may need
* to decode its prior frame).
*/
- bool fHasPriorFrame;
+ bool fHasPriorFrame;
+
+ /**
+ * Indicates whether we should do a linear premultiply or a legacy premultiply.
+ *
+ * In the case where the dst SkColorSpace is nullptr, this flag is ignored and
+ * we will always do a legacy premultiply.
+ */
+ SkTransferFunctionBehavior fPremulBehavior;
};
/**
virtual int onOutputScanline(int inputScanline) const;
- bool initializeColorXform(const SkImageInfo& dstInfo);
+ bool initializeColorXform(const SkImageInfo& dstInfo,
+ SkTransferFunctionBehavior premulBehavior);
SkColorSpaceXform* colorXform() const { return fColorXform.get(); }
virtual std::vector<FrameInfo> onGetFrameInfo() {
SkColorSpace() {}
};
+enum class SkTransferFunctionBehavior {
+ /**
+ * Converts to a linear space before premultiplying, unpremultiplying, or blending.
+ */
+ kRespect,
+
+ /**
+ * Premultiplies, unpremultiplies, and blends ignoring the transfer function. Pixels are
+ * treated as if they are linear, regardless of their transfer function encoding.
+ */
+ kIgnore,
+};
+
#endif
if (encodedSpace->isNumericalTransferFn(&fn)) {
// Leave the pixels in the encoded color space. Color space conversion
// will be handled after decode time.
- return as_CSB(encodedSpace)->makeWithNonLinearBlending();
+ return sk_ref_sp(encodedSpace);
}
if (is_wide_gamut(encodedSpace)) {
return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
- SkColorSpace::kDCIP3_D65_Gamut,
- SkColorSpace::kNonLinearBlending_ColorSpaceFlag);
+ SkColorSpace::kDCIP3_D65_Gamut);
}
- return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
- SkColorSpace::kSRGB_Gamut,
- SkColorSpace::kNonLinearBlending_ColorSpaceFlag);
+ return SkColorSpace::MakeSRGB();
}
case kRGBA_F16_SkColorType:
return SkColorSpace::MakeSRGBLinear();
SkCodec::Result SkBmpCodec::prepareToDecode(const SkImageInfo& dstInfo,
const SkCodec::Options& options, SkPMColor inputColorPtr[], int* inputColorCount) {
- if (!conversion_possible(dstInfo, this->getInfo()) || !this->initializeColorXform(dstInfo)) {
+ if (!conversion_possible(dstInfo, this->getInfo()) ||
+ !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+ {
return kInvalidConversion;
}
#include "SkCodec.h"
#include "SkCodecPriv.h"
#include "SkColorSpace.h"
-#include "SkColorSpaceXform.h"
+#include "SkColorSpaceXform_Base.h"
#include "SkData.h"
#include "SkGifCodec.h"
#include "SkHalf.h"
}
}
-bool SkCodec::initializeColorXform(const SkImageInfo& dstInfo) {
+bool SkCodec::initializeColorXform(const SkImageInfo& dstInfo,
+ SkTransferFunctionBehavior premulBehavior) {
fColorXform = nullptr;
- bool needsPremul = needs_premul(dstInfo, fEncodedInfo);
- if (needs_color_xform(dstInfo, fSrcInfo, needsPremul)) {
- fColorXform = SkColorSpaceXform::New(fSrcInfo.colorSpace(), dstInfo.colorSpace());
+ bool needsColorCorrectPremul = needs_premul(dstInfo, fEncodedInfo) &&
+ SkTransferFunctionBehavior::kRespect == premulBehavior;
+ if (needs_color_xform(dstInfo, fSrcInfo, needsColorCorrectPremul)) {
+ fColorXform = SkColorSpaceXform_Base::New(fSrcInfo.colorSpace(), dstInfo.colorSpace(),
+ premulBehavior);
if (!fColorXform) {
return false;
}
}
static inline bool needs_color_xform(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo,
- bool needsPremul) {
+ bool needsColorCorrectPremul) {
// We never perform a color xform in legacy mode.
if (!dstInfo.colorSpace()) {
return false;
bool srcDstNotEqual =
!SkColorSpace_Base::EqualsIgnoreFlags(srcInfo.colorSpace(), dstInfo.colorSpace());
- // We provide the option for both legacy premuls and color correct premuls.
- bool needsColorCorrectPremul =
- needsPremul && !as_CSB(dstInfo.colorSpace())->nonLinearBlending();
-
return needsColorCorrectPremul || isF16 || srcDstNotEqual;
}
SkCodec::Result SkGifCodec::prepareToDecode(const SkImageInfo& dstInfo, SkPMColor* inputColorPtr,
int* inputColorCount, const Options& opts) {
// Check for valid input parameters
- if (!conversion_possible(dstInfo, this->getInfo()) || !this->initializeColorXform(dstInfo)) {
+ if (!conversion_possible(dstInfo, this->getInfo()) ||
+ !this->initializeColorXform(dstInfo, opts.fPremulBehavior))
+ {
return gif_error("Cannot convert input type to output type.\n", kInvalidConversion);
}
return fDecoderMgr->returnFailure("setjmp", kInvalidInput);
}
- if (!this->initializeColorXform(dstInfo)) {
+ if (!this->initializeColorXform(dstInfo, options.fPremulBehavior)) {
return kInvalidConversion;
}
return kInvalidInput;
}
- if (!this->initializeColorXform(dstInfo)) {
+ if (!this->initializeColorXform(dstInfo, options.fPremulBehavior)) {
return kInvalidConversion;
}
// interlaced scanline decoder may need to rewind.
fSwizzler.reset(nullptr);
- if (!this->initializeColorXform(dstInfo)) {
+ if (!this->initializeColorXform(dstInfo, options.fPremulBehavior)) {
return false;
}
size_t dstRowBytes, const Options& options,
SkPMColor ctable[], int* ctableCount,
int* rowsDecoded) {
- if (!conversion_possible(dstInfo, this->getInfo()) || !this->initializeColorXform(dstInfo)) {
+ if (!conversion_possible(dstInfo, this->getInfo()) ||
+ !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+ {
SkCodecPrintf("Error: cannot convert input type to output type.\n");
return kInvalidConversion;
}
// Create an Options struct for the codec.
SkCodec::Options codecOptions;
codecOptions.fZeroInitialized = options.fZeroInitialized;
+ codecOptions.fPremulBehavior = SkTransferFunctionBehavior::kIgnore;
SkIRect* subset = options.fSubset;
if (!subset || subset->size() == this->codec()->getInfo().dimensions()) {
// Create options struct for the codec.
SkCodec::Options sampledOptions;
sampledOptions.fZeroInitialized = options.fZeroInitialized;
+ sampledOptions.fPremulBehavior = SkTransferFunctionBehavior::kIgnore;
// FIXME: This was already called by onGetAndroidPixels. Can we reduce that?
int sampleSize = options.fSampleSize;
SkCodec::Options codecOptions;
codecOptions.fZeroInitialized = options.fZeroInitialized;
codecOptions.fSubset = options.fSubset;
+ codecOptions.fPremulBehavior = SkTransferFunctionBehavior::kIgnore;
return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions, options.fColorPtr,
options.fColorCount);
}
SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
const Options& options, SkPMColor*, int*,
int* rowsDecodedPtr) {
- if (!conversion_possible(dstInfo, this->getInfo())) {
- return kInvalidConversion;
- }
-
- if (!this->initializeColorXform(dstInfo)) {
+ if (!conversion_possible(dstInfo, this->getInfo()) ||
+ !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+ {
return kInvalidConversion;
}
std::unique_ptr<SkColorSpaceXform> SkColorSpaceXform::New(SkColorSpace* srcSpace,
SkColorSpace* dstSpace) {
+ return SkColorSpaceXform_Base::New(srcSpace, dstSpace, SkTransferFunctionBehavior::kRespect);
+}
+
+std::unique_ptr<SkColorSpaceXform> SkColorSpaceXform_Base::New(SkColorSpace* srcSpace,
+ SkColorSpace* dstSpace, SkTransferFunctionBehavior premulBehavior) {
+
if (!srcSpace || !dstSpace) {
// Invalid input
return nullptr;
switch (csm) {
case kNone_ColorSpaceMatch:
return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
- <kNone_ColorSpaceMatch>(srcSpaceXYZ, srcToDst, dstSpaceXYZ));
+ <kNone_ColorSpaceMatch>(srcSpaceXYZ, srcToDst, dstSpaceXYZ, premulBehavior));
case kGamut_ColorSpaceMatch:
return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
- <kGamut_ColorSpaceMatch>(srcSpaceXYZ, srcToDst, dstSpaceXYZ));
+ <kGamut_ColorSpaceMatch>(srcSpaceXYZ, srcToDst, dstSpaceXYZ, premulBehavior));
case kFull_ColorSpaceMatch:
return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
- <kFull_ColorSpaceMatch>(srcSpaceXYZ, srcToDst, dstSpaceXYZ));
+ <kFull_ColorSpaceMatch>(srcSpaceXYZ, srcToDst, dstSpaceXYZ, premulBehavior));
default:
SkASSERT(false);
return nullptr;
template <ColorSpaceMatch kCSM>
SkColorSpaceXform_XYZ<kCSM>
::SkColorSpaceXform_XYZ(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst,
- SkColorSpace_XYZ* dstSpace)
- : fLinearBlending(!dstSpace->nonLinearBlending())
+ SkColorSpace_XYZ* dstSpace, SkTransferFunctionBehavior premulBehavior)
+ : fPremulBehavior(premulBehavior)
{
fSrcToDst[ 0] = srcToDst.get(0, 0);
fSrcToDst[ 1] = srcToDst.get(1, 0);
}
}
- if (kPremul_SkAlphaType == alphaType && fLinearBlending) {
+ if (kPremul_SkAlphaType == alphaType && SkTransferFunctionBehavior::kRespect == fPremulBehavior)
+ {
pipeline.append(SkRasterPipeline::premul);
}
break;
}
- if (kPremul_SkAlphaType == alphaType && !fLinearBlending) {
+ if (kPremul_SkAlphaType == alphaType && SkTransferFunctionBehavior::kIgnore == fPremulBehavior)
+ {
pipeline.append(SkRasterPipeline::premul);
}
pipeline.append(SkRasterPipeline::store_8888, &dst);
break;
case kRGBA_F16_ColorFormat:
- if (kLinear_DstGamma != fDstGamma || !fLinearBlending) {
+ if (kLinear_DstGamma != fDstGamma) {
return false;
}
pipeline.append(SkRasterPipeline::store_f16, &dst);
break;
case kRGBA_F32_ColorFormat:
- if (kLinear_DstGamma != fDstGamma || !fLinearBlending) {
+ if (kLinear_DstGamma != fDstGamma) {
return false;
}
pipeline.append(SkRasterPipeline::store_f32, &dst);
///////////////////////////////////////////////////////////////////////////////////////////////////
std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space) {
- return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
- <kNone_ColorSpaceMatch>(space, SkMatrix::I(), space));
+ return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ<kNone_ColorSpaceMatch>
+ (space, SkMatrix::I(), space, SkTransferFunctionBehavior::kRespect));
}
std::forward_list<std::vector<float>> fTableStorage;
std::vector<sk_sp<const SkGammas>> fGammaRefs;
- friend class SkColorSpaceXform;
+ friend class SkColorSpaceXform_Base;
};
#endif
public:
static constexpr int kDstGammaTableSize = 1024;
+ static std::unique_ptr<SkColorSpaceXform> New(SkColorSpace* srcSpace, SkColorSpace* dstSpace,
+ SkTransferFunctionBehavior premulBehavior);
+
protected:
virtual bool onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat, const void* src,
int count, SkAlphaType alphaType) const = 0;
int count, SkAlphaType alphaType) const;
SkColorSpaceXform_XYZ(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst,
- SkColorSpace_XYZ* dstSpace);
+ SkColorSpace_XYZ* dstSpace, SkTransferFunctionBehavior premulBehavior);
// Contain pointers into storage or pointers into precomputed tables.
- const float* fSrcGammaTables[3];
- SkAutoTMalloc<float> fSrcStorage;
- const uint8_t* fDstGammaTables[3];
- sk_sp<SkData> fDstStorage;
+ const float* fSrcGammaTables[3];
+ SkAutoTMalloc<float> fSrcStorage;
+ const uint8_t* fDstGammaTables[3];
+ sk_sp<SkData> fDstStorage;
// Holds a 3x4 matrix. Padding is useful for vector loading.
- float fSrcToDst[13];
+ float fSrcToDst[13];
- SrcGamma fSrcGamma;
- DstGamma fDstGamma;
- bool fLinearBlending;
+ SrcGamma fSrcGamma;
+ DstGamma fDstGamma;
+ SkTransferFunctionBehavior fPremulBehavior;
- friend class SkColorSpaceXform;
+ friend class SkColorSpaceXform_Base;
friend std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space);
};
#include "SkImageEncoder.h"
struct SkEncodeOptions {
- enum class ColorBehavior {
- // Convert to a linear space before premultiplying or unpremultiplying.
- kCorrect,
-
- // Ignore the transfer function when premultiplying or unpremultiplying.
- kLegacy,
- };
-
- ColorBehavior fColorBehavior = ColorBehavior::kLegacy;
+ SkTransferFunctionBehavior fUnpremulBehavior = SkTransferFunctionBehavior::kIgnore;
};
#ifdef SK_HAS_JPEG_LIBRARY
pixmap.colorSpace()->gammaIsLinear());
SkPixmap src = pixmap;
- if (SkEncodeOptions::ColorBehavior::kLegacy == opts.fColorBehavior) {
+ if (SkTransferFunctionBehavior::kIgnore == opts.fUnpremulBehavior) {
src.setColorSpace(nullptr);
} else {
// kCorrect behavior requires a color space. It's not actually critical in the
src.colorSpace()->gammaIsLinear());
SkPixmap pixmap = src;
- if (SkEncodeOptions::ColorBehavior::kLegacy == opts.fColorBehavior) {
+ if (SkTransferFunctionBehavior::kIgnore == opts.fUnpremulBehavior) {
pixmap.setColorSpace(nullptr);
} else {
if (!pixmap.colorSpace()) {
srcPixmap.colorSpace()->gammaIsLinear());
SkPixmap pixmap = srcPixmap;
- if (SkEncodeOptions::ColorBehavior::kLegacy == opts.fColorBehavior) {
+ if (SkTransferFunctionBehavior::kIgnore == opts.fUnpremulBehavior) {
pixmap.setColorSpace(nullptr);
} else {
if (!pixmap.colorSpace()) {
srgbBitmap.peekPixels(&pixmap);
SkDynamicMemoryWStream srgbBuf;
SkEncodeOptions opts;
- opts.fColorBehavior = SkEncodeOptions::ColorBehavior::kCorrect;
+ opts.fUnpremulBehavior = SkTransferFunctionBehavior::kRespect;
encode_format(&srgbBuf, pixmap, opts, format);
sk_sp<SkData> srgbData = srgbBuf.detachAsData();
std::unique_ptr<SkCodec> srgbCodec(SkCodec::NewFromData(srgbData));