void onDrawContent(SkCanvas* canvas) override { SkISize dsize = canvas->getBaseLayerSize(); canvas->clear(0xFFF0E0F0); for (int i = 0; i < N; ++i) { SkRect rect = SkRect::MakeWH(SkIntToScalar(fRandom.nextRangeU(10, 100)), SkIntToScalar(fRandom.nextRangeU(10, 100))); int x = fRandom.nextRangeU(0, dsize.fWidth); int y = fRandom.nextRangeU(0, dsize.fHeight); canvas->save(); canvas->translate(SkIntToScalar(x), SkIntToScalar(y)); // Uncomment to test rotated rect draw combining. if (false) { SkMatrix rotate; rotate.setRotate(fRandom.nextUScalar1() * 360, SkIntToScalar(x) + SkScalarHalf(rect.fRight), SkIntToScalar(y) + SkScalarHalf(rect.fBottom)); canvas->concat(rotate); } SkRect clipRect = rect; // This clip will always contain the entire rect. It's here to give the GPU op combining // code a little more challenge. clipRect.outset(10, 10); canvas->clipRect(clipRect); SkPaint paint; paint.setColor(fRandom.nextU()); canvas->drawRect(rect, paint); canvas->restore(); } }
bool GrAAConvexPathRenderer::onDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& vm, const SkPath& path, const GrStrokeInfo&, bool antiAlias) { if (path.isEmpty()) { return true; } // We outset our vertices one pixel and add one more pixel for precision. // TODO create tighter bounds when we start reordering. SkRect devRect = path.getBounds(); vm.mapRect(&devRect); devRect.outset(2, 2); AAConvexPathBatch::Geometry geometry; geometry.fColor = color; geometry.fViewMatrix = vm; geometry.fPath = path; SkAutoTUnref<GrBatch> batch(AAConvexPathBatch::Create(geometry)); target->drawBatch(pipelineBuilder, batch, &devRect); return true; }
static void draw_rect(SkCanvas* canvas, bool showGL, int flags) { SkPaint paint; paint.setAntiAlias(true); SkRect r = SkRect::MakeLTRB(50, 70, 250, 370); setFade(&paint, showGL); canvas->drawRect(r, paint); if (showGL) { show_mesh(canvas, r); } canvas->translate(320, 0); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(25); canvas->drawRect(r, paint); if (showGL) { SkScalar rad = paint.getStrokeWidth() / 2; SkPoint pts[8]; r.outset(rad, rad); r.toQuad(&pts[0]); r.inset(rad*2, rad*2); r.toQuad(&pts[4]); const uint16_t indices[] = { 0, 4, 1, 5, 2, 6, 3, 7, 0, 4 }; show_mesh(canvas, pts, indices, SK_ARRAY_COUNT(indices)); } }
static std::unique_ptr<GrLegacyMeshDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, const SkStrokeRec& stroke, bool snapToPixelCenters) { if (!allowed_stroke(stroke)) { return nullptr; } NonAAStrokeRectOp* op = new NonAAStrokeRectOp(); op->fColor = color; op->fViewMatrix = viewMatrix; op->fRect = rect; // Sort the rect for hairlines op->fRect.sort(); op->fStrokeWidth = stroke.getWidth(); SkScalar rad = SkScalarHalf(op->fStrokeWidth); SkRect bounds = rect; bounds.outset(rad, rad); // If our caller snaps to pixel centers then we have to round out the bounds if (snapToPixelCenters) { viewMatrix.mapRect(&bounds); // We want to be consistent with how we snap non-aa lines. To match what we do in // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a // pixel to force us to pixel centers. bounds.set(SkScalarFloorToScalar(bounds.fLeft), SkScalarFloorToScalar(bounds.fTop), SkScalarFloorToScalar(bounds.fRight), SkScalarFloorToScalar(bounds.fBottom)); bounds.offset(0.5f, 0.5f); op->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo); } else { op->setTransformedBounds(bounds, op->fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo); } return std::unique_ptr<GrLegacyMeshDrawOp>(op); }
void GrDrawTarget::drawBatch(GrPipelineBuilder* pipelineBuilder, GrBatch* batch) { SkASSERT(pipelineBuilder); // TODO some kind of checkdraw, but not at this level // Setup clip GrScissorState scissorState; GrPipelineBuilder::AutoRestoreFragmentProcessors arfp; GrPipelineBuilder::AutoRestoreStencil ars; if (!this->setupClip(pipelineBuilder, &arfp, &ars, &scissorState, &batch->bounds())) { return; } // Batch bounds are tight, so for dev copies // TODO move this into setupDstReadIfNecessary when paths are in batch SkRect bounds = batch->bounds(); bounds.outset(0.5f, 0.5f); GrDrawTarget::PipelineInfo pipelineInfo(pipelineBuilder, &scissorState, batch, &bounds, this); if (pipelineInfo.mustSkipDraw()) { return; } this->onDrawBatch(batch, pipelineInfo); }
bool check_bounds(const SkMatrix& viewMatrix, const SkRect& devBounds, void* vertices, int vCount) { SkRect tolDevBounds = devBounds; // The bounds ought to be tight, but in perspective the below code runs the verts // through the view matrix to get back to dev coords, which can introduce imprecision. if (viewMatrix.hasPerspective()) { tolDevBounds.outset(SK_Scalar1 / 1000, SK_Scalar1 / 1000); } else { // Non-persp matrices cause this path renderer to draw in device space. SkASSERT(viewMatrix.isIdentity()); } SkRect actualBounds; VertexType* verts = reinterpret_cast<VertexType*>(vertices); bool first = true; for (int i = 0; i < vCount; ++i) { SkPoint pos = verts[i].fPos; // This is a hack to workaround the fact that we move some degenerate segments offscreen. if (SK_ScalarMax == pos.fX) { continue; } viewMatrix.mapPoints(&pos, 1); if (first) { actualBounds.set(pos.fX, pos.fY, pos.fX, pos.fY); first = false; } else { actualBounds.growToInclude(pos.fX, pos.fY); } } if (!first) { return tolDevBounds.contains(actualBounds); } return true; }
void SkTileGrid::insert(SkAutoTMalloc<SkRect>* boundsArray, int N) { this->reserve(N); for (int i = 0; i < N; i++) { SkRect bounds = (*boundsArray)[i]; bounds.outset(fMarginWidth, fMarginHeight); this->commonAdjust(&bounds); // TODO(mtklein): can we assert this instead to save an intersection in Release mode, // or just allow out-of-bound insertions to insert anyway (clamped to nearest tile)? if (!SkRect::Intersects(bounds, fGridBounds)) { continue; } SkIRect grid; this->userToGrid(bounds, &grid); // This is just a loop over y then x. This compiles to a slightly faster and // more compact loop than if we just did fTiles[y * fXTiles + x].push(i). SkTDArray<unsigned>* row = &fTiles[grid.fTop * fXTiles + grid.fLeft]; for (int y = 0; y <= grid.fBottom - grid.fTop; y++) { SkTDArray<unsigned>* tile = row; for (int x = 0; x <= grid.fRight - grid.fLeft; x++) { (tile++)->push(i); } row += fXTiles; } } this->shrinkToFit(); }
static sk_sp<SkPicture> make_tri_picture() { SkPath tri = make_tri_path(SkScalarHalf(kTriSide), 0); SkPaint fill; fill.setStyle(SkPaint::kFill_Style); fill.setColor(sk_tool_utils::color_to_565(SK_ColorLTGRAY)); SkPaint stroke; stroke.setStyle(SkPaint::kStroke_Style); stroke.setStrokeWidth(3); SkPictureRecorder recorder; SkRTreeFactory bbhFactory; SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth), SkIntToScalar(kPicHeight), &bbhFactory); SkRect r = tri.getBounds(); r.outset(2.0f, 2.0f); // outset for stroke canvas->clipRect(r); // The saveLayer/restore block is to exercise layer hoisting canvas->saveLayer(nullptr, nullptr); canvas->drawPath(tri, fill); canvas->drawPath(tri, stroke); canvas->restore(); return recorder.finishRecordingAsPicture(); }
void GrDrawTarget::drawBatch(const GrPipelineBuilder& pipelineBuilder, GrDrawBatch* batch) { // TODO some kind of checkdraw, but not at this level // Setup clip GrScissorState scissorState; GrPipelineBuilder::AutoRestoreFragmentProcessorState arfps; GrPipelineBuilder::AutoRestoreStencil ars; if (!this->setupClip(pipelineBuilder, &arfps, &ars, &scissorState, &batch->bounds())) { return; } // Batch bounds are tight, so for dev copies // TODO move this into setupDstReadIfNecessary when paths are in batch SkRect bounds = batch->bounds(); bounds.outset(0.5f, 0.5f); GrDrawTarget::PipelineInfo pipelineInfo(&pipelineBuilder, &scissorState, batch, &bounds, this); if (!pipelineInfo.valid()) { return; } if (!batch->installPipeline(pipelineInfo.pipelineCreateArgs())) { return; } this->onDrawBatch(batch); }
void onDraw(SkCanvas* canvas) override { GrRenderTargetContext* renderTargetContext = canvas->internal_private_accessTopLayerRenderTargetContext(); if (!renderTargetContext) { skiagm::GM::DrawGpuOnlyMessage(canvas); return; } GrContext* context = canvas->getGrContext(); if (!context) { return; } GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider(); sk_sp<GrTextureProxy> proxy[3]; for (int i = 0; i < 3; ++i) { int index = (0 == i) ? 0 : 1; GrSurfaceDesc desc; desc.fWidth = fBmp[index].width(); desc.fHeight = fBmp[index].height(); desc.fConfig = SkImageInfo2GrPixelConfig(fBmp[index].info(), *context->caps()); SkASSERT(kUnknown_GrPixelConfig != desc.fConfig); proxy[i] = proxyProvider->createTextureProxy( desc, SkBudgeted::kYes, fBmp[index].getPixels(), fBmp[index].rowBytes()); if (!proxy[i]) { return; } } constexpr SkScalar kDrawPad = 10.f; constexpr SkScalar kTestPad = 10.f; constexpr SkScalar kColorSpaceOffset = 36.f; SkISize sizes[3] = {{YSIZE, YSIZE}, {USIZE, USIZE}, {VSIZE, VSIZE}}; for (int space = kJPEG_SkYUVColorSpace; space <= kLastEnum_SkYUVColorSpace; ++space) { SkRect renderRect = SkRect::MakeWH(SkIntToScalar(fBmp[0].width()), SkIntToScalar(fBmp[0].height())); renderRect.outset(kDrawPad, kDrawPad); SkScalar y = kDrawPad + kTestPad + space * kColorSpaceOffset; SkScalar x = kDrawPad + kTestPad; GrPaint grPaint; grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc)); auto fp = GrYUVtoRGBEffect::Make(proxy[0], proxy[1], proxy[2], sizes, static_cast<SkYUVColorSpace>(space), true); if (fp) { SkMatrix viewMatrix; viewMatrix.setTranslate(x, y); grPaint.addColorFragmentProcessor(std::move(fp)); std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill( std::move(grPaint), viewMatrix, renderRect, GrAAType::kNone)); renderTargetContext->priv().testingOnly_addDrawOp(std::move(op)); } } }
Bounds bounds(const DrawPoints& op) const { SkRect dst; dst.set(op.pts, op.count); // Pad the bounding box a little to make sure hairline points' bounds aren't empty. SkScalar stroke = SkMaxScalar(op.paint.getStrokeWidth(), 0.01f); dst.outset(stroke / 2, stroke / 2); return this->adjustAndMap(dst, &op.paint); }
SkRect SkDropShadowImageFilter::computeFastBounds(const SkRect& src) const { SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; SkRect shadowBounds = bounds; shadowBounds.offset(fDx, fDy); shadowBounds.outset(fSigmaX * 3, fSigmaY * 3); if (fShadowMode == kDrawShadowAndForeground_ShadowMode) { bounds.join(shadowBounds); } else { bounds = shadowBounds; } return bounds; }
void GrGLPathRendering::drawPath(const GrPath* path, SkPath::FillType fill) { GrGLuint id = static_cast<const GrGLPath*>(path)->pathID(); SkASSERT(NULL != fGpu->drawState()->getRenderTarget()); SkASSERT(NULL != fGpu->drawState()->getRenderTarget()->getStencilBuffer()); this->flushPathStencilSettings(fill); SkASSERT(!fHWPathStencilSettings.isTwoSided()); const SkStrokeRec& stroke = path->getStroke(); SkPath::FillType nonInvertedFill = SkPath::ConvertToNonInverseFillType(fill); GrGLenum fillMode = gr_stencil_op_to_gl_path_rendering_fill_mode(fHWPathStencilSettings.passOp(GrStencilSettings::kFront_Face)); GrGLint writeMask = fHWPathStencilSettings.writeMask(GrStencilSettings::kFront_Face); if (nonInvertedFill == fill) { if (stroke.needToApply()) { if (SkStrokeRec::kStrokeAndFill_Style == stroke.getStyle()) { GL_CALL(StencilFillPath(id, fillMode, writeMask)); } this->stencilThenCoverStrokePath(id, 0xffff, writeMask, GR_GL_BOUNDING_BOX); } else { this->stencilThenCoverFillPath(id, fillMode, writeMask, GR_GL_BOUNDING_BOX); } } else { if (stroke.isFillStyle() || SkStrokeRec::kStrokeAndFill_Style == stroke.getStyle()) { GL_CALL(StencilFillPath(id, fillMode, writeMask)); } if (stroke.needToApply()) { GL_CALL(StencilStrokePath(id, 0xffff, writeMask)); } GrDrawState* drawState = fGpu->drawState(); GrDrawState::AutoViewMatrixRestore avmr; SkRect bounds = SkRect::MakeLTRB(0, 0, SkIntToScalar(drawState->getRenderTarget()->width()), SkIntToScalar(drawState->getRenderTarget()->height())); SkMatrix vmi; // mapRect through persp matrix may not be correct if (!drawState->getViewMatrix().hasPerspective() && drawState->getViewInverse(&vmi)) { vmi.mapRect(&bounds); // theoretically could set bloat = 0, instead leave it because of matrix inversion // precision. SkScalar bloat = drawState->getViewMatrix().getMaxScale() * SK_ScalarHalf; bounds.outset(bloat, bloat); } else { avmr.setIdentity(drawState); } fGpu->drawSimpleRect(bounds); } }
void SkDropShadowImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const { if (getInput(0)) { getInput(0)->computeFastBounds(src, dst); } else { *dst = src; } SkRect shadowBounds = *dst; shadowBounds.offset(fDx, fDy); shadowBounds.outset(SkScalarMul(fSigmaX, SkIntToScalar(3)), SkScalarMul(fSigmaY, SkIntToScalar(3))); dst->join(shadowBounds); }
static void draw_donut(SkCanvas* canvas, const SkRect& r, const SkPaint& p) { SkRect rect; SkPath path; rect = r; rect.outset(STROKE_WIDTH/2, STROKE_WIDTH/2); path.addRect(rect); rect = r; rect.inset(STROKE_WIDTH/2, STROKE_WIDTH/2); path.addRect(rect); path.setFillType(SkPath::kEvenOdd_FillType); canvas->drawPath(path, p); }
static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description) { SkRect rect = SkRect::MakeWH(description.shape.roundRect.width, description.shape.roundRect.height); float rx = description.shape.roundRect.rx; float ry = description.shape.roundRect.ry; if (description.style == SkPaint::kStrokeAndFill_Style) { float outset = description.strokeWidth / 2; rect.outset(outset, outset); rx += outset; ry += outset; } SkPath path; path.addRoundRect(rect, rx, ry); return tessellatePath(description, path); }
Bounds bounds(const DrawTextOnPath& op) const { SkRect dst = op.path.getBounds(); // Pad all sides by the maximum padding in any direction we'd normally apply. SkRect pad = { 0, 0, 0, 0 }; AdjustTextForFontMetrics(&pad, op.paint); // That maximum padding happens to always be the right pad today. SkASSERT(pad.fLeft == -pad.fRight); SkASSERT(pad.fTop == -pad.fBottom); SkASSERT(pad.fRight > pad.fBottom); dst.outset(pad.fRight, pad.fRight); return this->adjustAndMap(dst, &op.paint); }
static void writePng(const SkConic& c, const SkConic ch[2], const char* name) { const int scale = 10; SkConic conic, chopped[2]; for (int index = 0; index < 3; ++index) { conic.fPts[index].fX = c.fPts[index].fX * scale; conic.fPts[index].fY = c.fPts[index].fY * scale; for (int chIndex = 0; chIndex < 2; ++chIndex) { chopped[chIndex].fPts[index].fX = ch[chIndex].fPts[index].fX * scale; chopped[chIndex].fPts[index].fY = ch[chIndex].fPts[index].fY * scale; } } conic.fW = c.fW; chopped[0].fW = ch[0].fW; chopped[1].fW = ch[1].fW; SkBitmap bitmap; SkRect bounds; conic.computeTightBounds(&bounds); bounds.outset(10, 10); bitmap.tryAllocPixels(SkImageInfo::MakeN32Premul( SkScalarRoundToInt(bounds.width()), SkScalarRoundToInt(bounds.height()))); SkCanvas canvas(bitmap); SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); canvas.translate(-bounds.fLeft, -bounds.fTop); canvas.drawColor(SK_ColorWHITE); SkPath path; path.moveTo(conic.fPts[0]); path.conicTo(conic.fPts[1], conic.fPts[2], conic.fW); paint.setARGB(0x80, 0xFF, 0, 0); canvas.drawPath(path, paint); path.reset(); path.moveTo(chopped[0].fPts[0]); path.conicTo(chopped[0].fPts[1], chopped[0].fPts[2], chopped[0].fW); path.moveTo(chopped[1].fPts[0]); path.conicTo(chopped[1].fPts[1], chopped[1].fPts[2], chopped[1].fW); paint.setARGB(0x80, 0, 0, 0xFF); canvas.drawPath(path, paint); SkString filename("c:\\Users\\caryclark\\Documents\\"); filename.appendf("%s.png", name); SkImageEncoder::EncodeFile(filename.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100); }
void SkBBoxRecord::drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { SkRect bbox; bbox.set(pts, SkToInt(count)); // Small min width value, just to ensure hairline point bounding boxes aren't empty. // Even though we know hairline primitives are drawn one pixel wide, we do not use a // minimum of 1 because the playback scale factor is unknown at record time. Later // outsets will take care of adding additional padding for antialiasing and rounding out // to integer device coordinates, guaranteeing that the rasterized pixels will be included // in the computed bounds. // Note: The device coordinate outset in SkBBoxHierarchyRecord::handleBBox is currently // done in the recording coordinate space, which is wrong. // http://code.google.com/p/skia/issues/detail?id=1021 static const SkScalar kMinWidth = 0.01f; SkScalar halfStrokeWidth = SkMaxScalar(paint.getStrokeWidth(), kMinWidth) / 2; bbox.outset(halfStrokeWidth, halfStrokeWidth); if (this->transformBounds(bbox, &paint)) { INHERITED::drawPoints(mode, count, pts, paint); } }
virtual void onDraw(SkCanvas* canvas) { if (!fInitialized) { this->make_checkerboard(); this->make_gradient_circle(64, 64); fInitialized = true; } canvas->clear(0x00000000); SkAutoTUnref<SkImageFilter> gradient(SkBitmapSource::Create(fGradientCircle)); SkAutoTUnref<SkImageFilter> checkerboard(SkBitmapSource::Create(fCheckerboard)); SkAutoTUnref<SkShader> noise(SkPerlinNoiseShader::CreateFractalNoise( SkDoubleToScalar(0.1), SkDoubleToScalar(0.05), 1, 0)); SkMatrix resizeMatrix; resizeMatrix.setScale(RESIZE_FACTOR_X, RESIZE_FACTOR_Y); SkImageFilter* filters[] = { SkBlurImageFilter::Create(SkIntToScalar(12), SkIntToScalar(12)), SkDropShadowImageFilter::Create(SkIntToScalar(10), SkIntToScalar(10), SkIntToScalar(3), SK_ColorGREEN), SkDisplacementMapEffect::Create(SkDisplacementMapEffect::kR_ChannelSelectorType, SkDisplacementMapEffect::kR_ChannelSelectorType, SkIntToScalar(12), gradient.get(), checkerboard.get()), SkDilateImageFilter::Create(2, 2, checkerboard.get()), SkErodeImageFilter::Create(2, 2, checkerboard.get()), SkOffsetImageFilter::Create(SkIntToScalar(-16), SkIntToScalar(32)), SkMatrixImageFilter::Create(resizeMatrix, SkPaint::kNone_FilterLevel), SkRectShaderImageFilter::Create(noise), }; SkRect r = SkRect::MakeWH(SkIntToScalar(64), SkIntToScalar(64)); SkScalar margin = SkIntToScalar(16); SkRect bounds = r; bounds.outset(margin, margin); for (int xOffset = 0; xOffset < 80; xOffset += 16) { canvas->save(); bounds.fLeft = SkIntToScalar(xOffset); for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) { SkPaint paint; paint.setColor(SK_ColorWHITE); paint.setImageFilter(filters[i]); paint.setAntiAlias(true); canvas->save(); canvas->clipRect(bounds); if (5 == i) { canvas->translate(SkIntToScalar(16), SkIntToScalar(-32)); } else if (6 == i) { canvas->scale(SkScalarInvert(RESIZE_FACTOR_X), SkScalarInvert(RESIZE_FACTOR_Y)); } canvas->drawCircle(r.centerX(), r.centerY(), SkScalarDiv(r.width()*2, SkIntToScalar(5)), paint); canvas->restore(); canvas->translate(r.width() + margin, 0); } canvas->restore(); canvas->translate(0, r.height() + margin); } for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) { SkSafeUnref(filters[i]); } }
/** * Generates the lines and quads to be rendered. Lines are always recorded in * device space. We will do a device space bloat to account for the 1pixel * thickness. * Quads are recorded in device space unless m contains * perspective, then in they are in src space. We do this because we will * subdivide large quads to reduce over-fill. This subdivision has to be * performed before applying the perspective matrix. */ static int gather_lines_and_quads(const SkPath& path, const SkMatrix& m, const SkIRect& devClipBounds, GrAAHairLinePathRenderer::PtArray* lines, GrAAHairLinePathRenderer::PtArray* quads, GrAAHairLinePathRenderer::PtArray* conics, GrAAHairLinePathRenderer::IntArray* quadSubdivCnts, GrAAHairLinePathRenderer::FloatArray* conicWeights) { SkPath::Iter iter(path, false); int totalQuadCount = 0; SkRect bounds; SkIRect ibounds; bool persp = m.hasPerspective(); for (;;) { SkPoint pathPts[4]; SkPoint devPts[4]; SkPath::Verb verb = iter.next(pathPts); switch (verb) { case SkPath::kConic_Verb: { SkConic dst[4]; // We chop the conics to create tighter clipping to hide error // that appears near max curvature of very thin conics. Thin // hyperbolas with high weight still show error. int conicCnt = chop_conic(pathPts, dst, iter.conicWeight()); for (int i = 0; i < conicCnt; ++i) { SkPoint* chopPnts = dst[i].fPts; m.mapPoints(devPts, chopPnts, 3); bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { if (is_degen_quad_or_conic(devPts)) { SkPoint* pts = lines->push_back_n(4); pts[0] = devPts[0]; pts[1] = devPts[1]; pts[2] = devPts[1]; pts[3] = devPts[2]; } else { // when in perspective keep conics in src space SkPoint* cPts = persp ? chopPnts : devPts; SkPoint* pts = conics->push_back_n(3); pts[0] = cPts[0]; pts[1] = cPts[1]; pts[2] = cPts[2]; conicWeights->push_back() = dst[i].fW; } } } break; } case SkPath::kMove_Verb: break; case SkPath::kLine_Verb: m.mapPoints(devPts, pathPts, 2); bounds.setBounds(devPts, 2); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { SkPoint* pts = lines->push_back_n(2); pts[0] = devPts[0]; pts[1] = devPts[1]; } break; case SkPath::kQuad_Verb: { SkPoint choppedPts[5]; // Chopping the quad helps when the quad is either degenerate or nearly degenerate. // When it is degenerate it allows the approximation with lines to work since the // chop point (if there is one) will be at the parabola's vertex. In the nearly // degenerate the QuadUVMatrix computed for the points is almost singular which // can cause rendering artifacts. int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts); for (int i = 0; i < n; ++i) { SkPoint* quadPts = choppedPts + i * 2; m.mapPoints(devPts, quadPts, 3); bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { int subdiv = num_quad_subdivs(devPts); SkASSERT(subdiv >= -1); if (-1 == subdiv) { SkPoint* pts = lines->push_back_n(4); pts[0] = devPts[0]; pts[1] = devPts[1]; pts[2] = devPts[1]; pts[3] = devPts[2]; } else { // when in perspective keep quads in src space SkPoint* qPts = persp ? quadPts : devPts; SkPoint* pts = quads->push_back_n(3); pts[0] = qPts[0]; pts[1] = qPts[1]; pts[2] = qPts[2]; quadSubdivCnts->push_back() = subdiv; totalQuadCount += 1 << subdiv; } } } break; } case SkPath::kCubic_Verb: m.mapPoints(devPts, pathPts, 4); bounds.setBounds(devPts, 4); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { PREALLOC_PTARRAY(32) q; // we don't need a direction if we aren't constraining the subdivision const SkPathPriv::FirstDirection kDummyDir = SkPathPriv::kCCW_FirstDirection; // We convert cubics to quadratics (for now). // In perspective have to do conversion in src space. if (persp) { SkScalar tolScale = GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds()); GrPathUtils::convertCubicToQuads(pathPts, tolScale, false, kDummyDir, &q); } else { GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, false, kDummyDir, &q); } for (int i = 0; i < q.count(); i += 3) { SkPoint* qInDevSpace; // bounds has to be calculated in device space, but q is // in src space when there is perspective. if (persp) { m.mapPoints(devPts, &q[i], 3); bounds.setBounds(devPts, 3); qInDevSpace = devPts; } else { bounds.setBounds(&q[i], 3); qInDevSpace = &q[i]; } bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { int subdiv = num_quad_subdivs(qInDevSpace); SkASSERT(subdiv >= -1); if (-1 == subdiv) { SkPoint* pts = lines->push_back_n(4); // lines should always be in device coords pts[0] = qInDevSpace[0]; pts[1] = qInDevSpace[1]; pts[2] = qInDevSpace[1]; pts[3] = qInDevSpace[2]; } else { SkPoint* pts = quads->push_back_n(3); // q is already in src space when there is no // perspective and dev coords otherwise. pts[0] = q[0 + i]; pts[1] = q[1 + i]; pts[2] = q[2 + i]; quadSubdivCnts->push_back() = subdiv; totalQuadCount += 1 << subdiv; } } } } break; case SkPath::kClose_Verb: break; case SkPath::kDone_Verb: return totalQuadCount; } } }
void GrAtlasTextBatch::onPrepareDraws(Target* target) const { // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix. // TODO actually only invert if we don't have RGBA SkMatrix localMatrix; if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) { SkDebugf("Cannot invert viewmatrix\n"); return; } GrTexture* texture = fFontCache->getTexture(this->maskFormat()); if (!texture) { SkDebugf("Could not allocate backing texture for atlas\n"); return; } GrMaskFormat maskFormat = this->maskFormat(); FlushInfo flushInfo; if (this->usesDistanceFields()) { flushInfo.fGeometryProcessor = this->setupDfProcessor(this->viewMatrix(), fFilteredColor, this->color(), texture); } else { GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode); flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(this->color(), texture, params, maskFormat, localMatrix, this->usesLocalCoords()); } flushInfo.fGlyphsToFlush = 0; size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride(); SkASSERT(vertexStride == GrAtlasTextBlob::GetVertexStride(maskFormat)); int glyphCount = this->numGlyphs(); const GrBuffer* vertexBuffer; void* vertices = target->makeVertexSpace(vertexStride, glyphCount * kVerticesPerGlyph, &vertexBuffer, &flushInfo.fVertexOffset); flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer)); flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer()); if (!vertices || !flushInfo.fVertexBuffer) { SkDebugf("Could not allocate vertices\n"); return; } unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices); GrBlobRegenHelper helper(this, target, &flushInfo); SkAutoGlyphCache glyphCache; for (int i = 0; i < fGeoCount; i++) { const Geometry& args = fGeoData[i]; Blob* blob = args.fBlob; size_t byteCount; void* blobVertices; int subRunGlyphCount; blob->regenInBatch(target, fFontCache, &helper, args.fRun, args.fSubRun, &glyphCache, vertexStride, args.fViewMatrix, args.fX, args.fY, args.fColor, &blobVertices, &byteCount, &subRunGlyphCount); // now copy all vertices memcpy(currVertex, blobVertices, byteCount); #ifdef SK_DEBUG // bounds sanity check SkRect rect; rect.setLargestInverted(); SkPoint* vertex = (SkPoint*) ((char*)blobVertices); rect.growToInclude(vertex, vertexStride, kVerticesPerGlyph * subRunGlyphCount); if (this->usesDistanceFields()) { args.fViewMatrix.mapRect(&rect); } // Allow for small numerical error in the bounds. SkRect bounds = this->bounds(); bounds.outset(0.001f, 0.001f); SkASSERT(bounds.contains(rect)); #endif currVertex += byteCount; } this->flush(target, &flushInfo); }
void SkDebugCanvas::drawTo(SkCanvas* canvas, int index) { SkASSERT(!fCommandVector.isEmpty()); SkASSERT(index < fCommandVector.count()); int saveCount = canvas->save(); SkRect windowRect = SkRect::MakeWH(SkIntToScalar(canvas->getBaseLayerSize().width()), SkIntToScalar(canvas->getBaseLayerSize().height())); bool pathOpsMode = getAllowSimplifyClip(); canvas->setAllowSimplifyClip(pathOpsMode); canvas->clear(SK_ColorTRANSPARENT); canvas->resetMatrix(); if (!windowRect.isEmpty()) { canvas->clipRect(windowRect, SkRegion::kReplace_Op); } this->applyUserTransform(canvas); if (fPaintFilterCanvas) { fPaintFilterCanvas->addCanvas(canvas); canvas = fPaintFilterCanvas.get(); } if (fMegaVizMode) { this->markActiveCommands(index); } for (int i = 0; i <= index; i++) { if (i == index && fFilter) { canvas->clear(0xAAFFFFFF); } if (fCommandVector[i]->isVisible()) { if (fMegaVizMode && fCommandVector[i]->active()) { // "active" commands execute their visualization behaviors: // All active saveLayers get replaced with saves so all draws go to the // visible canvas. // All active culls draw their cull box fCommandVector[i]->vizExecute(canvas); } else { fCommandVector[i]->setUserMatrix(fUserMatrix); fCommandVector[i]->execute(canvas); } } } if (fMegaVizMode) { canvas->save(); // nuke the CTM canvas->resetMatrix(); // turn off clipping if (!windowRect.isEmpty()) { SkRect r = windowRect; r.outset(SK_Scalar1, SK_Scalar1); canvas->clipRect(r, SkRegion::kReplace_Op); } // visualize existing clips SkDebugClipVisitor visitor(canvas); canvas->replayClips(&visitor); canvas->restore(); } if (pathOpsMode) { this->resetClipStackData(); const SkClipStack* clipStack = canvas->getClipStack(); SkClipStack::Iter iter(*clipStack, SkClipStack::Iter::kBottom_IterStart); const SkClipStack::Element* element; SkPath devPath; while ((element = iter.next())) { SkClipStack::Element::Type type = element->getType(); SkPath operand; if (type != SkClipStack::Element::kEmpty_Type) { element->asPath(&operand); } SkRegion::Op elementOp = element->getOp(); this->addClipStackData(devPath, operand, elementOp); if (elementOp == SkRegion::kReplace_Op) { devPath = operand; } else { Op(devPath, operand, (SkPathOp) elementOp, &devPath); } } this->lastClipStackData(devPath); } fMatrix = canvas->getTotalMatrix(); if (!canvas->getClipDeviceBounds(&fClip)) { fClip.setEmpty(); } canvas->restoreToCount(saveCount); if (fPaintFilterCanvas) { fPaintFilterCanvas->removeAll(); } }
SkRect SkDisplacementMapEffect::computeFastBounds(const SkRect& src) const { SkRect bounds = this->getColorInput() ? this->getColorInput()->computeFastBounds(src) : src; bounds.outset(SkScalarAbs(fScale) * SK_ScalarHalf, SkScalarAbs(fScale) * SK_ScalarHalf); return bounds; }
SkRect SkMorphologyImageFilter::computeFastBounds(const SkRect& src) const { SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; bounds.outset(SkIntToScalar(fRadius.width()), SkIntToScalar(fRadius.height())); return bounds; }
bool GrStencilAndCoverPathRenderer::onDrawPath(const DrawPathArgs& args) { GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), "GrStencilAndCoverPathRenderer::onDrawPath"); SkASSERT(!args.fShape->style().strokeRec().isHairlineStyle()); const SkMatrix& viewMatrix = *args.fViewMatrix; sk_sp<GrPath> path(get_gr_path(fResourceProvider, *args.fShape)); if (args.fShape->inverseFilled()) { SkMatrix invert = SkMatrix::I(); SkRect bounds = SkRect::MakeLTRB(0, 0, SkIntToScalar(args.fRenderTargetContext->width()), SkIntToScalar(args.fRenderTargetContext->height())); SkMatrix vmi; // mapRect through persp matrix may not be correct if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { vmi.mapRect(&bounds); // theoretically could set bloat = 0, instead leave it because of matrix inversion // precision. SkScalar bloat = viewMatrix.getMaxScale() * SK_ScalarHalf; bounds.outset(bloat, bloat); } else { if (!viewMatrix.invert(&invert)) { return false; } } const SkMatrix& viewM = viewMatrix.hasPerspective() ? SkMatrix::I() : viewMatrix; // fake inverse with a stencil and cover args.fRenderTargetContext->priv().stencilPath(*args.fClip, args.fAAType, viewMatrix, path.get()); { static constexpr GrUserStencilSettings kInvertedCoverPass( GrUserStencilSettings::StaticInit< 0x0000, // We know our rect will hit pixels outside the clip and the user bits will // be 0 outside the clip. So we can't just fill where the user bits are 0. We // also need to check that the clip bit is set. GrUserStencilTest::kEqualIfInClip, 0xffff, GrUserStencilOp::kKeep, GrUserStencilOp::kZero, 0xffff>() ); // We have to suppress enabling MSAA for mixed samples or we will get seams due to // coverage modulation along the edge where two triangles making up the rect meet. GrAAType coverAAType = args.fAAType; if (GrAAType::kMixedSamples == coverAAType) { coverAAType = GrAAType::kNone; } args.fRenderTargetContext->addDrawOp(*args.fClip, GrRectOpFactory::MakeNonAAFillWithLocalMatrix( std::move(args.fPaint), viewM, invert, bounds, coverAAType, &kInvertedCoverPass)); } } else { std::unique_ptr<GrDrawOp> op = GrDrawPathOp::Make(viewMatrix, std::move(args.fPaint), args.fAAType, path.get()); args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); } return true; }
bool GrStencilAndCoverPathRenderer::onDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) { SkASSERT(!antiAlias); SkASSERT(!stroke.getStrokeRec().isHairlineStyle()); SkASSERT(!stroke.isDashed()); SkASSERT(pipelineBuilder->getStencil().isDisabled()); SkAutoTUnref<GrPath> p(get_gr_path(fGpu, path, stroke.getStrokeRec())); if (path.isInverseFillType()) { GR_STATIC_CONST_SAME_STENCIL(kInvertedStencilPass, kZero_StencilOp, kZero_StencilOp, // We know our rect will hit pixels outside the clip and the user bits will be 0 // outside the clip. So we can't just fill where the user bits are 0. We also need to // check that the clip bit is set. kEqualIfInClip_StencilFunc, 0xffff, 0x0000, 0xffff); pipelineBuilder->setStencil(kInvertedStencilPass); // fake inverse with a stencil and cover SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(GrColor_WHITE, viewMatrix)); target->stencilPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType())); SkMatrix invert = SkMatrix::I(); SkRect bounds = SkRect::MakeLTRB(0, 0, SkIntToScalar(pipelineBuilder->getRenderTarget()->width()), SkIntToScalar(pipelineBuilder->getRenderTarget()->height())); SkMatrix vmi; // mapRect through persp matrix may not be correct if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { vmi.mapRect(&bounds); // theoretically could set bloat = 0, instead leave it because of matrix inversion // precision. SkScalar bloat = viewMatrix.getMaxScale() * SK_ScalarHalf; bounds.outset(bloat, bloat); } else { if (!viewMatrix.invert(&invert)) { return false; } } const SkMatrix& viewM = viewMatrix.hasPerspective() ? SkMatrix::I() : viewMatrix; target->drawRect(pipelineBuilder, color, viewM, bounds, NULL, &invert); } else { GR_STATIC_CONST_SAME_STENCIL(kStencilPass, kZero_StencilOp, kZero_StencilOp, kNotEqual_StencilFunc, 0xffff, 0x0000, 0xffff); pipelineBuilder->setStencil(kStencilPass); SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(color, viewMatrix)); target->drawPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType())); } pipelineBuilder->stencil()->setDisabled(); return true; }
static void draw_oval(SkCanvas* canvas, bool showGL, int flags) { SkPaint paint; paint.setAntiAlias(true); SkRect r = SkRect::MakeLTRB(50, 70, 250, 370); setFade(&paint, showGL); canvas->drawOval(r, paint); if (showGL) { switch (flags) { case 0: { SkPath path; path.addOval(r); show_glframe(canvas, path); } break; case 1: case 3: { SkPath src, dst; src.addOval(r); tesselate(src, &dst); show_fan(canvas, dst, r.centerX(), r.centerY()); } break; case 2: { SkPaint p(paint); show_mesh(canvas, r); setGLFrame(&p); paint.setStyle(SkPaint::kFill_Style); canvas->drawCircle(r.centerX(), r.centerY(), 3, p); } break; } } canvas->translate(320, 0); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(25); canvas->drawOval(r, paint); if (showGL) { switch (flags) { case 0: { SkPath path; SkScalar rad = paint.getStrokeWidth() / 2; r.outset(rad, rad); path.addOval(r); r.inset(rad*2, rad*2); path.addOval(r); show_glframe(canvas, path); } break; case 1: { SkPath path0, path1; SkScalar rad = paint.getStrokeWidth() / 2; r.outset(rad, rad); path0.addOval(r); r.inset(rad*2, rad*2); path1.addOval(r); show_mesh_between(canvas, path0, path1); } break; case 2: { SkPath path; path.addOval(r); show_glframe(canvas, path); SkScalar rad = paint.getStrokeWidth() / 2; r.outset(rad, rad); show_mesh(canvas, r); } break; case 3: { SkScalar rad = paint.getStrokeWidth() / 2; r.outset(rad, rad); SkPaint paint; paint.setAlpha(0x33); canvas->drawRect(r, paint); show_mesh(canvas, r); } break; } } }
SkRect SkBlurImageFilter::computeFastBounds(const SkRect& src) const { SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src; bounds.outset(SkScalarMul(fSigma.width(), SkIntToScalar(3)), SkScalarMul(fSigma.height(), SkIntToScalar(3))); return bounds; }
bool GrDrawTarget::setupDstReadIfNecessary(const GrPipelineBuilder& pipelineBuilder, const GrPipelineOptimizations& optimizations, GrXferProcessor::DstTexture* dstTexture, const SkRect& batchBounds) { SkRect bounds = batchBounds; bounds.outset(0.5f, 0.5f); if (!pipelineBuilder.willXPNeedDstTexture(*this->caps(), optimizations)) { return true; } GrRenderTarget* rt = pipelineBuilder.getRenderTarget(); if (this->caps()->textureBarrierSupport()) { if (GrTexture* rtTex = rt->asTexture()) { // The render target is a texture, so we can read from it directly in the shader. The XP // will be responsible to detect this situation and request a texture barrier. dstTexture->setTexture(rtTex); dstTexture->setOffset(0, 0); return true; } } SkIRect copyRect; pipelineBuilder.clip().getConservativeBounds(rt->width(), rt->height(), ©Rect); SkIRect drawIBounds; bounds.roundOut(&drawIBounds); if (!copyRect.intersect(drawIBounds)) { #ifdef SK_DEBUG GrCapsDebugf(this->caps(), "Missed an early reject. " "Bailing on draw from setupDstReadIfNecessary.\n"); #endif return false; } // MSAA consideration: When there is support for reading MSAA samples in the shader we could // have per-sample dst values by making the copy multisampled. GrSurfaceDesc desc; if (!fGpu->initCopySurfaceDstDesc(rt, &desc)) { desc.fOrigin = kDefault_GrSurfaceOrigin; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fConfig = rt->config(); } desc.fWidth = copyRect.width(); desc.fHeight = copyRect.height(); static const uint32_t kFlags = 0; SkAutoTUnref<GrTexture> copy(fResourceProvider->createApproxTexture(desc, kFlags)); if (!copy) { SkDebugf("Failed to create temporary copy of destination texture.\n"); return false; } SkIPoint dstPoint = {0, 0}; this->copySurface(copy, rt, copyRect, dstPoint); dstTexture->setTexture(copy); dstTexture->setOffset(copyRect.fLeft, copyRect.fTop); return true; }