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