bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint, const SkMatrix& viewMatrix) { // TODO: support perspective (need getMaxScale replacement) if (viewMatrix.hasPerspective()) { return false; } SkScalar maxScale = viewMatrix.getMaxScale(); SkScalar scaledTextSize = maxScale*paint.getTextSize(); // Scaling up beyond 2x yields undesireable artifacts if (scaledTextSize > 2*kLargeDFFontSize) { return false; } if (!fEnableDFRendering && !paint.isDistanceFieldTextTEMP() && scaledTextSize < kLargeDFFontSize) { return false; } // rasterizers and mask filters modify alpha, which doesn't // translate well to distance if (paint.getRasterizer() || paint.getMaskFilter() || !fContext->getTextTarget()->caps()->shaderDerivativeSupport()) { return false; } // TODO: add some stroking support if (paint.getStyle() != SkPaint::kFill_Style) { return false; } return true; }
bool GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder* pipelineBuilder, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { // TODO: Support inverse fill // TODO: Support strokes if (!target->caps()->shaderCaps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType() || path.isVolatile() || !stroke.isFillStyle()) { return false; } // currently don't support perspective if (viewMatrix.hasPerspective()) { return false; } // only support paths smaller than 64x64, scaled to less than 256x256 // the goal is to accelerate rendering of lots of small paths that may be scaling SkScalar maxScale = viewMatrix.getMaxScale(); const SkRect& bounds = path.getBounds(); SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height()); return maxDim < 64.f && maxDim * maxScale < 256.f; }
bool GrStencilAndCoverTextContext::canDraw(const GrRenderTarget* rt, const GrClip& clip, const GrPaint& paint, const SkPaint& skPaint, const SkMatrix& viewMatrix) { if (skPaint.getRasterizer()) { return false; } if (skPaint.getMaskFilter()) { return false; } if (SkPathEffect* pe = skPaint.getPathEffect()) { if (pe->asADash(NULL) != SkPathEffect::kDash_DashType) { return false; } } // No hairlines unless we can map the 1 px width to the object space. if (skPaint.getStyle() == SkPaint::kStroke_Style && skPaint.getStrokeWidth() == 0 && viewMatrix.hasPerspective()) { return false; } // No color bitmap fonts. SkScalerContext::Rec rec; SkScalerContext::MakeRec(skPaint, &fDeviceProperties, NULL, &rec); return rec.getFormat() != SkMask::kARGB32_Format; }
static void tesselate(intptr_t vertices, size_t vertexStride, GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, const GrQuad* localQuad) { SkPoint* positions = reinterpret_cast<SkPoint*>(vertices); positions->setRectFan(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, vertexStride); if (!viewMatrix.hasPerspective()) { viewMatrix.mapPointsWithStride(positions, vertexStride, NonAAFillRectBatchBase::kVertsPerInstance); } // Setup local coords // TODO we should only do this if local coords are being read if (localQuad) { static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor); for (int i = 0; i < NonAAFillRectBatchBase::kVertsPerInstance; i++) { SkPoint* coords = reinterpret_cast<SkPoint*>(vertices + kLocalOffset + i * vertexStride); *coords = localQuad->point(i); } } static const int kColorOffset = sizeof(SkPoint); GrColor* vertColor = reinterpret_cast<GrColor*>(vertices + kColorOffset); for (int j = 0; j < 4; ++j) { *vertColor = color; vertColor = (GrColor*) ((intptr_t) vertColor + vertexStride); } }
bool Append(GrBatch* origBatch, GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, const SkRect* localRect, const SkMatrix* localMatrix) { bool usePerspective = viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective()); if (usePerspective && origBatch->classID() != NonAAFillRectBatchPerspective::ClassID()) { return false; } if (!usePerspective) { NonAAFillRectBatchSimple* batch = origBatch->cast<NonAAFillRectBatchSimple>(); append_to_batch(batch, color, viewMatrix, rect, localRect, localMatrix); batch->updateBoundsAfterAppend(); } else { NonAAFillRectBatchPerspective* batch = origBatch->cast<NonAAFillRectBatchPerspective>(); const NonAAFillRectBatchPerspective::Geometry& geo = batch->geoData()->back(); if (!geo.fViewMatrix.cheapEqualTo(viewMatrix) || geo.fHasLocalRect != SkToBool(localRect) || geo.fHasLocalMatrix != SkToBool(localMatrix) || (geo.fHasLocalMatrix && !geo.fLocalMatrix.cheapEqualTo(*localMatrix))) { return false; } append_to_batch(batch, color, viewMatrix, rect, localRect, localMatrix); batch->updateBoundsAfterAppend(); } return true; }
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; }
GrFillRRectOp::GrFillRRectOp( GrAAType aaType, const SkRRect& rrect, Flags flags, const SkMatrix& totalShapeMatrix, GrPaint&& paint, const SkRect& devBounds) : GrDrawOp(ClassID()) , fAAType(aaType) , fOriginalColor(paint.getColor4f()) , fLocalRect(rrect.rect()) , fFlags(flags) , fProcessors(std::move(paint)) { SkASSERT((fFlags & Flags::kHasPerspective) == totalShapeMatrix.hasPerspective()); this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo); // Write the matrix attribs. const SkMatrix& m = totalShapeMatrix; if (!(fFlags & Flags::kHasPerspective)) { // Affine 2D transformation (float2x2 plus float2 translate). SkASSERT(!m.hasPerspective()); this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()); this->writeInstanceData(m.getTranslateX(), m.getTranslateY()); } else { // Perspective float3x3 transformation matrix. SkASSERT(m.hasPerspective()); m.get9(this->appendInstanceData<float>(9)); } // Convert the radii to [-1, -1, +1, +1] space and write their attribs. Sk4f radiiX, radiiY; Sk4f::Load2(SkRRectPriv::GetRadiiArray(rrect), &radiiX, &radiiY); (radiiX * (2/rrect.width())).store(this->appendInstanceData<float>(4)); (radiiY * (2/rrect.height())).store(this->appendInstanceData<float>(4)); // We will write the color and local rect attribs during finalize(). }
bool GrAAHairLinePathRenderer::createGeom( const SkPath& path, GrDrawTarget* target, int* lineCnt, int* quadCnt, GrDrawTarget::AutoReleaseGeometry* arg) { const GrDrawState& drawState = target->getDrawState(); int rtHeight = drawState.getRenderTarget()->height(); GrIRect devClipBounds; target->getClip()->getConservativeBounds(drawState.getRenderTarget(), &devClipBounds); GrVertexLayout layout = GrDrawState::kEdge_VertexLayoutBit; SkMatrix viewM = drawState.getViewMatrix(); PREALLOC_PTARRAY(128) lines; PREALLOC_PTARRAY(128) quads; IntArray qSubdivs; *quadCnt = generate_lines_and_quads(path, viewM, devClipBounds, &lines, &quads, &qSubdivs); *lineCnt = lines.count() / 2; int vertCnt = kVertsPerLineSeg * *lineCnt + kVertsPerQuad * *quadCnt; GrAssert(sizeof(Vertex) == GrDrawState::VertexSize(layout)); if (!arg->set(target, layout, vertCnt, 0)) { return false; } Vertex* verts = reinterpret_cast<Vertex*>(arg->vertices()); const SkMatrix* toDevice = NULL; const SkMatrix* toSrc = NULL; SkMatrix ivm; if (viewM.hasPerspective()) { if (viewM.invert(&ivm)) { toDevice = &viewM; toSrc = &ivm; } } for (int i = 0; i < *lineCnt; ++i) { add_line(&lines[2*i], rtHeight, toSrc, &verts); } int unsubdivQuadCnt = quads.count() / 3; for (int i = 0; i < unsubdivQuadCnt; ++i) { GrAssert(qSubdivs[i] >= 0); add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts); } return true; }
SkAxisAlignment SkComputeAxisAlignmentForHText(const SkMatrix& matrix) { SkASSERT(!matrix.hasPerspective()); if (0 == matrix[SkMatrix::kMSkewY]) { return kX_SkAxisAlignment; } if (0 == matrix[SkMatrix::kMScaleX]) { return kY_SkAxisAlignment; } return kNone_SkAxisAlignment; }
SkShader::Context::MatrixClass SkShader::Context::ComputeMatrixClass(const SkMatrix& mat) { MatrixClass mc = kLinear_MatrixClass; if (mat.hasPerspective()) { if (mat.isFixedStepInX()) { mc = kFixedStepInX_MatrixClass; } else { mc = kPerspective_MatrixClass; } } return mc; }
void GrDrawContext::drawPaint(GrRenderTarget* rt, const GrClip& clip, const GrPaint& origPaint, const SkMatrix& viewMatrix) { RETURN_IF_ABANDONED // set rect to be big enough to fill the space, but not super-huge, so we // don't overflow fixed-point implementations SkRect r; r.setLTRB(0, 0, SkIntToScalar(rt->width()), SkIntToScalar(rt->height())); SkTCopyOnFirstWrite<GrPaint> paint(origPaint); // by definition this fills the entire clip, no need for AA if (paint->isAntiAlias()) { paint.writable()->setAntiAlias(false); } bool isPerspective = viewMatrix.hasPerspective(); // We attempt to map r by the inverse matrix and draw that. mapRect will // map the four corners and bound them with a new rect. This will not // produce a correct result for some perspective matrices. if (!isPerspective) { SkMatrix inverse; if (!viewMatrix.invert(&inverse)) { SkDebugf("Could not invert matrix\n"); return; } inverse.mapRect(&r); this->drawRect(rt, clip, *paint, viewMatrix, r); } else { SkMatrix localMatrix; if (!viewMatrix.invert(&localMatrix)) { SkDebugf("Could not invert matrix\n"); return; } AutoCheckFlush acf(fContext); if (!this->prepareToDraw(rt)) { return; } GrPipelineBuilder pipelineBuilder(*paint, rt, clip); fDrawTarget->drawBWRect(pipelineBuilder, paint->getColor(), SkMatrix::I(), r, NULL, &localMatrix); } }
void GrTextUtils::InitDistanceFieldPaint(GrAtlasTextBlob* blob, SkPaint* skPaint, SkScalar* textRatio, const SkMatrix& viewMatrix) { // getMaxScale doesn't support perspective, so neither do we at the moment SkASSERT(!viewMatrix.hasPerspective()); SkScalar maxScale = viewMatrix.getMaxScale(); SkScalar textSize = skPaint->getTextSize(); SkScalar scaledTextSize = textSize; // if we have non-unity scale, we need to choose our base text size // based on the SkPaint's text size multiplied by the max scale factor // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { scaledTextSize *= maxScale; } // We have three sizes of distance field text, and within each size 'bucket' there is a floor // and ceiling. A scale outside of this range would require regenerating the distance fields SkScalar dfMaskScaleFloor; SkScalar dfMaskScaleCeil; if (scaledTextSize <= kSmallDFFontLimit) { dfMaskScaleFloor = kMinDFFontSize; dfMaskScaleCeil = kSmallDFFontLimit; *textRatio = textSize / kSmallDFFontSize; skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize)); } else if (scaledTextSize <= kMediumDFFontLimit) { dfMaskScaleFloor = kSmallDFFontLimit; dfMaskScaleCeil = kMediumDFFontLimit; *textRatio = textSize / kMediumDFFontSize; skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize)); } else { dfMaskScaleFloor = kMediumDFFontLimit; dfMaskScaleCeil = kLargeDFFontLimit; *textRatio = textSize / kLargeDFFontSize; skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize)); } // Because there can be multiple runs in the blob, we want the overall maxMinScale, and // minMaxScale to make regeneration decisions. Specifically, we want the maximum minimum scale // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can // tolerate before we'd have to move to a large mip size. When we actually test these values // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test // against these values to decide if we can reuse or not(ie, will a given scale change our mip // level) SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil); blob->setMinAndMaxScale(dfMaskScaleFloor / scaledTextSize, dfMaskScaleCeil / scaledTextSize); skPaint->setLCDRenderText(false); skPaint->setAutohinted(false); skPaint->setHinting(SkPaint::kNormal_Hinting); skPaint->setSubpixelText(true); }
static inline bool get_direction(const SkPath& path, const SkMatrix& m, SkPath::Direction* dir) { if (!path.cheapComputeDirection(dir)) { return false; } // check whether m reverses the orientation SkASSERT(!m.hasPerspective()); SkScalar det2x2 = SkScalarMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMScaleY)) - SkScalarMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSkewY)); if (det2x2 < 0) { *dir = SkPath::OppositeDirection(*dir); } return true; }
GrCCPathCache::MaskTransform::MaskTransform(const SkMatrix& m, SkIVector* shift) : fMatrix2x2{m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()} { SkASSERT(!m.hasPerspective()); Sk2f translate = Sk2f(m.getTranslateX(), m.getTranslateY()); Sk2f transFloor; #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK // On Android framework we pre-round view matrix translates to integers for better caching. transFloor = translate; #else transFloor = translate.floor(); (translate - transFloor).store(fSubpixelTranslate); #endif shift->set((int)transFloor[0], (int)transFloor[1]); SkASSERT((float)shift->fX == transFloor[0]); // Make sure transFloor had integer values. SkASSERT((float)shift->fY == transFloor[1]); }
SkScalar scaled_text_size(const SkScalar textSize, const SkMatrix& viewMatrix) { SkScalar scaledTextSize = textSize; if (viewMatrix.hasPerspective()) { // for perspective, we simply force to the medium size // TODO: compute a size based on approximate screen area scaledTextSize = kMediumDFFontLimit; } else { SkScalar maxScale = viewMatrix.getMaxScale(); // if we have non-unity scale, we need to choose our base text size // based on the SkPaint's text size multiplied by the max scale factor // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { scaledTextSize *= maxScale; } } return scaledTextSize; }
inline static void append_to_batch(NonAAFillRectBatchPerspective* batch, GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, const SkRect* localRect, const SkMatrix* localMatrix) { SkASSERT(viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective())); NonAAFillRectBatchPerspective::Geometry& geo = batch->geoData()->push_back(); geo.fColor = color; geo.fViewMatrix = viewMatrix; geo.fRect = rect; geo.fHasLocalRect = SkToBool(localRect); geo.fHasLocalMatrix = SkToBool(localMatrix); if (localMatrix) { geo.fLocalMatrix = *localMatrix; } if (localRect) { geo.fLocalRect = *localRect; } }
inline static void append_to_batch(NonAAFillRectBatchSimple* batch, GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, const SkRect* localRect, const SkMatrix* localMatrix) { SkASSERT(!viewMatrix.hasPerspective() && (!localMatrix || !localMatrix->hasPerspective())); NonAAFillRectBatchSimple::Geometry& geo = batch->geoData()->push_back(); geo.fColor = color; geo.fViewMatrix = viewMatrix; geo.fRect = rect; if (localRect && localMatrix) { geo.fLocalQuad.setFromMappedRect(*localRect, *localMatrix); } else if (localRect) { geo.fLocalQuad.set(*localRect); } else if (localMatrix) { geo.fLocalQuad.setFromMappedRect(rect, *localMatrix); } else { geo.fLocalQuad.set(rect); } }
bool GrTextUtils::CanDrawAsDistanceFields(const SkPaint& skPaint, const SkMatrix& viewMatrix, const SkSurfaceProps& props, const GrShaderCaps& caps) { // TODO: support perspective (need getMaxScale replacement) if (viewMatrix.hasPerspective()) { return false; } SkScalar maxScale = viewMatrix.getMaxScale(); SkScalar scaledTextSize = maxScale*skPaint.getTextSize(); // Hinted text looks far better at small resolutions // Scaling up beyond 2x yields undesireable artifacts if (scaledTextSize < kMinDFFontSize || scaledTextSize > kLargeDFFontLimit) { return false; } bool useDFT = props.isUseDeviceIndependentFonts(); #if SK_FORCE_DISTANCE_FIELD_TEXT useDFT = true; #endif if (!useDFT && scaledTextSize < kLargeDFFontSize) { return false; } // rasterizers and mask filters modify alpha, which doesn't // translate well to distance if (skPaint.getRasterizer() || skPaint.getMaskFilter() || !caps.shaderDerivativeSupport()) { return false; } // TODO: add some stroking support if (skPaint.getStyle() != SkPaint::kFill_Style) { return false; } return true; }
/** * Returns PS function code that applies inverse perspective * to a x, y point. * The function assumes that the stack has at least two elements, * and that the top 2 elements are numeric values. * After executing this code on a PS stack, the last 2 elements are updated * while the rest of the stack is preserved intact. * inversePerspectiveMatrix is the inverse perspective matrix. */ static SkString apply_perspective_to_coordinates( const SkMatrix& inversePerspectiveMatrix) { SkString code; if (!inversePerspectiveMatrix.hasPerspective()) { return code; } // Perspective matrix should be: // 1 0 0 // 0 1 0 // p0 p1 p2 const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0]; const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1]; const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2]; // y = y / (p2 + p0 x + p1 y) // x = x / (p2 + p0 x + p1 y) // Input on stack: x y code.append(" dup "); // x y y code.appendScalar(p1); // x y y p1 code.append(" mul " // x y y*p1 " 2 index "); // x y y*p1 x code.appendScalar(p0); // x y y p1 x p0 code.append(" mul "); // x y y*p1 x*p0 code.appendScalar(p2); // x y y p1 x*p0 p2 code.append(" add " // x y y*p1 x*p0+p2 "add " // x y y*p1+x*p0+p2 "3 1 roll " // y*p1+x*p0+p2 x y "2 index " // z x y y*p1+x*p0+p2 "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2) "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2 "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2) "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2) return code; }
void GrGLGeometryProcessor::setupPosition(GrGLGPBuilder* pb, GrGPArgs* gpArgs, const char* posName, const SkMatrix& mat) { GrGLVertexBuilder* vsBuilder = pb->getVertexShaderBuilder(); if (mat.isIdentity()) { gpArgs->fPositionVar.set(kVec2f_GrSLType, "pos2"); vsBuilder->codeAppendf("vec2 %s = %s;", gpArgs->fPositionVar.c_str(), posName); } else if (!mat.hasPerspective()) { this->addUniformViewMatrix(pb); gpArgs->fPositionVar.set(kVec2f_GrSLType, "pos2"); vsBuilder->codeAppendf("vec2 %s = vec2(%s * vec3(%s, 1));", gpArgs->fPositionVar.c_str(), this->uViewM(), posName); } else { this->addUniformViewMatrix(pb); gpArgs->fPositionVar.set(kVec3f_GrSLType, "pos3"); vsBuilder->codeAppendf("vec3 %s = %s * vec3(%s, 1);", gpArgs->fPositionVar.c_str(), this->uViewM(), posName); } }
bool GrTextContext::CanDrawAsDistanceFields(const SkPaint& paint, const SkFont& font, const SkMatrix& viewMatrix, const SkSurfaceProps& props, bool contextSupportsDistanceFieldText, const Options& options) { if (!viewMatrix.hasPerspective()) { SkScalar maxScale = viewMatrix.getMaxScale(); SkScalar scaledTextSize = maxScale * font.getSize(); // Hinted text looks far better at small resolutions // Scaling up beyond 2x yields undesireable artifacts if (scaledTextSize < options.fMinDistanceFieldFontSize || scaledTextSize > options.fMaxDistanceFieldFontSize) { return false; } bool useDFT = props.isUseDeviceIndependentFonts(); #if SK_FORCE_DISTANCE_FIELD_TEXT useDFT = true; #endif if (!useDFT && scaledTextSize < kLargeDFFontSize) { return false; } } // mask filters modify alpha, which doesn't translate well to distance if (paint.getMaskFilter() || !contextSupportsDistanceFieldText) { return false; } // TODO: add some stroking support if (paint.getStyle() != SkPaint::kFill_Style) { return false; } return true; }
/** * Returns PS function code that applies inverse perspective * to a x, y point. * The function assumes that the stack has at least two elements, * and that the top 2 elements are numeric values. * After executing this code on a PS stack, the last 2 elements are updated * while the rest of the stack is preserved intact. * inversePerspectiveMatrix is the inverse perspective matrix. */ static void apply_perspective_to_coordinates(const SkMatrix& inversePerspectiveMatrix, SkDynamicMemoryWStream* code) { if (!inversePerspectiveMatrix.hasPerspective()) { return; } // Perspective matrix should be: // 1 0 0 // 0 1 0 // p0 p1 p2 const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0]; const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1]; const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2]; // y = y / (p2 + p0 x + p1 y) // x = x / (p2 + p0 x + p1 y) // Input on stack: x y code->writeText(" dup "); // x y y SkPDFUtils::AppendScalar(p1, code); // x y y p1 code->writeText(" mul " // x y y*p1 " 2 index "); // x y y*p1 x SkPDFUtils::AppendScalar(p0, code); // x y y p1 x p0 code->writeText(" mul "); // x y y*p1 x*p0 SkPDFUtils::AppendScalar(p2, code); // x y y p1 x*p0 p2 code->writeText(" add " // x y y*p1 x*p0+p2 "add " // x y y*p1+x*p0+p2 "3 1 roll " // y*p1+x*p0+p2 x y "2 index " // z x y y*p1+x*p0+p2 "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2) "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2 "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2) "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2) }
SkPDFFunctionShader* SkPDFFunctionShader::Create( SkPDFCanon* canon, SkAutoTDelete<SkPDFShader::State>* autoState) { const SkPDFShader::State& state = **autoState; SkString (*codeFunction)(const SkShader::GradientInfo& info, const SkMatrix& perspectiveRemover) = NULL; SkPoint transformPoints[2]; // Depending on the type of the gradient, we want to transform the // coordinate space in different ways. const SkShader::GradientInfo* info = &state.fInfo; transformPoints[0] = info->fPoint[0]; transformPoints[1] = info->fPoint[1]; switch (state.fType) { case SkShader::kLinear_GradientType: codeFunction = &linearCode; break; case SkShader::kRadial_GradientType: transformPoints[1] = transformPoints[0]; transformPoints[1].fX += info->fRadius[0]; codeFunction = &radialCode; break; case SkShader::kRadial2_GradientType: { // Bail out if the radii are the same. if (info->fRadius[0] == info->fRadius[1]) { return NULL; } transformPoints[1] = transformPoints[0]; SkScalar dr = info->fRadius[1] - info->fRadius[0]; transformPoints[1].fX += dr; codeFunction = &twoPointRadialCode; break; } case SkShader::kConical_GradientType: { transformPoints[1] = transformPoints[0]; transformPoints[1].fX += SK_Scalar1; codeFunction = &twoPointConicalCode; break; } case SkShader::kSweep_GradientType: transformPoints[1] = transformPoints[0]; transformPoints[1].fX += SK_Scalar1; codeFunction = &sweepCode; break; case SkShader::kColor_GradientType: case SkShader::kNone_GradientType: default: return NULL; } // Move any scaling (assuming a unit gradient) or translation // (and rotation for linear gradient), of the final gradient from // info->fPoints to the matrix (updating bbox appropriately). Now // the gradient can be drawn on on the unit segment. SkMatrix mapperMatrix; unitToPointsMatrix(transformPoints, &mapperMatrix); SkMatrix finalMatrix = state.fCanvasTransform; finalMatrix.preConcat(state.fShaderTransform); finalMatrix.preConcat(mapperMatrix); // Preserves as much as posible in the final matrix, and only removes // the perspective. The inverse of the perspective is stored in // perspectiveInverseOnly matrix and has 3 useful numbers // (p0, p1, p2), while everything else is either 0 or 1. // In this way the shader will handle it eficiently, with minimal code. SkMatrix perspectiveInverseOnly = SkMatrix::I(); if (finalMatrix.hasPerspective()) { if (!split_perspective(finalMatrix, &finalMatrix, &perspectiveInverseOnly)) { return NULL; } } SkRect bbox; bbox.set(state.fBBox); if (!inverse_transform_bbox(finalMatrix, &bbox)) { return NULL; } SkAutoTUnref<SkPDFArray> domain(new SkPDFArray); domain->reserve(4); domain->appendScalar(bbox.fLeft); domain->appendScalar(bbox.fRight); domain->appendScalar(bbox.fTop); domain->appendScalar(bbox.fBottom); SkString functionCode; // The two point radial gradient further references // state.fInfo // in translating from x, y coordinates to the t parameter. So, we have // to transform the points and radii according to the calculated matrix. if (state.fType == SkShader::kRadial2_GradientType) { SkShader::GradientInfo twoPointRadialInfo = *info; SkMatrix inverseMapperMatrix; if (!mapperMatrix.invert(&inverseMapperMatrix)) { return NULL; } inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2); twoPointRadialInfo.fRadius[0] = inverseMapperMatrix.mapRadius(info->fRadius[0]); twoPointRadialInfo.fRadius[1] = inverseMapperMatrix.mapRadius(info->fRadius[1]); functionCode = codeFunction(twoPointRadialInfo, perspectiveInverseOnly); } else { functionCode = codeFunction(*info, perspectiveInverseOnly); } SkAutoTUnref<SkPDFDict> pdfShader(new SkPDFDict); pdfShader->insertInt("ShadingType", 1); pdfShader->insertName("ColorSpace", "DeviceRGB"); pdfShader->insert("Domain", domain.get()); SkAutoTUnref<SkPDFStream> function( make_ps_function(functionCode, domain.get())); pdfShader->insert("Function", new SkPDFObjRef(function))->unref(); SkAutoTUnref<SkPDFArray> matrixArray( SkPDFUtils::MatrixToArray(finalMatrix)); SkPDFFunctionShader* pdfFunctionShader = SkNEW_ARGS(SkPDFFunctionShader, (autoState->detach())); pdfFunctionShader->insertInt("PatternType", 2); pdfFunctionShader->insert("Matrix", matrixArray.get()); pdfFunctionShader->insert("Shading", pdfShader.get()); canon->addFunctionShader(pdfFunctionShader); return pdfFunctionShader; }
bool GrAtlasTextBlob::mustRegenerate(const SkPaint& paint, GrColor color, const SkMaskFilter::BlurRec& blurRec, const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { // If we have LCD text then our canonical color will be set to transparent, in this case we have // to regenerate the blob on any color change // We use the grPaint to get any color filter effects if (fKey.fCanonicalColor == SK_ColorTRANSPARENT && fPaintColor != color) { return true; } if (fInitialViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) { return true; } if (fInitialViewMatrix.hasPerspective() && !fInitialViewMatrix.cheapEqualTo(viewMatrix)) { return true; } // We only cache one masked version if (fKey.fHasBlur && (fBlurRec.fSigma != blurRec.fSigma || fBlurRec.fStyle != blurRec.fStyle || fBlurRec.fQuality != blurRec.fQuality)) { return true; } // Similarly, we only cache one version for each style if (fKey.fStyle != SkPaint::kFill_Style && (fStrokeInfo.fFrameWidth != paint.getStrokeWidth() || fStrokeInfo.fMiterLimit != paint.getStrokeMiter() || fStrokeInfo.fJoin != paint.getStrokeJoin())) { return true; } // Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls // for mixed blobs if this becomes an issue. if (this->hasBitmap() && this->hasDistanceField()) { // Identical viewmatrices and we can reuse in all cases if (fInitialViewMatrix.cheapEqualTo(viewMatrix) && x == fInitialX && y == fInitialY) { return false; } return true; } if (this->hasBitmap()) { if (fInitialViewMatrix.getScaleX() != viewMatrix.getScaleX() || fInitialViewMatrix.getScaleY() != viewMatrix.getScaleY() || fInitialViewMatrix.getSkewX() != viewMatrix.getSkewX() || fInitialViewMatrix.getSkewY() != viewMatrix.getSkewY()) { return true; } // We can update the positions in the cachedtextblobs without regenerating the whole blob, // but only for integer translations. // This cool bit of math will determine the necessary translation to apply to the already // generated vertex coordinates to move them to the correct position SkScalar transX = viewMatrix.getTranslateX() + viewMatrix.getScaleX() * (x - fInitialX) + viewMatrix.getSkewX() * (y - fInitialY) - fInitialViewMatrix.getTranslateX(); SkScalar transY = viewMatrix.getTranslateY() + viewMatrix.getSkewY() * (x - fInitialX) + viewMatrix.getScaleY() * (y - fInitialY) - fInitialViewMatrix.getTranslateY(); if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY)) { return true; } } else if (this->hasDistanceField()) { // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different // distance field being generated, so we have to regenerate in those cases SkScalar newMaxScale = viewMatrix.getMaxScale(); SkScalar oldMaxScale = fInitialViewMatrix.getMaxScale(); SkScalar scaleAdjust = newMaxScale / oldMaxScale; if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) { return true; } } // It is possible that a blob has neither distanceField nor bitmaptext. This is in the case // when all of the runs inside the blob are drawn as paths. In this case, we always regenerate // the blob anyways at flush time, so no need to regenerate explicitly return false; }
/** * 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; } } }
static void test_matrix_min_max_scale(skiatest::Reporter* reporter) { SkScalar scales[2]; bool success; SkMatrix identity; identity.reset(); REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxScale()); success = identity.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success && SK_Scalar1 == scales[0] && SK_Scalar1 == scales[1]); SkMatrix scale; scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4); REPORTER_ASSERT(reporter, SK_Scalar1 * 2 == scale.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxScale()); success = scale.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success && SK_Scalar1 * 2 == scales[0] && SK_Scalar1 * 4 == scales[1]); SkMatrix rot90Scale; rot90Scale.setRotate(90 * SK_Scalar1); rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2); REPORTER_ASSERT(reporter, SK_Scalar1 / 4 == rot90Scale.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxScale()); success = rot90Scale.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success && SK_Scalar1 / 4 == scales[0] && SK_Scalar1 / 2 == scales[1]); SkMatrix rotate; rotate.setRotate(128 * SK_Scalar1); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMinScale(), SK_ScalarNearlyZero)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMaxScale(), SK_ScalarNearlyZero)); success = rotate.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, scales[0], SK_ScalarNearlyZero)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, scales[1], SK_ScalarNearlyZero)); SkMatrix translate; translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1); REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxScale()); success = translate.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success && SK_Scalar1 == scales[0] && SK_Scalar1 == scales[1]); SkMatrix perspX; perspX.reset(); perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000)); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMinScale()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxScale()); // Verify that getMinMaxScales() doesn't update the scales array on failure. scales[0] = -5; scales[1] = -5; success = perspX.getMinMaxScales(scales); REPORTER_ASSERT(reporter, !success && -5 * SK_Scalar1 == scales[0] && -5 * SK_Scalar1 == scales[1]); SkMatrix perspY; perspY.reset(); perspY.setPerspY(SkScalarToPersp(-SK_Scalar1 / 500)); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMinScale()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxScale()); scales[0] = -5; scales[1] = -5; success = perspY.getMinMaxScales(scales); REPORTER_ASSERT(reporter, !success && -5 * SK_Scalar1 == scales[0] && -5 * SK_Scalar1 == scales[1]); SkMatrix baseMats[] = {scale, rot90Scale, rotate, translate, perspX, perspY}; SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)]; for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) { mats[i] = baseMats[i]; bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]); REPORTER_ASSERT(reporter, invertable); } SkRandom rand; for (int m = 0; m < 1000; ++m) { SkMatrix mat; mat.reset(); for (int i = 0; i < 4; ++i) { int x = rand.nextU() % SK_ARRAY_COUNT(mats); mat.postConcat(mats[x]); } SkScalar minScale = mat.getMinScale(); SkScalar maxScale = mat.getMaxScale(); REPORTER_ASSERT(reporter, (minScale < 0) == (maxScale < 0)); REPORTER_ASSERT(reporter, (maxScale < 0) == mat.hasPerspective()); SkScalar scales[2]; bool success = mat.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success == !mat.hasPerspective()); REPORTER_ASSERT(reporter, !success || (scales[0] == minScale && scales[1] == maxScale)); if (mat.hasPerspective()) { m -= 1; // try another non-persp matrix continue; } // test a bunch of vectors. All should be scaled by between minScale and maxScale // (modulo some error) and we should find a vector that is scaled by almost each. static const SkScalar gVectorScaleTol = (105 * SK_Scalar1) / 100; static const SkScalar gCloseScaleTol = (97 * SK_Scalar1) / 100; SkScalar max = 0, min = SK_ScalarMax; SkVector vectors[1000]; for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) { vectors[i].fX = rand.nextSScalar1(); vectors[i].fY = rand.nextSScalar1(); if (!vectors[i].normalize()) { i -= 1; continue; } } mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors)); for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) { SkScalar d = vectors[i].length(); REPORTER_ASSERT(reporter, SkScalarDiv(d, maxScale) < gVectorScaleTol); REPORTER_ASSERT(reporter, SkScalarDiv(minScale, d) < gVectorScaleTol); if (max < d) { max = d; } if (min > d) { min = d; } } REPORTER_ASSERT(reporter, SkScalarDiv(max, maxScale) >= gCloseScaleTol); REPORTER_ASSERT(reporter, SkScalarDiv(minScale, min) >= gCloseScaleTol); } }
static void test_matrix_max_stretch(skiatest::Reporter* reporter) { SkMatrix identity; identity.reset(); REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch()); SkMatrix scale; scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4); REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch()); SkMatrix rot90Scale; rot90Scale.setRotate(90 * SK_Scalar1); rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2); REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch()); SkMatrix rotate; rotate.setRotate(128 * SK_Scalar1); REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero); SkMatrix translate; translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1); REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch()); SkMatrix perspX; perspX.reset(); perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000)); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch()); SkMatrix perspY; perspY.reset(); perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500)); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch()); SkMatrix baseMats[] = {scale, rot90Scale, rotate, translate, perspX, perspY}; SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)]; for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) { mats[i] = baseMats[i]; bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]); REPORTER_ASSERT(reporter, invertable); } SkMWCRandom rand; for (int m = 0; m < 1000; ++m) { SkMatrix mat; mat.reset(); for (int i = 0; i < 4; ++i) { int x = rand.nextU() % SK_ARRAY_COUNT(mats); mat.postConcat(mats[x]); } SkScalar stretch = mat.getMaxStretch(); if ((stretch < 0) != mat.hasPerspective()) { stretch = mat.getMaxStretch(); } REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective()); if (mat.hasPerspective()) { m -= 1; // try another non-persp matrix continue; } // test a bunch of vectors. None should be scaled by more than stretch // (modulo some error) and we should find a vector that is scaled by // almost stretch. static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100; static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100; SkScalar max = 0; SkVector vectors[1000]; for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) { vectors[i].fX = rand.nextSScalar1(); vectors[i].fY = rand.nextSScalar1(); if (!vectors[i].normalize()) { i -= 1; continue; } } mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors)); for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) { SkScalar d = vectors[i].length(); REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol); if (max < d) { max = d; } } REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol); } }
/** * 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, SkScalar capLength, bool convertConicsToQuads, 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(); // Whenever a degenerate, zero-length contour is encountered, this code will insert a // 'capLength' x-aligned line segment. Since this is rendering hairlines it is hoped this will // suffice for AA square & circle capping. int verbsInContour = 0; // Does not count moves bool seenZeroLengthVerb = false; SkPoint zeroVerbPt; // Adds a quad that has already been chopped to the list and checks for quads that are close to // lines. Also does a bounding box check. It takes points that are in src space and device // space. The src points are only required if the view matrix has perspective. auto addChoppedQuad = [&](const SkPoint srcPts[3], const SkPoint devPts[4], bool isContourStart) { SkRect bounds; SkIRect ibounds; bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); // We only need the src space space pts when not in perspective. SkASSERT(srcPts || !persp); 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]; if (isContourStart && pts[0] == pts[1] && pts[2] == pts[3]) { seenZeroLengthVerb = true; zeroVerbPt = pts[0]; } } else { // when in perspective keep quads in src space const SkPoint* qPts = persp ? srcPts : 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; } } }; // Applies the view matrix to quad src points and calls the above helper. auto addSrcChoppedQuad = [&](const SkPoint srcSpaceQuadPts[3], bool isContourStart) { SkPoint devPts[3]; m.mapPoints(devPts, srcSpaceQuadPts, 3); addChoppedQuad(srcSpaceQuadPts, devPts, isContourStart); }; for (;;) { SkPoint pathPts[4]; SkPath::Verb verb = iter.next(pathPts, false); switch (verb) { case SkPath::kConic_Verb: if (convertConicsToQuads) { SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; const SkPoint* quadPts = converter.computeQuads(pathPts, weight, 0.25f); for (int i = 0; i < converter.countQuads(); ++i) { addSrcChoppedQuad(quadPts + 2 * i, !verbsInContour && 0 == i); } } else { 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 devPts[4]; 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]; if (verbsInContour == 0 && i == 0 && pts[0] == pts[1] && pts[2] == pts[3]) { seenZeroLengthVerb = true; zeroVerbPt = pts[0]; } } 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; } } } } verbsInContour++; break; case SkPath::kMove_Verb: // New contour (and last one was unclosed). If it was just a zero length drawing // operation, and we're supposed to draw caps, then add a tiny line. if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) { SkPoint* pts = lines->push_back_n(2); pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); } verbsInContour = 0; seenZeroLengthVerb = false; break; case SkPath::kLine_Verb: { SkPoint devPts[2]; 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]; if (verbsInContour == 0 && pts[0] == pts[1]) { seenZeroLengthVerb = true; zeroVerbPt = pts[0]; } } verbsInContour++; 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) { addSrcChoppedQuad(choppedPts + i * 2, !verbsInContour && 0 == i); } verbsInContour++; break; } case SkPath::kCubic_Verb: { SkPoint devPts[4]; 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 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, &q); } else { GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, &q); } for (int i = 0; i < q.count(); i += 3) { if (persp) { addSrcChoppedQuad(&q[i], !verbsInContour && 0 == i); } else { addChoppedQuad(nullptr, &q[i], !verbsInContour && 0 == i); } } } verbsInContour++; break; } case SkPath::kClose_Verb: // Contour is closed, so we don't need to grow the starting line, unless it's // *just* a zero length subpath. (SVG Spec 11.4, 'stroke'). if (capLength > 0) { if (seenZeroLengthVerb && verbsInContour == 1) { SkPoint* pts = lines->push_back_n(2); pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); } else if (verbsInContour == 0) { // Contour was (moveTo, close). Add a line. SkPoint devPts[2]; m.mapPoints(devPts, pathPts, 1); devPts[1] = devPts[0]; 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] = SkPoint::Make(devPts[0].fX - capLength, devPts[0].fY); pts[1] = SkPoint::Make(devPts[1].fX + capLength, devPts[1].fY); } } } break; case SkPath::kDone_Verb: if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) { // Path ended with a dangling (moveTo, line|quad|etc). If the final verb is // degenerate, we need to draw a line. SkPoint* pts = lines->push_back_n(2); pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); } return totalQuadCount; } } }
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; }
bool GrDefaultPathRenderer::internalDrawPath(GrDrawContext* drawContext, const GrPaint& paint, const GrUserStencilSettings& userStencilSettings, const GrClip& clip, const SkMatrix& viewMatrix, const GrShape& shape, bool stencilOnly) { SkPath path; shape.asPath(&path); SkScalar hairlineCoverage; uint8_t newCoverage = 0xff; bool isHairline = false; if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) { newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); isHairline = true; } else { SkASSERT(shape.style().isSimpleFill()); } int passCount = 0; const GrUserStencilSettings* passes[3]; GrDrawFace drawFace[3]; bool reverse = false; bool lastPassIsBounds; if (isHairline) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = &userStencilSettings; } lastPassIsBounds = false; drawFace[0] = GrDrawFace::kBoth; } else { if (single_pass_shape(shape)) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = &userStencilSettings; } drawFace[0] = GrDrawFace::kBoth; lastPassIsBounds = false; } else { switch (path.getFillType()) { case SkPath::kInverseEvenOdd_FillType: reverse = true; // fallthrough case SkPath::kEvenOdd_FillType: passes[0] = &gEOStencilPass; if (stencilOnly) { passCount = 1; lastPassIsBounds = false; } else { passCount = 2; lastPassIsBounds = true; if (reverse) { passes[1] = &gInvEOColorPass; } else { passes[1] = &gEOColorPass; } } drawFace[0] = drawFace[1] = GrDrawFace::kBoth; break; case SkPath::kInverseWinding_FillType: reverse = true; // fallthrough case SkPath::kWinding_FillType: if (fSeparateStencil) { if (fStencilWrapOps) { passes[0] = &gWindStencilSeparateWithWrap; } else { passes[0] = &gWindStencilSeparateNoWrap; } passCount = 2; drawFace[0] = GrDrawFace::kBoth; } else { if (fStencilWrapOps) { passes[0] = &gWindSingleStencilWithWrapInc; passes[1] = &gWindSingleStencilWithWrapDec; } else { passes[0] = &gWindSingleStencilNoWrapInc; passes[1] = &gWindSingleStencilNoWrapDec; } // which is cw and which is ccw is arbitrary. drawFace[0] = GrDrawFace::kCW; drawFace[1] = GrDrawFace::kCCW; passCount = 3; } if (stencilOnly) { lastPassIsBounds = false; --passCount; } else { lastPassIsBounds = true; drawFace[passCount-1] = GrDrawFace::kBoth; if (reverse) { passes[passCount-1] = &gInvWindColorPass; } else { passes[passCount-1] = &gWindColorPass; } } break; default: SkDEBUGFAIL("Unknown path fFill!"); return false; } } } SkScalar tol = GrPathUtils::kDefaultTolerance; SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds()); SkRect devBounds; GetPathDevBounds(path, drawContext->width(), drawContext->height(), viewMatrix, &devBounds); for (int p = 0; p < passCount; ++p) { if (lastPassIsBounds && (p == passCount-1)) { SkRect bounds; SkMatrix localMatrix = SkMatrix::I(); if (reverse) { // draw over the dev bounds (which will be the whole dst surface for inv fill). bounds = devBounds; SkMatrix vmi; // mapRect through persp matrix may not be correct if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { vmi.mapRect(&bounds); } else { if (!viewMatrix.invert(&localMatrix)) { return false; } } } else { bounds = path.getBounds(); } const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() : viewMatrix; SkAutoTUnref<GrDrawBatch> batch( GrRectBatchFactory::CreateNonAAFill(paint.getColor(), viewM, bounds, nullptr, &localMatrix)); SkASSERT(GrDrawFace::kBoth == drawFace[p]); GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint)); pipelineBuilder.setDrawFace(drawFace[p]); pipelineBuilder.setUserStencil(passes[p]); drawContext->drawBatch(pipelineBuilder, clip, batch); } else { SkAutoTUnref<GrDrawBatch> batch(new DefaultPathBatch(paint.getColor(), path, srcSpaceTol, newCoverage, viewMatrix, isHairline, devBounds)); GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint)); pipelineBuilder.setDrawFace(drawFace[p]); pipelineBuilder.setUserStencil(passes[p]); if (passCount > 1) { pipelineBuilder.setDisableColorXPFactory(); } drawContext->drawBatch(pipelineBuilder, clip, batch); } } return true; }