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