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