SkTableColorFilter.cpp revision f3f5bad7ded35265c0b5d042cc4174386b197a33
1
2#include "SkBitmap.h"
3#include "SkTableColorFilter.h"
4#include "SkColorPriv.h"
5#include "SkReadBuffer.h"
6#include "SkWriteBuffer.h"
7#include "SkUnPreMultiply.h"
8#include "SkString.h"
9
10class SkTable_ColorFilter : public SkColorFilter {
11public:
12    SkTable_ColorFilter(const uint8_t tableA[], const uint8_t tableR[],
13                        const uint8_t tableG[], const uint8_t tableB[]) {
14        fBitmap = NULL;
15        fFlags = 0;
16
17        uint8_t* dst = fStorage;
18        if (tableA) {
19            memcpy(dst, tableA, 256);
20            dst += 256;
21            fFlags |= kA_Flag;
22        }
23        if (tableR) {
24            memcpy(dst, tableR, 256);
25            dst += 256;
26            fFlags |= kR_Flag;
27        }
28        if (tableG) {
29            memcpy(dst, tableG, 256);
30            dst += 256;
31            fFlags |= kG_Flag;
32        }
33        if (tableB) {
34            memcpy(dst, tableB, 256);
35            fFlags |= kB_Flag;
36        }
37    }
38
39    virtual ~SkTable_ColorFilter() {
40        SkDELETE(fBitmap);
41    }
42
43    virtual bool asComponentTable(SkBitmap* table) const SK_OVERRIDE;
44
45#if SK_SUPPORT_GPU
46    virtual GrFragmentProcessor* asFragmentProcessor(GrContext* context) const SK_OVERRIDE;
47#endif
48
49    virtual void filterSpan(const SkPMColor src[], int count,
50                            SkPMColor dst[]) const SK_OVERRIDE;
51
52    SK_TO_STRING_OVERRIDE()
53
54    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTable_ColorFilter)
55
56    enum {
57        kA_Flag = 1 << 0,
58        kR_Flag = 1 << 1,
59        kG_Flag = 1 << 2,
60        kB_Flag = 1 << 3,
61    };
62
63protected:
64    virtual void flatten(SkWriteBuffer&) const SK_OVERRIDE;
65
66private:
67    mutable const SkBitmap* fBitmap; // lazily allocated
68
69    uint8_t fStorage[256 * 4];
70    unsigned fFlags;
71
72    friend class SkTableColorFilter;
73
74    typedef SkColorFilter INHERITED;
75};
76
77static const uint8_t gIdentityTable[] = {
78    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
79    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
80    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
81    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
82    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
83    0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
84    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
85    0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
86    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
87    0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
88    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
89    0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
90    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
91    0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
92    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
93    0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
94    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
95    0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
96    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
97    0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
98    0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
99    0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
100    0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
101    0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
102    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
103    0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
104    0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
105    0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
106    0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
107    0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
108    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
109    0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
110};
111
112void SkTable_ColorFilter::filterSpan(const SkPMColor src[], int count,
113                                     SkPMColor dst[]) const {
114    const uint8_t* table = fStorage;
115    const uint8_t* tableA = gIdentityTable;
116    const uint8_t* tableR = gIdentityTable;
117    const uint8_t* tableG = gIdentityTable;
118    const uint8_t* tableB = gIdentityTable;
119    if (fFlags & kA_Flag) {
120        tableA = table; table += 256;
121    }
122    if (fFlags & kR_Flag) {
123        tableR = table; table += 256;
124    }
125    if (fFlags & kG_Flag) {
126        tableG = table; table += 256;
127    }
128    if (fFlags & kB_Flag) {
129        tableB = table;
130    }
131
132    const SkUnPreMultiply::Scale* scaleTable = SkUnPreMultiply::GetScaleTable();
133    for (int i = 0; i < count; ++i) {
134        SkPMColor c = src[i];
135        unsigned a, r, g, b;
136        if (0 == c) {
137            a = r = g = b = 0;
138        } else {
139            a = SkGetPackedA32(c);
140            r = SkGetPackedR32(c);
141            g = SkGetPackedG32(c);
142            b = SkGetPackedB32(c);
143
144            if (a < 255) {
145                SkUnPreMultiply::Scale scale = scaleTable[a];
146                r = SkUnPreMultiply::ApplyScale(scale, r);
147                g = SkUnPreMultiply::ApplyScale(scale, g);
148                b = SkUnPreMultiply::ApplyScale(scale, b);
149            }
150        }
151        dst[i] = SkPremultiplyARGBInline(tableA[a], tableR[r],
152                                         tableG[g], tableB[b]);
153    }
154}
155
156#ifndef SK_IGNORE_TO_STRING
157void SkTable_ColorFilter::toString(SkString* str) const {
158    const uint8_t* table = fStorage;
159    const uint8_t* tableA = gIdentityTable;
160    const uint8_t* tableR = gIdentityTable;
161    const uint8_t* tableG = gIdentityTable;
162    const uint8_t* tableB = gIdentityTable;
163    if (fFlags & kA_Flag) {
164        tableA = table; table += 256;
165    }
166    if (fFlags & kR_Flag) {
167        tableR = table; table += 256;
168    }
169    if (fFlags & kG_Flag) {
170        tableG = table; table += 256;
171    }
172    if (fFlags & kB_Flag) {
173        tableB = table;
174    }
175
176    str->append("SkTable_ColorFilter (");
177
178    for (int i = 0; i < 256; ++i) {
179        str->appendf("%d: %d,%d,%d,%d\n",
180                     i, tableR[i], tableG[i], tableB[i], tableA[i]);
181    }
182
183    str->append(")");
184}
185#endif
186
187static const uint8_t gCountNibBits[] = {
188    0, 1, 1, 2,
189    1, 2, 2, 3,
190    1, 2, 2, 3,
191    2, 3, 3, 4
192};
193
194#include "SkPackBits.h"
195
196void SkTable_ColorFilter::flatten(SkWriteBuffer& buffer) const {
197    uint8_t storage[5*256];
198    int count = gCountNibBits[fFlags & 0xF];
199    size_t size = SkPackBits::Pack8(fStorage, count * 256, storage);
200    SkASSERT(size <= sizeof(storage));
201
202    buffer.write32(fFlags);
203    buffer.writeByteArray(storage, size);
204}
205
206SkFlattenable* SkTable_ColorFilter::CreateProc(SkReadBuffer& buffer) {
207    const int flags = buffer.read32();
208    const size_t count = gCountNibBits[flags & 0xF];
209    SkASSERT(count <= 4);
210
211    uint8_t packedStorage[5*256];
212    size_t packedSize = buffer.getArrayCount();
213    if (!buffer.validate(packedSize <= sizeof(packedStorage))) {
214        return NULL;
215    }
216    if (!buffer.readByteArray(packedStorage, packedSize)) {
217        return NULL;
218    }
219
220    uint8_t unpackedStorage[4*256];
221    size_t unpackedSize = SkPackBits::Unpack8(packedStorage, packedSize, unpackedStorage);
222    // now check that we got the size we expected
223    if (!buffer.validate(unpackedSize == count*256)) {
224        return NULL;
225    }
226
227    const uint8_t* a = NULL;
228    const uint8_t* r = NULL;
229    const uint8_t* g = NULL;
230    const uint8_t* b = NULL;
231    const uint8_t* ptr = unpackedStorage;
232
233    if (flags & kA_Flag) {
234        a = ptr;
235        ptr += 256;
236    }
237    if (flags & kR_Flag) {
238        r = ptr;
239        ptr += 256;
240    }
241    if (flags & kG_Flag) {
242        g = ptr;
243        ptr += 256;
244    }
245    if (flags & kB_Flag) {
246        b = ptr;
247        ptr += 256;
248    }
249    return SkTableColorFilter::CreateARGB(a, r, g, b);
250}
251
252bool SkTable_ColorFilter::asComponentTable(SkBitmap* table) const {
253    if (table) {
254        if (NULL == fBitmap) {
255            SkBitmap* bmp = SkNEW(SkBitmap);
256            bmp->allocPixels(SkImageInfo::MakeA8(256, 4));
257            uint8_t* bitmapPixels = bmp->getAddr8(0, 0);
258            int offset = 0;
259            static const unsigned kFlags[] = { kA_Flag, kR_Flag, kG_Flag, kB_Flag };
260
261            for (int x = 0; x < 4; ++x) {
262                if (!(fFlags & kFlags[x])) {
263                    memcpy(bitmapPixels, gIdentityTable, sizeof(gIdentityTable));
264                } else {
265                    memcpy(bitmapPixels, fStorage + offset, 256);
266                    offset += 256;
267                }
268                bitmapPixels += 256;
269            }
270            fBitmap = bmp;
271        }
272        *table = *fBitmap;
273    }
274    return true;
275}
276
277#if SK_SUPPORT_GPU
278
279#include "GrFragmentProcessor.h"
280#include "GrInvariantOutput.h"
281#include "SkGr.h"
282#include "effects/GrTextureStripAtlas.h"
283#include "gl/GrGLProcessor.h"
284#include "gl/builders/GrGLProgramBuilder.h"
285
286class ColorTableEffect : public GrFragmentProcessor {
287public:
288    static GrFragmentProcessor* Create(GrContext* context, SkBitmap bitmap, unsigned flags);
289
290    virtual ~ColorTableEffect();
291
292    virtual const char* name() const SK_OVERRIDE { return "ColorTable"; }
293
294    virtual void getGLProcessorKey(const GrGLCaps&, GrProcessorKeyBuilder*) const SK_OVERRIDE;
295
296    virtual GrGLFragmentProcessor* createGLInstance() const SK_OVERRIDE;
297
298    const GrTextureStripAtlas* atlas() const { return fAtlas; }
299    int atlasRow() const { return fRow; }
300
301private:
302    virtual bool onIsEqual(const GrFragmentProcessor&) const SK_OVERRIDE;
303
304    virtual void onComputeInvariantOutput(GrInvariantOutput* inout) const SK_OVERRIDE;
305
306    ColorTableEffect(GrTexture* texture, GrTextureStripAtlas* atlas, int row, unsigned flags);
307
308    GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
309
310    GrTextureAccess         fTextureAccess;
311
312    // currently not used in shader code, just to assist onComputeInvariantOutput().
313    unsigned                fFlags;
314
315    GrTextureStripAtlas*    fAtlas;
316    int                     fRow;
317
318    typedef GrFragmentProcessor INHERITED;
319};
320
321class GLColorTableEffect : public GrGLFragmentProcessor {
322public:
323    GLColorTableEffect(const GrProcessor&);
324
325    virtual void emitCode(GrGLFPBuilder*,
326                          const GrFragmentProcessor&,
327                          const char* outputColor,
328                          const char* inputColor,
329                          const TransformedCoordsArray&,
330                          const TextureSamplerArray&) SK_OVERRIDE;
331
332    virtual void setData(const GrGLProgramDataManager&, const GrProcessor&) SK_OVERRIDE;
333
334    static void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder* b) {}
335
336private:
337    UniformHandle fRGBAYValuesUni;
338    typedef GrGLFragmentProcessor INHERITED;
339};
340
341GLColorTableEffect::GLColorTableEffect(const GrProcessor&) {
342}
343
344void GLColorTableEffect::setData(const GrGLProgramDataManager& pdm, const GrProcessor& proc) {
345    // The textures are organized in a strip where the rows are ordered a, r, g, b.
346    float rgbaYValues[4];
347    const ColorTableEffect& cte = proc.cast<ColorTableEffect>();
348    if (cte.atlas()) {
349        SkScalar yDelta = cte.atlas()->getNormalizedTexelHeight();
350        rgbaYValues[3] = cte.atlas()->getYOffset(cte.atlasRow()) + SK_ScalarHalf * yDelta;
351        rgbaYValues[0] = rgbaYValues[3] + yDelta;
352        rgbaYValues[1] = rgbaYValues[0] + yDelta;
353        rgbaYValues[2] = rgbaYValues[1] + yDelta;
354    } else {
355        rgbaYValues[3] = 0.125;
356        rgbaYValues[0] = 0.375;
357        rgbaYValues[1] = 0.625;
358        rgbaYValues[2] = 0.875;
359    }
360    pdm.set4fv(fRGBAYValuesUni, 1, rgbaYValues);
361}
362
363void GLColorTableEffect::emitCode(GrGLFPBuilder* builder,
364                                  const GrFragmentProcessor&,
365                                  const char* outputColor,
366                                  const char* inputColor,
367                                  const TransformedCoordsArray&,
368                                  const TextureSamplerArray& samplers) {
369    const char* yoffsets;
370    fRGBAYValuesUni = builder->addUniform(GrGLFPBuilder::kFragment_Visibility,
371                                          kVec4f_GrSLType, kDefault_GrSLPrecision,
372                                          "yoffsets", &yoffsets);
373    static const float kColorScaleFactor = 255.0f / 256.0f;
374    static const float kColorOffsetFactor = 1.0f / 512.0f;
375    GrGLFPFragmentBuilder* fsBuilder = builder->getFragmentShaderBuilder();
376    if (NULL == inputColor) {
377        // the input color is solid white (all ones).
378        static const float kMaxValue = kColorScaleFactor + kColorOffsetFactor;
379        fsBuilder->codeAppendf("\t\tvec4 coord = vec4(%f, %f, %f, %f);\n",
380                               kMaxValue, kMaxValue, kMaxValue, kMaxValue);
381
382    } else {
383        fsBuilder->codeAppendf("\t\tfloat nonZeroAlpha = max(%s.a, .0001);\n", inputColor);
384        fsBuilder->codeAppendf("\t\tvec4 coord = vec4(%s.rgb / nonZeroAlpha, nonZeroAlpha);\n", inputColor);
385        fsBuilder->codeAppendf("\t\tcoord = coord * %f + vec4(%f, %f, %f, %f);\n",
386                              kColorScaleFactor,
387                              kColorOffsetFactor, kColorOffsetFactor,
388                              kColorOffsetFactor, kColorOffsetFactor);
389    }
390
391    SkString coord;
392
393    fsBuilder->codeAppendf("\t\t%s.a = ", outputColor);
394    coord.printf("vec2(coord.a, %s.a)", yoffsets);
395    fsBuilder->appendTextureLookup(samplers[0], coord.c_str());
396    fsBuilder->codeAppend(";\n");
397
398    fsBuilder->codeAppendf("\t\t%s.r = ", outputColor);
399    coord.printf("vec2(coord.r, %s.r)", yoffsets);
400    fsBuilder->appendTextureLookup(samplers[0], coord.c_str());
401    fsBuilder->codeAppend(";\n");
402
403    fsBuilder->codeAppendf("\t\t%s.g = ", outputColor);
404    coord.printf("vec2(coord.g, %s.g)", yoffsets);
405    fsBuilder->appendTextureLookup(samplers[0], coord.c_str());
406    fsBuilder->codeAppend(";\n");
407
408    fsBuilder->codeAppendf("\t\t%s.b = ", outputColor);
409    coord.printf("vec2(coord.b, %s.b)", yoffsets);
410    fsBuilder->appendTextureLookup(samplers[0], coord.c_str());
411    fsBuilder->codeAppend(";\n");
412
413    fsBuilder->codeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
414}
415
416///////////////////////////////////////////////////////////////////////////////
417GrFragmentProcessor* ColorTableEffect::Create(GrContext* context, SkBitmap bitmap, unsigned flags) {
418
419    GrTextureStripAtlas::Desc desc;
420    desc.fWidth  = bitmap.width();
421    desc.fHeight = 128;
422    desc.fRowHeight = bitmap.height();
423    desc.fContext = context;
424    desc.fConfig = SkImageInfo2GrPixelConfig(bitmap.info());
425    GrTextureStripAtlas* atlas = GrTextureStripAtlas::GetAtlas(desc);
426    int row = atlas->lockRow(bitmap);
427    SkAutoTUnref<GrTexture> texture;
428    if (-1 == row) {
429        atlas = NULL;
430        // Passing params=NULL because this effect does no tiling or filtering.
431        texture.reset(GrRefCachedBitmapTexture(context, bitmap, NULL));
432    } else {
433        texture.reset(SkRef(atlas->getTexture()));
434    }
435
436    return SkNEW_ARGS(ColorTableEffect, (texture, atlas, row, flags));
437}
438
439ColorTableEffect::ColorTableEffect(GrTexture* texture, GrTextureStripAtlas* atlas, int row,
440                                   unsigned flags)
441    : fTextureAccess(texture, "a")
442    , fFlags(flags)
443    , fAtlas(atlas)
444    , fRow(row) {
445    this->initClassID<ColorTableEffect>();
446    this->addTextureAccess(&fTextureAccess);
447}
448
449ColorTableEffect::~ColorTableEffect() {
450    if (fAtlas) {
451        fAtlas->unlockRow(fRow);
452    }
453}
454
455void ColorTableEffect::getGLProcessorKey(const GrGLCaps& caps,
456                                         GrProcessorKeyBuilder* b) const {
457    GLColorTableEffect::GenKey(*this, caps, b);
458}
459
460GrGLFragmentProcessor* ColorTableEffect::createGLInstance() const {
461    return SkNEW_ARGS(GLColorTableEffect, (*this));
462}
463
464bool ColorTableEffect::onIsEqual(const GrFragmentProcessor& other) const {
465    // For non-atlased instances, the texture (compared by base class) is sufficient to
466    // differentiate different tables. For atlased instances we ensure they are using the
467    // same row.
468    const ColorTableEffect& that = other.cast<ColorTableEffect>();
469    SkASSERT(SkToBool(fAtlas) == SkToBool(that.fAtlas));
470    // Ok to always do this comparison since both would be -1 if non-atlased.
471    return fRow == that.fRow;
472}
473
474void ColorTableEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const {
475    // If we kept the table in the effect then we could actually run known inputs through the
476    // table.
477    uint8_t invalidateFlags = 0;
478    if (fFlags & SkTable_ColorFilter::kR_Flag) {
479        invalidateFlags |= kR_GrColorComponentFlag;
480    }
481    if (fFlags & SkTable_ColorFilter::kG_Flag) {
482        invalidateFlags |= kG_GrColorComponentFlag;
483    }
484    if (fFlags & SkTable_ColorFilter::kB_Flag) {
485        invalidateFlags |= kB_GrColorComponentFlag;
486    }
487    if (fFlags & SkTable_ColorFilter::kA_Flag) {
488        invalidateFlags |= kA_GrColorComponentFlag;
489    }
490    inout->invalidateComponents(invalidateFlags, GrInvariantOutput::kWill_ReadInput);
491}
492
493///////////////////////////////////////////////////////////////////////////////
494
495GR_DEFINE_FRAGMENT_PROCESSOR_TEST(ColorTableEffect);
496
497GrFragmentProcessor* ColorTableEffect::TestCreate(SkRandom* random,
498                                                  GrContext* context,
499                                                  const GrDrawTargetCaps&,
500                                                  GrTexture* textures[]) {
501    int flags = 0;
502    uint8_t luts[256][4];
503    do {
504        for (int i = 0; i < 4; ++i) {
505            flags |= random->nextBool() ? (1  << i): 0;
506        }
507    } while (!flags);
508    for (int i = 0; i < 4; ++i) {
509        if (flags & (1 << i)) {
510            for (int j = 0; j < 256; ++j) {
511                luts[j][i] = SkToU8(random->nextBits(8));
512            }
513        }
514    }
515    SkAutoTUnref<SkColorFilter> filter(SkTableColorFilter::CreateARGB(
516        (flags & (1 << 0)) ? luts[0] : NULL,
517        (flags & (1 << 1)) ? luts[1] : NULL,
518        (flags & (1 << 2)) ? luts[2] : NULL,
519        (flags & (1 << 3)) ? luts[3] : NULL
520    ));
521    return filter->asFragmentProcessor(context);
522}
523
524GrFragmentProcessor* SkTable_ColorFilter::asFragmentProcessor(GrContext* context) const {
525    SkBitmap bitmap;
526    this->asComponentTable(&bitmap);
527
528    return ColorTableEffect::Create(context, bitmap, fFlags);
529}
530
531#endif // SK_SUPPORT_GPU
532
533///////////////////////////////////////////////////////////////////////////////
534
535#ifdef SK_CPU_BENDIAN
536#else
537    #define SK_A32_INDEX    (3 - (SK_A32_SHIFT >> 3))
538    #define SK_R32_INDEX    (3 - (SK_R32_SHIFT >> 3))
539    #define SK_G32_INDEX    (3 - (SK_G32_SHIFT >> 3))
540    #define SK_B32_INDEX    (3 - (SK_B32_SHIFT >> 3))
541#endif
542
543///////////////////////////////////////////////////////////////////////////////
544
545SkColorFilter* SkTableColorFilter::Create(const uint8_t table[256]) {
546    return SkNEW_ARGS(SkTable_ColorFilter, (table, table, table, table));
547}
548
549SkColorFilter* SkTableColorFilter::CreateARGB(const uint8_t tableA[256],
550                                              const uint8_t tableR[256],
551                                              const uint8_t tableG[256],
552                                              const uint8_t tableB[256]) {
553    return SkNEW_ARGS(SkTable_ColorFilter, (tableA, tableR, tableG, tableB));
554}
555
556SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkTableColorFilter)
557    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTable_ColorFilter)
558SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
559