xref: /aosp_15_r20/external/skia/src/core/SkMaskGamma.h (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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