#define DIRECT_BLIT_LOOP(writable_method, value) \ do { \ for (auto p : devPts) { \ int x = SkScalarFloorToInt(p.fX); \ int y = SkScalarFloorToInt(p.fY); \ if (cr.contains(x, y)) { \
*pm.writable_method(x, y) = value; \
} \
} \
} while (0)
staticvoid bw_pt_hair_proc(const PtProcRec& rec, SkSpan<const SkPoint> devPts,
SkBlitter* blitter) { constauto direct = blitter->canDirectBlit(); if (direct && rec.fClip->isRect()) { const SkIRect cr = rec.fClip->getBounds(); auto pm = direct->pm; constauto v = direct->value; switch (pm.info().bytesPerPixel()) { case1: DIRECT_BLIT_LOOP(writable_addr8, v); break; case2: DIRECT_BLIT_LOOP(writable_addr16, v); break; case4: DIRECT_BLIT_LOOP(writable_addr32, v); break; case8: DIRECT_BLIT_LOOP(writable_addr64, v); break; default: SkASSERT(false);
}
} else { for (auto p : devPts) { int x = SkScalarFloorToInt(p.fX); int y = SkScalarFloorToInt(p.fY); if (rec.fClip->contains(x, y)) {
blitter->blitH(x, y, 1);
}
}
}
}
staticvoid bw_line_hair_proc(const PtProcRec& rec, SkSpan<const SkPoint> devPts,
SkBlitter* blitter) { for (size_t i = 0; i+1 < devPts.size(); i += 2) {
SkScan::HairLine({&devPts[i], 2}, *rec.fRC, blitter);
}
}
// each of these costs 8-bytes of stack space, so don't make it too large // must be even for lines/polygon to work #define MAX_DEV_PTS 32
void Draw::drawPoints(SkCanvas::PointMode mode,
SkSpan<const SkPoint> points, const SkPaint& paint,
SkDevice* device) const { // if we're in lines mode, force count to be even if (SkCanvas::kLines_PointMode == mode) {
points = points.first(points.size() & ~1); // force it to be even
}
SkDEBUGCODE(this->validate();)
// nothing to draw if (points.empty() || fRC->isEmpty()) { return;
}
PtProcRec rec; if (!device && rec.init(mode, paint, fCTM, fRC)) { // Can't easily get bounds of points so don't try.
SkAutoBlitterChoose blitter(*this, nullptr, paint, SkRect::MakeEmpty());
SkPoint devPts[MAX_DEV_PTS];
SkBlitter* bltr = blitter.get();
PtProcRec::Proc proc = rec.chooseProc(&bltr); // we have to back up subsequent passes if we're in polygon mode const size_t backup = (SkCanvas::kPolygon_PointMode == mode);
auto count = points.size(); auto pts = points.data(); do {
size_t n = count; if (n > MAX_DEV_PTS) {
n = MAX_DEV_PTS;
}
fCTM->mapPoints({devPts, n}, {pts, n}); if (!SkIsFinite(&devPts[0].fX, n * 2)) { return;
}
proc(rec, {devPts, n}, bltr);
pts += n - backup;
SkASSERT(count >= n);
count -= n; if (count > 0) {
count += backup;
}
} while (count != 0);
} else { this->drawDevicePoints(mode, points, paint, device);
}
}
staticbool clipHandlesSprite(const SkRasterClip& clip, int x, int y, const SkPixmap& pmap) { return clip.isBW() || clip.quickContains(SkIRect::MakeXYWH(x, y, pmap.width(), pmap.height()));
}
SkTCopyOnFirstWrite<SkPaint> paint(origPaint); if (origPaint.getStyle() != SkPaint::kFill_Style) {
paint.writable()->setStyle(SkPaint::kFill_Style);
}
SkMatrix matrix = *fCTM * prematrix;
if (clipped_out(matrix, *fRC, bitmap.width(), bitmap.height())) { return;
}
if (!SkColorTypeIsAlphaOnly(bitmap.colorType()) &&
SkTreatAsSprite(matrix, bitmap.dimensions(), sampling, paint->isAntiAlias())) { // // It is safe to call lock pixels now, since we know the matrix is // (more or less) identity. //
SkPixmap pmap; if (!bitmap.peekPixels(&pmap)) { return;
} int ix = SkScalarRoundToInt(matrix.getTranslateX()); int iy = SkScalarRoundToInt(matrix.getTranslateY()); if (clipHandlesSprite(*fRC, ix, iy, pmap)) {
SkSTArenaAlloc<kSkBlitterContextSize> allocator; // blitter will be owned by the allocator.
SkBlitter* blitter = SkBlitter::ChooseSprite(fDst, *paint, pmap, ix, iy, &allocator,
fRC->clipShader()); if (blitter) {
SkScan::FillIRect(SkIRect::MakeXYWH(ix, iy, pmap.width(), pmap.height()),
*fRC, blitter); return;
} // if !blitter, then we fall-through to the slower case
}
}
// now make a temp draw on the stack, and use it
Draw draw(*this);
draw.fCTM = &matrix;
// For a long time, the CPU backend treated A8 bitmaps as coverage, rather than alpha. This was // inconsistent with the GPU backend (skbug.com/40041022). When this was fixed, it altered behavior // for some Android apps (b/231400686). Thus: keep the old behavior in the framework. #ifdefined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) if (bitmap.colorType() == kAlpha_8_SkColorType && !paint->getColorFilter()) {
draw.drawBitmapAsMask(bitmap, sampling, *paint, nullptr); return;
} #endif
SkPixmap pmap; if (!bitmap.peekPixels(&pmap)) { return;
}
if (nullptr == paint.getColorFilter() && clipHandlesSprite(*fRC, x, y, pmap)) { // blitter will be owned by the allocator.
SkSTArenaAlloc<kSkBlitterContextSize> allocator;
SkBlitter* blitter = SkBlitter::ChooseSprite(fDst, paint, pmap, x, y, &allocator,
fRC->clipShader()); if (blitter) {
SkScan::FillIRect(bounds, *fRC, blitter); return;
}
}
SkMatrix matrix;
SkRect r;
// get a scalar version of our rect
r.set(bounds);
// create shader with offset
matrix.setTranslate(r.fLeft, r.fTop);
SkPaint paintWithShader = make_paint_with_image(paint, bitmap, SkSamplingOptions(), &matrix);
Draw draw(*this);
draw.fCTM = &SkMatrix::I(); // call ourself with a rect
draw.drawRect(r, paintWithShader);
}
// nothing to draw if (fRC->isEmpty()) { return;
}
if (SkTreatAsSprite(*fCTM, bitmap.dimensions(), sampling, paint.isAntiAlias()))
{ int ix = SkScalarRoundToInt(fCTM->getTranslateX()); int iy = SkScalarRoundToInt(fCTM->getTranslateY());
// set the mask's bounds to the transformed bitmap-bounds, // clipped to the actual device and further limited by the clip bounds
{
SkASSERT(fDst.bounds().contains(fRC->getBounds()));
SkIRect devBounds = fDst.bounds();
devBounds.intersect(fRC->getBounds().makeOutset(1, 1)); // need intersect(l, t, r, b) on irect if (!mask.bounds().intersect(devBounds)) { return;
}
}
mask.format() = SkMask::kA8_Format;
mask.rowBytes() = SkAlign4(mask.fBounds.width());
size_t size = mask.computeImageSize(); if (0 == size) { // the mask is too big to allocated, draw nothing return;
}
// allocate (and clear) our temp buffer to hold the transformed bitmap
AutoTMalloc<uint8_t> storage(size);
mask.image() = storage.get();
memset(mask.image(), 0, size);
// now draw our bitmap(src) into mask(dst), transformed by the matrix
{
SkBitmap device;
device.installPixels(SkImageInfo::MakeA8(mask.fBounds.width(), mask.fBounds.height()),
mask.image(), mask.fRowBytes);
SkCanvas c(device); // need the unclipped top/left for the translate
c.translate(-SkIntToScalar(mask.fBounds.fLeft),
-SkIntToScalar(mask.fBounds.fTop));
c.concat(*fCTM);
// We can't call drawBitmap, or we'll infinitely recurse. Instead // we manually build a shader and draw that into our new mask
SkPaint tmpPaint;
tmpPaint.setAntiAlias(paint.isAntiAlias());
tmpPaint.setDither(paint.isDither());
SkPaint paintWithShader = make_paint_with_image(tmpPaint, bitmap, sampling, nullptr);
SkRect rr;
rr.setIWH(bitmap.width(), bitmap.height());
c.drawRect(rr, paintWithShader);
} this->drawDevMask(mask, paint, paintMatrix);
}
}
SkRect devRect; const SkRect& paintRect = paintMatrix ? *postPaintRect : prePaintRect; // skip the paintMatrix when transforming the rect by the CTM
fCTM->mapPoints(rect_points(devRect), rect_points(paintRect));
devRect.sort();
// look for the quick exit, before we build a blitter
SkRect bbox = devRect; if (paint.getStyle() != SkPaint::kFill_Style) { // extra space for hairlines if (paint.getStrokeWidth() == 0) {
bbox.outset(1, 1);
} else { // For RectType::kStroke, strokeSize is already computed. const SkPoint& ssize = (RectType::kStroke == rtype)
? strokeSize
: compute_stroke_size(paint, *fCTM);
bbox.outset(SkScalarHalf(ssize.x()), SkScalarHalf(ssize.y()));
}
} if (SkPathPriv::TooBigForMath(bbox)) { return;
}
// we want to "fill" if we are kFill or kStrokeAndFill, since in the latter // case we are also hairline (if we've gotten to here), which devolves to // effectively just kFill switch (rtype) { case RectType::kFill: if (paint.isAntiAlias()) {
SkScan::AntiFillRect(devRect, clip, blitter);
} else {
SkScan::FillRect(devRect, clip, blitter);
} break; case RectType::kStroke: if (paint.isAntiAlias()) {
SkScan::AntiFrameRect(devRect, strokeSize, clip, blitter);
} else {
SkScan::FrameRect(devRect, strokeSize, clip, blitter);
} break; case RectType::kHair: if (paint.isAntiAlias()) {
SkScan::AntiHairRect(devRect, clip, blitter);
} else {
SkScan::HairRect(devRect, clip, blitter);
} break; default:
SkDEBUGFAIL("bad rtype");
}
}
static SkScalar fast_len(const SkVector& vec) {
SkScalar x = SkScalarAbs(vec.fX);
SkScalar y = SkScalarAbs(vec.fY); if (x < y) { using std::swap;
swap(x, y);
} return x + SkScalarHalf(y);
}
bool DrawTreatAAStrokeAsHairline(SkScalar strokeWidth, const SkMatrix& matrix, SkScalar* coverage) {
SkASSERT(strokeWidth > 0); // We need to try to fake a thick-stroke with a modulated hairline.
{ // TODO: Investigate optimizing these options. They are in the same // order as skcpu::Draw::drawPath, which handles each case. It may be // that there is no way to optimize for these using the SkRRect path.
SkScalar coverage; if (skcpu::DrawTreatAsHairline(paint, *fCTM, &coverage)) { goto DRAW_PATH;
}
if (paint.getMaskFilter()) {
SkStrokeRec::InitStyle style = doFill ? SkStrokeRec::kFill_InitStyle
: SkStrokeRec::kHairline_InitStyle;
SkResourceCache* cache = nullptr; // TODO(kjlubick) get this from fCtx if (as_MFB(paint.getMaskFilter())->filterPath(raw, *fCTM, *fRC, blitter, style, cache)) { return; // filterPath() called the blitter, so we're done
}
}
void (*proc)(const SkPathRaw&, const SkRasterClip&, SkBlitter*); if (doFill) { if (paint.isAntiAlias()) {
proc = SkScan::AntiFillPath;
} else {
proc = SkScan::FillPath;
}
} else { // hairline if (paint.isAntiAlias()) { switch (paint.getStrokeCap()) { case SkPaint::kButt_Cap:
proc = SkScan::AntiHairPath; break; case SkPaint::kSquare_Cap:
proc = SkScan::AntiHairSquarePath; break; case SkPaint::kRound_Cap:
proc = SkScan::AntiHairRoundPath; break;
}
} else { switch (paint.getStrokeCap()) { case SkPaint::kButt_Cap:
proc = SkScan::HairPath; break; case SkPaint::kSquare_Cap:
proc = SkScan::HairSquarePath; break; case SkPaint::kRound_Cap:
proc = SkScan::HairRoundPath; break;
}
}
}
proc(raw, *fRC, blitter);
}
/* *Trickyidea:canwetreatthinstrokesashairlines?Ifso,dependingonhow *thin,wemaydecidetomodulatethepaint'salphato'simulate'verythink *strokes,eventhoughhairlineisalways1-pixelwide. * *Themotivationatthetimewasperformance:hairlinesdrawfasterthanconstructing *theinner/outercontoursandfillingthat(aswedofornormalstroking). * *Questionabledecision,sinceourhairlinealgorithmdrawseachsegmentofthepath *separately,meaningapaththatcrossesitselfcanhaveblendingartifacts. *Note:thisdoesn'thappenwithnormalstroking,asthebuiltinner/outerpath *neverdouble-hitsapixel.
*/ static std::optional<SkPaint> modifyPaintForHairlines(const SkPaint& origPaint, const SkMatrix& matrix) { float coverage; if (DrawTreatAsHairline(origPaint, matrix, &coverage)) { constauto bm = origPaint.asBlendMode(); if (coverage == 1) {
SkPaint paint(origPaint);
paint.setStrokeWidth(0); return paint;
} elseif (bm && SkBlendMode_SupportsCoverageAsAlpha(bm.value())) {
U8CPU newAlpha; #if0
newAlpha = SkToU8(SkScalarRoundToInt(coverage * origPaint.getAlpha())); #else // this is the old technique, which we preserve for now so // we don't change previous results (testing) // the new way seems fine, its just (a tiny bit) different int scale = (int)(coverage * 256);
newAlpha = origPaint.getAlpha() * scale >> 8; #endif
SkPaint paint(origPaint);
paint.setStrokeWidth(0);
paint.setAlpha(newAlpha); return paint;
}
} return {};
}
// trim the bounds to reflect the clip (plus whatever slop the filter needs) // Ugh. Guard against gigantic margins from wacky filters. Without this // check we can request arbitrary amounts of slop beyond our visible // clip, and bring down the renderer (at least on finite RAM machines // like handsets, etc.). Need to balance this invented value between // quality of large filters like blurs, and the corresponding memory // requests. static constexpr int kMaxMargin = 128; if (!bounds->intersect(clipBounds.makeOutset(std::min(margin.fX, kMaxMargin),
std::min(margin.fY, kMaxMargin)))) { returnfalse;
}
// transform a copy of the points, so we can apply the ctm/translate
skia_private::AutoSTArray<32, SkPoint> devPoints(raw.fPoints.size());
translate.mapPoints(devPoints, raw.fPoints);
raw.fPoints = devPoints;
raw.fBounds = raw.fBounds.makeOffset(dx, dy); if (!raw.fBounds.isFinite()) { return;
}
if (SkMaskBuilder::kJustRenderImage_CreateMode != mode) { // By using infinite bounds for inverse fills, compute_mask_bounds is able to clip it to // 'clipBounds' outset by whatever extra margin the mask filter requires. staticconst SkRect kInverseBounds = {SK_ScalarNegativeInfinity,
SK_ScalarNegativeInfinity,
SK_ScalarInfinity,
SK_ScalarInfinity};
SkRect pathBounds = devRaw.isInverseFillType() ? kInverseBounds : devRaw.bounds(); if (!compute_mask_bounds(pathBounds, clipBounds, filter, filterMatrix, &dst->bounds())) { returnfalse;
}
}
if (SkMaskBuilder::kComputeBoundsAndRenderImage_CreateMode == mode) {
dst->format() = SkMask::kA8_Format;
dst->rowBytes() = dst->fBounds.width();
size_t size = dst->computeImageSize(); if (0 == size) { // we're too big to allocate the mask, abort returnfalse;
}
dst->image() = SkMaskBuilder::AllocImage(size, SkMaskBuilder::kZeroInit_Alloc);
}
void Draw::drawDevicePoints(SkCanvas::PointMode mode,
SkSpan<const SkPoint> points, const SkPaint& paint,
SkDevice* device) const { // if we're in lines mode, force count to be even if (SkCanvas::kLines_PointMode == mode) {
points = points.first(points.size() & ~1); // force it to be even
}
SkDEBUGCODE(this->validate();)
// nothing to draw if (points.empty() || fRC->isEmpty()) { return;
}
// needed? if (!SkIsFinite(&points[0].fX, points.size() * 2)) { return;
}
switch (mode) { case SkCanvas::kPoints_PointMode: { // temporarily mark the paint as filling.
SkPaint newPaint(paint);
newPaint.setStyle(SkPaint::kFill_Style);
for (constauto& pt : points) {
r.fLeft = pt.fX - radius;
r.fTop = pt.fY - radius;
r.fRight = r.fLeft + width;
r.fBottom = r.fTop + width; if (device) {
device->drawRect(r, newPaint);
} else { this->drawRect(r, newPaint);
}
}
} break;
} case SkCanvas::kLines_PointMode: if (2 == points.size() && paint.getPathEffect()) { // most likely a dashed line - see if it is one of the ones // we can accelerate
SkStrokeRec stroke(paint);
SkPathEffectBase::PointData pointData;
SkPath path = SkPath::Line(points[0], points[1]);
SkRect cullRect = SkRect::Make(fRC->getBounds());
if (as_PEB(paint.getPathEffect())
->asPoints(&pointData, path, stroke, *fCTM, &cullRect)) { // 'asPoints' managed to find some fast path
if (!pointData.fFirst.isEmpty()) { if (device) {
device->drawPath(pointData.fFirst, newP);
} else { this->drawPath(pointData.fFirst, newP, nullptr);
}
}
if (!pointData.fLast.isEmpty()) { if (device) {
device->drawPath(pointData.fLast, newP);
} else { this->drawPath(pointData.fLast, newP, nullptr);
}
}
if (pointData.fSize.fX == pointData.fSize.fY) { // The rest of the dashed line can just be drawn as points
SkASSERT(pointData.fSize.fX == SkScalarHalf(newP.getStrokeWidth()));
if (device) {
device->drawPoints(
SkCanvas::kPoints_PointMode, pointData.points(), newP);
} else { this->drawDevicePoints(
SkCanvas::kPoints_PointMode, pointData.points(), newP, device);
} break;
} else { // The rest of the dashed line must be drawn as rects
SkASSERT(!(SkPathEffectBase::PointData::kCircles_PointFlag &
pointData.fFlags));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.