void SkTextView::onDraw(SkCanvas* canvas) { this->INHERITED::onDraw(canvas); if (fText.size() == 0) return; SkPaint::Align align = fPaint.getTextAlign(); SkScalar x, y; switch (align) { case SkPaint::kLeft_Align: x = fMargin.fX; break; case SkPaint::kCenter_Align: x = SkScalarHalf(this->width()); break; default: SkASSERT(align == SkPaint::kRight_Align); x = this->width() - fMargin.fX; break; } fPaint.measureText(nil, 0, &y, nil); y = fMargin.fY - y; if (fInterp) { if (fInterp->draw(canvas, fText, x, y, fPaint)) this->inval(nil); else { delete fInterp; fInterp = nil; } } else canvas->drawText(fText.c_str(), fText.size(), x, y, fPaint); }
// quadApprox - makes an approximation, which we hope is faster static void quadApprox(SkPath &fPath, const SkPoint &p0, const SkPoint &p1, const SkPoint &p2) { //divide the cubic up into two cubics, then convert them into quadratics //define our points SkPoint c,j,k,l,m,n,o,p,q, mid; fPath.getLastPt(&c); midPt(j, p0, c); midPt(k, p0, p1); midPt(l, p1, p2); midPt(o, j, k); midPt(p, k, l); midPt(q, o, p); //compute the first half m.set(SkScalarHalf(3*j.fX - c.fX), SkScalarHalf(3*j.fY - c.fY)); n.set(SkScalarHalf(3*o.fX -q.fX), SkScalarHalf(3*o.fY - q.fY)); midPt(mid,m,n); fPath.quadTo(mid,q); c = q; //compute the second half m.set(SkScalarHalf(3*p.fX - c.fX), SkScalarHalf(3*p.fY - c.fY)); n.set(SkScalarHalf(3*l.fX -p2.fX),SkScalarHalf(3*l.fY -p2.fY)); midPt(mid,m,n); fPath.quadTo(mid,p2); }
bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec, int intervalCount, SkScalar intervalLength) { if (rec->isHairlineStyle() || !src.isLine(fPts)) { return false; } // can relax this in the future, if we handle square and round caps if (SkPaint::kButt_Cap != rec->getCap()) { return false; } SkScalar pathLength = SkPoint::Distance(fPts[0], fPts[1]); fTangent = fPts[1] - fPts[0]; if (fTangent.isZero()) { return false; } fPathLength = pathLength; fTangent.scale(SkScalarInvert(pathLength)); fTangent.rotateCCW(&fNormal); fNormal.scale(SkScalarHalf(rec->getWidth())); // now estimate how many quads will be added to the path // resulting segments = pathLen * intervalCount / intervalLen // resulting points = 4 * segments SkScalar ptCount = SkScalarMulDiv(pathLength, SkIntToScalar(intervalCount), intervalLength); ptCount = SkTMin(ptCount, SkDashPath::kMaxDashCount); int n = SkScalarCeilToInt(ptCount) << 2; dst->incReserve(n); // we will take care of the stroking rec->setFillStyle(); return true; }
static const SkPicture* make_tri_picture() { SkPath tri = make_tri_path(SkScalarHalf(kTriSide), 0); SkPaint fill; fill.setStyle(SkPaint::kFill_Style); fill.setColor(SK_ColorLTGRAY);; SkPaint stroke; stroke.setStyle(SkPaint::kStroke_Style); stroke.setStrokeWidth(3); SkPictureRecorder recorder; SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth), SkIntToScalar(kPicHeight)); // The saveLayer/restore block is to exercise layer hoisting canvas->saveLayer(NULL, NULL); canvas->drawPath(tri, fill); canvas->drawPath(tri, stroke); canvas->restore(); return recorder.endRecording(); }
int Font::offsetForPositionForComplexText(const TextRun& run, int x, bool includePartialGlyphs) const { SkPaint paint; int count = run.length(); SkAutoSTMalloc<64, SkScalar> storage(count); SkScalar* widths = storage.get(); primaryFont()->platformData().setupPaint(&paint); count = paint.getTextWidths(run.characters(), count << 1, widths); if (count > 0) { SkScalar pos = 0; for (int i = 0; i < count; i++) { if (x < SkScalarRound(pos + SkScalarHalf(widths[i]))) return i; pos += widths[i]; } } return count; }
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { SkASSERT(&src != NULL && dst != NULL); SkScalar radius = SkScalarHalf(fWidth); AutoTmpPath tmp(src, &dst); if (radius <= 0) { return; } // If src is really a rect, call our specialty strokeRect() method { bool isClosed; SkPath::Direction dir; if (src.isRect(&isClosed, &dir) && isClosed) { this->strokeRect(src.getBounds(), dst, dir); // our answer should preserve the inverseness of the src if (src.isInverseFillType()) { SkASSERT(!dst->isInverseFillType()); dst->toggleInverseFillType(); } return; } } SkAutoConicToQuads converter; const SkScalar conicTol = SK_Scalar1 / 4; SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin()); SkPath::Iter iter(src, false); SkPath::Verb lastSegment = SkPath::kMove_Verb; for (;;) { SkPoint pts[4]; switch (iter.next(pts, false)) { case SkPath::kMove_Verb: stroker.moveTo(pts[0]); break; case SkPath::kLine_Verb: stroker.lineTo(pts[1]); lastSegment = SkPath::kLine_Verb; break; case SkPath::kQuad_Verb: stroker.quadTo(pts[1], pts[2]); lastSegment = SkPath::kQuad_Verb; break; case SkPath::kConic_Verb: { // todo: if we had maxcurvature for conics, perhaps we should // natively extrude the conic instead of converting to quads. const SkPoint* quadPts = converter.computeQuads(pts, iter.conicWeight(), conicTol); for (int i = 0; i < converter.countQuads(); ++i) { stroker.quadTo(quadPts[1], quadPts[2]); quadPts += 2; } lastSegment = SkPath::kQuad_Verb; } break; case SkPath::kCubic_Verb: stroker.cubicTo(pts[1], pts[2], pts[3]); lastSegment = SkPath::kCubic_Verb; break; case SkPath::kClose_Verb: stroker.close(lastSegment == SkPath::kLine_Verb); break; case SkPath::kDone_Verb: goto DONE; } } DONE: stroker.done(dst, lastSegment == SkPath::kLine_Verb); if (fDoFill) { if (src.cheapIsDirection(SkPath::kCCW_Direction)) { dst->reverseAddPath(src); } else { dst->addPath(src); } } else { // Seems like we can assume that a 2-point src would always result in // a convex stroke, but testing has proved otherwise. // TODO: fix the stroker to make this assumption true (without making // it slower that the work that will be done in computeConvexity()) #if 0 // this test results in a non-convex stroke :( static void test(SkCanvas* canvas) { SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; SkPaint paint; paint.setStrokeWidth(7); paint.setStrokeCap(SkPaint::kRound_Cap); canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); } #endif #if 0 if (2 == src.countPoints()) { dst->setIsConvex(true); } #endif }
static SkScalar center_align_proc(SkScalar childLimit, SkScalar parentLimit) { return SkScalarHalf(parentLimit - childLimit); }
void TextArtView::onDrawContent(SkCanvas* canvas) { const std::string TEXT = "Helpo TextArt Effects"; SkTypeface* typeface = SkTypeface::CreateFromName("Georgia", SkTypeface::kBold); if (!initialized) { initialized = true; WarpFrameT warpFrame; SkMatrix warpMatrix; warpMatrix.setIdentity(); getWarpParams(warpFrame, warpMatrix); bSkeleton_ = warpFrame[0]; if (warpFrame.size()>1) tSkeleton_ = warpFrame[1]; TextArt::EnvelopeWarp textArt(bSkeleton_, warpMatrix); textArt.setTopSkeleton(tSkeleton_); textArt.setIsNormalRotated(getIsNormalRotated()); textArt.setIsSymmetric(getIsSymmetric()); textArt.setIsTopBased(getIsTopBased()); path_ = textArt.warp(TEXT, typeface); } SkPaint paint; paint.setAntiAlias(true); paint.setTextSize(SkIntToScalar(64)); paint.setStyle(SkPaint::kStroke_Style); paint.setTypeface(typeface); canvas->save(); //center path on the cnavas const SkRect& pathBounds = path_.getBounds(); SkISize screenSize = canvas->getDeviceSize(); SkScalar xOffset = SkScalarHalf( SkIntToScalar(screenSize.width()) ) - pathBounds.centerX(); SkScalar yOffset = SkScalarHalf( SkIntToScalar(screenSize.height()) ) - pathBounds.centerY(); canvas->translate(xOffset, yOffset); paint.setColor(0x33FF3333); // canvas->drawRect(pathBounds, paint); /* paint.setColor(SK_ColorGREEN); canvas->drawPath(textArt.bSkeleton_, paint); canvas->drawPath(textArt.bWarped_, paint); paint.setColor(SK_ColorCYAN); canvas->drawPath(textArt.tSkeleton_, paint); canvas->drawPath(textArt.tWarped_, paint); */ //draw the skeleton path paint.setColor(SK_ColorBLUE); canvas->drawPath(bSkeleton_, paint); canvas->drawPath(tSkeleton_, paint); //draw the path paint.setColor(SK_ColorBLACK); paint.setStyle(SkPaint::kFill_Style); canvas->drawPath(path_, paint); /* paint.setColor(SK_ColorRED); canvas->drawPath(textArt.normal, paint); canvas->drawPath(textArt.tFlattern, paint); for(int i=0; i<textArt.intersections.size(); ++i) { canvas->drawCircle(textArt.intersections[i].fX, textArt.intersections[i].fY, 1.5, paint); } */ canvas->restore(); }
void GrBitmapTextContext::onDrawText(const GrPaint& paint, const SkPaint& skPaint, const char text[], size_t byteLength, SkScalar x, SkScalar y) { SkASSERT(byteLength == 0 || text != NULL); // nothing to draw if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) { return; } this->init(paint, skPaint); SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, &fContext->getMatrix()); SkGlyphCache* cache = autoCache.getCache(); GrFontScaler* fontScaler = GetGrFontScaler(cache); // transform our starting point { SkPoint loc; fContext->getMatrix().mapXY(x, y, &loc); x = loc.fX; y = loc.fY; } // need to measure first int numGlyphs; if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) { SkVector stopVector; numGlyphs = MeasureText(cache, glyphCacheProc, text, byteLength, &stopVector); SkScalar stopX = stopVector.fX; SkScalar stopY = stopVector.fY; if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) { stopX = SkScalarHalf(stopX); stopY = SkScalarHalf(stopY); } x -= stopX; y -= stopY; } else { numGlyphs = fSkPaint.textToGlyphs(text, byteLength, NULL); } fTotalVertexCount = kVerticesPerGlyph*numGlyphs; const char* stop = text + byteLength; SkAutoKern autokern; SkFixed fxMask = ~0; SkFixed fyMask = ~0; SkFixed halfSampleX, halfSampleY; if (cache->isSubpixel()) { halfSampleX = halfSampleY = (SK_FixedHalf >> SkGlyph::kSubBits); SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(fContext->getMatrix()); if (kX_SkAxisAlignment == baseline) { fyMask = 0; halfSampleY = SK_FixedHalf; } else if (kY_SkAxisAlignment == baseline) { fxMask = 0; halfSampleX = SK_FixedHalf; } } else {
static void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal, const SkPoint& pivot, const SkVector& afterUnitNormal, SkScalar radius, SkScalar invMiterLimit, bool prevIsLine, bool currIsLine) { // negate the dot since we're using normals instead of tangents SkScalar dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal); AngleType angleType = Dot2AngleType(dotProd); SkVector before = beforeUnitNormal; SkVector after = afterUnitNormal; SkVector mid; SkScalar sinHalfAngle; bool ccw; if (angleType == kNearlyLine_AngleType) return; if (angleType == kNearly180_AngleType) { currIsLine = false; goto DO_BLUNT; } ccw = !is_clockwise(before, after); if (ccw) { SkTSwap<SkPath*>(outer, inner); before.negate(); after.negate(); } /* Before we enter the world of square-roots and divides, check if we're trying to join an upright right angle (common case for stroking rectangles). If so, special case that (for speed an accuracy). Note: we only need to check one normal if dot==0 */ if (0 == dotProd && invMiterLimit <= kOneOverSqrt2) { mid.set(SkScalarMul(before.fX + after.fX, radius), SkScalarMul(before.fY + after.fY, radius)); goto DO_MITER; } /* midLength = radius / sinHalfAngle if (midLength > miterLimit * radius) abort if (radius / sinHalf > miterLimit * radius) abort if (1 / sinHalf > miterLimit) abort if (1 / miterLimit > sinHalf) abort My dotProd is opposite sign, since it is built from normals and not tangents hence 1 + dot instead of 1 - dot in the formula */ sinHalfAngle = SkScalarSqrt(SkScalarHalf(SK_Scalar1 + dotProd)); if (sinHalfAngle < invMiterLimit) { currIsLine = false; goto DO_BLUNT; } // choose the most accurate way to form the initial mid-vector if (angleType == kSharp_AngleType) { mid.set(after.fY - before.fY, before.fX - after.fX); if (ccw) mid.negate(); } else mid.set(before.fX + after.fX, before.fY + after.fY); mid.setLength(SkScalarDiv(radius, sinHalfAngle)); DO_MITER: if (prevIsLine) outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY); else outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY); DO_BLUNT: after.scale(radius); if (!currIsLine) outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY); HandleInnerJoin(inner, pivot, after); }
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { SkASSERT(&src != NULL && dst != NULL); SkScalar radius = SkScalarHalf(fWidth); dst->reset(); if (radius <= 0) { return; } #ifdef SK_SCALAR_IS_FIXED void (*proc)(SkPoint pts[], int count) = identity_proc; if (needs_to_shrink(src)) { proc = shift_down_2_proc; radius >>= 2; if (radius == 0) { return; } } #endif SkPathStroker stroker(radius, fMiterLimit, this->getCap(), this->getJoin()); SkPath::Iter iter(src, false); SkPoint pts[4]; SkPath::Verb verb, lastSegment = SkPath::kMove_Verb; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kMove_Verb: APPLY_PROC(proc, &pts[0], 1); stroker.moveTo(pts[0]); break; case SkPath::kLine_Verb: APPLY_PROC(proc, &pts[1], 1); stroker.lineTo(pts[1]); lastSegment = verb; break; case SkPath::kQuad_Verb: APPLY_PROC(proc, &pts[1], 2); stroker.quadTo(pts[1], pts[2]); lastSegment = verb; break; case SkPath::kCubic_Verb: APPLY_PROC(proc, &pts[1], 3); stroker.cubicTo(pts[1], pts[2], pts[3]); lastSegment = verb; break; case SkPath::kClose_Verb: stroker.close(lastSegment == SkPath::kLine_Verb); break; default: break; } } stroker.done(dst, lastSegment == SkPath::kLine_Verb); #ifdef SK_SCALAR_IS_FIXED // undo our previous down_shift if (shift_down_2_proc == proc) { // need a real shift methid on path. antialias paths could use this too SkMatrix matrix; matrix.setScale(SkIntToScalar(4), SkIntToScalar(4)); dst->transform(matrix); } #endif if (fDoFill) { if (src.cheapIsDirection(SkPath::kCCW_Direction)) { dst->reverseAddPath(src); } else { dst->addPath(src); } } else { // Seems like we can assume that a 2-point src would always result in // a convex stroke, but testing has proved otherwise. // TODO: fix the stroker to make this assumption true (without making // it slower that the work that will be done in computeConvexity()) #if 0 // this test results in a non-convex stroke :( static void test(SkCanvas* canvas) { SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; SkPaint paint; paint.setStrokeWidth(7); paint.setStrokeCap(SkPaint::kRound_Cap); canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); } #endif #if 0 if (2 == src.countPoints()) { dst->setIsConvex(true); } #endif }
void GrTextUtils::DrawDFText(GrAtlasTextBlob* blob, int runIndex, GrBatchFontCache* fontCache, const SkSurfaceProps& props, const SkPaint& skPaint, GrColor color, const SkMatrix& viewMatrix, const char text[], size_t byteLength, SkScalar x, SkScalar y) { SkASSERT(byteLength == 0 || text != nullptr); // nothing to draw if (text == nullptr || byteLength == 0) { return; } SkPaint::GlyphCacheProc glyphCacheProc = skPaint.getGlyphCacheProc(true); SkAutoDescriptor desc; skPaint.getScalerContextDescriptor(&desc, props, SkPaint::FakeGamma::Off, nullptr); SkGlyphCache* origPaintCache = SkGlyphCache::DetachCache(skPaint.getTypeface(), desc.getDesc()); SkTArray<SkScalar> positions; const char* textPtr = text; SkFixed stopX = 0; SkFixed stopY = 0; SkFixed origin = 0; switch (skPaint.getTextAlign()) { case SkPaint::kRight_Align: origin = SK_Fixed1; break; case SkPaint::kCenter_Align: origin = SK_FixedHalf; break; case SkPaint::kLeft_Align: origin = 0; break; } SkAutoKern autokern; const char* stop = text + byteLength; while (textPtr < stop) { // don't need x, y here, since all subpixel variants will have the // same advance const SkGlyph& glyph = glyphCacheProc(origPaintCache, &textPtr); SkFixed width = glyph.fAdvanceX + autokern.adjust(glyph); positions.push_back(SkFixedToScalar(stopX + SkFixedMul(origin, width))); SkFixed height = glyph.fAdvanceY; positions.push_back(SkFixedToScalar(stopY + SkFixedMul(origin, height))); stopX += width; stopY += height; } SkASSERT(textPtr == stop); SkGlyphCache::AttachCache(origPaintCache); // now adjust starting point depending on alignment SkScalar alignX = SkFixedToScalar(stopX); SkScalar alignY = SkFixedToScalar(stopY); if (skPaint.getTextAlign() == SkPaint::kCenter_Align) { alignX = SkScalarHalf(alignX); alignY = SkScalarHalf(alignY); } else if (skPaint.getTextAlign() == SkPaint::kLeft_Align) { alignX = 0; alignY = 0; } x -= alignX; y -= alignY; SkPoint offset = SkPoint::Make(x, y); DrawDFPosText(blob, runIndex, fontCache, props, skPaint, color, viewMatrix, text, byteLength, positions.begin(), 2, offset); }
// Need a quick way to know a maximum distance between drawText calls to know if // they are part of the same logical phrase when searching. By crude // inspection, half the point size seems a good guess at the width of a space // character. static inline SkScalar approximateSpaceWidth(const SkPaint& paint) { return SkScalarHalf(paint.getTextSize()); }
// Test out the basic API entry points static void test_round_rect_basic(skiatest::Reporter* reporter) { // Test out initialization methods SkPoint zeroPt = { 0, 0 }; SkRRect empty; empty.setEmpty(); REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == empty.type()); REPORTER_ASSERT(reporter, empty.rect().isEmpty()); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, zeroPt == empty.radii((SkRRect::Corner) i)); } //---- SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); SkRRect rr1; rr1.setRect(rect); REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr1.type()); REPORTER_ASSERT(reporter, rr1.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, zeroPt == rr1.radii((SkRRect::Corner) i)); } SkRRect rr1_2; // construct the same RR using the most general set function SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; rr1_2.setRectRadii(rect, rr1_2_radii); REPORTER_ASSERT(reporter, rr1_2 == rr1 && rr1_2.getType() == rr1.getType()); SkRRect rr1_3; // construct the same RR using the nine patch set function rr1_3.setNinePatch(rect, 0, 0, 0, 0); REPORTER_ASSERT(reporter, rr1_3 == rr1 && rr1_3.getType() == rr1.getType()); //---- SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) }; SkRRect rr2; rr2.setOval(rect); REPORTER_ASSERT(reporter, SkRRect::kOval_Type == rr2.type()); REPORTER_ASSERT(reporter, rr2.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, rr2.radii((SkRRect::Corner) i).equalsWithinTolerance(halfPoint)); } SkRRect rr2_2; // construct the same RR using the most general set function SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY } }; rr2_2.setRectRadii(rect, rr2_2_radii); REPORTER_ASSERT(reporter, rr2_2 == rr2 && rr2_2.getType() == rr2.getType()); SkRRect rr2_3; // construct the same RR using the nine patch set function rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY); REPORTER_ASSERT(reporter, rr2_3 == rr2 && rr2_3.getType() == rr2.getType()); //---- SkPoint p = { 5, 5 }; SkRRect rr3; rr3.setRectXY(rect, p.fX, p.fY); REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr3.type()); REPORTER_ASSERT(reporter, rr3.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, p == rr3.radii((SkRRect::Corner) i)); } SkRRect rr3_2; // construct the same RR using the most general set function SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } }; rr3_2.setRectRadii(rect, rr3_2_radii); REPORTER_ASSERT(reporter, rr3_2 == rr3 && rr3_2.getType() == rr3.getType()); SkRRect rr3_3; // construct the same RR using the nine patch set function rr3_3.setNinePatch(rect, 5, 5, 5, 5); REPORTER_ASSERT(reporter, rr3_3 == rr3 && rr3_3.getType() == rr3.getType()); //---- test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true); { // Test out the rrect from skia:3466 SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f, 0.806214333f); test_9patch_rrect(reporter, rect2, 0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f, false); } //---- SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } }; SkRRect rr5; rr5.setRectRadii(rect, radii2); REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr5.type()); REPORTER_ASSERT(reporter, rr5.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, radii2[i] == rr5.radii((SkRRect::Corner) i)); } // Test out == & != REPORTER_ASSERT(reporter, empty != rr3); REPORTER_ASSERT(reporter, rr3 != rr5); }
void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint) { SkASSERT(canvas && &paint && (text || len == 0)); SkScalar marginWidth = fBox.width(); if (marginWidth <= 0 || len == 0) return; const char* textStop = text + len; SkScalar x, y, scaledSpacing, height, fontHeight; SkPaint::FontMetrics metrics; switch (paint.getTextAlign()) { case SkPaint::kLeft_Align: x = 0; break; case SkPaint::kCenter_Align: x = SkScalarHalf(marginWidth); break; default: x = marginWidth; break; } x += fBox.fLeft; fontHeight = paint.getFontMetrics(&metrics); scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd; height = fBox.height(); // compute Y position for first line { SkScalar textHeight = fontHeight; if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign) { int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth); SkASSERT(count > 0); textHeight += scaledSpacing * (count - 1); } switch (fSpacingAlign) { case kStart_SpacingAlign: y = 0; break; case kCenter_SpacingAlign: y = SkScalarHalf(height - textHeight); break; default: SkASSERT(fSpacingAlign == kEnd_SpacingAlign); y = height - textHeight; break; } y += fBox.fTop - metrics.fAscent; } for (;;) { len = linebreak(text, textStop, paint, marginWidth); if (y + metrics.fDescent + metrics.fLeading > 0) canvas->drawText(text, len, x, y, paint); text += len; if (text >= textStop) break; y += scaledSpacing; if (y + metrics.fAscent >= height) break; } }
SkRSXform asRSXform() const { return SkRSXform::MakeFromRadians(fScale, fRadian, fCenter.x(), fCenter.y(), SkScalarHalf(kCellSize), SkScalarHalf(kCellSize)); }
void SkRRect::setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, SkScalar rightRad, SkScalar bottomRad) { fRect = rect; fRect.sort(); if (fRect.isEmpty() || !fRect.isFinite()) { this->setEmpty(); return; } const SkScalar array[4] = { leftRad, topRad, rightRad, bottomRad }; if (!SkScalarsAreFinite(array, 4)) { this->setRect(rect); // devolve into a simple rect return; } leftRad = SkMaxScalar(leftRad, 0); topRad = SkMaxScalar(topRad, 0); rightRad = SkMaxScalar(rightRad, 0); bottomRad = SkMaxScalar(bottomRad, 0); SkScalar scale = SK_Scalar1; if (leftRad + rightRad > fRect.width()) { scale = fRect.width() / (leftRad + rightRad); } if (topRad + bottomRad > fRect.height()) { scale = SkMinScalar(scale, fRect.height() / (topRad + bottomRad)); } if (scale < SK_Scalar1) { leftRad = SkScalarMul(leftRad, scale); topRad = SkScalarMul(topRad, scale); rightRad = SkScalarMul(rightRad, scale); bottomRad = SkScalarMul(bottomRad, scale); } if (leftRad == rightRad && topRad == bottomRad) { if (leftRad >= SkScalarHalf(fRect.width()) && topRad >= SkScalarHalf(fRect.height())) { fType = kOval_Type; } else if (0 == leftRad || 0 == topRad) { // If the left and (by equality check above) right radii are zero then it is a rect. // Same goes for top/bottom. fType = kRect_Type; leftRad = 0; topRad = 0; rightRad = 0; bottomRad = 0; } else { fType = kSimple_Type; } } else { fType = kNinePatch_Type; } fRadii[kUpperLeft_Corner].set(leftRad, topRad); fRadii[kUpperRight_Corner].set(rightRad, topRad); fRadii[kLowerRight_Corner].set(rightRad, bottomRad); fRadii[kLowerLeft_Corner].set(leftRad, bottomRad); SkDEBUGCODE(this->validate();) }
void GrStencilAndCoverTextContext::TextRun::setText(const char text[], size_t byteLength, SkScalar x, SkScalar y) { SkASSERT(byteLength == 0 || text != nullptr); SkGlyphCache* glyphCache = this->getGlyphCache(); SkDrawCacheProc glyphCacheProc = fFont.getDrawCacheProc(); fTotalGlyphCount = fFont.countText(text, byteLength); fInstanceData.reset(InstanceData::Alloc(GrPathRendering::kTranslate_PathTransformType, fTotalGlyphCount)); const char* stop = text + byteLength; // Measure first if needed. if (fFont.getTextAlign() != SkPaint::kLeft_Align) { SkFixed stopX = 0; SkFixed stopY = 0; const char* textPtr = text; while (textPtr < stop) { // We don't need x, y here, since all subpixel variants will have the // same advance. const SkGlyph& glyph = glyphCacheProc(glyphCache, &textPtr, 0, 0); stopX += glyph.fAdvanceX; stopY += glyph.fAdvanceY; } SkASSERT(textPtr == stop); SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio; SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio; if (fFont.getTextAlign() == SkPaint::kCenter_Align) { alignX = SkScalarHalf(alignX); alignY = SkScalarHalf(alignY); } x -= alignX; y -= alignY; } SkAutoKern autokern; SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio); SkFixed fx = SkScalarToFixed(x); SkFixed fy = SkScalarToFixed(y); FallbackBlobBuilder fallback; while (text < stop) { const SkGlyph& glyph = glyphCacheProc(glyphCache, &text, 0, 0); fx += SkFixedMul(autokern.adjust(glyph), fixedSizeRatio); if (glyph.fWidth) { this->appendGlyph(glyph, SkPoint::Make(SkFixedToScalar(fx), SkFixedToScalar(fy)), &fallback); } fx += SkFixedMul(glyph.fAdvanceX, fixedSizeRatio); fy += SkFixedMul(glyph.fAdvanceY, fixedSizeRatio); } fFallbackTextBlob.reset(fallback.buildIfNeeded(&fFallbackGlyphCount)); }
static void test_intersectline(skiatest::Reporter* reporter) { static const SkScalar L = 0; static const SkScalar T = 0; static const SkScalar R = SkIntToScalar(100); static const SkScalar B = SkIntToScalar(100); static const SkScalar CX = SkScalarHalf(L + R); static const SkScalar CY = SkScalarHalf(T + B); static const SkRect gR = { L, T, R, B }; size_t i; SkPoint dst[2]; static const SkPoint gEmpty[] = { // sides { L, CY }, { L - 10, CY }, { R, CY }, { R + 10, CY }, { CX, T }, { CX, T - 10 }, { CX, B }, { CX, B + 10 }, // corners { L, T }, { L - 10, T - 10 }, { L, B }, { L - 10, B + 10 }, { R, T }, { R + 10, T - 10 }, { R, B }, { R + 10, B + 10 }, }; for (i = 0; i < SK_ARRAY_COUNT(gEmpty); i += 2) { bool valid = SkLineClipper::IntersectLine(&gEmpty[i], gR, dst); if (valid) { SkDebugf("----- [%d] %g %g -> %g %g\n", i/2, dst[0].fX, dst[0].fY, dst[1].fX, dst[1].fY); } REPORTER_ASSERT(reporter, !valid); } static const SkPoint gFull[] = { // diagonals, chords { L, T }, { R, B }, { L, B }, { R, T }, { CX, T }, { CX, B }, { L, CY }, { R, CY }, { CX, T }, { R, CY }, { CX, T }, { L, CY }, { L, CY }, { CX, B }, { R, CY }, { CX, B }, // edges { L, T }, { L, B }, { R, T }, { R, B }, { L, T }, { R, T }, { L, B }, { R, B }, }; for (i = 0; i < SK_ARRAY_COUNT(gFull); i += 2) { bool valid = SkLineClipper::IntersectLine(&gFull[i], gR, dst); if (!valid || memcmp(&gFull[i], dst, sizeof(dst))) { SkDebugf("++++ [%d] %g %g -> %g %g\n", i/2, dst[0].fX, dst[0].fY, dst[1].fX, dst[1].fY); } REPORTER_ASSERT(reporter, valid && !memcmp(&gFull[i], dst, sizeof(dst))); } static const SkPoint gPartial[] = { { L - 10, CY }, { CX, CY }, { L, CY }, { CX, CY }, { CX, T - 10 }, { CX, CY }, { CX, T }, { CX, CY }, { R + 10, CY }, { CX, CY }, { R, CY }, { CX, CY }, { CX, B + 10 }, { CX, CY }, { CX, B }, { CX, CY }, // extended edges { L, T - 10 }, { L, B + 10 }, { L, T }, { L, B }, { R, T - 10 }, { R, B + 10 }, { R, T }, { R, B }, { L - 10, T }, { R + 10, T }, { L, T }, { R, T }, { L - 10, B }, { R + 10, B }, { L, B }, { R, B }, }; for (i = 0; i < SK_ARRAY_COUNT(gPartial); i += 4) { bool valid = SkLineClipper::IntersectLine(&gPartial[i], gR, dst); if (!valid || memcmp(&gPartial[i+2], dst, sizeof(dst))) { SkDebugf("++++ [%d] %g %g -> %g %g\n", i/2, dst[0].fX, dst[0].fY, dst[1].fX, dst[1].fY); } REPORTER_ASSERT(reporter, valid && !memcmp(&gPartial[i+2], dst, sizeof(dst))); } }
void GrDistanceFieldTextContext::onDrawText(GrRenderTarget* rt, const GrClip& clip, const GrPaint& paint, const SkPaint& skPaint, const SkMatrix& viewMatrix, const char text[], size_t byteLength, SkScalar x, SkScalar y, const SkIRect& regionClipBounds) { SkASSERT(byteLength == 0 || text != NULL); // nothing to draw if (text == NULL || byteLength == 0) { return; } fViewMatrix = viewMatrix; SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc(); SkAutoGlyphCache autoCache(skPaint, &fDeviceProperties, NULL); SkGlyphCache* cache = autoCache.getCache(); SkTArray<SkScalar> positions; const char* textPtr = text; SkFixed stopX = 0; SkFixed stopY = 0; SkFixed origin; switch (skPaint.getTextAlign()) { case SkPaint::kRight_Align: origin = SK_Fixed1; break; case SkPaint::kCenter_Align: origin = SK_FixedHalf; break; case SkPaint::kLeft_Align: origin = 0; break; default: SkFAIL("Invalid paint origin"); return; } SkAutoKern autokern; const char* stop = text + byteLength; while (textPtr < stop) { // don't need x, y here, since all subpixel variants will have the // same advance const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0); SkFixed width = glyph.fAdvanceX + autokern.adjust(glyph); positions.push_back(SkFixedToScalar(stopX + SkFixedMul(origin, width))); SkFixed height = glyph.fAdvanceY; positions.push_back(SkFixedToScalar(stopY + SkFixedMul(origin, height))); stopX += width; stopY += height; } SkASSERT(textPtr == stop); // now adjust starting point depending on alignment SkScalar alignX = SkFixedToScalar(stopX); SkScalar alignY = SkFixedToScalar(stopY); if (skPaint.getTextAlign() == SkPaint::kCenter_Align) { alignX = SkScalarHalf(alignX); alignY = SkScalarHalf(alignY); } else if (skPaint.getTextAlign() == SkPaint::kLeft_Align) { alignX = 0; alignY = 0; } x -= alignX; y -= alignY; SkPoint offset = SkPoint::Make(x, y); this->onDrawPosText(rt, clip, paint, skPaint, viewMatrix, text, byteLength, positions.begin(), 2, offset, regionClipBounds); }
SkPath TextArt::EnvelopeWarp::warp(const std::string& text, SkTypeface* typeface) { SkPath warpedPath; if (text.empty()) return warpedPath; //prepare paint SkPaint paint; paint.setTextSize(SkIntToScalar(64)); paint.setTypeface(typeface); paint.setTextAlign(SkPaint::kCenter_Align); //measure Bottom path to center text on it SkPathMeasure bMeasure(bSkeleton_, false); SkScalar hBOffset = 0; if (paint.getTextAlign() != SkPaint::kLeft_Align) { SkScalar pathLen = bMeasure.getLength(); if (paint.getTextAlign() == SkPaint::kCenter_Align) { pathLen = SkScalarHalf(pathLen); } hBOffset += pathLen; } //get text boundaries on normal(non-warped) state { SkMatrix scaleMartix; scaleMartix.setIdentity(); SkTextToPathIter iter(text.c_str(), text.size(), paint, true); const SkPath* glypthPath; SkScalar xpos; SkScalar scale = iter.getPathScale(); scaleMartix.setScale(scale, scale); while (iter.next(&glypthPath, &xpos)) { if (glypthPath) { //prepare resulting transformatiom Matrix SkMatrix compositeMatrix(scaleMartix); compositeMatrix.postTranslate(xpos + hBOffset, 0); SkPath p; (*glypthPath).transform(compositeMatrix, &p); //get normal(without any warps) text boundaries boundsRect_.join( p.getBounds() ); } } } //center text on Top skeleton SkPathMeasure tMeasure(tSkeleton_, false); SkScalar hTOffset = 0; { if (paint.getTextAlign() != SkPaint::kLeft_Align) { SkScalar pathLen = tMeasure.getLength(); if (paint.getTextAlign() == SkPaint::kCenter_Align) { pathLen = SkScalarHalf(pathLen); } hTOffset += pathLen; } } //warp text on Bottom and Top skeletons { SkTextToPathIter iter(text.c_str(), text.size(), paint, true); SkScalar xpos; SkMatrix scaleMartix; SkScalar scale = iter.getPathScale(); scaleMartix.setScale(scale, scale); SkPath line; line.lineTo(SkIntToScalar(100), SkIntToScalar(0)); SkPathMeasure lineMeasure(line, false); SkPathCrossing bCrossing(bSkeleton_); SkPathCrossing tCrossing(tSkeleton_); const SkPath* glypthPathOrig; while (iter.next(&glypthPathOrig, &xpos)) { if (glypthPathOrig) { SkPath glypthPath; SkRect glypthBound; glypthBound = (*glypthPathOrig).getBounds(); glypthPathOrig->offset(-glypthBound.fLeft, 0, &glypthPath); morph(bSkeleton_, bMeasure, bCrossing, tSkeleton_, tMeasure, tCrossing, glypthPath, lineMeasure, scaleMartix, xpos, hBOffset, hTOffset, warpedPath); } } } return warpedPath; }
SkScalar SkTextBox::visit(Visitor& visitor, const char text[], size_t len, const SkPaint& paint) const { SkScalar marginWidth = fBox.width(); if (marginWidth <= 0 || len == 0) { return fBox.top(); } const char* textStop = text + len; SkScalar x, y, scaledSpacing, height, fontHeight; SkPaint::FontMetrics metrics; switch (paint.getTextAlign()) { case SkPaint::kLeft_Align: x = 0; break; case SkPaint::kCenter_Align: x = SkScalarHalf(marginWidth); break; default: x = marginWidth; break; } x += fBox.fLeft; fontHeight = paint.getFontMetrics(&metrics); scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd; height = fBox.height(); // compute Y position for first line { SkScalar textHeight = fontHeight; if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign) { int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth); SkASSERT(count > 0); textHeight += scaledSpacing * (count - 1); } switch (fSpacingAlign) { case kStart_SpacingAlign: y = 0; break; case kCenter_SpacingAlign: y = SkScalarHalf(height - textHeight); break; default: SkASSERT(fSpacingAlign == kEnd_SpacingAlign); y = height - textHeight; break; } y += fBox.fTop - metrics.fAscent; } for (;;) { size_t trailing; len = linebreak(text, textStop, paint, marginWidth, &trailing); if (y + metrics.fDescent + metrics.fLeading > 0) { visitor(text, len - trailing, x, y, paint); } text += len; if (text >= textStop) { break; } y += scaledSpacing; if (y + metrics.fAscent >= fBox.fBottom) { break; } } return y + metrics.fDescent + metrics.fLeading; }
void GrStencilAndCoverTextContext::onDrawText(GrDrawContext* drawContext, GrRenderTarget* rt, const GrClip& clip, const GrPaint& paint, const SkPaint& skPaint, const SkMatrix& viewMatrix, const char text[], size_t byteLength, SkScalar x, SkScalar y, const SkIRect& regionClipBounds) { SkASSERT(byteLength == 0 || text != NULL); if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) { return; } // This is the slow path, mainly used by Skia unit tests. The other // backends (8888, gpu, ...) use device-space dependent glyph caches. In // order to match the glyph positions that the other code paths produce, we // must also use device-space dependent glyph cache. This has the // side-effect that the glyph shape outline will be in device-space, // too. This in turn has the side-effect that NVPR can not stroke the paths, // as the stroke in NVPR is defined in object-space. // NOTE: here we have following coincidence that works at the moment: // - When using the device-space glyphs, the transforms we pass to NVPR // instanced drawing are the global transforms, and the view transform is // identity. NVPR can not use non-affine transforms in the instanced // drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it // will turn off the use of device-space glyphs when perspective transforms // are in use. this->init(rt, clip, paint, skPaint, byteLength, kMaxAccuracy_RenderMode, viewMatrix, regionClipBounds); // Transform our starting point. if (fUsingDeviceSpaceGlyphs) { SkPoint loc; fContextInitialMatrix.mapXY(x, y, &loc); x = loc.fX; y = loc.fY; } SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); const char* stop = text + byteLength; // Measure first if needed. if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) { SkFixed stopX = 0; SkFixed stopY = 0; const char* textPtr = text; while (textPtr < stop) { // We don't need x, y here, since all subpixel variants will have the // same advance. const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &textPtr, 0, 0); stopX += glyph.fAdvanceX; stopY += glyph.fAdvanceY; } SkASSERT(textPtr == stop); SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio; SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio; if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) { alignX = SkScalarHalf(alignX); alignY = SkScalarHalf(alignY); } x -= alignX; y -= alignY; } SkAutoKern autokern; SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio); SkFixed fx = SkScalarToFixed(x); SkFixed fy = SkScalarToFixed(y); while (text < stop) { const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0); fx += SkFixedMul(autokern.adjust(glyph), fixedSizeRatio); if (glyph.fWidth) { this->appendGlyph(drawContext, glyph, SkPoint::Make(SkFixedToScalar(fx), SkFixedToScalar(fy))); } fx += SkFixedMul(glyph.fAdvanceX, fixedSizeRatio); fy += SkFixedMul(glyph.fAdvanceY, fixedSizeRatio); } this->finish(drawContext); }
void GrBitmapTextContext::onDrawText(GrRenderTarget* rt, const GrClip& clip, const GrPaint& paint, const SkPaint& skPaint, const SkMatrix& viewMatrix, const char text[], size_t byteLength, SkScalar x, SkScalar y) { SkASSERT(byteLength == 0 || text != NULL); // nothing to draw if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) { return; } this->init(rt, clip, paint, skPaint); SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, &viewMatrix); SkGlyphCache* cache = autoCache.getCache(); GrFontScaler* fontScaler = GetGrFontScaler(cache); // transform our starting point { SkPoint loc; viewMatrix.mapXY(x, y, &loc); x = loc.fX; y = loc.fY; } // need to measure first int numGlyphs; if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) { SkVector stopVector; numGlyphs = MeasureText(cache, glyphCacheProc, text, byteLength, &stopVector); SkScalar stopX = stopVector.fX; SkScalar stopY = stopVector.fY; if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) { stopX = SkScalarHalf(stopX); stopY = SkScalarHalf(stopY); } x -= stopX; y -= stopY; } else { numGlyphs = fSkPaint.textToGlyphs(text, byteLength, NULL); } fTotalVertexCount = kVerticesPerGlyph*numGlyphs; const char* stop = text + byteLength; SkAutoKern autokern; SkFixed fxMask = ~0; SkFixed fyMask = ~0; SkScalar halfSampleX, halfSampleY; if (cache->isSubpixel()) { halfSampleX = halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound); SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix); if (kX_SkAxisAlignment == baseline) { fyMask = 0; halfSampleY = SK_ScalarHalf; } else if (kY_SkAxisAlignment == baseline) { fxMask = 0; halfSampleX = SK_ScalarHalf; } } else { halfSampleX = halfSampleY = SK_ScalarHalf; } Sk48Dot16 fx = SkScalarTo48Dot16(x + halfSampleX); Sk48Dot16 fy = SkScalarTo48Dot16(y + halfSampleY); // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix, but for // performance reasons we just invert here instead if (!viewMatrix.invert(&fLocalMatrix)) { SkDebugf("Cannot invert viewmatrix\n"); return; } while (text < stop) { const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask); fx += autokern.adjust(glyph); if (glyph.fWidth) { this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), glyph.getSubXFixed(), glyph.getSubYFixed(), GrGlyph::kCoverage_MaskStyle), Sk48Dot16FloorToInt(fx), Sk48Dot16FloorToInt(fy), fontScaler); } fx += glyph.fAdvanceX; fy += glyph.fAdvanceY; } this->finish(); }
// Test out the basic API entry points static void test_round_rect_basic(skiatest::Reporter* reporter) { // Test out initialization methods SkPoint zeroPt = { 0, 0 }; SkRRect empty; empty.setEmpty(); REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == empty.type()); REPORTER_ASSERT(reporter, empty.rect().isEmpty()); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, zeroPt == empty.radii((SkRRect::Corner) i)); } //---- SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); SkRRect rr1; rr1.setRect(rect); REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr1.type()); REPORTER_ASSERT(reporter, rr1.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, zeroPt == rr1.radii((SkRRect::Corner) i)); } //---- SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) }; SkRRect rr2; rr2.setOval(rect); REPORTER_ASSERT(reporter, SkRRect::kOval_Type == rr2.type()); REPORTER_ASSERT(reporter, rr2.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, rr2.radii((SkRRect::Corner) i).equalsWithinTolerance(halfPoint)); } //---- SkPoint p = { 5, 5 }; SkRRect rr3; rr3.setRectXY(rect, p.fX, p.fY); REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr3.type()); REPORTER_ASSERT(reporter, rr3.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, p == rr3.radii((SkRRect::Corner) i)); } //---- SkPoint radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } }; SkRRect rr4; rr4.setRectRadii(rect, radii); REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr4.type()); REPORTER_ASSERT(reporter, rr4.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, radii[i] == rr4.radii((SkRRect::Corner) i)); } //---- SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } }; SkRRect rr5; rr5.setRectRadii(rect, radii2); REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr5.type()); REPORTER_ASSERT(reporter, rr5.rect() == rect); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, radii2[i] == rr5.radii((SkRRect::Corner) i)); } // Test out == & != REPORTER_ASSERT(reporter, empty != rr3); REPORTER_ASSERT(reporter, rr3 == rr4); REPORTER_ASSERT(reporter, rr4 != rr5); }
bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath, const SkStrokeRec& stroke, GrDrawTarget* target, bool antiAlias) { if (origPath.isEmpty()) { return true; } SkScalar width = stroke.getWidth(); if (width <= 0) { return false; } // Get the join type SkPaint::Join join = stroke.getJoin(); SkScalar miterLimit = stroke.getMiter(); SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit); if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) { // If the miter limit is small, treat it as a bevel join join = SkPaint::kBevel_Join; } const bool isMiter = (join == SkPaint::kMiter_Join); const bool isBevel = (join == SkPaint::kBevel_Join); SkScalar invMiterLimit = isMiter ? SK_Scalar1 / miterLimit : 0; SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit); // Allocate vertices const int nbQuads = origPath.countPoints() + 1; // Could be "-1" if path is not closed const int extraVerts = isMiter || isBevel ? 1 : 0; const int maxVertexCount = nbQuads * (4 + extraVerts); const int maxIndexCount = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle target->drawState()->setDefaultVertexAttribs(); GrDrawTarget::AutoReleaseGeometry arg(target, maxVertexCount, maxIndexCount); if (!arg.succeeded()) { return false; } SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices()); uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices()); int vCount = 0, iCount = 0; // Transform the path into a list of triangles SkPath::Iter iter(origPath, false); SkPoint pts[4]; const SkScalar radius = SkScalarMul(width, 0.5f); SkPoint *firstPt = verts, *lastPt = NULL; SkVector firstDir, dir; firstDir.set(0, 0); dir.set(0, 0); bool isOpen = true; for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) { switch(v) { case SkPath::kMove_Verb: // This will already be handled as pts[0] of the 1st line break; case SkPath::kClose_Verb: isOpen = (lastPt == NULL); break; case SkPath::kLine_Verb: { SkVector v0 = dir; dir = pts[1] - pts[0]; if (dir.setLength(radius)) { SkVector dirT; dirT.set(dir.fY, -dir.fX); // Get perpendicular direction SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT, l2a = pts[0]-dirT, l2b = pts[1]-dirT; SkPoint miterPt[2]; bool useMiterPoint = false; int idx0(-1), idx1(-1); if (NULL == lastPt) { firstDir = dir; } else { SkVector v1 = dir; if (v0.normalize() && v1.normalize()) { SkScalar dotProd = v0.dot(v1); // No need for bevel or miter join if the angle // is either 0 or 180 degrees if (!SkScalarNearlyZero(dotProd + SK_Scalar1) && !SkScalarNearlyZero(dotProd - SK_Scalar1)) { bool ccw = !is_clockwise(v0, v1); int offset = ccw ? 1 : 0; idx0 = vCount-2+offset; idx1 = vCount+offset; const SkPoint* pt0 = &(lastPt[offset]); const SkPoint* pt1 = ccw ? &l2a : &l1a; switch(join) { case SkPaint::kMiter_Join: { // *Note : Logic is from MiterJoiner // FIXME : Special case if we have a right angle ? // if (SkScalarNearlyZero(dotProd)) {...} SkScalar sinHalfAngleSq = SkScalarHalf(SK_Scalar1 + dotProd); if (sinHalfAngleSq >= invMiterLimitSq) { // Find the miter point (or points if it is further // than the miter limit) const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1; if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) != kNone_IntersectionType) { SkPoint miterPt0 = miterPt[0] - *pt0; SkPoint miterPt1 = miterPt[0] - *pt1; SkScalar sqDist0 = miterPt0.dot(miterPt0); SkScalar sqDist1 = miterPt1.dot(miterPt1); const SkScalar rSq = SkScalarDiv(SkScalarMul(radius, radius), sinHalfAngleSq); const SkScalar sqRLimit = SkScalarMul(sqMiterLimit, rSq); if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) { if (sqDist1 > sqRLimit) { v1.setLength(SkScalarSqrt(sqRLimit)); miterPt[1] = *pt1+v1; } else { miterPt[1] = miterPt[0]; } if (sqDist0 > sqRLimit) { v0.setLength(SkScalarSqrt(sqRLimit)); miterPt[0] = *pt0+v0; } } else { miterPt[1] = miterPt[0]; } useMiterPoint = true; } } if (useMiterPoint && (miterPt[1] == miterPt[0])) { break; } } default: case SkPaint::kBevel_Join: { // Note : This currently causes some overdraw where both // lines initially intersect. We'd need to add // another line intersection check here if the // overdraw becomes an issue instead of using the // current point directly. // Add center point *verts++ = pts[0]; // Use current point directly // This idx is passed the current point so increment it ++idx1; // Add center triangle *idxs++ = idx0; *idxs++ = vCount; *idxs++ = idx1; vCount++; iCount += 3; } break; } } } } *verts++ = l1a; *verts++ = l2a; lastPt = verts; *verts++ = l1b; *verts++ = l2b; if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) { firstPt[idx0] = miterPt[0]; firstPt[idx1] = miterPt[1]; } // 1st triangle *idxs++ = vCount+0; *idxs++ = vCount+2; *idxs++ = vCount+1; // 2nd triangle *idxs++ = vCount+1; *idxs++ = vCount+2; *idxs++ = vCount+3; vCount += 4; iCount += 6; } } break; case SkPath::kQuad_Verb: case SkPath::kCubic_Verb: SkDEBUGFAIL("Curves not supported!"); default: // Unhandled cases SkASSERT(false); } } if (isOpen) { // Add caps switch (stroke.getCap()) { case SkPaint::kSquare_Cap: firstPt[0] -= firstDir; firstPt[1] -= firstDir; lastPt [0] += dir; lastPt [1] += dir; break; case SkPaint::kRound_Cap: SkDEBUGFAIL("Round caps not supported!"); default: // No cap break; } } SkASSERT(vCount <= maxVertexCount); SkASSERT(iCount <= maxIndexCount); if (vCount > 0) { target->drawIndexed(kTriangles_GrPrimitiveType, 0, // start vertex 0, // start index vCount, iCount); } return true; }
void onOnceBeforeDraw() override { { SkPath* lineAnglesPath = &fPaths.push_back(); enum { kNumAngles = 15, kRadius = 40, }; for (int i = 0; i < kNumAngles; ++i) { SkScalar angle = SK_ScalarPI * SkIntToScalar(i) / kNumAngles; SkScalar x = kRadius * SkScalarCos(angle); SkScalar y = kRadius * SkScalarSin(angle); lineAnglesPath->moveTo(x, y); lineAnglesPath->lineTo(-x, -y); } } { SkPath* kindaTightQuad = &fPaths.push_back(); kindaTightQuad->moveTo(0, -10 * SK_Scalar1); kindaTightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -10 * SK_Scalar1, 0); } { SkPath* tightQuad = &fPaths.push_back(); tightQuad->moveTo(0, -5 * SK_Scalar1); tightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -5 * SK_Scalar1, 0); } { SkPath* tighterQuad = &fPaths.push_back(); tighterQuad->moveTo(0, -2 * SK_Scalar1); tighterQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -2 * SK_Scalar1, 0); } { SkPath* unevenTighterQuad = &fPaths.push_back(); unevenTighterQuad->moveTo(0, -1 * SK_Scalar1); SkPoint p; p.set(-2 * SK_Scalar1 + 3 * SkIntToScalar(102) / 4, SkIntToScalar(75)); unevenTighterQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), p.fX, p.fY); } { SkPath* reallyTightQuad = &fPaths.push_back(); reallyTightQuad->moveTo(0, -1 * SK_Scalar1); reallyTightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -1 * SK_Scalar1, 0); } { SkPath* closedQuad = &fPaths.push_back(); closedQuad->moveTo(0, -0); closedQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), 0, 0); } { SkPath* unevenClosedQuad = &fPaths.push_back(); unevenClosedQuad->moveTo(0, -0); unevenClosedQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), SkIntToScalar(75), SkIntToScalar(75)); } // Two problem cases for gpu hairline renderer found by shapeops testing. These used // to assert that the computed bounding box didn't contain all the vertices. { SkPath* problem1 = &fPaths.push_back(); problem1->moveTo(SkIntToScalar(4), SkIntToScalar(6)); problem1->cubicTo(SkIntToScalar(5), SkIntToScalar(6), SkIntToScalar(5), SkIntToScalar(4), SkIntToScalar(4), SkIntToScalar(0)); problem1->close(); } { SkPath* problem2 = &fPaths.push_back(); problem2->moveTo(SkIntToScalar(5), SkIntToScalar(1)); problem2->lineTo(4.32787323f, 1.67212653f); problem2->cubicTo(2.75223875f, 3.24776125f, 3.00581908f, 4.51236057f, 3.7580452f, 4.37367964f); problem2->cubicTo(4.66472578f, 3.888381f, 5.f, 2.875f, 5.f, 1.f); problem2->close(); } // Three paths that show the same bug (missing end caps) { // A caret (crbug.com/131770) SkPath* bug0 = &fPaths.push_back(); bug0->moveTo(6.5f,5.5f); bug0->lineTo(3.5f,0.5f); bug0->moveTo(0.5f,5.5f); bug0->lineTo(3.5f,0.5f); } { // An X (crbug.com/137317) SkPath* bug1 = &fPaths.push_back(); bug1->moveTo(1, 1); bug1->lineTo(6, 6); bug1->moveTo(1, 6); bug1->lineTo(6, 1); } { // A right angle (crbug.com/137465 and crbug.com/256776) SkPath* bug2 = &fPaths.push_back(); bug2->moveTo(5.5f, 5.5f); bug2->lineTo(5.5f, 0.5f); bug2->lineTo(0.5f, 0.5f); } { // Arc example to test imperfect truncation bug (crbug.com/295626) static const SkScalar kRad = SkIntToScalar(2000); static const SkScalar kStartAngle = 262.59717f; static const SkScalar kSweepAngle = SkScalarHalf(17.188717f); SkPath* bug = &fPaths.push_back(); // Add a circular arc SkRect circle = SkRect::MakeLTRB(-kRad, -kRad, kRad, kRad); bug->addArc(circle, kStartAngle, kSweepAngle); // Now add the chord that should cap the circular arc SkScalar cosV, sinV = SkScalarSinCos(SkDegreesToRadians(kStartAngle), &cosV); SkPoint p0 = SkPoint::Make(kRad * cosV, kRad * sinV); sinV = SkScalarSinCos(SkDegreesToRadians(kStartAngle + kSweepAngle), &cosV); SkPoint p1 = SkPoint::Make(kRad * cosV, kRad * sinV); bug->moveTo(p0); bug->lineTo(p1); } }
void PlatformGraphicsContextSkia::drawLine(const IntPoint& point1, const IntPoint& point2) { StrokeStyle style = m_state->strokeStyle; if (style == NoStroke) return; SkPaint paint; SkCanvas* canvas = mCanvas; const int idx = SkAbs32(point2.x() - point1.x()); const int idy = SkAbs32(point2.y() - point1.y()); // Special-case horizontal and vertical lines that are really just dots if (setupPaintStroke(&paint, 0, !idy) && (!idx || !idy)) { const SkScalar diameter = paint.getStrokeWidth(); const SkScalar radius = SkScalarHalf(diameter); SkScalar x = SkIntToScalar(SkMin32(point1.x(), point2.x())); SkScalar y = SkIntToScalar(SkMin32(point1.y(), point2.y())); SkScalar dx, dy; int count; SkRect bounds; if (!idy) { // Horizontal bounds.set(x, y - radius, x + SkIntToScalar(idx), y + radius); x += radius; dx = diameter * 2; dy = 0; count = idx; } else { // Vertical bounds.set(x - radius, y, x + radius, y + SkIntToScalar(idy)); y += radius; dx = 0; dy = diameter * 2; count = idy; } // The actual count is the number of ONs we hit alternating // ON(diameter), OFF(diameter), ... { SkScalar width = SkScalarDiv(SkIntToScalar(count), diameter); // Now compute the number of cells (ON and OFF) count = SkScalarRound(width); // Now compute the number of ONs count = (count + 1) >> 1; } SkAutoMalloc storage(count * sizeof(SkPoint)); SkPoint* verts = (SkPoint*)storage.get(); // Now build the array of vertices to past to drawPoints for (int i = 0; i < count; i++) { verts[i].set(x, y); x += dx; y += dy; } paint.setStyle(SkPaint::kFill_Style); paint.setPathEffect(0); // Clipping to bounds is not required for correctness, but it does // allow us to reject the entire array of points if we are completely // offscreen. This is common in a webpage for android, where most of // the content is clipped out. If drawPoints took an (optional) bounds // parameter, that might even be better, as we would *just* use it for // culling, and not both wacking the canvas' save/restore stack. canvas->save(SkCanvas::kClip_SaveFlag); canvas->clipRect(bounds); canvas->drawPoints(SkCanvas::kPoints_PointMode, count, verts, paint); canvas->restore(); } else {
// Currently asPoints is more restrictive then it needs to be. In the future // we need to: // allow kRound_Cap capping (could allow rotations in the matrix with this) // allow paths to be returned bool SkDashPathEffect::asPoints(PointData* results, const SkPath& src, const SkStrokeRec& rec, const SkMatrix& matrix, const SkRect* cullRect) const { // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out if (fInitialDashLength < 0 || 0 >= rec.getWidth()) { return false; } // TODO: this next test could be eased up. We could allow any number of // intervals as long as all the ons match and all the offs match. // Additionally, they do not necessarily need to be integers. // We cannot allow arbitrary intervals since we want the returned points // to be uniformly sized. if (fCount != 2 || !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) || !SkScalarIsInt(fIntervals[0]) || !SkScalarIsInt(fIntervals[1])) { return false; } SkPoint pts[2]; if (!src.isLine(pts)) { return false; } // TODO: this test could be eased up to allow circles if (SkPaint::kButt_Cap != rec.getCap()) { return false; } // TODO: this test could be eased up for circles. Rotations could be allowed. if (!matrix.rectStaysRect()) { return false; } // See if the line can be limited to something plausible. if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) { return false; } SkScalar length = SkPoint::Distance(pts[1], pts[0]); SkVector tangent = pts[1] - pts[0]; if (tangent.isZero()) { return false; } tangent.scale(SkScalarInvert(length)); // TODO: make this test for horizontal & vertical lines more robust bool isXAxis = true; if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) || SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) { results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth())); } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) || SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) { results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0])); isXAxis = false; } else if (SkPaint::kRound_Cap != rec.getCap()) { // Angled lines don't have axis-aligned boxes. return false; } if (results) { results->fFlags = 0; SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength); if (SkPaint::kRound_Cap == rec.getCap()) { results->fFlags |= PointData::kCircles_PointFlag; } results->fNumPoints = 0; SkScalar len2 = length; if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { SkASSERT(len2 >= clampedInitialDashLength); if (0 == fInitialDashIndex) { if (clampedInitialDashLength > 0) { if (clampedInitialDashLength >= fIntervals[0]) { ++results->fNumPoints; // partial first dash } len2 -= clampedInitialDashLength; } len2 -= fIntervals[1]; // also skip first space if (len2 < 0) { len2 = 0; } } else { len2 -= clampedInitialDashLength; // skip initial partial empty } } int numMidPoints = SkScalarFloorToInt(len2 / fIntervalLength); results->fNumPoints += numMidPoints; len2 -= numMidPoints * fIntervalLength; bool partialLast = false; if (len2 > 0) { if (len2 < fIntervals[0]) { partialLast = true; } else { ++numMidPoints; ++results->fNumPoints; } } results->fPoints = new SkPoint[results->fNumPoints]; SkScalar distance = 0; int curPt = 0; if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { SkASSERT(clampedInitialDashLength <= length); if (0 == fInitialDashIndex) { if (clampedInitialDashLength > 0) { // partial first block SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength)); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength)); SkScalar halfWidth, halfHeight; if (isXAxis) { halfWidth = SkScalarHalf(clampedInitialDashLength); halfHeight = SkScalarHalf(rec.getWidth()); } else { halfWidth = SkScalarHalf(rec.getWidth()); halfHeight = SkScalarHalf(clampedInitialDashLength); } if (clampedInitialDashLength < fIntervals[0]) { // This one will not be like the others results->fFirst.addRect(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight); } else { SkASSERT(curPt < results->fNumPoints); results->fPoints[curPt].set(x, y); ++curPt; } distance += clampedInitialDashLength; } distance += fIntervals[1]; // skip over the next blank block too } else { distance += clampedInitialDashLength; } } if (0 != numMidPoints) { distance += SkScalarHalf(fIntervals[0]); for (int i = 0; i < numMidPoints; ++i) { SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance); SkASSERT(curPt < results->fNumPoints); results->fPoints[curPt].set(x, y); ++curPt; distance += fIntervalLength; } distance -= SkScalarHalf(fIntervals[0]); } if (partialLast) { // partial final block SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles SkScalar temp = length - distance; SkASSERT(temp < fIntervals[0]); SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp)); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp)); SkScalar halfWidth, halfHeight; if (isXAxis) { halfWidth = SkScalarHalf(temp); halfHeight = SkScalarHalf(rec.getWidth()); } else { halfWidth = SkScalarHalf(rec.getWidth()); halfHeight = SkScalarHalf(temp); } results->fLast.addRect(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight); } SkASSERT(curPt == results->fNumPoints); } return true; }
void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, MediaButton buttonType, bool translucent, bool drawBackground, const IntRect& thumb)//4.2 Merge { if (!gDecoded) { Decode(); } if (!canvas) return; // If we failed to decode, do nothing. This way the browser still works, // and webkit will still draw the label and layout space for us. if (gDecodingFailed) return; bool drawsNinePatch = false; bool drawsImage = true; int ninePatchIndex = 0; int imageIndex = 0; SkRect bounds(r); SkScalar imageMargin = 8; SkPaint paint; int alpha = 255; if (translucent) alpha = 190; SkColor backgroundColor = SkColorSetARGB(alpha, 34, 34, 34); SkColor trackBackgroundColor = SkColorSetARGB(255, 100, 100, 100); paint.setColor(backgroundColor); //Android KITKAT Merge - START // paint.setFlags(SkPaint::kFilterBitmap_Flag); //P140210-04273 : // In D2 Device if we pass kMedium_FilterLevel image gets corrupted while drawing the image into the canvas. So loading icons was corrupting // kLow_FilterLevel works for other kitkat devices. //WAS paint.setFilterLevel(SkPaint::kMedium_FilterLevel); paint.setFilterLevel(SkPaint::kLow_FilterLevel); //Android KITKAT Merge - END switch (buttonType) { case PAUSE: case PLAY: case MUTE: case REWIND: case FORWARD: case FULLSCREEN: { imageIndex = buttonType + 1; paint.setColor(backgroundColor); break; } case SPINNER_OUTER: case SPINNER_INNER: case VIDEO: { imageIndex = buttonType + 1; break; } case BACKGROUND_SLIDER: { drawsImage = false; break; } case SLIDER_TRACK: { drawsNinePatch = true; drawsImage = false; ninePatchIndex = buttonType + 1; break; } case SLIDER_THUMB: { imageMargin = 0; imageIndex = buttonType + 1; break; } default: return; } if (drawBackground) { canvas->drawRect(r, paint); } if (drawsNinePatch) { const PatchData& pd = gFiles[ninePatchIndex]; int marginValue = pd.margin + pd.outset; SkIRect margin; margin.set(marginValue, marginValue, marginValue, marginValue); if (buttonType == SLIDER_TRACK) { // Cut the height in half (with some extra slop determined by trial // and error to get the placement just right. SkScalar quarterHeight = SkScalarHalf(SkScalarHalf(bounds.height())); bounds.fTop += quarterHeight + SkScalarHalf(3); bounds.fBottom += -quarterHeight + SK_ScalarHalf; if (!thumb.isEmpty()) {//4.2 Merge // Inset the track by half the width of the thumb, so the track // does not appear to go beyond the space where the thumb can // be. SkScalar thumbHalfWidth = SkIntToScalar(thumb.width()/2); bounds.fLeft += thumbHalfWidth; bounds.fRight -= thumbHalfWidth; if (thumb.x() > 0) { // The video is past the starting point. Show the area to // left of the thumb as having been played. SkScalar alreadyPlayed = SkIntToScalar(thumb.center().x() + r.x()); SkRect playedRect(bounds); playedRect.fRight = alreadyPlayed; SkNinePatch::DrawNine(canvas, playedRect, gButton[0], margin); bounds.fLeft = alreadyPlayed; } } } SkNinePatch::DrawNine(canvas, bounds, gButton[ninePatchIndex], margin); } if (drawsImage) { SkScalar SIZE = gButton[imageIndex].width(); SkScalar width = r.width(); SkScalar scale = SkScalarDiv(width - 2*imageMargin, SIZE); int saveScaleCount = canvas->save(); canvas->translate(bounds.fLeft + imageMargin, bounds.fTop + imageMargin); canvas->scale(scale, scale); canvas->drawBitmap(gButton[imageIndex], 0, 0, &paint); canvas->restoreToCount(saveScaleCount); } }