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