GrTexture* GaussianBlur(GrContext* context, GrTexture* srcTexture, bool canClobberSrc, const SkRect& dstBounds, const SkRect* srcBounds, float sigmaX, float sigmaY, GrTextureProvider::SizeConstraint constraint) { SkASSERT(context); SkIRect clearRect; int scaleFactorX, radiusX; int scaleFactorY, radiusY; int maxTextureSize = context->caps()->maxTextureSize(); sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX); sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY); SkPoint srcOffset = SkPoint::Make(-dstBounds.x(), -dstBounds.y()); SkRect localDstBounds = SkRect::MakeWH(dstBounds.width(), dstBounds.height()); SkRect localSrcBounds; SkRect srcRect; if (srcBounds) { srcRect = localSrcBounds = *srcBounds; srcRect.offset(srcOffset); srcBounds = &localSrcBounds; } else { srcRect = localDstBounds; } scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); srcRect.roundOut(&srcRect); scale_rect(&srcRect, static_cast<float>(scaleFactorX), static_cast<float>(scaleFactorY)); // setup new clip GrClip clip(localDstBounds); SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() || kRGBA_8888_GrPixelConfig == srcTexture->config() || kAlpha_8_GrPixelConfig == srcTexture->config()); GrSurfaceDesc desc; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fWidth = SkScalarFloorToInt(dstBounds.width()); desc.fHeight = SkScalarFloorToInt(dstBounds.height()); desc.fConfig = srcTexture->config(); GrTexture* dstTexture; GrTexture* tempTexture; SkAutoTUnref<GrTexture> temp1, temp2; temp1.reset(context->textureProvider()->createTexture(desc, constraint)); dstTexture = temp1.get(); if (canClobberSrc) { tempTexture = srcTexture; } else { temp2.reset(context->textureProvider()->createTexture(desc, constraint)); tempTexture = temp2.get(); } if (nullptr == dstTexture || nullptr == tempTexture) { return nullptr; } SkAutoTUnref<GrDrawContext> srcDrawContext; for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { GrPaint paint; SkMatrix matrix; matrix.setIDiv(srcTexture->width(), srcTexture->height()); SkRect dstRect(srcRect); if (srcBounds && i == 1) { SkRect domain; matrix.mapRect(&domain, *srcBounds); domain.inset((i < scaleFactorX) ? SK_ScalarHalf / srcTexture->width() : 0.0f, (i < scaleFactorY) ? SK_ScalarHalf / srcTexture->height() : 0.0f); SkAutoTUnref<const GrFragmentProcessor> fp(GrTextureDomainEffect::Create( srcTexture, matrix, domain, GrTextureDomain::kDecal_Mode, GrTextureParams::kBilerp_FilterMode)); paint.addColorFragmentProcessor(fp); srcRect.offset(-srcOffset); srcOffset.set(0, 0); } else { GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); paint.addColorTextureProcessor(srcTexture, matrix, params); } paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f, i < scaleFactorY ? 0.5f : 1.0f); SkAutoTUnref<GrDrawContext> dstDrawContext( context->drawContext(dstTexture->asRenderTarget())); if (!dstDrawContext) { return nullptr; } dstDrawContext->fillRectToRect(clip, paint, SkMatrix::I(), dstRect, srcRect); srcDrawContext.swap(dstDrawContext); srcRect = dstRect; srcTexture = dstTexture; SkTSwap(dstTexture, tempTexture); localSrcBounds = srcRect; } // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just // launch a single non separable kernel vs two launches srcRect = localDstBounds; if (sigmaX > 0.0f && sigmaY > 0.0f && (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { // We shouldn't be scaling because this is a small size blur SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY)); SkAutoTUnref<GrDrawContext> dstDrawContext( context->drawContext(dstTexture->asRenderTarget())); if (!dstDrawContext) { return nullptr; } convolve_gaussian_2d(dstDrawContext, clip, srcRect, srcOffset, srcTexture, radiusX, radiusY, sigmaX, sigmaY, srcBounds); srcDrawContext.swap(dstDrawContext); srcRect.offsetTo(0, 0); srcTexture = dstTexture; SkTSwap(dstTexture, tempTexture); } else { scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); srcRect.roundOut(&srcRect); const SkIRect srcIRect = srcRect.roundOut(); if (sigmaX > 0.0f) { if (scaleFactorX > 1) { // TODO: if we pass in the source draw context we don't need this here if (!srcDrawContext) { srcDrawContext.reset(context->drawContext(srcTexture->asRenderTarget())); if (!srcDrawContext) { return nullptr; } } // Clear out a radius to the right of the srcRect to prevent the // X convolution from reading garbage. clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, radiusX, srcIRect.height()); srcDrawContext->clear(&clearRect, 0x0, false); } SkAutoTUnref<GrDrawContext> dstDrawContext( context->drawContext(dstTexture->asRenderTarget())); if (!dstDrawContext) { return nullptr; } convolve_gaussian(dstDrawContext, clip, srcRect, srcTexture, Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, srcBounds, srcOffset); srcDrawContext.swap(dstDrawContext); srcTexture = dstTexture; srcRect.offsetTo(0, 0); SkTSwap(dstTexture, tempTexture); localSrcBounds = srcRect; srcOffset.set(0, 0); } if (sigmaY > 0.0f) { if (scaleFactorY > 1 || sigmaX > 0.0f) { // TODO: if we pass in the source draw context we don't need this here if (!srcDrawContext) { srcDrawContext.reset(context->drawContext(srcTexture->asRenderTarget())); if (!srcDrawContext) { return nullptr; } } // Clear out a radius below the srcRect to prevent the Y // convolution from reading garbage. clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, srcIRect.width(), radiusY); srcDrawContext->clear(&clearRect, 0x0, false); } SkAutoTUnref<GrDrawContext> dstDrawContext( context->drawContext(dstTexture->asRenderTarget())); if (!dstDrawContext) { return nullptr; } convolve_gaussian(dstDrawContext, clip, srcRect, srcTexture, Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, srcBounds, srcOffset); srcDrawContext.swap(dstDrawContext); srcTexture = dstTexture; srcRect.offsetTo(0, 0); SkTSwap(dstTexture, tempTexture); } } const SkIRect srcIRect = srcRect.roundOut(); if (scaleFactorX > 1 || scaleFactorY > 1) { SkASSERT(srcDrawContext); // Clear one pixel to the right and below, to accommodate bilinear // upsampling. clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, srcIRect.width() + 1, 1); srcDrawContext->clear(&clearRect, 0x0, false); clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, 1, srcIRect.height()); srcDrawContext->clear(&clearRect, 0x0, false); SkMatrix matrix; matrix.setIDiv(srcTexture->width(), srcTexture->height()); GrPaint paint; // FIXME: this should be mitchell, not bilinear. GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); paint.addColorTextureProcessor(srcTexture, matrix, params); paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); SkRect dstRect(srcRect); scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY); SkAutoTUnref<GrDrawContext> dstDrawContext( context->drawContext(dstTexture->asRenderTarget())); if (!dstDrawContext) { return nullptr; } dstDrawContext->fillRectToRect(clip, paint, SkMatrix::I(), dstRect, srcRect); srcDrawContext.swap(dstDrawContext); srcRect = dstRect; srcTexture = dstTexture; SkTSwap(dstTexture, tempTexture); } return SkRef(srcTexture); }
static SkSize computeSize(const SkBitmap& bm, const SkMatrix& mat) { SkRect bounds = SkRect::MakeWH(SkIntToScalar(bm.width()), SkIntToScalar(bm.height())); mat.mapRect(&bounds); return SkSize::Make(bounds.width(), bounds.height()); }
// test drawing with strips of fading gradient above and below static void test_fade(SkCanvas* canvas) { SkAutoCanvasRestore ar(canvas, true); SkRect r; SkPaint p; p.setAlpha(0x88); SkAutoCanvasRestore ar2(canvas, false); // create the layers r.set(0, 0, SkIntToScalar(100), SkIntToScalar(100)); canvas->clipRect(r); r.fBottom = SkIntToScalar(20); canvas->saveLayer(&r, NULL); r.fTop = SkIntToScalar(80); r.fBottom = SkIntToScalar(100); canvas->saveLayer(&r, NULL); // now draw the "content" if (true) { r.set(0, 0, SkIntToScalar(100), SkIntToScalar(100)); canvas->saveLayerAlpha(&r, 0x80); SkPaint p; p.setColor(SK_ColorRED); p.setAntiAlias(true); canvas->drawOval(r, p); dump_layers("inside layer alpha", canvas); canvas->restore(); } else { r.set(0, 0, SkIntToScalar(100), SkIntToScalar(100)); SkPaint p; p.setColor(SK_ColorRED); p.setAntiAlias(true); canvas->drawOval(r, p); } // return; dump_layers("outside layer alpha", canvas); // now apply an effect SkMatrix m; m.setScale(SK_Scalar1, -SK_Scalar1); m.postTranslate(0, SkIntToScalar(100)); SkPaint paint; make_paint(&paint, m); r.set(0, 0, SkIntToScalar(100), SkIntToScalar(20)); // SkDebugf("--------- draw top grad\n"); canvas->drawRect(r, paint); r.fTop = SkIntToScalar(80); r.fBottom = SkIntToScalar(100); // SkDebugf("--------- draw bot grad\n"); canvas->drawRect(r, paint); }
void SkScalerContext::getImage(const SkGlyph& origGlyph) { const SkGlyph* glyph = &origGlyph; SkGlyph tmpGlyph; if (fMaskFilter) { // restore the prefilter bounds tmpGlyph.init(origGlyph.fID); // need the original bounds, sans our maskfilter SkMaskFilter* mf = fMaskFilter; fMaskFilter = NULL; // temp disable this->getMetrics(&tmpGlyph); fMaskFilter = mf; // restore tmpGlyph.fImage = origGlyph.fImage; // we need the prefilter bounds to be <= filter bounds SkASSERT(tmpGlyph.fWidth <= origGlyph.fWidth); SkASSERT(tmpGlyph.fHeight <= origGlyph.fHeight); glyph = &tmpGlyph; } if (fRec.fFrameWidth > 0 || fPathEffect != NULL || fRasterizer != NULL) { SkPath devPath, fillPath; SkMatrix fillToDevMatrix; this->internalGetPath(*glyph, &fillPath, &devPath, &fillToDevMatrix); const bool lcdMode = fRec.fMaskFormat == SkMask::kHorizontalLCD_Format || fRec.fMaskFormat == SkMask::kVerticalLCD_Format; if (fRasterizer) { SkMask mask; glyph->toMask(&mask); mask.fFormat = SkMask::kA8_Format; sk_bzero(glyph->fImage, mask.computeImageSize()); if (!fRasterizer->rasterize(fillPath, fillToDevMatrix, NULL, fMaskFilter, &mask, SkMask::kJustRenderImage_CreateMode)) { return; } } else { SkBitmap bm; SkBitmap::Config config; SkMatrix matrix; SkRegion clip; SkPaint paint; SkDraw draw; if (SkMask::kA8_Format == fRec.fMaskFormat || lcdMode) { config = SkBitmap::kA8_Config; paint.setAntiAlias(true); } else { SkASSERT(SkMask::kBW_Format == fRec.fMaskFormat); config = SkBitmap::kA1_Config; paint.setAntiAlias(false); } clip.setRect(0, 0, glyph->fWidth, glyph->fHeight); matrix.setTranslate(-SkIntToScalar(glyph->fLeft), -SkIntToScalar(glyph->fTop)); bm.setConfig(config, glyph->fWidth, glyph->fHeight, glyph->rowBytes()); bm.setPixels(glyph->fImage); sk_bzero(glyph->fImage, bm.height() * bm.rowBytes()); draw.fClip = &clip; draw.fMatrix = &matrix; draw.fBitmap = &bm; draw.fBounder = NULL; draw.drawPath(devPath, paint); } if (lcdMode) glyph->expandA8ToLCD(); } else { this->getGlyphContext(*glyph)->generateImage(*glyph); } if (fMaskFilter) { SkMask srcM, dstM; SkMatrix matrix; // the src glyph image shouldn't be 3D SkASSERT(SkMask::k3D_Format != glyph->fMaskFormat); glyph->toMask(&srcM); fRec.getMatrixFrom2x2(&matrix); if (fMaskFilter->filterMask(&dstM, srcM, matrix, NULL)) { int width = SkFastMin32(origGlyph.fWidth, dstM.fBounds.width()); int height = SkFastMin32(origGlyph.fHeight, dstM.fBounds.height()); int dstRB = origGlyph.rowBytes(); int srcRB = dstM.fRowBytes; const uint8_t* src = (const uint8_t*)dstM.fImage; uint8_t* dst = (uint8_t*)origGlyph.fImage; if (SkMask::k3D_Format == dstM.fFormat) { // we have to copy 3 times as much height *= 3; } // clean out our glyph, since it may be larger than dstM //sk_bzero(dst, height * dstRB); while (--height >= 0) { memcpy(dst, src, width); src += srcRB; dst += dstRB; } SkMask::FreeImage(dstM.fImage); } } // check to see if we should filter the alpha channel if (NULL == fMaskFilter && fRec.fMaskFormat != SkMask::kBW_Format && fRec.fMaskFormat != SkMask::kLCD16_Format && (fRec.fFlags & (kGammaForBlack_Flag | kGammaForWhite_Flag)) != 0) { const uint8_t* table = (fRec.fFlags & kGammaForBlack_Flag) ? gBlackGammaTable : gWhiteGammaTable; if (NULL != table) { uint8_t* dst = (uint8_t*)origGlyph.fImage; unsigned rowBytes = origGlyph.rowBytes(); for (int y = origGlyph.fHeight - 1; y >= 0; --y) { for (int x = origGlyph.fWidth - 1; x >= 0; --x) dst[x] = table[dst[x]]; dst += rowBytes; } } } }
// Attempt to trim the line to minimally cover the cull rect (currently // only works for horizontal and vertical lines). // Return true if processing should continue; false otherwise. static bool cull_line(SkPoint* pts, const SkStrokeRec& rec, const SkMatrix& ctm, const SkRect* cullRect, const SkScalar intervalLength) { if (nullptr == cullRect) { SkASSERT(false); // Shouldn't ever occur in practice return false; } SkScalar dx = pts[1].x() - pts[0].x(); SkScalar dy = pts[1].y() - pts[0].y(); if ((dx && dy) || (!dx && !dy)) { return false; } SkRect bounds = *cullRect; outset_for_stroke(&bounds, rec); // cullRect is in device space while pts are in the local coordinate system // defined by the ctm. We want our answer in the local coordinate system. SkASSERT(ctm.rectStaysRect()); SkMatrix inv; if (!ctm.invert(&inv)) { return false; } inv.mapRect(&bounds); if (dx) { SkASSERT(dx && !dy); SkScalar minX = pts[0].fX; SkScalar maxX = pts[1].fX; if (dx < 0) { SkTSwap(minX, maxX); } SkASSERT(minX < maxX); if (maxX <= bounds.fLeft || minX >= bounds.fRight) { return false; } // Now we actually perform the chop, removing the excess to the left and // right of the bounds (keeping our new line "in phase" with the dash, // hence the (mod intervalLength). if (minX < bounds.fLeft) { minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX, intervalLength); } if (maxX > bounds.fRight) { maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight, intervalLength); } SkASSERT(maxX > minX); if (dx < 0) { SkTSwap(minX, maxX); } pts[0].fX = minX; pts[1].fX = maxX; } else { SkASSERT(dy && !dx); SkScalar minY = pts[0].fY; SkScalar maxY = pts[1].fY; if (dy < 0) { SkTSwap(minY, maxY); } SkASSERT(minY < maxY); if (maxY <= bounds.fTop || minY >= bounds.fBottom) { return false; } // Now we actually perform the chop, removing the excess to the top and // bottom of the bounds (keeping our new line "in phase" with the dash, // hence the (mod intervalLength). if (minY < bounds.fTop) { minY = bounds.fTop - SkScalarMod(bounds.fTop - minY, intervalLength); } if (maxY > bounds.fBottom) { maxY = bounds.fBottom + SkScalarMod(maxY - bounds.fBottom, intervalLength); } SkASSERT(maxY > minY); if (dy < 0) { SkTSwap(minY, maxY); } pts[0].fY = minY; pts[1].fY = maxY; } return true; }
bool SkScalerContext_CairoFT::computeShapeMatrix(const SkMatrix& m) { // Compute a shape matrix compatible with Cairo's _compute_transform. // Finds major/minor scales and uses them to normalize the transform. double scaleX = m.getScaleX(); double skewX = m.getSkewX(); double skewY = m.getSkewY(); double scaleY = m.getScaleY(); double det = scaleX * scaleY - skewY * skewX; if (!std::isfinite(det)) { fScaleX = fRec.fTextSize * fRec.fPreScaleX; fScaleY = fRec.fTextSize; fHaveShape = false; return false; } double major = det != 0.0 ? hypot(scaleX, skewY) : 0.0; double minor = major != 0.0 ? fabs(det) / major : 0.0; // Limit scales to be above 1pt. major = SkTMax(major, 1.0); minor = SkTMax(minor, 1.0); // If the font is not scalable, then choose the best available size. CairoLockedFTFace faceLock(fScaledFont); FT_Face face = faceLock.getFace(); if (face && !FT_IS_SCALABLE(face)) { double bestDist = DBL_MAX; FT_Int bestSize = -1; for (FT_Int i = 0; i < face->num_fixed_sizes; i++) { // Distance is positive if strike is larger than desired size, // or negative if smaller. If previously a found smaller strike, // then prefer a larger strike. Otherwise, minimize distance. double dist = face->available_sizes[i].y_ppem / 64.0 - minor; if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) { bestDist = dist; bestSize = i; } } if (bestSize < 0) { fScaleX = fRec.fTextSize * fRec.fPreScaleX; fScaleY = fRec.fTextSize; fHaveShape = false; return false; } major = face->available_sizes[bestSize].x_ppem / 64.0; minor = face->available_sizes[bestSize].y_ppem / 64.0; fHaveShape = true; } else { fHaveShape = !m.isScaleTranslate(); } fScaleX = SkDoubleToScalar(major); fScaleY = SkDoubleToScalar(minor); if (fHaveShape) { // Normalize the transform and convert to fixed-point. double invScaleX = 65536.0 / major; double invScaleY = 65536.0 / minor; fShapeMatrix.xx = (FT_Fixed)(scaleX * invScaleX); fShapeMatrix.yx = -(FT_Fixed)(skewY * invScaleX); fShapeMatrix.xy = -(FT_Fixed)(skewX * invScaleY); fShapeMatrix.yy = (FT_Fixed)(scaleY * invScaleY); } return true; }
SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state) : SkPDFDict("Pattern"), fState(state) { SkString (*codeFunction)(const SkShader::GradientInfo& info) = 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 = &fState.get()->fInfo; transformPoints[0] = info->fPoint[0]; transformPoints[1] = info->fPoint[1]; switch (fState.get()->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. Empty fResources signals // an error and isValid will return false. if (info->fRadius[0] == info->fRadius[1]) { return; } transformPoints[1] = transformPoints[0]; SkScalar dr = info->fRadius[1] - info->fRadius[0]; transformPoints[1].fX += dr; codeFunction = &twoPointRadialCode; 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; } // 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 = fState.get()->fCanvasTransform; finalMatrix.preConcat(mapperMatrix); finalMatrix.preConcat(fState.get()->fShaderTransform); SkRect bbox; bbox.set(fState.get()->fBBox); if (!transformBBox(finalMatrix, &bbox)) { return; } SkRefPtr<SkPDFArray> domain = new SkPDFArray; domain->unref(); // SkRefPtr and new both took a reference. 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 fState.get()->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 (fState.get()->fType == SkShader::kRadial2_GradientType) { SkShader::GradientInfo twoPointRadialInfo = *info; SkMatrix inverseMapperMatrix; if (!mapperMatrix.invert(&inverseMapperMatrix)) { return; } inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2); twoPointRadialInfo.fRadius[0] = inverseMapperMatrix.mapRadius(info->fRadius[0]); twoPointRadialInfo.fRadius[1] = inverseMapperMatrix.mapRadius(info->fRadius[1]); functionCode = codeFunction(twoPointRadialInfo); } else { functionCode = codeFunction(*info); } SkRefPtr<SkPDFStream> function = makePSFunction(functionCode, domain.get()); // Pass one reference to fResources, SkRefPtr and new both took a reference. fResources.push(function.get()); SkRefPtr<SkPDFDict> pdfShader = new SkPDFDict; pdfShader->unref(); // SkRefPtr and new both took a reference. pdfShader->insertInt("ShadingType", 1); pdfShader->insertName("ColorSpace", "DeviceRGB"); pdfShader->insert("Domain", domain.get()); pdfShader->insert("Function", new SkPDFObjRef(function.get()))->unref(); insertInt("PatternType", 2); insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref(); insert("Shading", pdfShader.get()); }
void GetLocalBounds(const SkPath& path, const SkDrawShadowRec& rec, const SkMatrix& ctm, SkRect* bounds) { SkRect ambientBounds = path.getBounds(); SkScalar occluderZ; if (SkScalarNearlyZero(rec.fZPlaneParams.fX) && SkScalarNearlyZero(rec.fZPlaneParams.fY)) { occluderZ = rec.fZPlaneParams.fZ; } else { occluderZ = compute_z(ambientBounds.fLeft, ambientBounds.fTop, rec.fZPlaneParams); occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fTop, rec.fZPlaneParams)); occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fLeft, ambientBounds.fBottom, rec.fZPlaneParams)); occluderZ = SkTMax(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fBottom, rec.fZPlaneParams)); } SkScalar ambientBlur; SkScalar spotBlur; SkScalar spotScale; SkPoint spotOffset; if (ctm.hasPerspective()) { // transform ambient and spot bounds into device space ctm.mapRect(&ambientBounds); // get ambient blur (in device space) ambientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ); // get spot params (in device space) SkPoint devLightPos = SkPoint::Make(rec.fLightPos.fX, rec.fLightPos.fY); ctm.mapPoints(&devLightPos, 1); SkDrawShadowMetrics::GetSpotParams(occluderZ, devLightPos.fX, devLightPos.fY, rec.fLightPos.fZ, rec.fLightRadius, &spotBlur, &spotScale, &spotOffset); } else { SkScalar devToSrcScale = SkScalarInvert(ctm.getMinScale()); // get ambient blur (in local space) SkScalar devSpaceAmbientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ); ambientBlur = devSpaceAmbientBlur*devToSrcScale; // get spot params (in local space) SkDrawShadowMetrics::GetSpotParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY, rec.fLightPos.fZ, rec.fLightRadius, &spotBlur, &spotScale, &spotOffset); // convert spot blur to local space spotBlur *= devToSrcScale; } // in both cases, adjust ambient and spot bounds SkRect spotBounds = ambientBounds; ambientBounds.outset(ambientBlur, ambientBlur); spotBounds.fLeft *= spotScale; spotBounds.fTop *= spotScale; spotBounds.fRight *= spotScale; spotBounds.fBottom *= spotScale; spotBounds.offset(spotOffset.fX, spotOffset.fY); spotBounds.outset(spotBlur, spotBlur); // merge bounds *bounds = ambientBounds; bounds->join(spotBounds); // outset a bit to account for floating point error bounds->outset(1, 1); // if perspective, transform back to src space if (ctm.hasPerspective()) { // TODO: create tighter mapping from dev rect back to src rect SkMatrix inverse; if (ctm.invert(&inverse)) { inverse.mapRect(bounds); } } }
bool GetSpotShadowTransform(const SkPoint3& lightPos, SkScalar lightRadius, const SkMatrix& ctm, const SkPoint3& zPlaneParams, const SkRect& pathBounds, SkMatrix* shadowTransform, SkScalar* radius) { auto heightFunc = [zPlaneParams] (SkScalar x, SkScalar y) { return zPlaneParams.fX*x + zPlaneParams.fY*y + zPlaneParams.fZ; }; SkScalar occluderHeight = heightFunc(pathBounds.centerX(), pathBounds.centerY()); if (!ctm.hasPerspective()) { SkScalar scale; SkVector translate; SkDrawShadowMetrics::GetSpotParams(occluderHeight, lightPos.fX, lightPos.fY, lightPos.fZ, lightRadius, radius, &scale, &translate); shadowTransform->setScaleTranslate(scale, scale, translate.fX, translate.fY); shadowTransform->preConcat(ctm); } else { if (SkScalarNearlyZero(pathBounds.width()) || SkScalarNearlyZero(pathBounds.height())) { return false; } // get rotated quad in 3D SkPoint pts[4]; ctm.mapRectToQuad(pts, pathBounds); // No shadows for bowties or other degenerate cases if (!SkIsConvexPolygon(pts, 4)) { return false; } SkPoint3 pts3D[4]; SkScalar z = heightFunc(pathBounds.fLeft, pathBounds.fTop); pts3D[0].set(pts[0].fX, pts[0].fY, z); z = heightFunc(pathBounds.fRight, pathBounds.fTop); pts3D[1].set(pts[1].fX, pts[1].fY, z); z = heightFunc(pathBounds.fRight, pathBounds.fBottom); pts3D[2].set(pts[2].fX, pts[2].fY, z); z = heightFunc(pathBounds.fLeft, pathBounds.fBottom); pts3D[3].set(pts[3].fX, pts[3].fY, z); // project from light through corners to z=0 plane for (int i = 0; i < 4; ++i) { SkScalar dz = lightPos.fZ - pts3D[i].fZ; // light shouldn't be below or at a corner's z-location if (dz <= SK_ScalarNearlyZero) { return false; } SkScalar zRatio = pts3D[i].fZ / dz; pts3D[i].fX -= (lightPos.fX - pts3D[i].fX)*zRatio; pts3D[i].fY -= (lightPos.fY - pts3D[i].fY)*zRatio; pts3D[i].fZ = SK_Scalar1; } // Generate matrix that projects from [-1,1]x[-1,1] square to projected quad SkPoint3 h0, h1, h2; // Compute homogenous crossing point between top and bottom edges (gives new x-axis). h0 = (pts3D[1].cross(pts3D[0])).cross(pts3D[2].cross(pts3D[3])); // Compute homogenous crossing point between left and right edges (gives new y-axis). h1 = (pts3D[0].cross(pts3D[3])).cross(pts3D[1].cross(pts3D[2])); // Compute homogenous crossing point between diagonals (gives new origin). h2 = (pts3D[0].cross(pts3D[2])).cross(pts3D[1].cross(pts3D[3])); // If h2 is a vector (z=0 in 2D homogeneous space), that means that at least // two of the quad corners are coincident and we don't have a realistic projection if (SkScalarNearlyZero(h2.fZ)) { return false; } // In some cases the crossing points are in the wrong direction // to map (-1,-1) to pts3D[0], so we need to correct for that. // Want h0 to be to the right of the left edge. SkVector3 v = pts3D[3] - pts3D[0]; SkVector3 w = h0 - pts3D[0]; SkScalar perpDot = v.fX*w.fY - v.fY*w.fX; if (perpDot > 0) { h0 = -h0; } // Want h1 to be above the bottom edge. v = pts3D[1] - pts3D[0]; perpDot = v.fX*w.fY - v.fY*w.fX; if (perpDot < 0) { h1 = -h1; } shadowTransform->setAll(h0.fX / h2.fZ, h1.fX / h2.fZ, h2.fX / h2.fZ, h0.fY / h2.fZ, h1.fY / h2.fZ, h2.fY / h2.fZ, h0.fZ / h2.fZ, h1.fZ / h2.fZ, 1); // generate matrix that transforms from bounds to [-1,1]x[-1,1] square SkMatrix toHomogeneous; SkScalar xScale = 2/(pathBounds.fRight - pathBounds.fLeft); SkScalar yScale = 2/(pathBounds.fBottom - pathBounds.fTop); toHomogeneous.setAll(xScale, 0, -xScale*pathBounds.fLeft - 1, 0, yScale, -yScale*pathBounds.fTop - 1, 0, 0, 1); shadowTransform->preConcat(toHomogeneous); *radius = SkDrawShadowMetrics::SpotBlurRadius(occluderHeight, lightPos.fZ, lightRadius); } return true; }
void TestMatrix(skiatest::Reporter* reporter) { SkMatrix mat, inverse, iden1, iden2; mat.reset(); mat.setTranslate(SK_Scalar1, SK_Scalar1); mat.invert(&inverse); iden1.setConcat(mat, inverse); REPORTER_ASSERT(reporter, is_identity(iden1)); mat.setScale(SkIntToScalar(2), SkIntToScalar(2)); mat.invert(&inverse); iden1.setConcat(mat, inverse); REPORTER_ASSERT(reporter, is_identity(iden1)); test_flatten(reporter, mat); mat.setScale(SK_Scalar1/2, SK_Scalar1/2); mat.invert(&inverse); iden1.setConcat(mat, inverse); REPORTER_ASSERT(reporter, is_identity(iden1)); test_flatten(reporter, mat); mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0); mat.postRotate(SkIntToScalar(25)); REPORTER_ASSERT(reporter, mat.invert(NULL)); mat.invert(&inverse); iden1.setConcat(mat, inverse); REPORTER_ASSERT(reporter, is_identity(iden1)); iden2.setConcat(inverse, mat); REPORTER_ASSERT(reporter, is_identity(iden2)); test_flatten(reporter, mat); test_flatten(reporter, iden2); // rectStaysRect test { static const struct { SkScalar m00, m01, m10, m11; bool mStaysRect; } gRectStaysRectSamples[] = { { 0, 0, 0, 0, false }, { 0, 0, 0, SK_Scalar1, false }, { 0, 0, SK_Scalar1, 0, false }, { 0, 0, SK_Scalar1, SK_Scalar1, false }, { 0, SK_Scalar1, 0, 0, false }, { 0, SK_Scalar1, 0, SK_Scalar1, false }, { 0, SK_Scalar1, SK_Scalar1, 0, true }, { 0, SK_Scalar1, SK_Scalar1, SK_Scalar1, false }, { SK_Scalar1, 0, 0, 0, false }, { SK_Scalar1, 0, 0, SK_Scalar1, true }, { SK_Scalar1, 0, SK_Scalar1, 0, false }, { SK_Scalar1, 0, SK_Scalar1, SK_Scalar1, false }, { SK_Scalar1, SK_Scalar1, 0, 0, false }, { SK_Scalar1, SK_Scalar1, 0, SK_Scalar1, false }, { SK_Scalar1, SK_Scalar1, SK_Scalar1, 0, false }, { SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, false } }; for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) { SkMatrix m; m.reset(); m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00); m.set(SkMatrix::kMSkewX, gRectStaysRectSamples[i].m01); m.set(SkMatrix::kMSkewY, gRectStaysRectSamples[i].m10); m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11); REPORTER_ASSERT(reporter, m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect); } } mat.reset(); mat.set(SkMatrix::kMScaleX, SkIntToScalar(1)); mat.set(SkMatrix::kMSkewX, SkIntToScalar(2)); mat.set(SkMatrix::kMTransX, SkIntToScalar(3)); mat.set(SkMatrix::kMSkewY, SkIntToScalar(4)); mat.set(SkMatrix::kMScaleY, SkIntToScalar(5)); mat.set(SkMatrix::kMTransY, SkIntToScalar(6)); SkScalar affine[6]; REPORTER_ASSERT(reporter, mat.asAffine(affine)); #define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e) REPORTER_ASSERT(reporter, affineEqual(ScaleX)); REPORTER_ASSERT(reporter, affineEqual(SkewY)); REPORTER_ASSERT(reporter, affineEqual(SkewX)); REPORTER_ASSERT(reporter, affineEqual(ScaleY)); REPORTER_ASSERT(reporter, affineEqual(TransX)); REPORTER_ASSERT(reporter, affineEqual(TransY)); #undef affineEqual mat.set(SkMatrix::kMPersp1, SkScalarToPersp(SK_Scalar1 / 2)); REPORTER_ASSERT(reporter, !mat.asAffine(affine)); SkMatrix mat2; mat2.reset(); mat.reset(); SkScalar zero = 0; mat.set(SkMatrix::kMSkewX, -zero); REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2)); mat2.reset(); mat.reset(); mat.set(SkMatrix::kMSkewX, SK_ScalarNaN); mat2.set(SkMatrix::kMSkewX, SK_ScalarNaN); // fixed pt doesn't have the property that NaN does not equal itself. #ifdef SK_SCALAR_IS_FIXED REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2)); #else REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2)); #endif test_matrix_max_stretch(reporter); }
static bool is_identity(const SkMatrix& m) { SkMatrix identity; identity.reset(); return nearly_equal(m, identity); }
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); } 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 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); } }
void SkScalerContext_FreeType_Base::generateGlyphImage( FT_Face face, const SkGlyph& glyph, const SkMatrix& bitmapTransform) { const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); switch ( face->glyph->format ) { case FT_GLYPH_FORMAT_OUTLINE: { FT_Outline* outline = &face->glyph->outline; FT_BBox bbox; FT_Bitmap target; int dx = 0, dy = 0; if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { dx = SkFixedToFDot6(glyph.getSubXFixed()); dy = SkFixedToFDot6(glyph.getSubYFixed()); // negate dy since freetype-y-goes-up and skia-y-goes-down dy = -dy; } FT_Outline_Get_CBox(outline, &bbox); /* what we really want to do for subpixel is offset(dx, dy) compute_bounds offset(bbox & !63) but that is two calls to offset, so we do the following, which achieves the same thing with only one offset call. */ FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63), dy - ((bbox.yMin + dy) & ~63)); if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); SkMask mask; glyph.toMask(&mask); if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(face->glyph->bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(face->glyph->bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } } else { target.width = glyph.fWidth; target.rows = glyph.fHeight; target.pitch = glyph.rowBytes(); target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage); target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); } } break; case FT_GLYPH_FORMAT_BITMAP: { FT_Pixel_Mode pixel_mode = static_cast<FT_Pixel_Mode>(face->glyph->bitmap.pixel_mode); SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat); // Assume that the other formats do not exist. SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode || FT_PIXEL_MODE_GRAY == pixel_mode || FT_PIXEL_MODE_BGRA == pixel_mode); // These are the only formats this ScalerContext should request. SkASSERT(SkMask::kBW_Format == maskFormat || SkMask::kA8_Format == maskFormat || SkMask::kARGB32_Format == maskFormat || SkMask::kLCD16_Format == maskFormat); // If no scaling needed, directly copy glyph bitmap. if (bitmapTransform.isIdentity()) { SkMask dstMask; glyph.toMask(&dstMask); copyFTBitmap(face->glyph->bitmap, dstMask); break; } // Otherwise, scale the bitmap. // Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB) SkBitmap unscaledBitmap; // TODO: mark this as sRGB when the blits will be sRGB. unscaledBitmap.allocPixels(SkImageInfo::Make(face->glyph->bitmap.width, face->glyph->bitmap.rows, SkColorType_for_FTPixelMode(pixel_mode), kPremul_SkAlphaType)); SkMask unscaledBitmapAlias; unscaledBitmapAlias.fImage = reinterpret_cast<uint8_t*>(unscaledBitmap.getPixels()); unscaledBitmapAlias.fBounds.set(0, 0, unscaledBitmap.width(), unscaledBitmap.height()); unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes(); unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkColorType(unscaledBitmap.colorType()); copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias); // Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD. // BW requires an A8 target for resizing, which can then be down sampled. // LCD should use a 4x A8 target, which will then be down sampled. // For simplicity, LCD uses A8 and is replicated. int bitmapRowBytes = 0; if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) { bitmapRowBytes = glyph.rowBytes(); } SkBitmap dstBitmap; // TODO: mark this as sRGB when the blits will be sRGB. dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight, SkColorType_for_SkMaskFormat(maskFormat), kPremul_SkAlphaType), bitmapRowBytes); if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) { dstBitmap.allocPixels(); } else { dstBitmap.setPixels(glyph.fImage); } // Scale unscaledBitmap into dstBitmap. SkCanvas canvas(dstBitmap); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE canvas.clear(0x33FF0000); #else canvas.clear(SK_ColorTRANSPARENT); #endif canvas.translate(-glyph.fLeft, -glyph.fTop); canvas.concat(bitmapTransform); canvas.translate(face->glyph->bitmap_left, -face->glyph->bitmap_top); SkPaint paint; paint.setFilterQuality(kMedium_SkFilterQuality); canvas.drawBitmap(unscaledBitmap, 0, 0, &paint); // If the destination is BW or LCD, convert from A8. if (SkMask::kBW_Format == maskFormat) { // Copy the A8 dstBitmap into the A1 glyph.fImage. SkMask dstMask; glyph.toMask(&dstMask); packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes()); } else if (SkMask::kLCD16_Format == maskFormat) { // Copy the A8 dstBitmap into the LCD16 glyph.fImage. uint8_t* src = dstBitmap.getAddr8(0, 0); uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage); for (int y = dstBitmap.height(); y --> 0;) { for (int x = 0; x < dstBitmap.width(); ++x) { dst[x] = grayToRGB16(src[x]); } dst = (uint16_t*)((char*)dst + glyph.rowBytes()); src += dstBitmap.rowBytes(); } } } break; default: SkDEBUGFAIL("unknown glyph format"); memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); return; } // We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, // it is optional #if defined(SK_GAMMA_APPLY_TO_A8) if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) { uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; unsigned rowBytes = glyph.rowBytes(); for (int y = glyph.fHeight - 1; y >= 0; --y) { for (int x = glyph.fWidth - 1; x >= 0; --x) { dst[x] = fPreBlend.fG[dst[x]]; } dst += rowBytes; } } #endif }
void Font::drawGlyphs(GraphicsContext* gc, const SimpleFontData* font, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const { // compile-time assert SkASSERT(sizeof(GlyphBufferGlyph) == sizeof(uint16_t)); if (numGlyphs == 1 && glyphBuffer.glyphAt(from) == 0x3) { // Webkit likes to draw end text control command for some reason // Just ignore it return; } SkPaint paint; if (!setupForText(&paint, gc, font)) { return; } SkScalar x = SkFloatToScalar(point.x()); SkScalar y = SkFloatToScalar(point.y()); const GlyphBufferGlyph* glyphs = glyphBuffer.glyphs(from); const GlyphBufferAdvance* adv = glyphBuffer.advances(from); SkAutoSTMalloc<32, SkPoint> storage(numGlyphs), storage2(numGlyphs), storage3(numGlyphs); SkPoint* pos = storage.get(); SkCanvas* canvas = gc->platformContext()->recordingCanvas(); /* We need an array of [x,y,x,y,x,y,...], but webkit is giving us point.xy + [width, height, width, height, ...], so we have to convert */ if (font->platformData().orientation() == Vertical) { float yOffset = SkFloatToScalar(font->fontMetrics().floatAscent(IdeographicBaseline) - font->fontMetrics().floatAscent()); gc->platformContext()->setTextOffset(FloatSize(0.0f, -yOffset)); // compensate for offset in bounds calculation y += yOffset; } if (EmojiFont::IsAvailable()) { // set filtering, to make scaled images look nice(r) paint.setFilterBitmap(true); SkMatrix rotator; rotator.reset(); if (font->platformData().orientation() == Vertical) { canvas->save(); canvas->rotate(-90); rotator.setRotate(90); } int localIndex = 0; int localCount = 0; for (int i = 0; i < numGlyphs; i++) { if (EmojiFont::IsEmojiGlyph(glyphs[i])) { if (localCount) { rotator.mapPoints(&pos[localIndex], localCount); canvas->drawPosText(&glyphs[localIndex], localCount * sizeof(uint16_t), &pos[localIndex], paint); } EmojiFont::Draw(canvas, glyphs[i], x, y, paint); // reset local index/count track for "real" glyphs localCount = 0; localIndex = i + 1; } else { pos[i].set(x, y); localCount += 1; } x += SkFloatToScalar(adv[i].width()); y += SkFloatToScalar(adv[i].height()); } // draw the last run of glyphs (if any) if (localCount) { rotator.mapPoints(&pos[localIndex], localCount); canvas->drawPosText(&glyphs[localIndex], localCount * sizeof(uint16_t), &pos[localIndex], paint); } if (font->platformData().orientation() == Vertical) canvas->restore(); } else { for (int i = 0; i < numGlyphs; i++) { pos[i].set(x, y); y += SkFloatToScalar(adv[i].height()); x += SkFloatToScalar(adv[i].width()); } if (font->platformData().orientation() == Vertical) { canvas->save(); canvas->rotate(-90); SkMatrix rotator; rotator.reset(); rotator.setRotate(90); rotator.mapPoints(pos, numGlyphs); } canvas->drawPosText(glyphs, numGlyphs * sizeof(uint16_t), pos, paint); if (font->platformData().orientation() == Vertical) canvas->restore(); } if (font->platformData().orientation() == Vertical) gc->platformContext()->setTextOffset(FloatSize()); // reset to undo above }
static SkMatrix translate(SkScalar dx, SkScalar dy) { SkMatrix matrix; matrix.setTranslate(dx, dy); return matrix; }
void SkBitmapDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { SkMatrix matrix; SkRect bitmapBounds, tmpSrc, tmpDst; SkBitmap tmpBitmap; bitmapBounds.isetWH(bitmap.width(), bitmap.height()); // Compute matrix from the two rectangles if (src) { tmpSrc = *src; } else { tmpSrc = bitmapBounds; } matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit); LogDrawScaleFactor(SkMatrix::Concat(*draw.fMatrix, matrix), paint.getFilterQuality()); const SkRect* dstPtr = &dst; const SkBitmap* bitmapPtr = &bitmap; // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if // needed (if the src was clipped). No check needed if src==null. if (src) { if (!bitmapBounds.contains(*src)) { if (!tmpSrc.intersect(bitmapBounds)) { return; // nothing to draw } // recompute dst, based on the smaller tmpSrc matrix.mapRect(&tmpDst, tmpSrc); dstPtr = &tmpDst; } } if (src && !src->contains(bitmapBounds) && SkCanvas::kFast_SrcRectConstraint == constraint && paint.getFilterQuality() != kNone_SkFilterQuality) { // src is smaller than the bounds of the bitmap, and we are filtering, so we don't know // how much more of the bitmap we need, so we can't use extractSubset or drawBitmap, // but we must use a shader w/ dst bounds (which can access all of the bitmap needed). goto USE_SHADER; } if (src) { // since we may need to clamp to the borders of the src rect within // the bitmap, we extract a subset. const SkIRect srcIR = tmpSrc.roundOut(); if (!bitmap.extractSubset(&tmpBitmap, srcIR)) { return; } bitmapPtr = &tmpBitmap; // Since we did an extract, we need to adjust the matrix accordingly SkScalar dx = 0, dy = 0; if (srcIR.fLeft > 0) { dx = SkIntToScalar(srcIR.fLeft); } if (srcIR.fTop > 0) { dy = SkIntToScalar(srcIR.fTop); } if (dx || dy) { matrix.preTranslate(dx, dy); } SkRect extractedBitmapBounds; extractedBitmapBounds.isetWH(bitmapPtr->width(), bitmapPtr->height()); if (extractedBitmapBounds == tmpSrc) { // no fractional part in src, we can just call drawBitmap goto USE_DRAWBITMAP; } } else { USE_DRAWBITMAP: // We can go faster by just calling drawBitmap, which will concat the // matrix with the CTM, and try to call drawSprite if it can. If not, // it will make a shader and call drawRect, as we do below. if (CanApplyDstMatrixAsCTM(matrix, paint)) { draw.drawBitmap(*bitmapPtr, matrix, dstPtr, paint); return; } } USE_SHADER: // Since the shader need only live for our stack-frame, pass in a custom allocator. This // can save malloc calls, and signals to SkMakeBitmapShader to not try to copy the bitmap // if its mutable, since that precaution is not needed (give the short lifetime of the shader). SkTBlitterAllocator allocator; // construct a shader, so we can call drawRect with the dst auto s = SkMakeBitmapShader(*bitmapPtr, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &matrix, kNever_SkCopyPixelsMode, &allocator); if (!s) { return; } // we deliberately add a ref, since the allocator wants to be the last owner s.get()->ref(); SkPaint paintWithShader(paint); paintWithShader.setStyle(SkPaint::kFill_Style); paintWithShader.setShader(s); // Call ourself, in case the subclass wanted to share this setup code // but handle the drawRect code themselves. this->drawRect(draw, *dstPtr, paintWithShader); }
SkScalerContext_CairoFT::SkScalerContext_CairoFT(SkTypeface* typeface, const SkDescriptor* desc, cairo_font_face_t* fontFace, FcPattern* pattern) : SkScalerContext_FreeType_Base(typeface, desc) , fLcdFilter(FT_LCD_FILTER_NONE) { SkMatrix matrix; fRec.getSingleMatrix(&matrix); cairo_matrix_t fontMatrix, ctMatrix; cairo_matrix_init(&fontMatrix, matrix.getScaleX(), matrix.getSkewY(), matrix.getSkewX(), matrix.getScaleY(), 0.0, 0.0); cairo_matrix_init_identity(&ctMatrix); cairo_font_options_t *fontOptions = cairo_font_options_create(); fScaledFont = cairo_scaled_font_create(fontFace, &fontMatrix, &ctMatrix, fontOptions); cairo_font_options_destroy(fontOptions); computeShapeMatrix(matrix); #ifdef CAIRO_HAS_FC_FONT resolvePattern(pattern); #endif FT_Int32 loadFlags = FT_LOAD_DEFAULT; if (SkMask::kBW_Format == fRec.fMaskFormat) { if (fRec.getHinting() == SkPaint::kNo_Hinting) { loadFlags |= FT_LOAD_NO_HINTING; } else { loadFlags = FT_LOAD_TARGET_MONO; } loadFlags |= FT_LOAD_MONOCHROME; } else { switch (fRec.getHinting()) { case SkPaint::kNo_Hinting: loadFlags |= FT_LOAD_NO_HINTING; break; case SkPaint::kSlight_Hinting: loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT break; case SkPaint::kNormal_Hinting: if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { loadFlags |= FT_LOAD_FORCE_AUTOHINT; } break; case SkPaint::kFull_Hinting: if (isLCD(fRec)) { if (fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag) { loadFlags = FT_LOAD_TARGET_LCD_V; } else { loadFlags = FT_LOAD_TARGET_LCD; } } if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { loadFlags |= FT_LOAD_FORCE_AUTOHINT; } break; default: SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting()); break; } } // Disable autohinting to disable hinting even for "tricky" fonts. if (!gFontHintingEnabled) { loadFlags |= FT_LOAD_NO_AUTOHINT; } if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) { loadFlags |= FT_LOAD_NO_BITMAP; } // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct // advances, as fontconfig and cairo do. // See http://code.google.com/p/skia/issues/detail?id=222. loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; if (fRec.fFlags & SkScalerContext::kVertical_Flag) { loadFlags |= FT_LOAD_VERTICAL_LAYOUT; } #ifdef FT_LOAD_COLOR loadFlags |= FT_LOAD_COLOR; #endif fLoadGlyphFlags = loadFlags; }
// Create a selection of imagefilter-based paints to test static void create_paints(SkImageFilter* source, SkTArray<SkPaint>* paints) { { SkMatrix scale; scale.setScale(2.0f, 2.0f); SkAutoTUnref<SkImageFilter> scaleMIF( SkImageFilter::CreateMatrixFilter(scale, kLow_SkFilterQuality, source)); add_paint(scaleMIF, paints); } { SkMatrix rot; rot.setRotate(-33.3f); SkAutoTUnref<SkImageFilter> rotMIF( SkImageFilter::CreateMatrixFilter(rot, kLow_SkFilterQuality, source)); add_paint(rotMIF, paints); } { SkRect src = SkRect::MakeXYWH(20, 20, 10, 10); SkRect dst = SkRect::MakeXYWH(30, 30, 30, 30); SkAutoTUnref<SkImageFilter> tileIF( SkTileImageFilter::Create(src, dst, nullptr)); add_paint(tileIF, paints); } { static const SkDropShadowImageFilter::ShadowMode kBoth = SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode; SkAutoTUnref<SkImageFilter> dsif( SkDropShadowImageFilter::Create(10.0f, 10.0f, 3.0f, 3.0f, SK_ColorRED, kBoth, source, nullptr)); add_paint(dsif, paints); } { SkAutoTUnref<SkImageFilter> dsif( SkDropShadowImageFilter::Create(27.0f, 27.0f, 3.0f, 3.0f, SK_ColorRED, SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode, source, nullptr)); add_paint(dsif, paints); } { SkAutoTUnref<SkImageFilter> bif(SkBlurImageFilter::Create(3, 3, source)); add_paint(bif, paints); } { SkAutoTUnref<SkImageFilter> oif(SkOffsetImageFilter::Create(15, 15, source)); add_paint(oif, paints); } }
void SkScalerContext_CairoFT::generateMetrics(SkGlyph* glyph) { SkASSERT(fScaledFont != nullptr); glyph->zeroMetrics(); CairoLockedFTFace faceLock(fScaledFont); FT_Face face = faceLock.getFace(); FT_Error err = FT_Load_Glyph( face, glyph->getGlyphID(), fLoadGlyphFlags ); if (err != 0) { return; } prepareGlyph(face->glyph); switch (face->glyph->format) { case FT_GLYPH_FORMAT_OUTLINE: if (!face->glyph->outline.n_contours) { break; } FT_BBox bbox; FT_Outline_Get_CBox(&face->glyph->outline, &bbox); bbox.xMin &= ~63; bbox.yMin &= ~63; bbox.xMax = (bbox.xMax + 63) & ~63; bbox.yMax = (bbox.yMax + 63) & ~63; glyph->fWidth = SkToU16(SkFDot6Floor(bbox.xMax - bbox.xMin)); glyph->fHeight = SkToU16(SkFDot6Floor(bbox.yMax - bbox.yMin)); glyph->fTop = -SkToS16(SkFDot6Floor(bbox.yMax)); glyph->fLeft = SkToS16(SkFDot6Floor(bbox.xMin)); if (isLCD(fRec) && gSetLcdFilter && (fLcdFilter == FT_LCD_FILTER_DEFAULT || fLcdFilter == FT_LCD_FILTER_LIGHT)) { if (fRec.fFlags & kLCD_Vertical_Flag) { glyph->fTop -= 1; glyph->fHeight += 2; } else { glyph->fLeft -= 1; glyph->fWidth += 2; } } break; case FT_GLYPH_FORMAT_BITMAP: #ifdef FT_LOAD_COLOR if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { glyph->fMaskFormat = SkMask::kARGB32_Format; } #endif if (isLCD(fRec)) { fRec.fMaskFormat = SkMask::kA8_Format; } if (fHaveShape) { // Apply the shape matrix to the glyph's bounding box. SkMatrix matrix; fRec.getSingleMatrix(&matrix); matrix.preScale(SkScalarInvert(fScaleX), SkScalarInvert(fScaleY)); SkRect srcRect = SkRect::MakeXYWH( SkIntToScalar(face->glyph->bitmap_left), -SkIntToScalar(face->glyph->bitmap_top), SkIntToScalar(face->glyph->bitmap.width), SkIntToScalar(face->glyph->bitmap.rows)); SkRect destRect; matrix.mapRect(&destRect, srcRect); SkIRect glyphRect = destRect.roundOut(); glyph->fWidth = SkToU16(glyphRect.width()); glyph->fHeight = SkToU16(glyphRect.height()); glyph->fTop = SkToS16(SkScalarRoundToInt(destRect.fTop)); glyph->fLeft = SkToS16(SkScalarRoundToInt(destRect.fLeft)); } else { glyph->fWidth = SkToU16(face->glyph->bitmap.width); glyph->fHeight = SkToU16(face->glyph->bitmap.rows); glyph->fTop = -SkToS16(face->glyph->bitmap_top); glyph->fLeft = SkToS16(face->glyph->bitmap_left); } break; default: SkDEBUGFAIL("unknown glyph format"); return; } if (fRec.fFlags & SkScalerContext::kVertical_Flag) { glyph->fAdvanceX = -SkFDot6ToFloat(face->glyph->advance.x); glyph->fAdvanceY = SkFDot6ToFloat(face->glyph->advance.y); } else { glyph->fAdvanceX = SkFDot6ToFloat(face->glyph->advance.x); glyph->fAdvanceY = -SkFDot6ToFloat(face->glyph->advance.y); } }
// Should we add this to canvas? static void postTranslate(SkCanvas* canvas, SkScalar dx, SkScalar dy) { SkMatrix m = canvas->getTotalMatrix(); m.postTranslate(dx, dy); canvas->setMatrix(m); }
SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { fState.get()->fImage.lockPixels(); SkMatrix finalMatrix = fState.get()->fCanvasTransform; finalMatrix.preConcat(fState.get()->fShaderTransform); SkRect surfaceBBox; surfaceBBox.set(fState.get()->fBBox); if (!transformBBox(finalMatrix, &surfaceBBox)) { return; } SkMatrix unflip; unflip.setTranslate(0, SkScalarRoundToScalar(surfaceBBox.height())); unflip.preScale(SK_Scalar1, -SK_Scalar1); SkISize size = SkISize::Make(SkScalarRound(surfaceBBox.width()), SkScalarRound(surfaceBBox.height())); SkPDFDevice pattern(size, size, unflip); SkCanvas canvas(&pattern); canvas.translate(-surfaceBBox.fLeft, -surfaceBBox.fTop); finalMatrix.preTranslate(surfaceBBox.fLeft, surfaceBBox.fTop); const SkBitmap* image = &fState.get()->fImage; SkScalar width = SkIntToScalar(image->width()); SkScalar height = SkIntToScalar(image->height()); SkShader::TileMode tileModes[2]; tileModes[0] = fState.get()->fImageTileModes[0]; tileModes[1] = fState.get()->fImageTileModes[1]; canvas.drawBitmap(*image, 0, 0); SkRect patternBBox = SkRect::MakeXYWH(-surfaceBBox.fLeft, -surfaceBBox.fTop, width, height); // Tiling is implied. First we handle mirroring. if (tileModes[0] == SkShader::kMirror_TileMode) { SkMatrix xMirror; xMirror.setScale(-1, 1); xMirror.postTranslate(2 * width, 0); canvas.drawBitmapMatrix(*image, xMirror); patternBBox.fRight += width; } if (tileModes[1] == SkShader::kMirror_TileMode) { SkMatrix yMirror; yMirror.setScale(SK_Scalar1, -SK_Scalar1); yMirror.postTranslate(0, 2 * height); canvas.drawBitmapMatrix(*image, yMirror); patternBBox.fBottom += height; } if (tileModes[0] == SkShader::kMirror_TileMode && tileModes[1] == SkShader::kMirror_TileMode) { SkMatrix mirror; mirror.setScale(-1, -1); mirror.postTranslate(2 * width, 2 * height); canvas.drawBitmapMatrix(*image, mirror); } // Then handle Clamping, which requires expanding the pattern canvas to // cover the entire surfaceBBox. // If both x and y are in clamp mode, we start by filling in the corners. // (Which are just a rectangles of the corner colors.) if (tileModes[0] == SkShader::kClamp_TileMode && tileModes[1] == SkShader::kClamp_TileMode) { SkPaint paint; SkRect rect; rect = SkRect::MakeLTRB(surfaceBBox.fLeft, surfaceBBox.fTop, 0, 0); if (!rect.isEmpty()) { paint.setColor(image->getColor(0, 0)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(width, surfaceBBox.fTop, surfaceBBox.fRight, 0); if (!rect.isEmpty()) { paint.setColor(image->getColor(image->width() - 1, 0)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(width, height, surfaceBBox.fRight, surfaceBBox.fBottom); if (!rect.isEmpty()) { paint.setColor(image->getColor(image->width() - 1, image->height() - 1)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(surfaceBBox.fLeft, height, 0, surfaceBBox.fBottom); if (!rect.isEmpty()) { paint.setColor(image->getColor(0, image->height() - 1)); canvas.drawRect(rect, paint); } } // Then expand the left, right, top, then bottom. if (tileModes[0] == SkShader::kClamp_TileMode) { SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image->height()); if (surfaceBBox.fLeft < 0) { SkBitmap left; SkAssertResult(image->extractSubset(&left, subset)); SkMatrix leftMatrix; leftMatrix.setScale(-surfaceBBox.fLeft, 1); leftMatrix.postTranslate(surfaceBBox.fLeft, 0); canvas.drawBitmapMatrix(left, leftMatrix); if (tileModes[1] == SkShader::kMirror_TileMode) { leftMatrix.postScale(SK_Scalar1, -SK_Scalar1); leftMatrix.postTranslate(0, 2 * height); canvas.drawBitmapMatrix(left, leftMatrix); } patternBBox.fLeft = 0; } if (surfaceBBox.fRight > width) { SkBitmap right; subset.offset(image->width() - 1, 0); SkAssertResult(image->extractSubset(&right, subset)); SkMatrix rightMatrix; rightMatrix.setScale(surfaceBBox.fRight - width, 1); rightMatrix.postTranslate(width, 0); canvas.drawBitmapMatrix(right, rightMatrix); if (tileModes[1] == SkShader::kMirror_TileMode) { rightMatrix.postScale(SK_Scalar1, -SK_Scalar1); rightMatrix.postTranslate(0, 2 * height); canvas.drawBitmapMatrix(right, rightMatrix); } patternBBox.fRight = surfaceBBox.width(); } } if (tileModes[1] == SkShader::kClamp_TileMode) { SkIRect subset = SkIRect::MakeXYWH(0, 0, image->width(), 1); if (surfaceBBox.fTop < 0) { SkBitmap top; SkAssertResult(image->extractSubset(&top, subset)); SkMatrix topMatrix; topMatrix.setScale(SK_Scalar1, -surfaceBBox.fTop); topMatrix.postTranslate(0, surfaceBBox.fTop); canvas.drawBitmapMatrix(top, topMatrix); if (tileModes[0] == SkShader::kMirror_TileMode) { topMatrix.postScale(-1, 1); topMatrix.postTranslate(2 * width, 0); canvas.drawBitmapMatrix(top, topMatrix); } patternBBox.fTop = 0; } if (surfaceBBox.fBottom > height) { SkBitmap bottom; subset.offset(0, image->height() - 1); SkAssertResult(image->extractSubset(&bottom, subset)); SkMatrix bottomMatrix; bottomMatrix.setScale(SK_Scalar1, surfaceBBox.fBottom - height); bottomMatrix.postTranslate(0, height); canvas.drawBitmapMatrix(bottom, bottomMatrix); if (tileModes[0] == SkShader::kMirror_TileMode) { bottomMatrix.postScale(-1, 1); bottomMatrix.postTranslate(2 * width, 0); canvas.drawBitmapMatrix(bottom, bottomMatrix); } patternBBox.fBottom = surfaceBBox.height(); } } SkRefPtr<SkPDFArray> patternBBoxArray = new SkPDFArray; patternBBoxArray->unref(); // SkRefPtr and new both took a reference. patternBBoxArray->reserve(4); patternBBoxArray->appendScalar(patternBBox.fLeft); patternBBoxArray->appendScalar(patternBBox.fTop); patternBBoxArray->appendScalar(patternBBox.fRight); patternBBoxArray->appendScalar(patternBBox.fBottom); // Put the canvas into the pattern stream (fContent). SkRefPtr<SkStream> content = pattern.content(); content->unref(); // SkRefPtr and content() both took a reference. pattern.getResources(&fResources, false); setData(content.get()); insertName("Type", "Pattern"); insertInt("PatternType", 1); insertInt("PaintType", 1); insertInt("TilingType", 1); insert("BBox", patternBBoxArray.get()); insertScalar("XStep", patternBBox.width()); insertScalar("YStep", patternBBox.height()); insert("Resources", pattern.getResourceDict()); insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref(); fState.get()->fImage.unlockPixels(); }
void SkBitmapDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) { SkMatrix matrix; SkRect bitmapBounds, tmpSrc, tmpDst; SkBitmap tmpBitmap; bitmapBounds.isetWH(bitmap.width(), bitmap.height()); // Compute matrix from the two rectangles if (src) { tmpSrc = *src; } else { tmpSrc = bitmapBounds; } matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit); const SkRect* dstPtr = &dst; const SkBitmap* bitmapPtr = &bitmap; // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if // needed (if the src was clipped). No check needed if src==null. if (src) { if (!bitmapBounds.contains(*src)) { if (!tmpSrc.intersect(bitmapBounds)) { return; // nothing to draw } // recompute dst, based on the smaller tmpSrc matrix.mapRect(&tmpDst, tmpSrc); dstPtr = &tmpDst; } // since we may need to clamp to the borders of the src rect within // the bitmap, we extract a subset. const SkIRect srcIR = tmpSrc.roundOut(); if(bitmap.pixelRef()->getTexture()) { // Accelerated source canvas, don't use extractSubset but readPixels to get the subset. // This way, the pixels are copied in CPU memory instead of GPU memory. bitmap.pixelRef()->readPixels(&tmpBitmap, &srcIR); } else { if (!bitmap.extractSubset(&tmpBitmap, srcIR)) { return; } } bitmapPtr = &tmpBitmap; // Since we did an extract, we need to adjust the matrix accordingly SkScalar dx = 0, dy = 0; if (srcIR.fLeft > 0) { dx = SkIntToScalar(srcIR.fLeft); } if (srcIR.fTop > 0) { dy = SkIntToScalar(srcIR.fTop); } if (dx || dy) { matrix.preTranslate(dx, dy); } SkRect extractedBitmapBounds; extractedBitmapBounds.isetWH(bitmapPtr->width(), bitmapPtr->height()); if (extractedBitmapBounds == tmpSrc) { // no fractional part in src, we can just call drawBitmap goto USE_DRAWBITMAP; } } else { USE_DRAWBITMAP: // We can go faster by just calling drawBitmap, which will concat the // matrix with the CTM, and try to call drawSprite if it can. If not, // it will make a shader and call drawRect, as we do below. draw.drawBitmap(*bitmapPtr, matrix, dstPtr, paint); return; } // construct a shader, so we can call drawRect with the dst SkShader* s = SkShader::CreateBitmapShader(*bitmapPtr, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &matrix); if (nullptr == s) { return; } SkPaint paintWithShader(paint); paintWithShader.setStyle(SkPaint::kFill_Style); paintWithShader.setShader(s)->unref(); // Call ourself, in case the subclass wanted to share this setup code // but handle the drawRect code themselves. this->drawRect(draw, *dstPtr, paintWithShader); }
// Currently asPoints is more restrictive then it needs to be. In the future // we need to: // allow kRound_Cap capping (could allow rotations in the matrix with this) // allow paths to be returned bool SkDashImpl::asPoints(PointData* results, const SkPath& src, const SkStrokeRec& rec, const SkMatrix& matrix, const SkRect* cullRect) const { // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out if (0 >= rec.getWidth()) { return false; } // TODO: this next test could be eased up. We could allow any number of // intervals as long as all the ons match and all the offs match. // Additionally, they do not necessarily need to be integers. // We cannot allow arbitrary intervals since we want the returned points // to be uniformly sized. if (fCount != 2 || !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) || !SkScalarIsInt(fIntervals[0]) || !SkScalarIsInt(fIntervals[1])) { return false; } SkPoint pts[2]; if (!src.isLine(pts)) { return false; } // TODO: this test could be eased up to allow circles if (SkPaint::kButt_Cap != rec.getCap()) { return false; } // TODO: this test could be eased up for circles. Rotations could be allowed. if (!matrix.rectStaysRect()) { return false; } // See if the line can be limited to something plausible. if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) { return false; } SkScalar length = SkPoint::Distance(pts[1], pts[0]); SkVector tangent = pts[1] - pts[0]; if (tangent.isZero()) { return false; } tangent.scale(SkScalarInvert(length)); // TODO: make this test for horizontal & vertical lines more robust bool isXAxis = true; if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) || SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) { results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth())); } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) || SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) { results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0])); isXAxis = false; } else if (SkPaint::kRound_Cap != rec.getCap()) { // Angled lines don't have axis-aligned boxes. return false; } if (results) { results->fFlags = 0; SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength); if (SkPaint::kRound_Cap == rec.getCap()) { results->fFlags |= PointData::kCircles_PointFlag; } results->fNumPoints = 0; SkScalar len2 = length; if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { SkASSERT(len2 >= clampedInitialDashLength); if (0 == fInitialDashIndex) { if (clampedInitialDashLength > 0) { if (clampedInitialDashLength >= fIntervals[0]) { ++results->fNumPoints; // partial first dash } len2 -= clampedInitialDashLength; } len2 -= fIntervals[1]; // also skip first space if (len2 < 0) { len2 = 0; } } else { len2 -= clampedInitialDashLength; // skip initial partial empty } } // Too many midpoints can cause results->fNumPoints to overflow or // otherwise cause the results->fPoints allocation below to OOM. // Cap it to a sane value. SkScalar numIntervals = len2 / fIntervalLength; if (!SkScalarIsFinite(numIntervals) || numIntervals > SkDashPath::kMaxDashCount) { return false; } int numMidPoints = SkScalarFloorToInt(numIntervals); results->fNumPoints += numMidPoints; len2 -= numMidPoints * fIntervalLength; bool partialLast = false; if (len2 > 0) { if (len2 < fIntervals[0]) { partialLast = true; } else { ++numMidPoints; ++results->fNumPoints; } } results->fPoints = new SkPoint[results->fNumPoints]; SkScalar distance = 0; int curPt = 0; if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { SkASSERT(clampedInitialDashLength <= length); if (0 == fInitialDashIndex) { if (clampedInitialDashLength > 0) { // partial first block SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles SkScalar x = pts[0].fX + tangent.fX * SkScalarHalf(clampedInitialDashLength); SkScalar y = pts[0].fY + tangent.fY * SkScalarHalf(clampedInitialDashLength); SkScalar halfWidth, halfHeight; if (isXAxis) { halfWidth = SkScalarHalf(clampedInitialDashLength); halfHeight = SkScalarHalf(rec.getWidth()); } else { halfWidth = SkScalarHalf(rec.getWidth()); halfHeight = SkScalarHalf(clampedInitialDashLength); } if (clampedInitialDashLength < fIntervals[0]) { // This one will not be like the others results->fFirst.addRect(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight); } else { SkASSERT(curPt < results->fNumPoints); results->fPoints[curPt].set(x, y); ++curPt; } distance += clampedInitialDashLength; } distance += fIntervals[1]; // skip over the next blank block too } else { distance += clampedInitialDashLength; } } if (0 != numMidPoints) { distance += SkScalarHalf(fIntervals[0]); for (int i = 0; i < numMidPoints; ++i) { SkScalar x = pts[0].fX + tangent.fX * distance; SkScalar y = pts[0].fY + tangent.fY * distance; SkASSERT(curPt < results->fNumPoints); results->fPoints[curPt].set(x, y); ++curPt; distance += fIntervalLength; } distance -= SkScalarHalf(fIntervals[0]); } if (partialLast) { // partial final block SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles SkScalar temp = length - distance; SkASSERT(temp < fIntervals[0]); SkScalar x = pts[0].fX + tangent.fX * (distance + SkScalarHalf(temp)); SkScalar y = pts[0].fY + tangent.fY * (distance + SkScalarHalf(temp)); SkScalar halfWidth, halfHeight; if (isXAxis) { halfWidth = SkScalarHalf(temp); halfHeight = SkScalarHalf(rec.getWidth()); } else { halfWidth = SkScalarHalf(rec.getWidth()); halfHeight = SkScalarHalf(temp); } results->fLast.addRect(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight); } SkASSERT(curPt == results->fNumPoints); } return true; }
bool SkBitmapProcInfo::init(const SkMatrix& inv, const SkPaint& paint) { const int origW = fProvider.info().width(); const int origH = fProvider.info().height(); fPixmap.reset(); fInvMatrix = inv; fFilterQuality = paint.getFilterQuality(); bool allow_ignore_fractional_translate = true; // historical default if (kMedium_SkFilterQuality == fFilterQuality) { allow_ignore_fractional_translate = false; } SkDefaultBitmapController controller; fBMState = controller.requestBitmap(fProvider, inv, paint.getFilterQuality(), fBMStateStorage.get(), fBMStateStorage.size()); // Note : we allow the controller to return an empty (zero-dimension) result. Should we? if (nullptr == fBMState || fBMState->pixmap().info().isEmpty()) { return false; } fPixmap = fBMState->pixmap(); fInvMatrix = fBMState->invMatrix(); fRealInvMatrix = fBMState->invMatrix(); fPaintColor = paint.getColor(); fFilterQuality = fBMState->quality(); SkASSERT(fPixmap.addr()); bool trivialMatrix = (fInvMatrix.getType() & ~SkMatrix::kTranslate_Mask) == 0; bool clampClamp = SkShader::kClamp_TileMode == fTileModeX && SkShader::kClamp_TileMode == fTileModeY; // Most of the scanline procs deal with "unit" texture coordinates, as this // makes it easy to perform tiling modes (repeat = (x & 0xFFFF)). To generate // those, we divide the matrix by its dimensions here. // // We don't do this if we're either trivial (can ignore the matrix) or clamping // in both X and Y since clamping to width,height is just as easy as to 0xFFFF. if (!(clampClamp || trivialMatrix)) { fInvMatrix.postIDiv(fPixmap.width(), fPixmap.height()); } // Now that all possible changes to the matrix have taken place, check // to see if we're really close to a no-scale matrix. If so, explicitly // set it to be so. Subsequent code may inspect this matrix to choose // a faster path in this case. // This code will only execute if the matrix has some scale component; // if it's already pure translate then we won't do this inversion. if (matrix_only_scale_translate(fInvMatrix)) { SkMatrix forward; if (fInvMatrix.invert(&forward)) { if ((clampClamp && allow_ignore_fractional_translate) ? just_trans_clamp(forward, fPixmap) : just_trans_general(forward)) { fInvMatrix.setTranslate(-forward.getTranslateX(), -forward.getTranslateY()); } } } fInvType = fInvMatrix.getType(); // If our target pixmap is the same as the original, then we revert back to legacy behavior // and allow the code to ignore fractional translate. // // The width/height check allows allow_ignore_fractional_translate to stay false if we // previously set it that way (e.g. we started in kMedium). // if (fPixmap.width() == origW && fPixmap.height() == origH) { allow_ignore_fractional_translate = true; } if (kLow_SkFilterQuality == fFilterQuality && allow_ignore_fractional_translate) { // Only try bilerp if the matrix is "interesting" and // the image has a suitable size. if (fInvType <= SkMatrix::kTranslate_Mask || !valid_for_filtering(fPixmap.width() | fPixmap.height())) { fFilterQuality = kNone_SkFilterQuality; } } return true; }
static bool get_segments(const SkPath& path, const SkMatrix& m, SegmentArray* segments, SkPoint* fanPt, int* vCount, int* iCount) { SkPath::Iter iter(path, true); // This renderer over-emphasizes very thin path regions. We use the distance // to the path from the sample to compute coverage. Every pixel intersected // by the path will be hit and the maximum distance is sqrt(2)/2. We don't // notice that the sample may be close to a very thin area of the path and // thus should be very light. This is particularly egregious for degenerate // line paths. We detect paths that are very close to a line (zero area) and // draw nothing. DegenerateTestData degenerateData; SkPathPriv::FirstDirection dir; // get_direction can fail for some degenerate paths. if (!get_direction(path, m, &dir)) { return false; } for (;;) { SkPoint pts[4]; SkPath::Verb verb = iter.next(pts); switch (verb) { case SkPath::kMove_Verb: m.mapPoints(pts, 1); update_degenerate_test(°enerateData, pts[0]); break; case SkPath::kLine_Verb: { m.mapPoints(&pts[1], 1); update_degenerate_test(°enerateData, pts[1]); add_line_to_segment(pts[1], segments); break; } case SkPath::kQuad_Verb: m.mapPoints(pts, 3); update_degenerate_test(°enerateData, pts[1]); update_degenerate_test(°enerateData, pts[2]); add_quad_segment(pts, segments); break; case SkPath::kConic_Verb: { m.mapPoints(pts, 3); SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.5f); for (int i = 0; i < converter.countQuads(); ++i) { update_degenerate_test(°enerateData, quadPts[2*i + 1]); update_degenerate_test(°enerateData, quadPts[2*i + 2]); add_quad_segment(quadPts + 2*i, segments); } break; } case SkPath::kCubic_Verb: { m.mapPoints(pts, 4); update_degenerate_test(°enerateData, pts[1]); update_degenerate_test(°enerateData, pts[2]); update_degenerate_test(°enerateData, pts[3]); add_cubic_segments(pts, dir, segments); break; }; case SkPath::kDone_Verb: if (degenerateData.isDegenerate()) { return false; } else { compute_vectors(segments, fanPt, dir, vCount, iCount); return true; } default: break; } } }
// true iff the matrix contains, at most, scale and translate elements static bool matrix_only_scale_translate(const SkMatrix& m) { return m.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask); }
static bool only_scale_and_translate(const SkMatrix& matrix) { unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask; return (matrix.getType() & ~mask) == 0; }
uint32_t SkPictureStateTree::Iterator::draw() { SkASSERT(this->isValid()); if (fPlaybackIndex >= fDraws->count()) { // restore back to where we started if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); } fCurrentNode = fCurrentNode->fParent; while (NULL != fCurrentNode) { if (fCurrentNode->fFlags & Node::kSave_Flag) { fCanvas->restore(); } if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); } fCurrentNode = fCurrentNode->fParent; } fCanvas->setMatrix(fPlaybackMatrix); return kDrawComplete; } Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]); Node* targetNode = draw->fNode; if (fSave) { fCanvas->save(SkCanvas::kClip_SaveFlag); fSave = false; } if (fCurrentNode != targetNode) { // If we're not at the target and we don't have a list of nodes to get there, we need to // figure out the path from our current node, to the target if (fNodes.count() == 0) { // Trace back up to a common ancestor, restoring to get our current state to match that // of the ancestor, and saving a list of nodes whose state we need to apply to get to // the target (we can restore up to the ancestor immediately, but we'll need to return // an offset for each node on the way down to the target, to apply the desired clips and // saveLayers, so it may take several draw() calls before the next draw actually occurs) Node* tmp = fCurrentNode; Node* ancestor = targetNode; while (tmp != ancestor) { uint16_t currentLevel = tmp->fLevel; uint16_t targetLevel = ancestor->fLevel; if (currentLevel >= targetLevel) { if (tmp != fCurrentNode && tmp->fFlags & Node::kSave_Flag) { fCanvas->restore(); } if (tmp->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); } tmp = tmp->fParent; } if (currentLevel <= targetLevel) { fNodes.push(ancestor); ancestor = ancestor->fParent; } } if (ancestor->fFlags & Node::kSave_Flag) { if (fCurrentNode != ancestor) { fCanvas->restore(); } if (targetNode != ancestor) { fCanvas->save(SkCanvas::kClip_SaveFlag); } } fCurrentNode = ancestor; } // If we're not at the target node yet, we'll need to return an offset to make the caller // apply the next clip or saveLayer. if (fCurrentNode != targetNode) { if (fCurrentMatrix != fNodes.top()->fMatrix) { fCurrentMatrix = fNodes.top()->fMatrix; SkMatrix tmp = *fNodes.top()->fMatrix; tmp.postConcat(fPlaybackMatrix); fCanvas->setMatrix(tmp); } uint32_t offset = fNodes.top()->fOffset; fCurrentNode = fNodes.top(); fSave = fCurrentNode != targetNode && fCurrentNode->fFlags & Node::kSave_Flag; fNodes.pop(); return offset; } } // If we got this far, the clip/saveLayer state is all set, so we can proceed to set the matrix // for the draw, and return its offset. if (fCurrentMatrix != draw->fMatrix) { SkMatrix tmp = *draw->fMatrix; tmp.postConcat(fPlaybackMatrix); fCanvas->setMatrix(tmp); fCurrentMatrix = draw->fMatrix; } ++fPlaybackIndex; return draw->fOffset; }
void SkPicturePlayback::handleOp(SkReader32* reader, DrawType op, uint32_t size, SkCanvas* canvas, const SkMatrix& initialMatrix) { switch (op) { case NOOP: { SkASSERT(size >= 4); reader->skip(size - 4); } break; case CLIP_PATH: { const SkPath& path = fPictureData->getPath(reader); uint32_t packed = reader->readInt(); SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed); bool doAA = ClipParams_unpackDoAA(packed); size_t offsetToRestore = reader->readInt(); SkASSERT(!offsetToRestore || offsetToRestore >= reader->offset()); canvas->clipPath(path, regionOp, doAA); if (canvas->isClipEmpty() && offsetToRestore) { reader->setOffset(offsetToRestore); } } break; case CLIP_REGION: { SkRegion region; reader->readRegion(®ion); uint32_t packed = reader->readInt(); SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed); size_t offsetToRestore = reader->readInt(); SkASSERT(!offsetToRestore || offsetToRestore >= reader->offset()); canvas->clipRegion(region, regionOp); if (canvas->isClipEmpty() && offsetToRestore) { reader->setOffset(offsetToRestore); } } break; case CLIP_RECT: { const SkRect& rect = reader->skipT<SkRect>(); uint32_t packed = reader->readInt(); SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed); bool doAA = ClipParams_unpackDoAA(packed); size_t offsetToRestore = reader->readInt(); SkASSERT(!offsetToRestore || offsetToRestore >= reader->offset()); canvas->clipRect(rect, regionOp, doAA); if (canvas->isClipEmpty() && offsetToRestore) { reader->setOffset(offsetToRestore); } } break; case CLIP_RRECT: { SkRRect rrect; reader->readRRect(&rrect); uint32_t packed = reader->readInt(); SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed); bool doAA = ClipParams_unpackDoAA(packed); size_t offsetToRestore = reader->readInt(); SkASSERT(!offsetToRestore || offsetToRestore >= reader->offset()); canvas->clipRRect(rrect, regionOp, doAA); if (canvas->isClipEmpty() && offsetToRestore) { reader->setOffset(offsetToRestore); } } break; case PUSH_CULL: break; // Deprecated, safe to ignore both push and pop. case POP_CULL: break; case CONCAT: { SkMatrix matrix; reader->readMatrix(&matrix); canvas->concat(matrix); break; } case DRAW_ATLAS: { const SkPaint* paint = fPictureData->getPaint(reader); const SkImage* atlas = fPictureData->getImage(reader); const uint32_t flags = reader->readU32(); const int count = reader->readU32(); const SkRSXform* xform = (const SkRSXform*)reader->skip(count * sizeof(SkRSXform)); const SkRect* tex = (const SkRect*)reader->skip(count * sizeof(SkRect)); const SkColor* colors = NULL; SkXfermode::Mode mode = SkXfermode::kDst_Mode; if (flags & DRAW_ATLAS_HAS_COLORS) { colors = (const SkColor*)reader->skip(count * sizeof(SkColor)); mode = (SkXfermode::Mode)reader->readU32(); } const SkRect* cull = NULL; if (flags & DRAW_ATLAS_HAS_CULL) { cull = (const SkRect*)reader->skip(sizeof(SkRect)); } canvas->drawAtlas(atlas, xform, tex, colors, count, mode, cull, paint); } break; case DRAW_BITMAP: { const SkPaint* paint = fPictureData->getPaint(reader); const SkBitmap bitmap = shallow_copy(fPictureData->getBitmap(reader)); const SkPoint& loc = reader->skipT<SkPoint>(); canvas->drawBitmap(bitmap, loc.fX, loc.fY, paint); } break; case DRAW_BITMAP_RECT_TO_RECT: { const SkPaint* paint = fPictureData->getPaint(reader); const SkBitmap bitmap = shallow_copy(fPictureData->getBitmap(reader)); const SkRect* src = get_rect_ptr(reader); // may be null const SkRect& dst = reader->skipT<SkRect>(); // required SkCanvas::DrawBitmapRectFlags flags; flags = (SkCanvas::DrawBitmapRectFlags) reader->readInt(); canvas->drawBitmapRectToRect(bitmap, src, dst, paint, flags); } break; case DRAW_BITMAP_MATRIX: { const SkPaint* paint = fPictureData->getPaint(reader); const SkBitmap bitmap = shallow_copy(fPictureData->getBitmap(reader)); SkMatrix matrix; reader->readMatrix(&matrix); SkAutoCanvasRestore acr(canvas, true); canvas->concat(matrix); canvas->drawBitmap(bitmap, 0, 0, paint); } break; case DRAW_BITMAP_NINE: { const SkPaint* paint = fPictureData->getPaint(reader); const SkBitmap bitmap = shallow_copy(fPictureData->getBitmap(reader)); const SkIRect& src = reader->skipT<SkIRect>(); const SkRect& dst = reader->skipT<SkRect>(); canvas->drawBitmapNine(bitmap, src, dst, paint); } break; case DRAW_CLEAR: canvas->clear(reader->readInt()); break; case DRAW_DATA: { // This opcode is now dead, just need to skip it for backwards compatibility size_t length = reader->readInt(); (void)reader->skip(length); // skip handles padding the read out to a multiple of 4 } break; case DRAW_DRRECT: { const SkPaint& paint = *fPictureData->getPaint(reader); SkRRect outer, inner; reader->readRRect(&outer); reader->readRRect(&inner); canvas->drawDRRect(outer, inner, paint); } break; case BEGIN_COMMENT_GROUP: reader->readString(); // deprecated (M44) break; case COMMENT: reader->readString(); reader->readString(); // deprecated (M44) break; case END_COMMENT_GROUP: // deprecated (M44) break; case DRAW_IMAGE: { const SkPaint* paint = fPictureData->getPaint(reader); const SkImage* image = fPictureData->getImage(reader); const SkPoint& loc = reader->skipT<SkPoint>(); canvas->drawImage(image, loc.fX, loc.fY, paint); } break; case DRAW_IMAGE_NINE: { const SkPaint* paint = fPictureData->getPaint(reader); const SkImage* image = fPictureData->getImage(reader); const SkIRect& center = reader->skipT<SkIRect>(); const SkRect& dst = reader->skipT<SkRect>(); canvas->drawImageNine(image, center, dst, paint); } break; case DRAW_IMAGE_RECT: { const SkPaint* paint = fPictureData->getPaint(reader); const SkImage* image = fPictureData->getImage(reader); const SkRect* src = get_rect_ptr(reader); // may be null const SkRect& dst = reader->skipT<SkRect>(); // required canvas->drawImageRect(image, src, dst, paint); } break; case DRAW_OVAL: { const SkPaint& paint = *fPictureData->getPaint(reader); canvas->drawOval(reader->skipT<SkRect>(), paint); } break; case DRAW_PAINT: canvas->drawPaint(*fPictureData->getPaint(reader)); break; case DRAW_PATCH: { const SkPaint& paint = *fPictureData->getPaint(reader); const SkPoint* cubics = (const SkPoint*)reader->skip(SkPatchUtils::kNumCtrlPts * sizeof(SkPoint)); uint32_t flag = reader->readInt(); const SkColor* colors = NULL; if (flag & DRAW_VERTICES_HAS_COLORS) { colors = (const SkColor*)reader->skip(SkPatchUtils::kNumCorners * sizeof(SkColor)); } const SkPoint* texCoords = NULL; if (flag & DRAW_VERTICES_HAS_TEXS) { texCoords = (const SkPoint*)reader->skip(SkPatchUtils::kNumCorners * sizeof(SkPoint)); } SkAutoTUnref<SkXfermode> xfer; if (flag & DRAW_VERTICES_HAS_XFER) { int mode = reader->readInt(); if (mode < 0 || mode > SkXfermode::kLastMode) { mode = SkXfermode::kModulate_Mode; } xfer.reset(SkXfermode::Create((SkXfermode::Mode)mode)); } canvas->drawPatch(cubics, colors, texCoords, xfer, paint); } break; case DRAW_PATH: { const SkPaint& paint = *fPictureData->getPaint(reader); canvas->drawPath(fPictureData->getPath(reader), paint); } break; case DRAW_PICTURE: canvas->drawPicture(fPictureData->getPicture(reader)); break; case DRAW_PICTURE_MATRIX_PAINT: { const SkPaint* paint = fPictureData->getPaint(reader); SkMatrix matrix; reader->readMatrix(&matrix); const SkPicture* pic = fPictureData->getPicture(reader); canvas->drawPicture(pic, &matrix, paint); } break; case DRAW_POINTS: { const SkPaint& paint = *fPictureData->getPaint(reader); SkCanvas::PointMode mode = (SkCanvas::PointMode)reader->readInt(); size_t count = reader->readInt(); const SkPoint* pts = (const SkPoint*)reader->skip(sizeof(SkPoint)* count); canvas->drawPoints(mode, count, pts, paint); } break; case DRAW_POS_TEXT: { const SkPaint& paint = *fPictureData->getPaint(reader); TextContainer text; get_text(reader, &text); size_t points = reader->readInt(); const SkPoint* pos = (const SkPoint*)reader->skip(points * sizeof(SkPoint)); canvas->drawPosText(text.text(), text.length(), pos, paint); } break; case DRAW_POS_TEXT_TOP_BOTTOM: { const SkPaint& paint = *fPictureData->getPaint(reader); TextContainer text; get_text(reader, &text); size_t points = reader->readInt(); const SkPoint* pos = (const SkPoint*)reader->skip(points * sizeof(SkPoint)); const SkScalar top = reader->readScalar(); const SkScalar bottom = reader->readScalar(); if (!canvas->quickRejectY(top, bottom)) { canvas->drawPosText(text.text(), text.length(), pos, paint); } } break; case DRAW_POS_TEXT_H: { const SkPaint& paint = *fPictureData->getPaint(reader); TextContainer text; get_text(reader, &text); size_t xCount = reader->readInt(); const SkScalar constY = reader->readScalar(); const SkScalar* xpos = (const SkScalar*)reader->skip(xCount * sizeof(SkScalar)); canvas->drawPosTextH(text.text(), text.length(), xpos, constY, paint); } break; case DRAW_POS_TEXT_H_TOP_BOTTOM: { const SkPaint& paint = *fPictureData->getPaint(reader); TextContainer text; get_text(reader, &text); size_t xCount = reader->readInt(); const SkScalar* xpos = (const SkScalar*)reader->skip((3 + xCount) * sizeof(SkScalar)); const SkScalar top = *xpos++; const SkScalar bottom = *xpos++; const SkScalar constY = *xpos++; if (!canvas->quickRejectY(top, bottom)) { canvas->drawPosTextH(text.text(), text.length(), xpos, constY, paint); } } break; case DRAW_RECT: { const SkPaint& paint = *fPictureData->getPaint(reader); canvas->drawRect(reader->skipT<SkRect>(), paint); } break; case DRAW_RRECT: { const SkPaint& paint = *fPictureData->getPaint(reader); SkRRect rrect; reader->readRRect(&rrect); canvas->drawRRect(rrect, paint); } break; case DRAW_SPRITE: { const SkPaint* paint = fPictureData->getPaint(reader); const SkBitmap bitmap = shallow_copy(fPictureData->getBitmap(reader)); int left = reader->readInt(); int top = reader->readInt(); canvas->drawSprite(bitmap, left, top, paint); } break; case DRAW_TEXT: { const SkPaint& paint = *fPictureData->getPaint(reader); TextContainer text; get_text(reader, &text); SkScalar x = reader->readScalar(); SkScalar y = reader->readScalar(); canvas->drawText(text.text(), text.length(), x, y, paint); } break; case DRAW_TEXT_BLOB: { const SkPaint& paint = *fPictureData->getPaint(reader); const SkTextBlob* blob = fPictureData->getTextBlob(reader); SkScalar x = reader->readScalar(); SkScalar y = reader->readScalar(); canvas->drawTextBlob(blob, x, y, paint); } break; case DRAW_TEXT_TOP_BOTTOM: { const SkPaint& paint = *fPictureData->getPaint(reader); TextContainer text; get_text(reader, &text); const SkScalar* ptr = (const SkScalar*)reader->skip(4 * sizeof(SkScalar)); // ptr[0] == x // ptr[1] == y // ptr[2] == top // ptr[3] == bottom if (!canvas->quickRejectY(ptr[2], ptr[3])) { canvas->drawText(text.text(), text.length(), ptr[0], ptr[1], paint); } } break; case DRAW_TEXT_ON_PATH: { const SkPaint& paint = *fPictureData->getPaint(reader); TextContainer text; get_text(reader, &text); const SkPath& path = fPictureData->getPath(reader); SkMatrix matrix; reader->readMatrix(&matrix); canvas->drawTextOnPath(text.text(), text.length(), path, &matrix, paint); } break; case DRAW_VERTICES: { SkAutoTUnref<SkXfermode> xfer; const SkPaint& paint = *fPictureData->getPaint(reader); DrawVertexFlags flags = (DrawVertexFlags)reader->readInt(); SkCanvas::VertexMode vmode = (SkCanvas::VertexMode)reader->readInt(); int vCount = reader->readInt(); const SkPoint* verts = (const SkPoint*)reader->skip(vCount * sizeof(SkPoint)); const SkPoint* texs = NULL; const SkColor* colors = NULL; const uint16_t* indices = NULL; int iCount = 0; if (flags & DRAW_VERTICES_HAS_TEXS) { texs = (const SkPoint*)reader->skip(vCount * sizeof(SkPoint)); } if (flags & DRAW_VERTICES_HAS_COLORS) { colors = (const SkColor*)reader->skip(vCount * sizeof(SkColor)); } if (flags & DRAW_VERTICES_HAS_INDICES) { iCount = reader->readInt(); indices = (const uint16_t*)reader->skip(iCount * sizeof(uint16_t)); } if (flags & DRAW_VERTICES_HAS_XFER) { int mode = reader->readInt(); if (mode < 0 || mode > SkXfermode::kLastMode) { mode = SkXfermode::kModulate_Mode; } xfer.reset(SkXfermode::Create((SkXfermode::Mode)mode)); } canvas->drawVertices(vmode, vCount, verts, texs, colors, xfer, indices, iCount, paint); } break; case RESTORE: canvas->restore(); break; case ROTATE: canvas->rotate(reader->readScalar()); break; case SAVE: // SKPs with version < 29 also store a SaveFlags param. if (size > 4) { SkASSERT(8 == size); reader->readInt(); } canvas->save(); break; case SAVE_LAYER: { const SkRect* boundsPtr = get_rect_ptr(reader); const SkPaint* paint = fPictureData->getPaint(reader); canvas->saveLayer(boundsPtr, paint, (SkCanvas::SaveFlags) reader->readInt()); } break; case SCALE: { SkScalar sx = reader->readScalar(); SkScalar sy = reader->readScalar(); canvas->scale(sx, sy); } break; case SET_MATRIX: { SkMatrix matrix; reader->readMatrix(&matrix); matrix.postConcat(initialMatrix); canvas->setMatrix(matrix); } break; case SKEW: { SkScalar sx = reader->readScalar(); SkScalar sy = reader->readScalar(); canvas->skew(sx, sy); } break; case TRANSLATE: { SkScalar dx = reader->readScalar(); SkScalar dy = reader->readScalar(); canvas->translate(dx, dy); } break; default: SkASSERTF(false, "Unknown draw type: %d", op); } }
void SkDumpCanvas::didSetMatrix(const SkMatrix& matrix) { SkString str; matrix.toString(&str); this->dump(kMatrix_Verb, NULL, "setMatrix(%s)", str.c_str()); this->INHERITED::didSetMatrix(matrix); }