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