| 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 | |