/** * Determine if the matrix can be treated as integral-only-translate, * for the purpose of filtering. */ static bool just_trans_integral(const SkMatrix& m) { static constexpr SkScalar tol = SK_Scalar1 / 256; return m.getType() <= SkMatrix::kTranslate_Mask && SkScalarNearlyEqual(m.getTranslateX(), SkScalarRoundToScalar(m.getTranslateX()), tol) && SkScalarNearlyEqual(m.getTranslateY(), SkScalarRoundToScalar(m.getTranslateY()), tol); }
void SkDeferredCanvas::Rec::setConcat(const SkMatrix& m) { SkASSERT(m.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)); if (m.getType() <= SkMatrix::kTranslate_Mask) { fType = kTrans_Type; fData.fTranslate.set(m.getTranslateX(), m.getTranslateY()); } else { fType = kScaleTrans_Type; fData.fScaleTrans.fScale.set(m.getScaleX(), m.getScaleY()); fData.fScaleTrans.fTrans.set(m.getTranslateX(), m.getTranslateY()); } }
void SkGPipeCanvas::recordTranslate(const SkMatrix& m) { if (this->needOpBytes(2 * sizeof(SkScalar))) { this->writeOp(kTranslate_DrawOp); fWriter.writeScalar(m.getTranslateX()); fWriter.writeScalar(m.getTranslateY()); } }
bool GrBicubicEffect::ShouldUseBicubic(const SkMatrix& matrix, GrTextureParams::FilterMode* filterMode) { if (matrix.isIdentity()) { *filterMode = GrTextureParams::kNone_FilterMode; return false; } SkScalar scales[2]; if (!matrix.getMinMaxScales(scales) || scales[0] < SK_Scalar1) { // Bicubic doesn't handle arbitrary minimization well, as src texels can be skipped // entirely, *filterMode = GrTextureParams::kMipMap_FilterMode; return false; } // At this point if scales[1] == SK_Scalar1 then the matrix doesn't do any scaling. if (scales[1] == SK_Scalar1) { if (matrix.rectStaysRect() && SkScalarIsInt(matrix.getTranslateX()) && SkScalarIsInt(matrix.getTranslateY())) { *filterMode = GrTextureParams::kNone_FilterMode; } else { // Use bilerp to handle rotation or fractional translation. *filterMode = GrTextureParams::kBilerp_FilterMode; } return false; } // When we use the bicubic filtering effect each sample is read from the texture using // nearest neighbor sampling. *filterMode = GrTextureParams::kNone_FilterMode; return true; }
void GrCCPathParser::parsePath(const SkMatrix& m, const SkPath& path, SkRect* devBounds, SkRect* devBounds45) { const SkPoint* pts = SkPathPriv::PointData(path); int numPts = path.countPoints(); SkASSERT(numPts + 1 <= fLocalDevPtsBuffer.count()); if (!numPts) { devBounds->setEmpty(); devBounds45->setEmpty(); this->parsePath(path, nullptr); return; } // m45 transforms path points into "45 degree" device space. A bounding box in this space gives // the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal // transform is not necessary as long as the shader uses the correct inverse. SkMatrix m45; m45.setSinCos(1, 1); m45.preConcat(m); // X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points: // device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords). // | 1 1 | Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY()); Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY()); Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY()); // Map the path's points to device space and accumulate bounding boxes. Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T); devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt); Sk4f topLeft = devPt; Sk4f bottomRight = devPt; // Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two, // and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must // be at least one larger than the number of points. devPt.store(&fLocalDevPtsBuffer[0]); for (int i = 1; i < numPts; ++i) { devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T); devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt); topLeft = Sk4f::Min(topLeft, devPt); bottomRight = Sk4f::Max(bottomRight, devPt); devPt.store(&fLocalDevPtsBuffer[i]); } SkPoint topLeftPts[2], bottomRightPts[2]; topLeft.store(topLeftPts); bottomRight.store(bottomRightPts); devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(), bottomRightPts[0].y()); devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(), bottomRightPts[1].y()); this->parsePath(path, fLocalDevPtsBuffer.get()); }
sk_sp<GrFragmentProcessor> SkPerlinNoiseShader::asFragmentProcessor(const AsFPArgs& args) const { SkASSERT(args.fContext); SkMatrix localMatrix = this->getLocalMatrix(); if (args.fLocalMatrix) { localMatrix.preConcat(*args.fLocalMatrix); } SkMatrix matrix = *args.fViewMatrix; matrix.preConcat(localMatrix); if (0 == fNumOctaves) { if (kFractalNoise_Type == fType) { // Extract the incoming alpha and emit rgba = (a/4, a/4, a/4, a/2) // TODO: Either treat the output of this shader as sRGB or allow client to specify a // color space of the noise. Either way, this case (and the GLSL) need to convert to // the destination. sk_sp<GrFragmentProcessor> inner( GrConstColorProcessor::Make(GrColor4f::FromGrColor(0x80404040), GrConstColorProcessor::kModulateRGBA_InputMode)); return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); } // Emit zero. return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(), GrConstColorProcessor::kIgnore_InputMode); } // Either we don't stitch tiles, either we have a valid tile size SkASSERT(!fStitchTiles || !fTileSize.isEmpty()); SkPerlinNoiseShader::PaintingData* paintingData = new PaintingData(fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, matrix); sk_sp<GrTextureProxy> permutationsProxy(GrMakeCachedBitmapProxy( args.fContext->resourceProvider(), paintingData->getPermutationsBitmap())); sk_sp<GrTextureProxy> noiseProxy(GrMakeCachedBitmapProxy(args.fContext->resourceProvider(), paintingData->getNoiseBitmap())); SkMatrix m = *args.fViewMatrix; m.setTranslateX(-localMatrix.getTranslateX() + SK_Scalar1); m.setTranslateY(-localMatrix.getTranslateY() + SK_Scalar1); if (permutationsProxy && noiseProxy) { sk_sp<GrFragmentProcessor> inner( GrPerlinNoiseEffect::Make(args.fContext->resourceProvider(), fType, fNumOctaves, fStitchTiles, paintingData, std::move(permutationsProxy), std::move(noiseProxy), m)); return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); } delete paintingData; return nullptr; }
void SkPictureRecord::recordTranslate(const SkMatrix& m) { SkASSERT(SkMatrix::kTranslate_Mask == m.getType()); // op + dx + dy size_t size = 1 * kUInt32Size + 2 * sizeof(SkScalar); size_t initialOffset = this->addDraw(TRANSLATE, &size); this->addScalar(m.getTranslateX()); this->addScalar(m.getTranslateY()); this->validate(initialOffset, size); }
static void calculate_translation(bool applyVM, const SkMatrix& newViewMatrix, SkScalar newX, SkScalar newY, const SkMatrix& currentViewMatrix, SkScalar currentX, SkScalar currentY, SkScalar* transX, SkScalar* transY) { if (applyVM) { *transX = newViewMatrix.getTranslateX() + newViewMatrix.getScaleX() * (newX - currentX) + newViewMatrix.getSkewX() * (newY - currentY) - currentViewMatrix.getTranslateX(); *transY = newViewMatrix.getTranslateY() + newViewMatrix.getSkewY() * (newX - currentX) + newViewMatrix.getScaleY() * (newY - currentY) - currentViewMatrix.getTranslateY(); } else { *transX = newX - currentX; *transY = newY - currentY; } }
const GrFragmentProcessor* SkPerlinNoiseShader::asFragmentProcessor( GrContext* context, const SkMatrix& viewM, const SkMatrix* externalLocalMatrix, SkFilterQuality) const { SkASSERT(context); SkMatrix localMatrix = this->getLocalMatrix(); if (externalLocalMatrix) { localMatrix.preConcat(*externalLocalMatrix); } SkMatrix matrix = viewM; matrix.preConcat(localMatrix); if (0 == fNumOctaves) { if (kFractalNoise_Type == fType) { // Extract the incoming alpha and emit rgba = (a/4, a/4, a/4, a/2) SkAutoTUnref<const GrFragmentProcessor> inner( GrConstColorProcessor::Create(0x80404040, GrConstColorProcessor::kModulateRGBA_InputMode)); return GrFragmentProcessor::MulOutputByInputAlpha(inner); } // Emit zero. return GrConstColorProcessor::Create(0x0, GrConstColorProcessor::kIgnore_InputMode); } // Either we don't stitch tiles, either we have a valid tile size SkASSERT(!fStitchTiles || !fTileSize.isEmpty()); SkPerlinNoiseShader::PaintingData* paintingData = new PaintingData(fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, matrix); SkAutoTUnref<GrTexture> permutationsTexture( GrRefCachedBitmapTexture(context, paintingData->getPermutationsBitmap(), GrTextureParams::ClampNoFilter())); SkAutoTUnref<GrTexture> noiseTexture( GrRefCachedBitmapTexture(context, paintingData->getNoiseBitmap(), GrTextureParams::ClampNoFilter())); SkMatrix m = viewM; m.setTranslateX(-localMatrix.getTranslateX() + SK_Scalar1); m.setTranslateY(-localMatrix.getTranslateY() + SK_Scalar1); if ((permutationsTexture) && (noiseTexture)) { SkAutoTUnref<GrFragmentProcessor> inner( GrPerlinNoiseEffect::Create(fType, fNumOctaves, fStitchTiles, paintingData, permutationsTexture, noiseTexture, m)); return GrFragmentProcessor::MulOutputByInputAlpha(inner); } delete paintingData; return nullptr; }
SkLinearBitmapPipeline::SkLinearBitmapPipeline( const SkMatrix& inverse, SkFilterQuality filterQuality, SkShader::TileMode xTile, SkShader::TileMode yTile, SkColor paintColor, const SkPixmap& srcPixmap) { SkISize dimensions = srcPixmap.info().dimensions(); const SkImageInfo& srcImageInfo = srcPixmap.info(); SkMatrix adjustedInverse = inverse; if (filterQuality == kNone_SkFilterQuality) { if (inverse.getScaleX() >= 0.0f) { adjustedInverse.setTranslateX( nextafterf(inverse.getTranslateX(), std::floor(inverse.getTranslateX()))); } if (inverse.getScaleY() >= 0.0f) { adjustedInverse.setTranslateY( nextafterf(inverse.getTranslateY(), std::floor(inverse.getTranslateY()))); } } SkScalar dx = adjustedInverse.getScaleX(); // If it is an index 8 color type, the sampler converts to unpremul for better fidelity. SkAlphaType alphaType = srcImageInfo.alphaType(); if (srcPixmap.colorType() == kIndex_8_SkColorType) { alphaType = kUnpremul_SkAlphaType; } float postAlpha = SkColorGetA(paintColor) * (1.0f / 255.0f); // As the stages are built, the chooser function may skip a stage. For example, with the // identity matrix, the matrix stage is skipped, and the tilerStage is the first stage. auto blenderStage = choose_blender_for_shading(alphaType, postAlpha, &fBlenderStage); auto samplerStage = choose_pixel_sampler( blenderStage, filterQuality, xTile, yTile, srcPixmap, paintColor, &fSampleStage, &fAccessor); auto tilerStage = choose_tiler(samplerStage, dimensions, xTile, yTile, filterQuality, dx, &fTileStage); fFirstStage = choose_matrix(tilerStage, adjustedInverse, &fMatrixStage); fLastStage = blenderStage; }
void SkLatticeIter::mapDstScaleTranslate(const SkMatrix& matrix) { SkASSERT(matrix.isScaleTranslate()); SkScalar tx = matrix.getTranslateX(); SkScalar sx = matrix.getScaleX(); for (int i = 0; i < fDstX.count(); i++) { fDstX[i] = fDstX[i] * sx + tx; } SkScalar ty = matrix.getTranslateY(); SkScalar sy = matrix.getScaleY(); for (int i = 0; i < fDstY.count(); i++) { fDstY[i] = fDstY[i] * sy + ty; } }
SkPerlinNoiseShader::PerlinNoiseShaderContext::PerlinNoiseShaderContext( const SkPerlinNoiseShader& shader, const ContextRec& rec) : INHERITED(shader, rec) { SkMatrix newMatrix = *rec.fMatrix; newMatrix.preConcat(shader.getLocalMatrix()); if (rec.fLocalMatrix) { newMatrix.preConcat(*rec.fLocalMatrix); } // This (1,1) translation is due to WebKit's 1 based coordinates for the noise // (as opposed to 0 based, usually). The same adjustment is in the setData() function. fMatrix.setTranslate(-newMatrix.getTranslateX() + SK_Scalar1, -newMatrix.getTranslateY() + SK_Scalar1); fPaintingData = SkNEW_ARGS(PaintingData, (shader.fTileSize, shader.fSeed, shader.fBaseFrequencyX, shader.fBaseFrequencyY, newMatrix)); }
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]); }
void SkFlatMatrix::dump() const { const SkMatrix* matrix = (const SkMatrix*) fMatrixData; char pBuffer[DUMP_BUFFER_SIZE]; char* bufferPtr = pBuffer; bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "matrix: "); SkScalar scaleX = matrix->getScaleX(); SkMatrix defaultMatrix; defaultMatrix.reset(); if (scaleX != defaultMatrix.getScaleX()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "scaleX:%g ", SkScalarToFloat(scaleX)); SkScalar scaleY = matrix->getScaleY(); if (scaleY != defaultMatrix.getScaleY()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "scaleY:%g ", SkScalarToFloat(scaleY)); SkScalar skewX = matrix->getSkewX(); if (skewX != defaultMatrix.getSkewX()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "skewX:%g ", SkScalarToFloat(skewX)); SkScalar skewY = matrix->getSkewY(); if (skewY != defaultMatrix.getSkewY()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "skewY:%g ", SkScalarToFloat(skewY)); SkScalar translateX = matrix->getTranslateX(); if (translateX != defaultMatrix.getTranslateX()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "translateX:%g ", SkScalarToFloat(translateX)); SkScalar translateY = matrix->getTranslateY(); if (translateY != defaultMatrix.getTranslateY()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "translateY:%g ", SkScalarToFloat(translateY)); SkScalar perspX = matrix->getPerspX(); if (perspX != defaultMatrix.getPerspX()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "perspX:%g ", SkFractToFloat(perspX)); SkScalar perspY = matrix->getPerspY(); if (perspY != defaultMatrix.getPerspY()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "perspY:%g ", SkFractToFloat(perspY)); SkDebugf("%s\n", pBuffer); }
void SkDumpCanvas::didConcat(const SkMatrix& matrix) { SkString str; switch (matrix.getType()) { case SkMatrix::kTranslate_Mask: this->dump(kMatrix_Verb, nullptr, "translate(%g %g)", SkScalarToFloat(matrix.getTranslateX()), SkScalarToFloat(matrix.getTranslateY())); break; case SkMatrix::kScale_Mask: this->dump(kMatrix_Verb, nullptr, "scale(%g %g)", SkScalarToFloat(matrix.getScaleX()), SkScalarToFloat(matrix.getScaleY())); break; default: matrix.toString(&str); this->dump(kMatrix_Verb, nullptr, "concat(%s)", str.c_str()); break; } this->INHERITED::didConcat(matrix); }
void LoggingCanvas::didConcat(const SkMatrix& matrix) { AutoLogger logger(this); RefPtr<JSONObject> params; switch (matrix.getType()) { case SkMatrix::kTranslate_Mask: params = logger.logItemWithParams("translate"); params->setNumber("dx", matrix.getTranslateX()); params->setNumber("dy", matrix.getTranslateY()); break; case SkMatrix::kScale_Mask: params = logger.logItemWithParams("scale"); params->setNumber("scaleX", matrix.getScaleX()); params->setNumber("scaleY", matrix.getScaleY()); break; default: params = logger.logItemWithParams("concat"); params->setArray("matrix", arrayForSkMatrix(matrix)); } this->SkCanvas::didConcat(matrix); }
std::unique_ptr<GrFillRRectOp> GrFillRRectOp::Make( GrRecordingContext* ctx, GrAAType aaType, const SkMatrix& viewMatrix, const SkRRect& rrect, const GrCaps& caps, GrPaint&& paint) { if (!caps.instanceAttribSupport()) { return nullptr; } Flags flags = Flags::kNone; if (GrAAType::kCoverage == aaType) { // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we // already use HW derivatives. The only trick will be adjusting the AA outset to account for // perspective. (i.e., outset = 0.5 * z.) if (viewMatrix.hasPerspective()) { return nullptr; } if (can_use_hw_derivatives_with_coverage(*caps.shaderCaps(), viewMatrix, rrect)) { // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms // in coverage mode. We use them as long as the approximation will be accurate enough. flags |= Flags::kUseHWDerivatives; } } else { if (GrAAType::kMSAA == aaType) { if (!caps.sampleLocationsSupport() || !caps.shaderCaps()->sampleVariablesSupport()) { return nullptr; } } if (viewMatrix.hasPerspective()) { // HW derivatives are consistently slower on all platforms in sample mask mode. We // therefore only use them when there is perspective, since then we can't interpolate // the symbolic screen-space gradient. flags |= Flags::kUseHWDerivatives | Flags::kHasPerspective; } } // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space. float l = rrect.rect().left(), r = rrect.rect().right(), t = rrect.rect().top(), b = rrect.rect().bottom(); SkMatrix m; // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b]. m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2); // Map to device space. m.postConcat(viewMatrix); SkRect devBounds; if (!(flags & Flags::kHasPerspective)) { // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's // device-space quad, it's quite simple to find the bounding rectangle: devBounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0); devBounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()), SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY())); } else { viewMatrix.mapRect(&devBounds, rrect.rect()); } if (GrAAType::kMSAA == aaType && caps.preferTrianglesOverSampleMask()) { // We are on a platform that prefers fine triangles instead of using the sample mask. See if // the round rect is large enough that it will be faster for us to send it off to the // default path renderer instead. The 200x200 threshold was arrived at using the // "shapes_rrect" benchmark on an ARM Galaxy S9. if (devBounds.height() * devBounds.width() > 200 * 200) { return nullptr; } } GrOpMemoryPool* pool = ctx->priv().opMemoryPool(); return pool->allocate<GrFillRRectOp>(aaType, rrect, flags, m, std::move(paint), devBounds); }
SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END bool SkImageShader::onAppendStages(const StageRec& rec) const { SkRasterPipeline* p = rec.fPipeline; SkArenaAlloc* alloc = rec.fAlloc; SkMatrix matrix; if (!this->computeTotalInverse(rec.fCTM, rec.fLocalM, &matrix)) { return false; } auto quality = rec.fPaint.getFilterQuality(); SkBitmapProvider provider(fImage.get(), rec.fDstCS); SkDefaultBitmapController controller; std::unique_ptr<SkBitmapController::State> state { controller.requestBitmap(provider, matrix, quality) }; if (!state) { return false; } const SkPixmap& pm = state->pixmap(); matrix = state->invMatrix(); quality = state->quality(); auto info = pm.info(); // When the matrix is just an integer translate, bilerp == nearest neighbor. if (quality == kLow_SkFilterQuality && matrix.getType() <= SkMatrix::kTranslate_Mask && matrix.getTranslateX() == (int)matrix.getTranslateX() && matrix.getTranslateY() == (int)matrix.getTranslateY()) { quality = kNone_SkFilterQuality; } // See skia:4649 and the GM image_scale_aligned. if (quality == kNone_SkFilterQuality) { if (matrix.getScaleX() >= 0) { matrix.setTranslateX(nextafterf(matrix.getTranslateX(), floorf(matrix.getTranslateX()))); } if (matrix.getScaleY() >= 0) { matrix.setTranslateY(nextafterf(matrix.getTranslateY(), floorf(matrix.getTranslateY()))); } } p->append(SkRasterPipeline::seed_shader); struct MiscCtx { std::unique_ptr<SkBitmapController::State> state; SkColor4f paint_color; }; auto misc = alloc->make<MiscCtx>(); misc->state = std::move(state); // Extend lifetime to match the pipeline's. misc->paint_color = SkColor4f_from_SkColor(rec.fPaint.getColor(), rec.fDstCS); p->append_matrix(alloc, matrix); auto gather = alloc->make<SkJumper_GatherCtx>(); gather->pixels = pm.addr(); gather->stride = pm.rowBytesAsPixels(); gather->width = pm.width(); gather->height = pm.height(); auto limit_x = alloc->make<SkJumper_TileCtx>(), limit_y = alloc->make<SkJumper_TileCtx>(); limit_x->scale = pm.width(); limit_x->invScale = 1.0f / pm.width(); limit_y->scale = pm.height(); limit_y->invScale = 1.0f / pm.height(); bool is_srgb = rec.fDstCS && (!info.colorSpace() || info.gammaCloseToSRGB()); SkJumper_DecalTileCtx* decal_ctx = nullptr; bool decal_x_and_y = fTileModeX == kDecal_TileMode && fTileModeY == kDecal_TileMode; if (fTileModeX == kDecal_TileMode || fTileModeY == kDecal_TileMode) { decal_ctx = alloc->make<SkJumper_DecalTileCtx>(); decal_ctx->limit_x = limit_x->scale; decal_ctx->limit_y = limit_y->scale; } auto append_tiling_and_gather = [&] { if (decal_x_and_y) { p->append(SkRasterPipeline::decal_x_and_y, decal_ctx); } else { switch (fTileModeX) { case kClamp_TileMode: /* The gather_xxx stage will clamp for us. */ break; case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, limit_x); break; case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, limit_x); break; case kDecal_TileMode: p->append(SkRasterPipeline::decal_x, decal_ctx); break; } switch (fTileModeY) { case kClamp_TileMode: /* The gather_xxx stage will clamp for us. */ break; case kMirror_TileMode: p->append(SkRasterPipeline::mirror_y, limit_y); break; case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_y, limit_y); break; case kDecal_TileMode: p->append(SkRasterPipeline::decal_y, decal_ctx); break; } } void* ctx = gather; switch (info.colorType()) { case kAlpha_8_SkColorType: p->append(SkRasterPipeline::gather_a8, ctx); break; case kGray_8_SkColorType: p->append(SkRasterPipeline::gather_g8, ctx); break; case kRGB_565_SkColorType: p->append(SkRasterPipeline::gather_565, ctx); break; case kARGB_4444_SkColorType: p->append(SkRasterPipeline::gather_4444, ctx); break; case kBGRA_8888_SkColorType: p->append(SkRasterPipeline::gather_bgra, ctx); break; case kRGBA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); break; case kRGBA_1010102_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); break; case kRGBA_F16_SkColorType: p->append(SkRasterPipeline::gather_f16, ctx); break; case kRGB_888x_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); p->append(SkRasterPipeline::force_opaque ); break; case kRGB_101010x_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); p->append(SkRasterPipeline::force_opaque ); break; default: SkASSERT(false); } if (decal_ctx) { p->append(SkRasterPipeline::check_decal_mask, decal_ctx); } if (is_srgb) { p->append(SkRasterPipeline::from_srgb); } }; auto append_misc = [&] { if (info.colorType() == kAlpha_8_SkColorType) { p->append(SkRasterPipeline::set_rgb, &misc->paint_color); } if (info.colorType() == kAlpha_8_SkColorType || info.alphaType() == kUnpremul_SkAlphaType) { p->append(SkRasterPipeline::premul); } if (quality > kLow_SkFilterQuality) { // Bicubic filtering naturally produces out of range values on both sides. p->append(SkRasterPipeline::clamp_0); p->append(fClampAsIfUnpremul ? SkRasterPipeline::clamp_1 : SkRasterPipeline::clamp_a); } append_gamut_transform(p, alloc, info.colorSpace(), rec.fDstCS, fClampAsIfUnpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType); return true; }; // We've got a fast path for 8888 bilinear clamp/clamp non-color-managed sampling. auto ct = info.colorType(); if (true && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) && quality == kLow_SkFilterQuality && fTileModeX == SkShader::kClamp_TileMode && fTileModeY == SkShader::kClamp_TileMode && !is_srgb) { p->append(SkRasterPipeline::bilerp_clamp_8888, gather); if (ct == kBGRA_8888_SkColorType) { p->append(SkRasterPipeline::swap_rb); } return append_misc(); } SkJumper_SamplerCtx* sampler = nullptr; if (quality != kNone_SkFilterQuality) { sampler = alloc->make<SkJumper_SamplerCtx>(); } auto sample = [&](SkRasterPipeline::StockStage setup_x, SkRasterPipeline::StockStage setup_y) { p->append(setup_x, sampler); p->append(setup_y, sampler); append_tiling_and_gather(); p->append(SkRasterPipeline::accumulate, sampler); }; if (quality == kNone_SkFilterQuality) { append_tiling_and_gather(); } else if (quality == kLow_SkFilterQuality) { p->append(SkRasterPipeline::save_xy, sampler); sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_ny); sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_ny); sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_py); sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_py); p->append(SkRasterPipeline::move_dst_src); } else { p->append(SkRasterPipeline::save_xy, sampler); sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n3y); sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n3y); sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n3y); sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n3y); sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n1y); sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n1y); sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n1y); sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n1y); sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p1y); sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p1y); sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p1y); sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p1y); sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p3y); sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p3y); sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p3y); sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p3y); p->append(SkRasterPipeline::move_dst_src); } return append_misc(); }
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; }
bool SkBitmapProcState::chooseProcs(const SkMatrix& inv, const SkPaint& paint) { SkASSERT(fOrigBitmap.width() && fOrigBitmap.height()); fBitmap = NULL; fInvMatrix = inv; fFilterLevel = paint.getFilterLevel(); // possiblyScaleImage will look to see if it can rescale the image as a // preprocess; either by scaling up to the target size, or by selecting // a nearby mipmap level. If it does, it will adjust the working // matrix as well as the working bitmap. It may also adjust the filter // quality to avoid re-filtering an already perfectly scaled image. if (!this->possiblyScaleImage()) { if (!this->lockBaseBitmap()) { return false; } } // The above logic should have always assigned fBitmap, but in case it // didn't, we check for that now... // TODO(dominikg): Ask humper@ if we can just use an SkASSERT(fBitmap)? if (NULL == fBitmap) { return false; } // If we are "still" kMedium_FilterLevel, then the request was not fulfilled by possiblyScale, // so we downgrade to kLow (so the rest of the sniffing code can assume that) if (SkPaint::kMedium_FilterLevel == fFilterLevel) { fFilterLevel = SkPaint::kLow_FilterLevel; } bool trivialMatrix = (fInvMatrix.getType() & ~SkMatrix::kTranslate_Mask) == 0; bool clampClamp = SkShader::kClamp_TileMode == fTileModeX && SkShader::kClamp_TileMode == fTileModeY; if (!(fAdjustedMatrix || clampClamp || trivialMatrix)) { fInvMatrix.postIDiv(fOrigBitmap.width(), fOrigBitmap.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 ? just_trans_clamp(forward, *fBitmap) : just_trans_general(forward)) { SkScalar tx = -SkScalarRoundToScalar(forward.getTranslateX()); SkScalar ty = -SkScalarRoundToScalar(forward.getTranslateY()); fInvMatrix.setTranslate(tx, ty); } } } fInvProc = fInvMatrix.getMapXYProc(); fInvType = fInvMatrix.getType(); fInvSx = SkScalarToFixed(fInvMatrix.getScaleX()); fInvSxFractionalInt = SkScalarToFractionalInt(fInvMatrix.getScaleX()); fInvKy = SkScalarToFixed(fInvMatrix.getSkewY()); fInvKyFractionalInt = SkScalarToFractionalInt(fInvMatrix.getSkewY()); fAlphaScale = SkAlpha255To256(paint.getAlpha()); fShaderProc32 = NULL; fShaderProc16 = NULL; fSampleProc32 = NULL; fSampleProc16 = NULL; // recompute the triviality of the matrix here because we may have // changed it! trivialMatrix = (fInvMatrix.getType() & ~SkMatrix::kTranslate_Mask) == 0; if (SkPaint::kHigh_FilterLevel == fFilterLevel) { // If this is still set, that means we wanted HQ sampling // but couldn't do it as a preprocess. Let's try to install // the scanline version of the HQ sampler. If that process fails, // downgrade to bilerp. // NOTE: Might need to be careful here in the future when we want // to have the platform proc have a shot at this; it's possible that // the chooseBitmapFilterProc will fail to install a shader but a // platform-specific one might succeed, so it might be premature here // to fall back to bilerp. This needs thought. if (!this->setBitmapFilterProcs()) { fFilterLevel = SkPaint::kLow_FilterLevel; } } if (SkPaint::kLow_FilterLevel == fFilterLevel) { // Only try bilerp if the matrix is "interesting" and // the image has a suitable size. if (fInvType <= SkMatrix::kTranslate_Mask || !valid_for_filtering(fBitmap->width() | fBitmap->height())) { fFilterLevel = SkPaint::kNone_FilterLevel; } } // At this point, we know exactly what kind of sampling the per-scanline // shader will perform. fMatrixProc = this->chooseMatrixProc(trivialMatrix); // TODO(dominikg): SkASSERT(fMatrixProc) instead? chooseMatrixProc never returns NULL. if (NULL == fMatrixProc) { return false; } /////////////////////////////////////////////////////////////////////// const SkAlphaType at = fBitmap->alphaType(); // No need to do this if we're doing HQ sampling; if filter quality is // still set to HQ by the time we get here, then we must have installed // the shader procs above and can skip all this. if (fFilterLevel < SkPaint::kHigh_FilterLevel) { int index = 0; if (fAlphaScale < 256) { // note: this distinction is not used for D16 index |= 1; } if (fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)) { index |= 2; } if (fFilterLevel > SkPaint::kNone_FilterLevel) { index |= 4; } // bits 3,4,5 encoding the source bitmap format switch (fBitmap->colorType()) { case kN32_SkColorType: if (kPremul_SkAlphaType != at && kOpaque_SkAlphaType != at) { return false; } index |= 0; break; case kRGB_565_SkColorType: index |= 8; break; case kIndex_8_SkColorType: if (kPremul_SkAlphaType != at && kOpaque_SkAlphaType != at) { return false; } index |= 16; break; case kARGB_4444_SkColorType: if (kPremul_SkAlphaType != at && kOpaque_SkAlphaType != at) { return false; } index |= 24; break; case kAlpha_8_SkColorType: index |= 32; fPaintPMColor = SkPreMultiplyColor(paint.getColor()); break; default: // TODO(dominikg): Should we ever get here? SkASSERT(false) instead? return false; } #if !SK_ARM_NEON_IS_ALWAYS static const SampleProc32 gSkBitmapProcStateSample32[] = { S32_opaque_D32_nofilter_DXDY, S32_alpha_D32_nofilter_DXDY, S32_opaque_D32_nofilter_DX, S32_alpha_D32_nofilter_DX, S32_opaque_D32_filter_DXDY, S32_alpha_D32_filter_DXDY, S32_opaque_D32_filter_DX, S32_alpha_D32_filter_DX, S16_opaque_D32_nofilter_DXDY, S16_alpha_D32_nofilter_DXDY, S16_opaque_D32_nofilter_DX, S16_alpha_D32_nofilter_DX, S16_opaque_D32_filter_DXDY, S16_alpha_D32_filter_DXDY, S16_opaque_D32_filter_DX, S16_alpha_D32_filter_DX, SI8_opaque_D32_nofilter_DXDY, SI8_alpha_D32_nofilter_DXDY, SI8_opaque_D32_nofilter_DX, SI8_alpha_D32_nofilter_DX, SI8_opaque_D32_filter_DXDY, SI8_alpha_D32_filter_DXDY, SI8_opaque_D32_filter_DX, SI8_alpha_D32_filter_DX, S4444_opaque_D32_nofilter_DXDY, S4444_alpha_D32_nofilter_DXDY, S4444_opaque_D32_nofilter_DX, S4444_alpha_D32_nofilter_DX, S4444_opaque_D32_filter_DXDY, S4444_alpha_D32_filter_DXDY, S4444_opaque_D32_filter_DX, S4444_alpha_D32_filter_DX, // A8 treats alpha/opaque the same (equally efficient) SA8_alpha_D32_nofilter_DXDY, SA8_alpha_D32_nofilter_DXDY, SA8_alpha_D32_nofilter_DX, SA8_alpha_D32_nofilter_DX, SA8_alpha_D32_filter_DXDY, SA8_alpha_D32_filter_DXDY, SA8_alpha_D32_filter_DX, SA8_alpha_D32_filter_DX }; static const SampleProc16 gSkBitmapProcStateSample16[] = { S32_D16_nofilter_DXDY, S32_D16_nofilter_DX, S32_D16_filter_DXDY, S32_D16_filter_DX, S16_D16_nofilter_DXDY, S16_D16_nofilter_DX, S16_D16_filter_DXDY, S16_D16_filter_DX, SI8_D16_nofilter_DXDY, SI8_D16_nofilter_DX, SI8_D16_filter_DXDY, SI8_D16_filter_DX, // Don't support 4444 -> 565 NULL, NULL, NULL, NULL, // Don't support A8 -> 565 NULL, NULL, NULL, NULL }; #endif fSampleProc32 = SK_ARM_NEON_WRAP(gSkBitmapProcStateSample32)[index]; index >>= 1; // shift away any opaque/alpha distinction fSampleProc16 = SK_ARM_NEON_WRAP(gSkBitmapProcStateSample16)[index]; // our special-case shaderprocs if (SK_ARM_NEON_WRAP(S16_D16_filter_DX) == fSampleProc16) { if (clampClamp) { fShaderProc16 = SK_ARM_NEON_WRAP(Clamp_S16_D16_filter_DX_shaderproc); } else if (SkShader::kRepeat_TileMode == fTileModeX && SkShader::kRepeat_TileMode == fTileModeY) { fShaderProc16 = SK_ARM_NEON_WRAP(Repeat_S16_D16_filter_DX_shaderproc); } } else if (SK_ARM_NEON_WRAP(SI8_opaque_D32_filter_DX) == fSampleProc32 && clampClamp) { fShaderProc32 = SK_ARM_NEON_WRAP(Clamp_SI8_opaque_D32_filter_DX_shaderproc); } if (NULL == fShaderProc32) { fShaderProc32 = this->chooseShaderProc32(); } }
bool SkBitmapProcInfo::init(const SkMatrix& inv, const SkPaint& paint) { SkASSERT(inv.isScaleTranslate()); fPixmap.reset(); fInvMatrix = inv; fFilterQuality = paint.getFilterQuality(); fBMState = SkBitmapController::RequestBitmap(fProvider, inv, paint.getFilterQuality(), &fAlloc); // 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(fFilterQuality <= kLow_SkFilterQuality); SkASSERT(fPixmap.addr()); bool integral_translate_only = just_trans_integral(fInvMatrix); if (!integral_translate_only) { // 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 (fTileModeX != SkShader::kClamp_TileMode || fTileModeY != SkShader::kClamp_TileMode) { 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) && just_trans_general(forward)) { fInvMatrix.setTranslate(-forward.getTranslateX(), -forward.getTranslateY()); } } // Recompute the flag after matrix adjustments. integral_translate_only = just_trans_integral(fInvMatrix); } fInvType = fInvMatrix.getType(); if (kLow_SkFilterQuality == fFilterQuality && (!valid_for_filtering(fPixmap.width() | fPixmap.height()) || integral_translate_only)) { fFilterQuality = kNone_SkFilterQuality; } return true; }
void SkSVGPaint::setSave(SkSVGParser& parser) { SkTDArray<SkString*> clips; SkSVGPaint* walking = parser.fHead; int index; SkMatrix sum; sum.reset(); while (walking != NULL) { for (index = kInitial + 1; index < kTerminal; index++) { SkString* lastAttr = (*walking)[index]; if (lastAttr->size() == 0) continue; if (index == kTransform) { const char* str = lastAttr->c_str(); SkASSERT(strncmp(str, "matrix(", 7) == 0); str += 6; const char* strEnd = strrchr(str, ')'); SkASSERT(strEnd != NULL); SkString mat(str, strEnd - str); SkSVGParser::ConvertToArray(mat); SkScalar values[6]; SkParse::FindScalars(mat.c_str() + 1, values, 6); SkMatrix matrix; matrix.reset(); matrix.setScaleX(values[0]); matrix.setSkewY(values[1]); matrix.setSkewX(values[2]); matrix.setScaleY(values[3]); matrix.setTranslateX(values[4]); matrix.setTranslateY(values[5]); sum.setConcat(matrix, sum); continue; } if ( index == kClipPath) *clips.insert(0) = lastAttr; } walking = walking->fNext; } if ((sum == parser.fLastTransform) == false) { SkMatrix inverse; bool success = parser.fLastTransform.invert(&inverse); SkASSERT(success == true); SkMatrix output; output.setConcat(inverse, sum); parser.fLastTransform = sum; SkString outputStr; outputStr.appendUnichar('['); outputStr.appendScalar(output.getScaleX()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getSkewX()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getTranslateX()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getSkewY()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getScaleY()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getTranslateY()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getPerspX()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getPerspY()); outputStr.append(",1]"); parser._startElement("matrix"); parser._addAttributeLen("matrix", outputStr.c_str(), outputStr.size()); parser._endElement(); } #if 0 // incomplete if (parser.fTransformClips.size() > 0) { // need to reset the clip when the 'g' scope is ended parser._startElement("add"); const char* start = strchr(current->f_clipPath.c_str(), '#') + 1; SkASSERT(start); parser._addAttributeLen("use", start, strlen(start) - 1); parser._endElement(); // clip } #endif }
/* * Analyze filter-quality and matrix, and decide how to implement that. * * In general, we cascade down the request level [ High ... None ] * - for a given level, if we can fulfill it, fine, else * - else we downgrade to the next lower level and try again. * We can always fulfill requests for Low and None * - sometimes we will "ignore" Low and give None, but this is likely a legacy perf hack * and may be removed. */ bool SkBitmapProcState::chooseProcs(const SkMatrix& inv, const SkPaint& paint) { fPixmap.reset(); fInvMatrix = inv; fFilterLevel = paint.getFilterQuality(); const int origW = fProvider.info().width(); const int origH = fProvider.info().height(); bool allow_ignore_fractional_translate = true; // historical default if (kMedium_SkFilterQuality == fFilterLevel) { 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(); fFilterLevel = 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()); } } } fInvProc = fInvMatrix.getMapXYProc(); fInvType = fInvMatrix.getType(); fInvSx = SkScalarToFixed(fInvMatrix.getScaleX()); fInvSxFractionalInt = SkScalarToFractionalInt(fInvMatrix.getScaleX()); fInvKy = SkScalarToFixed(fInvMatrix.getSkewY()); fInvKyFractionalInt = SkScalarToFractionalInt(fInvMatrix.getSkewY()); fAlphaScale = SkAlpha255To256(paint.getAlpha()); fShaderProc32 = nullptr; fShaderProc16 = nullptr; fSampleProc32 = nullptr; // recompute the triviality of the matrix here because we may have // changed it! trivialMatrix = (fInvMatrix.getType() & ~SkMatrix::kTranslate_Mask) == 0; // 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 == fFilterLevel && 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())) { fFilterLevel = kNone_SkFilterQuality; } } return this->chooseScanlineProcs(trivialMatrix, clampClamp, paint); }