static void drawKernText(SkCanvas* canvas, const void* text, size_t len, SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint) { SkTypeface* face = font.getTypefaceOrDefault(); if (!face) { canvas->drawSimpleText(text, len, SkTextEncoding::kUTF8, x, y, font, paint); return; } SkAutoSTMalloc<128, uint16_t> glyphStorage(len); uint16_t* glyphs = glyphStorage.get(); int glyphCount = font.textToGlyphs(text, len, SkTextEncoding::kUTF8, glyphs, len); if (glyphCount < 1) { return; } SkAutoSTMalloc<128, int32_t> adjustmentStorage(glyphCount - 1); int32_t* adjustments = adjustmentStorage.get(); if (!face->getKerningPairAdjustments(glyphs, glyphCount, adjustments)) { canvas->drawSimpleText(text, len, SkTextEncoding::kUTF8, x, y, font, paint); return; } SkTextBlobBuilder builder; auto rec = builder.allocRunPos(font, glyphCount); memcpy(rec.glyphs, glyphs, glyphCount * sizeof(SkGlyphID)); getGlyphPositions(font, glyphs, glyphCount, x, y, rec.points()); applyKerning(rec.points(), adjustments, glyphCount, font); canvas->drawTextBlob(builder.make(), 0, 0, paint); }
void init() { fTypeface = chinese_typeface(); SkPaint paint; paint.setAntiAlias(true); paint.setColor(0xDE000000); paint.setTypeface(fTypeface); paint.setTextSize(11); paint.setTextEncoding(SkPaint::kUTF32_TextEncoding); paint.getFontMetrics(&fMetrics); SkUnichar glyphs[45]; for (int32_t i = 0; i < kNumBlobs; ++i) { SkTextBlobBuilder builder; auto paragraphLength = kParagraphLength; SkScalar y = 0; while (paragraphLength - 45 > 0) { auto currentLineLength = SkTMin(45, paragraphLength - 45); this->createRandomLine(glyphs, currentLineLength); sk_tool_utils::add_to_text_blob_w_len(&builder, (const char*) glyphs, currentLineLength*4, paint, 0, y); y += fMetrics.fDescent - fMetrics.fAscent + fMetrics.fLeading; paragraphLength -= 45; } fBlobs.emplace_back(builder.make()); } fIndex = 0; }
DEF_TEST(TextBlob_extended, reporter) { SkTextBlobBuilder textBlobBuilder; SkFont font; const char text1[] = "Foo"; const char text2[] = "Bar"; int glyphCount = font.countText(text1, strlen(text1), SkTextEncoding::kUTF8); SkAutoTMalloc<uint16_t> glyphs(glyphCount); (void)font.textToGlyphs(text1, strlen(text1), SkTextEncoding::kUTF8, glyphs.get(), glyphCount); auto run = SkTextBlobBuilderPriv::AllocRunText(&textBlobBuilder, font, glyphCount, 0, 0, SkToInt(strlen(text2)), SkString(), nullptr); memcpy(run.glyphs, glyphs.get(), sizeof(uint16_t) * glyphCount); memcpy(run.utf8text, text2, strlen(text2)); for (int i = 0; i < glyphCount; ++i) { run.clusters[i] = SkTMin(SkToU32(i), SkToU32(strlen(text2))); } sk_sp<SkTextBlob> blob(textBlobBuilder.make()); REPORTER_ASSERT(reporter, blob); for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) { REPORTER_ASSERT(reporter, it.glyphCount() == (uint32_t)glyphCount); for (uint32_t i = 0; i < it.glyphCount(); ++i) { REPORTER_ASSERT(reporter, it.glyphs()[i] == glyphs[i]); } REPORTER_ASSERT(reporter, SkTextBlobRunIterator::kDefault_Positioning == it.positioning()); REPORTER_ASSERT(reporter, (SkPoint{0.0f, 0.0f}) == it.offset()); REPORTER_ASSERT(reporter, it.textSize() > 0); REPORTER_ASSERT(reporter, it.clusters()); for (uint32_t i = 0; i < it.glyphCount(); ++i) { REPORTER_ASSERT(reporter, i == it.clusters()[i]); } REPORTER_ASSERT(reporter, 0 == strncmp(text2, it.text(), it.textSize())); } }
/* * Build a blob with more than one typeface. * Draw it into an offscreen, * then serialize and deserialize, * Then draw the new instance and assert it draws the same as the original. */ DEF_TEST(TextBlob_serialize, reporter) { sk_sp<SkTextBlob> blob0 = []() { sk_sp<SkTypeface> tf = SkTypeface::MakeDefault(); SkTextBlobBuilder builder; add_run(&builder, "Hello", 10, 20, nullptr); // don't flatten a typeface add_run(&builder, "World", 10, 40, tf); // do flatten this typeface return builder.make(); }(); SkTDArray<SkTypeface*> array; SkSerialProcs serializeProcs; serializeProcs.fTypefaceProc = &SerializeTypeface; serializeProcs.fTypefaceCtx = (void*) &array; sk_sp<SkData> data = blob0->serialize(serializeProcs); REPORTER_ASSERT(reporter, array.count() == 1); SkDeserialProcs deserializeProcs; deserializeProcs.fTypefaceProc = &DeserializeTypeface; deserializeProcs.fTypefaceCtx = (void*) &array; sk_sp<SkTextBlob> blob1 = SkTextBlob::Deserialize(data->data(), data->size(), deserializeProcs); sk_sp<SkImage> img0 = render(blob0.get()); sk_sp<SkImage> img1 = render(blob1.get()); if (img0 && img1) { REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img0.get(), img1.get())); } }
SkRect Text::onRevalidate(InvalidationController*, const SkMatrix&) { // TODO: we could potentially track invals which don't require rebuilding the blob. SkPaint font; font.setFlags(fFlags); font.setTypeface(fTypeface); font.setTextSize(fSize); font.setTextScaleX(fScaleX); font.setTextSkewX(fSkewX); font.setTextAlign(fAlign); font.setHinting(fHinting); // First, convert to glyphIDs. font.setTextEncoding(SkPaint::kUTF8_TextEncoding); SkSTArray<256, SkGlyphID, true> glyphs; glyphs.reset(font.textToGlyphs(fText.c_str(), fText.size(), nullptr)); SkAssertResult(font.textToGlyphs(fText.c_str(), fText.size(), glyphs.begin()) == glyphs.count()); font.setTextEncoding(SkPaint::kGlyphID_TextEncoding); // Next, build the cached blob. SkTextBlobBuilder builder; const auto& buf = builder.allocRun(font, glyphs.count(), 0, 0, nullptr); if (!buf.glyphs) { fBlob.reset(); return SkRect::MakeEmpty(); } memcpy(buf.glyphs, glyphs.begin(), glyphs.count() * sizeof(SkGlyphID)); fBlob = builder.make(); return fBlob ? fBlob->bounds().makeOffset(fPosition.x(), fPosition.y()) : SkRect::MakeEmpty(); }
void draw(SkCanvas* canvas) { SkTextBlobBuilder builder; SkPaint paint; SkFont font; const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPosH(font, 5, 20); paint.textToGlyphs("hello", 5, run.glyphs); SkScalar positions[] = {0, 10, 20, 40, 80}; memcpy(run.pos, positions, sizeof(positions)); canvas->drawTextBlob(builder.make(), 20, 20, paint); }
void draw(SkCanvas* canvas) { SkTextBlobBuilder textBlobBuilder; SkFont font; font.setSize(50); const SkTextBlobBuilder::RunBuffer& run = textBlobBuilder.allocRun(font, 1, 20, 100); run.glyphs[0] = 20; sk_sp<const SkTextBlob> blob = textBlobBuilder.make(); SkPaint paint; paint.setColor(SK_ColorBLUE); canvas->drawTextBlob(blob.get(), 0, 0, paint); }
static void RunBuilderTest(skiatest::Reporter* reporter, SkTextBlobBuilder& builder, const RunDef in[], unsigned inCount, const RunDef out[], unsigned outCount) { SkFont font; unsigned glyphCount = 0; unsigned posCount = 0; for (unsigned i = 0; i < inCount; ++i) { AddRun(font, in[i].count, in[i].pos, SkPoint::Make(in[i].x, in[i].y), builder); glyphCount += in[i].count; posCount += in[i].count * in[i].pos; } sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, (inCount > 0) == SkToBool(blob)); if (!blob) { return; } SkTextBlobRunIterator it(blob.get()); for (unsigned i = 0; i < outCount; ++i) { REPORTER_ASSERT(reporter, !it.done()); REPORTER_ASSERT(reporter, out[i].pos == it.positioning()); REPORTER_ASSERT(reporter, out[i].count == it.glyphCount()); if (SkTextBlobRunIterator::kDefault_Positioning == out[i].pos) { REPORTER_ASSERT(reporter, out[i].x == it.offset().x()); REPORTER_ASSERT(reporter, out[i].y == it.offset().y()); } else if (SkTextBlobRunIterator::kHorizontal_Positioning == out[i].pos) { REPORTER_ASSERT(reporter, out[i].y == it.offset().y()); } for (unsigned k = 0; k < it.glyphCount(); ++k) { REPORTER_ASSERT(reporter, k % 128 == it.glyphs()[k]); if (SkTextBlobRunIterator::kHorizontal_Positioning == it.positioning()) { REPORTER_ASSERT(reporter, SkIntToScalar(k % 128) == it.pos()[k]); } else if (SkTextBlobRunIterator::kFull_Positioning == it.positioning()) { REPORTER_ASSERT(reporter, SkIntToScalar(k % 128) == it.pos()[k * 2]); REPORTER_ASSERT(reporter, -SkIntToScalar(k % 128) == it.pos()[k * 2 + 1]); } } it.next(); } REPORTER_ASSERT(reporter, it.done()); }
// Verify that text-related properties are captured in run paints. static void TestPaintProps(skiatest::Reporter* reporter) { SkFont font; // Kitchen sink font. font.setSize(42); font.setScaleX(4.2f); font.setTypeface(ToolUtils::create_portable_typeface()); font.setSkewX(0.42f); font.setHinting(SkFontHinting::kFull); font.setEdging(SkFont::Edging::kSubpixelAntiAlias); font.setEmbolden(true); font.setLinearMetrics(true); font.setSubpixel(true); font.setEmbeddedBitmaps(true); font.setForceAutoHinting(true); // Ensure we didn't pick default values by mistake. SkFont defaultFont; REPORTER_ASSERT(reporter, defaultFont.getSize() != font.getSize()); REPORTER_ASSERT(reporter, defaultFont.getScaleX() != font.getScaleX()); REPORTER_ASSERT(reporter, defaultFont.getTypefaceOrDefault() != font.getTypefaceOrDefault()); REPORTER_ASSERT(reporter, defaultFont.getSkewX() != font.getSkewX()); REPORTER_ASSERT(reporter, defaultFont.getHinting() != font.getHinting()); REPORTER_ASSERT(reporter, defaultFont.getEdging() != font.getEdging()); REPORTER_ASSERT(reporter, defaultFont.isEmbolden() != font.isEmbolden()); REPORTER_ASSERT(reporter, defaultFont.isLinearMetrics() != font.isLinearMetrics()); REPORTER_ASSERT(reporter, defaultFont.isSubpixel() != font.isSubpixel()); REPORTER_ASSERT(reporter, defaultFont.isEmbeddedBitmaps() != font.isEmbeddedBitmaps()); REPORTER_ASSERT(reporter, defaultFont.isForceAutoHinting() != font.isForceAutoHinting()); SkTextBlobBuilder builder; AddRun(font, 1, SkTextBlobRunIterator::kDefault_Positioning, SkPoint::Make(0, 0), builder); AddRun(font, 1, SkTextBlobRunIterator::kHorizontal_Positioning, SkPoint::Make(0, 0), builder); AddRun(font, 1, SkTextBlobRunIterator::kFull_Positioning, SkPoint::Make(0, 0), builder); sk_sp<SkTextBlob> blob(builder.make()); SkTextBlobRunIterator it(blob.get()); while (!it.done()) { REPORTER_ASSERT(reporter, it.font() == font); it.next(); } }
void init() { fTypeface = chinese_typeface(); SkPaint paint; make_paint(&paint, fTypeface); paint.getFontMetrics(&fMetrics); SkUnichar glyphs[kWordLength]; for (int32_t i = 0; i < kNumBlobs; ++i) { this->createRandomWord(glyphs); SkTextBlobBuilder builder; sk_tool_utils::add_to_text_blob_w_len(&builder, (const char*) glyphs, kWordLength*4, paint, 0, 0); fBlobs.emplace_back(builder.make()); } fIndex = 0; }
void onDraw(SkCanvas* canvas) override { SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kFill_Style); const char text[] = "hambur"; sk_tool_utils::set_portable_typeface(&paint); paint.setTextSize(256); paint.setAntiAlias(true); // setup up maskfilter const SkScalar kSigma = SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(50)); SkPaint blurPaint(paint); blurPaint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, kSigma)); SkTextBlobBuilder builder; sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, 0); sk_sp<SkTextBlob> blob(builder.make()); SkPaint clearPaint(paint); clearPaint.setColor(SK_ColorWHITE); canvas->save(); canvas->translate(0, 0); canvas->clipRect(SkRect::MakeLTRB(0, 0, WIDTH, 256)); draw_text(canvas, blob, paint, blurPaint, clearPaint); canvas->restore(); canvas->save(); canvas->translate(0, 256); canvas->clipRect(SkRect::MakeLTRB(0, 256, WIDTH, 510)); draw_text(canvas, blob, paint, blurPaint, clearPaint); canvas->restore(); }
// This test hammers the GPU textblobcache and font atlas static void text_blob_cache_inner(skiatest::Reporter* reporter, GrContext* context, int maxTotalText, int maxGlyphID, int maxFamilies, bool normal, bool stressTest) { // setup surface uint32_t flags = 0; SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType); // configure our context for maximum stressing of cache and atlas if (stressTest) { GrTest::SetupAlwaysEvictAtlas(context); context->setTextBlobCacheLimit_ForTesting(0); } SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kN32_SkColorType, kPremul_SkAlphaType); auto surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, &props)); REPORTER_ASSERT(reporter, surface); if (!surface) { return; } SkCanvas* canvas = surface->getCanvas(); sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); int count = SkMin32(fm->countFamilies(), maxFamilies); // make a ton of text SkAutoTArray<uint16_t> text(maxTotalText); for (int i = 0; i < maxTotalText; i++) { text[i] = i % maxGlyphID; } // generate textblobs SkTArray<sk_sp<SkTextBlob>> blobs; for (int i = 0; i < count; i++) { SkPaint paint; paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setTextSize(48); // draw big glyphs to really stress the atlas SkString familyName; fm->getFamilyName(i, &familyName); sk_sp<SkFontStyleSet> set(fm->createStyleSet(i)); for (int j = 0; j < set->count(); ++j) { SkFontStyle fs; set->getStyle(j, &fs, nullptr); // We use a typeface which randomy returns unexpected mask formats to fuzz sk_sp<SkTypeface> orig(set->createTypeface(j)); if (normal) { paint.setTypeface(orig); } else { paint.setTypeface(sk_make_sp<SkRandomTypeface>(orig, paint, true)); } SkTextBlobBuilder builder; for (int aa = 0; aa < 2; aa++) { for (int subpixel = 0; subpixel < 2; subpixel++) { for (int lcd = 0; lcd < 2; lcd++) { paint.setAntiAlias(SkToBool(aa)); paint.setSubpixelText(SkToBool(subpixel)); paint.setLCDRenderText(SkToBool(lcd)); if (!SkToBool(lcd)) { paint.setTextSize(160); } const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(paint, maxTotalText, 0, 0, nullptr); memcpy(run.glyphs, text.get(), maxTotalText * sizeof(uint16_t)); } } } blobs.emplace_back(builder.make()); } } // create surface where LCD is impossible info = SkImageInfo::MakeN32Premul(kWidth, kHeight); SkSurfaceProps propsNoLCD(0, kUnknown_SkPixelGeometry); auto surfaceNoLCD(canvas->makeSurface(info, &propsNoLCD)); REPORTER_ASSERT(reporter, surface); if (!surface) { return; } SkCanvas* canvasNoLCD = surfaceNoLCD->getCanvas(); // test redraw draw(canvas, 2, blobs); draw(canvasNoLCD, 2, blobs); // test draw after free context->freeGpuResources(); draw(canvas, 1, blobs); context->freeGpuResources(); draw(canvasNoLCD, 1, blobs); // test draw after abandon context->abandonContext(); draw(canvas, 1, blobs); }
sk_sp<SkTextBlob> makeBlob(unsigned blobIndex) { SkTextBlobBuilder builder; SkFont font; font.setSubpixel(true); font.setEdging(SkFont::Edging::kAntiAlias); font.setTypeface(fTypeface); for (unsigned l = 0; l < SK_ARRAY_COUNT(blobConfigs[blobIndex]); ++l) { unsigned currentGlyph = 0; for (unsigned c = 0; c < SK_ARRAY_COUNT(blobConfigs[blobIndex][l]); ++c) { const BlobCfg* cfg = &blobConfigs[blobIndex][l][c]; unsigned count = cfg->count; if (count > fGlyphs.count() - currentGlyph) { count = fGlyphs.count() - currentGlyph; } if (0 == count) { break; } font.setSize(kFontSize * cfg->scale); const SkScalar advanceX = font.getSize() * 0.85f; const SkScalar advanceY = font.getSize() * 1.5f; SkPoint offset = SkPoint::Make(currentGlyph * advanceX + c * advanceX, advanceY * l); switch (cfg->pos) { case kDefault_Pos: { const SkTextBlobBuilder::RunBuffer& buf = builder.allocRun(font, count, offset.x(), offset.y()); memcpy(buf.glyphs, fGlyphs.begin() + currentGlyph, count * sizeof(uint16_t)); } break; case kScalar_Pos: { const SkTextBlobBuilder::RunBuffer& buf = builder.allocRunPosH(font, count, offset.y()); SkTDArray<SkScalar> pos; for (unsigned i = 0; i < count; ++i) { *pos.append() = offset.x() + i * advanceX; } memcpy(buf.glyphs, fGlyphs.begin() + currentGlyph, count * sizeof(uint16_t)); memcpy(buf.pos, pos.begin(), count * sizeof(SkScalar)); } break; case kPoint_Pos: { const SkTextBlobBuilder::RunBuffer& buf = builder.allocRunPos(font, count); SkTDArray<SkScalar> pos; for (unsigned i = 0; i < count; ++i) { *pos.append() = offset.x() + i * advanceX; *pos.append() = offset.y() + i * (advanceY / count); } memcpy(buf.glyphs, fGlyphs.begin() + currentGlyph, count * sizeof(uint16_t)); memcpy(buf.pos, pos.begin(), count * sizeof(SkScalar) * 2); } break; default: SK_ABORT("unhandled pos value"); } currentGlyph += count; } } return builder.make(); }
// This unit test verifies blob bounds computation. static void TestBounds(skiatest::Reporter* reporter) { SkTextBlobBuilder builder; SkFont font; // Explicit bounds. { sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, !blob); } { SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); builder.allocRun(font, 16, 0, 0, &r1); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds() == r1); } { SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); builder.allocRunPosH(font, 16, 0, &r1); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds() == r1); } { SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); builder.allocRunPos(font, 16, &r1); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds() == r1); } { SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); SkRect r2 = SkRect::MakeXYWH(15, 20, 50, 50); SkRect r3 = SkRect::MakeXYWH(0, 5, 10, 5); builder.allocRun(font, 16, 0, 0, &r1); builder.allocRunPosH(font, 16, 0, &r2); builder.allocRunPos(font, 16, &r3); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds() == SkRect::MakeXYWH(0, 5, 65, 65)); } { sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, !blob); } // Implicit bounds { // Exercise the empty bounds path, and ensure that RunRecord-aligned pos buffers // don't trigger asserts (http://crbug.com/542643). SkFont font; font.setSize(0); const char* txt = "BOOO"; const size_t txtLen = strlen(txt); const int glyphCount = font.countText(txt, txtLen, SkTextEncoding::kUTF8); const SkTextBlobBuilder::RunBuffer& buffer = builder.allocRunPos(font, glyphCount); font.textToGlyphs(txt, txtLen, SkTextEncoding::kUTF8, buffer.glyphs, glyphCount); memset(buffer.pos, 0, sizeof(SkScalar) * glyphCount * 2); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds().isEmpty()); } }
void onOnceBeforeDraw() override { SkTextBlobBuilder builder; // LCD SkPaint paint; paint.setTextSize(32); const char* text = "The quick brown fox jumps over the lazy dog"; paint.setSubpixelText(true); paint.setLCDRenderText(true); paint.setAntiAlias(true); sk_tool_utils::set_portable_typeface(&paint); add_to_text_blob(&builder, text, paint, 0, 0); fBlob = builder.make(); // create a looper which sandwhiches an effect in two normal draws LooperSettings looperSandwhich[] = { { SkBlendMode::kSrc, SK_ColorMAGENTA, SkPaint::kFill_Style, 0, 0, 0, false }, { SkBlendMode::kSrcOver, 0x88000000, SkPaint::kFill_Style, 0, 10.f, 0, true }, { SkBlendMode::kSrcOver, 0x50FF00FF, SkPaint::kFill_Style, 0, 20.f, 0, false }, }; LooperSettings compound[] = { { SkBlendMode::kSrc, SK_ColorWHITE, SkPaint::kStroke_Style, 1.f * 3/4, 0, 0, false }, { SkBlendMode::kSrc, SK_ColorRED, SkPaint::kStroke_Style, 4.f, 0, 0, false }, { SkBlendMode::kSrc, SK_ColorBLUE, SkPaint::kFill_Style, 0, 0, 0, false }, { SkBlendMode::kSrcOver, 0x88000000, SkPaint::kFill_Style, 0, 10.f, 0, true } }; LooperSettings xfermode[] = { { SkBlendMode::kDifference, SK_ColorWHITE, SkPaint::kFill_Style, 0, 0, 0, false }, { SkBlendMode::kSrcOver, 0xFF000000, SkPaint::kFill_Style, 0, 1.f, 0, true }, { SkBlendMode::kSrcOver, 0x50FF00FF, SkPaint::kFill_Style, 0, 2.f, 0, false }, }; // NOTE, this should be ignored by textblobs LooperSettings skew[] = { { SkBlendMode::kSrc, SK_ColorRED, SkPaint::kFill_Style, 0, 0, -1.f, false }, { SkBlendMode::kSrc, SK_ColorGREEN, SkPaint::kFill_Style, 0, 10.f, -1.f, false }, { SkBlendMode::kSrc, SK_ColorBLUE, SkPaint::kFill_Style, 0, 20.f, -1.f, false }, }; LooperSettings kitchenSink[] = { { SkBlendMode::kSrc, SK_ColorWHITE, SkPaint::kStroke_Style, 1.f * 3/4, 0, 0, false }, { SkBlendMode::kSrc, SK_ColorBLACK, SkPaint::kFill_Style, 0, 0, 0, false }, { SkBlendMode::kDifference, SK_ColorWHITE, SkPaint::kFill_Style, 1.f, 10.f, 0, false }, { SkBlendMode::kSrc, SK_ColorWHITE, SkPaint::kFill_Style, 0, 10.f, 0, true }, { SkBlendMode::kSrcOver, 0x50FF00FF, SkPaint::kFill_Style, 0, 20.f, 0, false }, }; fLoopers.push_back(setupLooper(SkLayerDrawLooper::kMaskFilter_Bit | SkLayerDrawLooper::kXfermode_Bit | SkLayerDrawLooper::kStyle_Bit, &mask_filter, compound, SK_ARRAY_COUNT(compound))); fLoopers.push_back(setupLooper(SkLayerDrawLooper::kPathEffect_Bit | SkLayerDrawLooper::kXfermode_Bit, &path_effect, looperSandwhich, SK_ARRAY_COUNT(looperSandwhich))); fLoopers.push_back(setupLooper(SkLayerDrawLooper::kShader_Bit | SkLayerDrawLooper::kColorFilter_Bit | SkLayerDrawLooper::kXfermode_Bit, &color_filter, looperSandwhich, SK_ARRAY_COUNT(looperSandwhich))); fLoopers.push_back(setupLooper(SkLayerDrawLooper::kShader_Bit | SkLayerDrawLooper::kColorFilter_Bit | SkLayerDrawLooper::kXfermode_Bit, &color_filter, xfermode, SK_ARRAY_COUNT(xfermode))); fLoopers.push_back(setupLooper(0, nullptr, skew, SK_ARRAY_COUNT(skew))); fLoopers.push_back(setupLooper(SkLayerDrawLooper::kMaskFilter_Bit | SkLayerDrawLooper::kShader_Bit | SkLayerDrawLooper::kColorFilter_Bit | SkLayerDrawLooper::kPathEffect_Bit | SkLayerDrawLooper::kStyle_Bit | SkLayerDrawLooper::kXfermode_Bit, &kitchen_sink, kitchenSink, SK_ARRAY_COUNT(kitchenSink))); // Test we respect overrides fLoopers.push_back(setupLooper(0, &kitchen_sink, kitchenSink, SK_ARRAY_COUNT(kitchenSink))); }