| 1 | /* | 
|---|
| 2 | * Copyright 2012 Google Inc. | 
|---|
| 3 | * | 
|---|
| 4 | * Use of this source code is governed by a BSD-style license that can be | 
|---|
| 5 | * found in the LICENSE file. | 
|---|
| 6 | */ | 
|---|
| 7 |  | 
|---|
| 8 | #ifndef SkMaskGamma_DEFINED | 
|---|
| 9 | #define SkMaskGamma_DEFINED | 
|---|
| 10 |  | 
|---|
| 11 | #include "include/core/SkColor.h" | 
|---|
| 12 | #include "include/core/SkRefCnt.h" | 
|---|
| 13 | #include "include/core/SkTypes.h" | 
|---|
| 14 | #include "include/private/SkColorData.h" | 
|---|
| 15 | #include "include/private/SkNoncopyable.h" | 
|---|
| 16 |  | 
|---|
| 17 | /** | 
|---|
| 18 | * SkColorSpaceLuminance is used to convert luminances to and from linear and | 
|---|
| 19 | * perceptual color spaces. | 
|---|
| 20 | * | 
|---|
| 21 | * Luma is used to specify a linear luminance value [0.0, 1.0]. | 
|---|
| 22 | * Luminance is used to specify a luminance value in an arbitrary color space [0.0, 1.0]. | 
|---|
| 23 | */ | 
|---|
| 24 | class SkColorSpaceLuminance : SkNoncopyable { | 
|---|
| 25 | public: | 
|---|
| 26 | virtual ~SkColorSpaceLuminance() { } | 
|---|
| 27 |  | 
|---|
| 28 | /** Converts a color component luminance in the color space to a linear luma. */ | 
|---|
| 29 | virtual SkScalar toLuma(SkScalar gamma, SkScalar luminance) const = 0; | 
|---|
| 30 | /** Converts a linear luma to a color component luminance in the color space. */ | 
|---|
| 31 | virtual SkScalar fromLuma(SkScalar gamma, SkScalar luma) const = 0; | 
|---|
| 32 |  | 
|---|
| 33 | /** Converts a color to a luminance value. */ | 
|---|
| 34 | static U8CPU computeLuminance(SkScalar gamma, SkColor c) { | 
|---|
| 35 | const SkColorSpaceLuminance& luminance = Fetch(gamma); | 
|---|
| 36 | SkScalar r = luminance.toLuma(gamma, SkIntToScalar(SkColorGetR(c)) / 255); | 
|---|
| 37 | SkScalar g = luminance.toLuma(gamma, SkIntToScalar(SkColorGetG(c)) / 255); | 
|---|
| 38 | SkScalar b = luminance.toLuma(gamma, SkIntToScalar(SkColorGetB(c)) / 255); | 
|---|
| 39 | SkScalar luma = r * SK_LUM_COEFF_R + | 
|---|
| 40 | g * SK_LUM_COEFF_G + | 
|---|
| 41 | b * SK_LUM_COEFF_B; | 
|---|
| 42 | SkASSERT(luma <= SK_Scalar1); | 
|---|
| 43 | return SkScalarRoundToInt(luminance.fromLuma(gamma, luma) * 255); | 
|---|
| 44 | } | 
|---|
| 45 |  | 
|---|
| 46 | /** Retrieves the SkColorSpaceLuminance for the given gamma. */ | 
|---|
| 47 | static const SkColorSpaceLuminance& Fetch(SkScalar gamma); | 
|---|
| 48 | }; | 
|---|
| 49 |  | 
|---|
| 50 | ///@{ | 
|---|
| 51 | /** | 
|---|
| 52 | * Scales base <= 2^N-1 to 2^8-1 | 
|---|
| 53 | * @param N [1, 8] the number of bits used by base. | 
|---|
| 54 | * @param base the number to be scaled to [0, 255]. | 
|---|
| 55 | */ | 
|---|
| 56 | template<U8CPU N> static inline U8CPU sk_t_scale255(U8CPU base) { | 
|---|
| 57 | base <<= (8 - N); | 
|---|
| 58 | U8CPU lum = base; | 
|---|
| 59 | for (unsigned int i = N; i < 8; i += N) { | 
|---|
| 60 | lum |= base >> i; | 
|---|
| 61 | } | 
|---|
| 62 | return lum; | 
|---|
| 63 | } | 
|---|
| 64 | template<> /*static*/ inline U8CPU sk_t_scale255<1>(U8CPU base) { | 
|---|
| 65 | return base * 0xFF; | 
|---|
| 66 | } | 
|---|
| 67 | template<> /*static*/ inline U8CPU sk_t_scale255<2>(U8CPU base) { | 
|---|
| 68 | return base * 0x55; | 
|---|
| 69 | } | 
|---|
| 70 | template<> /*static*/ inline U8CPU sk_t_scale255<4>(U8CPU base) { | 
|---|
| 71 | return base * 0x11; | 
|---|
| 72 | } | 
|---|
| 73 | template<> /*static*/ inline U8CPU sk_t_scale255<8>(U8CPU base) { | 
|---|
| 74 | return base; | 
|---|
| 75 | } | 
|---|
| 76 | ///@} | 
|---|
| 77 |  | 
|---|
| 78 | template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskPreBlend; | 
|---|
| 79 |  | 
|---|
| 80 | void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast, | 
|---|
| 81 | const SkColorSpaceLuminance& srcConvert, SkScalar srcGamma, | 
|---|
| 82 | const SkColorSpaceLuminance& dstConvert, SkScalar dstGamma); | 
|---|
| 83 |  | 
|---|
| 84 | /** | 
|---|
| 85 | * A regular mask contains linear alpha values. A gamma correcting mask | 
|---|
| 86 | * contains non-linear alpha values in an attempt to create gamma correct blits | 
|---|
| 87 | * in the presence of a gamma incorrect (linear) blend in the blitter. | 
|---|
| 88 | * | 
|---|
| 89 | * SkMaskGamma creates and maintains tables which convert linear alpha values | 
|---|
| 90 | * to gamma correcting alpha values. | 
|---|
| 91 | * @param R The number of luminance bits to use [1, 8] from the red channel. | 
|---|
| 92 | * @param G The number of luminance bits to use [1, 8] from the green channel. | 
|---|
| 93 | * @param B The number of luminance bits to use [1, 8] from the blue channel. | 
|---|
| 94 | */ | 
|---|
| 95 | template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskGamma : public SkRefCnt { | 
|---|
| 96 |  | 
|---|
| 97 | public: | 
|---|
| 98 |  | 
|---|
| 99 | /** Creates a linear SkTMaskGamma. */ | 
|---|
| 100 | SkTMaskGamma() : fIsLinear(true) { } | 
|---|
| 101 |  | 
|---|
| 102 | /** | 
|---|
| 103 | * Creates tables to convert linear alpha values to gamma correcting alpha | 
|---|
| 104 | * values. | 
|---|
| 105 | * | 
|---|
| 106 | * @param contrast A value in the range [0.0, 1.0] which indicates the | 
|---|
| 107 | *                 amount of artificial contrast to add. | 
|---|
| 108 | * @param paint The color space in which the paint color was chosen. | 
|---|
| 109 | * @param device The color space of the target device. | 
|---|
| 110 | */ | 
|---|
| 111 | SkTMaskGamma(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma) : fIsLinear(false) { | 
|---|
| 112 | const SkColorSpaceLuminance& paintConvert = SkColorSpaceLuminance::Fetch(paintGamma); | 
|---|
| 113 | const SkColorSpaceLuminance& deviceConvert = SkColorSpaceLuminance::Fetch(deviceGamma); | 
|---|
| 114 | for (U8CPU i = 0; i < (1 << MAX_LUM_BITS); ++i) { | 
|---|
| 115 | U8CPU lum = sk_t_scale255<MAX_LUM_BITS>(i); | 
|---|
| 116 | SkTMaskGamma_build_correcting_lut(fGammaTables[i], lum, contrast, | 
|---|
| 117 | paintConvert, paintGamma, | 
|---|
| 118 | deviceConvert, deviceGamma); | 
|---|
| 119 | } | 
|---|
| 120 | } | 
|---|
| 121 |  | 
|---|
| 122 | /** Given a color, returns the closest canonical color. */ | 
|---|
| 123 | static SkColor CanonicalColor(SkColor color) { | 
|---|
| 124 | return SkColorSetRGB( | 
|---|
| 125 | sk_t_scale255<R_LUM_BITS>(SkColorGetR(color) >> (8 - R_LUM_BITS)), | 
|---|
| 126 | sk_t_scale255<G_LUM_BITS>(SkColorGetG(color) >> (8 - G_LUM_BITS)), | 
|---|
| 127 | sk_t_scale255<B_LUM_BITS>(SkColorGetB(color) >> (8 - B_LUM_BITS))); | 
|---|
| 128 | } | 
|---|
| 129 |  | 
|---|
| 130 | /** The type of the mask pre-blend which will be returned from preBlend(SkColor). */ | 
|---|
| 131 | typedef SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> PreBlend; | 
|---|
| 132 |  | 
|---|
| 133 | /** | 
|---|
| 134 | * Provides access to the tables appropriate for converting linear alpha | 
|---|
| 135 | * values into gamma correcting alpha values when drawing the given color | 
|---|
| 136 | * through the mask. The destination color will be approximated. | 
|---|
| 137 | */ | 
|---|
| 138 | PreBlend preBlend(SkColor color) const; | 
|---|
| 139 |  | 
|---|
| 140 | /** | 
|---|
| 141 | * Get dimensions for the full table set, so it can be allocated as a block. | 
|---|
| 142 | */ | 
|---|
| 143 | void getGammaTableDimensions(int* tableWidth, int* numTables) const { | 
|---|
| 144 | *tableWidth = 256; | 
|---|
| 145 | *numTables = (1 << MAX_LUM_BITS); | 
|---|
| 146 | } | 
|---|
| 147 |  | 
|---|
| 148 | /** | 
|---|
| 149 | * Provides direct access to the full table set, so it can be uploaded | 
|---|
| 150 | * into a texture or analyzed in other ways. | 
|---|
| 151 | * Returns nullptr if fGammaTables hasn't been initialized. | 
|---|
| 152 | */ | 
|---|
| 153 | const uint8_t* getGammaTables() const { | 
|---|
| 154 | return fIsLinear ? nullptr : (const uint8_t*) fGammaTables; | 
|---|
| 155 | } | 
|---|
| 156 |  | 
|---|
| 157 | private: | 
|---|
| 158 | static const int MAX_LUM_BITS = | 
|---|
| 159 | B_LUM_BITS > (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS) | 
|---|
| 160 | ? B_LUM_BITS : (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS); | 
|---|
| 161 | uint8_t fGammaTables[1 << MAX_LUM_BITS][256]; | 
|---|
| 162 | bool fIsLinear; | 
|---|
| 163 |  | 
|---|
| 164 | typedef SkRefCnt INHERITED; | 
|---|
| 165 | }; | 
|---|
| 166 |  | 
|---|
| 167 |  | 
|---|
| 168 | /** | 
|---|
| 169 | * SkTMaskPreBlend is a tear-off of SkTMaskGamma. It provides the tables to | 
|---|
| 170 | * convert a linear alpha value for a given channel to a gamma correcting alpha | 
|---|
| 171 | * value for that channel. This class is immutable. | 
|---|
| 172 | * | 
|---|
| 173 | * If fR, fG, or fB is nullptr, all of them will be. This indicates that no mask | 
|---|
| 174 | * pre blend should be applied. SkTMaskPreBlend::isApplicable() is provided as | 
|---|
| 175 | * a convenience function to test for the absence of this case. | 
|---|
| 176 | */ | 
|---|
| 177 | template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskPreBlend { | 
|---|
| 178 | private: | 
|---|
| 179 | SkTMaskPreBlend(sk_sp<const SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>> parent, | 
|---|
| 180 | const uint8_t* r, const uint8_t* g, const uint8_t* b) | 
|---|
| 181 | : fParent(std::move(parent)), fR(r), fG(g), fB(b) { } | 
|---|
| 182 |  | 
|---|
| 183 | sk_sp<const SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>> fParent; | 
|---|
| 184 | friend class SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>; | 
|---|
| 185 | public: | 
|---|
| 186 | /** Creates a non applicable SkTMaskPreBlend. */ | 
|---|
| 187 | SkTMaskPreBlend() : fParent(), fR(nullptr), fG(nullptr), fB(nullptr) { } | 
|---|
| 188 |  | 
|---|
| 189 | /** | 
|---|
| 190 | * This copy contructor exists for correctness, but should never be called | 
|---|
| 191 | * when return value optimization is enabled. | 
|---|
| 192 | */ | 
|---|
| 193 | SkTMaskPreBlend(const SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>& that) | 
|---|
| 194 | : fParent(that.fParent), fR(that.fR), fG(that.fG), fB(that.fB) { } | 
|---|
| 195 |  | 
|---|
| 196 | ~SkTMaskPreBlend() { } | 
|---|
| 197 |  | 
|---|
| 198 | /** True if this PreBlend should be applied. When false, fR, fG, and fB are nullptr. */ | 
|---|
| 199 | bool isApplicable() const { return SkToBool(this->fG); } | 
|---|
| 200 |  | 
|---|
| 201 | const uint8_t* fR; | 
|---|
| 202 | const uint8_t* fG; | 
|---|
| 203 | const uint8_t* fB; | 
|---|
| 204 | }; | 
|---|
| 205 |  | 
|---|
| 206 | template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> | 
|---|
| 207 | SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> | 
|---|
| 208 | SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>::preBlend(SkColor color) const { | 
|---|
| 209 | return fIsLinear ? SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>() | 
|---|
| 210 | : SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>(sk_ref_sp(this), | 
|---|
| 211 | fGammaTables[SkColorGetR(color) >> (8 - MAX_LUM_BITS)], | 
|---|
| 212 | fGammaTables[SkColorGetG(color) >> (8 - MAX_LUM_BITS)], | 
|---|
| 213 | fGammaTables[SkColorGetB(color) >> (8 - MAX_LUM_BITS)]); | 
|---|
| 214 | } | 
|---|
| 215 |  | 
|---|
| 216 | ///@{ | 
|---|
| 217 | /** | 
|---|
| 218 | *  If APPLY_LUT is false, returns component unchanged. | 
|---|
| 219 | *  If APPLY_LUT is true, returns lut[component]. | 
|---|
| 220 | *  @param APPLY_LUT whether or not the look-up table should be applied to component. | 
|---|
| 221 | *  @component the initial component. | 
|---|
| 222 | *  @lut a look-up table which transforms the component. | 
|---|
| 223 | */ | 
|---|
| 224 | template<bool APPLY_LUT> static inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t*) { | 
|---|
| 225 | return component; | 
|---|
| 226 | } | 
|---|
| 227 | template<> /*static*/ inline U8CPU sk_apply_lut_if<true>(U8CPU component, const uint8_t* lut) { | 
|---|
| 228 | return lut[component]; | 
|---|
| 229 | } | 
|---|
| 230 | ///@} | 
|---|
| 231 |  | 
|---|
| 232 | #endif | 
|---|
| 233 |  | 
|---|