/* * Copyright 2012 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef SkMaskGamma_DEFINED #define SkMaskGamma_DEFINED #include "SkColor.h" #include "SkColorData.h" #include "SkNoncopyable.h" #include "SkRefCnt.h" #include "SkTypes.h" /** * SkColorSpaceLuminance is used to convert luminances to and from linear and * perceptual color spaces. * * Luma is used to specify a linear luminance value [0.0, 1.0]. * Luminance is used to specify a luminance value in an arbitrary color space [0.0, 1.0]. */ class SkColorSpaceLuminance : SkNoncopyable { public: virtual ~SkColorSpaceLuminance() { } /** Converts a color component luminance in the color space to a linear luma. */ virtual SkScalar toLuma(SkScalar gamma, SkScalar luminance) const = 0; /** Converts a linear luma to a color component luminance in the color space. */ virtual SkScalar fromLuma(SkScalar gamma, SkScalar luma) const = 0; /** Converts a color to a luminance value. */ static U8CPU computeLuminance(SkScalar gamma, SkColor c) { const SkColorSpaceLuminance& luminance = Fetch(gamma); SkScalar r = luminance.toLuma(gamma, SkIntToScalar(SkColorGetR(c)) / 255); SkScalar g = luminance.toLuma(gamma, SkIntToScalar(SkColorGetG(c)) / 255); SkScalar b = luminance.toLuma(gamma, SkIntToScalar(SkColorGetB(c)) / 255); SkScalar luma = r * SK_LUM_COEFF_R + g * SK_LUM_COEFF_G + b * SK_LUM_COEFF_B; SkASSERT(luma <= SK_Scalar1); return SkScalarRoundToInt(luminance.fromLuma(gamma, luma) * 255); } /** Retrieves the SkColorSpaceLuminance for the given gamma. */ static const SkColorSpaceLuminance& Fetch(SkScalar gamma); }; ///@{ /** * Scales base <= 2^N-1 to 2^8-1 * @param N [1, 8] the number of bits used by base. * @param base the number to be scaled to [0, 255]. */ template static inline U8CPU sk_t_scale255(U8CPU base) { base <<= (8 - N); U8CPU lum = base; for (unsigned int i = N; i < 8; i += N) { lum |= base >> i; } return lum; } template<> /*static*/ inline U8CPU sk_t_scale255<1>(U8CPU base) { return base * 0xFF; } template<> /*static*/ inline U8CPU sk_t_scale255<2>(U8CPU base) { return base * 0x55; } template<> /*static*/ inline U8CPU sk_t_scale255<4>(U8CPU base) { return base * 0x11; } template<> /*static*/ inline U8CPU sk_t_scale255<8>(U8CPU base) { return base; } ///@} template class SkTMaskPreBlend; void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast, const SkColorSpaceLuminance& srcConvert, SkScalar srcGamma, const SkColorSpaceLuminance& dstConvert, SkScalar dstGamma); /** * A regular mask contains linear alpha values. A gamma correcting mask * contains non-linear alpha values in an attempt to create gamma correct blits * in the presence of a gamma incorrect (linear) blend in the blitter. * * SkMaskGamma creates and maintains tables which convert linear alpha values * to gamma correcting alpha values. * @param R The number of luminance bits to use [1, 8] from the red channel. * @param G The number of luminance bits to use [1, 8] from the green channel. * @param B The number of luminance bits to use [1, 8] from the blue channel. */ template class SkTMaskGamma : public SkRefCnt { public: /** Creates a linear SkTMaskGamma. */ SkTMaskGamma() : fIsLinear(true) { } /** * Creates tables to convert linear alpha values to gamma correcting alpha * values. * * @param contrast A value in the range [0.0, 1.0] which indicates the * amount of artificial contrast to add. * @param paint The color space in which the paint color was chosen. * @param device The color space of the target device. */ SkTMaskGamma(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma) : fIsLinear(false) { const SkColorSpaceLuminance& paintConvert = SkColorSpaceLuminance::Fetch(paintGamma); const SkColorSpaceLuminance& deviceConvert = SkColorSpaceLuminance::Fetch(deviceGamma); for (U8CPU i = 0; i < (1 << MAX_LUM_BITS); ++i) { U8CPU lum = sk_t_scale255(i); SkTMaskGamma_build_correcting_lut(fGammaTables[i], lum, contrast, paintConvert, paintGamma, deviceConvert, deviceGamma); } } /** Given a color, returns the closest canonical color. */ static SkColor CanonicalColor(SkColor color) { return SkColorSetRGB( sk_t_scale255(SkColorGetR(color) >> (8 - R_LUM_BITS)), sk_t_scale255(SkColorGetG(color) >> (8 - G_LUM_BITS)), sk_t_scale255(SkColorGetB(color) >> (8 - B_LUM_BITS))); } /** The type of the mask pre-blend which will be returned from preBlend(SkColor). */ typedef SkTMaskPreBlend PreBlend; /** * Provides access to the tables appropriate for converting linear alpha * values into gamma correcting alpha values when drawing the given color * through the mask. The destination color will be approximated. */ PreBlend preBlend(SkColor color) const; /** * Get dimensions for the full table set, so it can be allocated as a block. */ void getGammaTableDimensions(int* tableWidth, int* numTables) const { *tableWidth = 256; *numTables = (1 << MAX_LUM_BITS); } /** * Provides direct access to the full table set, so it can be uploaded * into a texture or analyzed in other ways. * Returns nullptr if fGammaTables hasn't been initialized. */ const uint8_t* getGammaTables() const { return fIsLinear ? nullptr : (const uint8_t*) fGammaTables; } private: static const int MAX_LUM_BITS = B_LUM_BITS > (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS) ? B_LUM_BITS : (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS); uint8_t fGammaTables[1 << MAX_LUM_BITS][256]; bool fIsLinear; typedef SkRefCnt INHERITED; }; /** * SkTMaskPreBlend is a tear-off of SkTMaskGamma. It provides the tables to * convert a linear alpha value for a given channel to a gamma correcting alpha * value for that channel. This class is immutable. * * If fR, fG, or fB is nullptr, all of them will be. This indicates that no mask * pre blend should be applied. SkTMaskPreBlend::isApplicable() is provided as * a convenience function to test for the absence of this case. */ template class SkTMaskPreBlend { private: SkTMaskPreBlend(sk_sp> parent, const uint8_t* r, const uint8_t* g, const uint8_t* b) : fParent(std::move(parent)), fR(r), fG(g), fB(b) { } sk_sp> fParent; friend class SkTMaskGamma; public: /** Creates a non applicable SkTMaskPreBlend. */ SkTMaskPreBlend() : fParent(), fR(nullptr), fG(nullptr), fB(nullptr) { } /** * This copy contructor exists for correctness, but should never be called * when return value optimization is enabled. */ SkTMaskPreBlend(const SkTMaskPreBlend& that) : fParent(that.fParent), fR(that.fR), fG(that.fG), fB(that.fB) { } ~SkTMaskPreBlend() { } /** True if this PreBlend should be applied. When false, fR, fG, and fB are nullptr. */ bool isApplicable() const { return SkToBool(this->fG); } const uint8_t* fR; const uint8_t* fG; const uint8_t* fB; }; template SkTMaskPreBlend SkTMaskGamma::preBlend(SkColor color) const { return fIsLinear ? SkTMaskPreBlend() : SkTMaskPreBlend(sk_ref_sp(this), fGammaTables[SkColorGetR(color) >> (8 - MAX_LUM_BITS)], fGammaTables[SkColorGetG(color) >> (8 - MAX_LUM_BITS)], fGammaTables[SkColorGetB(color) >> (8 - MAX_LUM_BITS)]); } ///@{ /** * If APPLY_LUT is false, returns component unchanged. * If APPLY_LUT is true, returns lut[component]. * @param APPLY_LUT whether or not the look-up table should be applied to component. * @component the initial component. * @lut a look-up table which transforms the component. */ template static inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t*) { return component; } template<> /*static*/ inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t* lut) { return lut[component]; } ///@} #endif