/** * Check to ensure that copying a GPU-backed SkBitmap behaved as expected. * @param reporter Used to report failures. * @param desiredConfig Config being copied to. If the copy succeeded, dst must have this Config. * @param success True if the copy succeeded. * @param src A GPU-backed SkBitmap that had copyTo or deepCopyTo called on it. * @param dst SkBitmap that was copied to. * @param deepCopy True if deepCopyTo was used; false if copyTo was used. */ static void TestIndividualCopy(skiatest::Reporter* reporter, const SkBitmap::Config desiredConfig, const bool success, const SkBitmap& src, const SkBitmap& dst, const bool deepCopy = true) { if (success) { REPORTER_ASSERT(reporter, src.width() == dst.width()); REPORTER_ASSERT(reporter, src.height() == dst.height()); REPORTER_ASSERT(reporter, dst.config() == desiredConfig); if (src.config() == dst.config()) { // FIXME: When calling copyTo (so deepCopy is false here), sometimes we copy the pixels // exactly, in which case the IDs should be the same, but sometimes we do a bitmap draw, // in which case the IDs should not be the same. Is there any way to determine which is // the case at this point? if (deepCopy) { REPORTER_ASSERT(reporter, src.getGenerationID() == dst.getGenerationID()); } REPORTER_ASSERT(reporter, src.pixelRef() != NULL && dst.pixelRef() != NULL); // Do read backs and make sure that the two are the same. SkBitmap srcReadBack, dstReadBack; { SkASSERT(src.getTexture() != NULL); bool readBack = src.pixelRef()->readPixels(&srcReadBack); REPORTER_ASSERT(reporter, readBack); } if (dst.getTexture() != NULL) { bool readBack = dst.pixelRef()->readPixels(&dstReadBack); REPORTER_ASSERT(reporter, readBack); } else { // If dst is not a texture, do a copy instead, to the same config as srcReadBack. bool copy = dst.copyTo(&dstReadBack, srcReadBack.config()); REPORTER_ASSERT(reporter, copy); } SkAutoLockPixels srcLock(srcReadBack); SkAutoLockPixels dstLock(dstReadBack); REPORTER_ASSERT(reporter, srcReadBack.readyToDraw() && dstReadBack.readyToDraw()); const char* srcP = static_cast<const char*>(srcReadBack.getAddr(0, 0)); const char* dstP = static_cast<const char*>(dstReadBack.getAddr(0, 0)); REPORTER_ASSERT(reporter, srcP != dstP); REPORTER_ASSERT(reporter, !memcmp(srcP, dstP, srcReadBack.getSize())); } else { REPORTER_ASSERT(reporter, src.getGenerationID() != dst.getGenerationID()); } } else { // dst should be unchanged from its initial state REPORTER_ASSERT(reporter, dst.config() == SkBitmap::kNo_Config); REPORTER_ASSERT(reporter, dst.width() == 0); REPORTER_ASSERT(reporter, dst.height() == 0); } }
sk_sp<SkSpecialImage> SkMorphologyImageFilter::onFilterImage(SkSpecialImage* source, const Context& ctx, SkIPoint* offset) const { SkIPoint inputOffset = SkIPoint::Make(0, 0); sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset)); if (!input) { return nullptr; } SkIRect bounds; input = this->applyCropRect(this->mapContext(ctx), input.get(), &inputOffset, &bounds); if (!input) { return nullptr; } SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()), SkIntToScalar(this->radius().height())); ctx.ctm().mapVectors(&radius, 1); int width = SkScalarFloorToInt(radius.fX); int height = SkScalarFloorToInt(radius.fY); if (width < 0 || height < 0) { return nullptr; } SkIRect srcBounds = bounds; srcBounds.offset(-inputOffset); if (0 == width && 0 == height) { offset->fX = bounds.left(); offset->fY = bounds.top(); return input->makeSubset(srcBounds); } #if SK_SUPPORT_GPU if (source->isTextureBacked()) { GrContext* context = source->getContext(); auto type = (kDilate_Op == this->op()) ? GrMorphologyEffect::kDilate_MorphologyType : GrMorphologyEffect::kErode_MorphologyType; sk_sp<SkSpecialImage> result(apply_morphology(context, input.get(), srcBounds, type, SkISize::Make(width, height))); if (result) { offset->fX = bounds.left(); offset->fY = bounds.top(); } return result; } #endif SkBitmap inputBM; if (!input->getROPixels(&inputBM)) { return nullptr; } if (inputBM.colorType() != kN32_SkColorType) { return nullptr; } SkImageInfo info = SkImageInfo::Make(bounds.width(), bounds.height(), inputBM.colorType(), inputBM.alphaType()); SkBitmap dst; if (!dst.tryAllocPixels(info)) { return nullptr; } SkAutoLockPixels inputLock(inputBM), dstLock(dst); SkMorphologyImageFilter::Proc procX, procY; if (kDilate_Op == this->op()) { procX = SkOpts::dilate_x; procY = SkOpts::dilate_y; } else { procX = SkOpts::erode_x; procY = SkOpts::erode_y; } if (width > 0 && height > 0) { SkBitmap tmp; if (!tmp.tryAllocPixels(info)) { return nullptr; } SkAutoLockPixels tmpLock(tmp); call_proc_X(procX, inputBM, &tmp, width, srcBounds); SkIRect tmpBounds = SkIRect::MakeWH(srcBounds.width(), srcBounds.height()); call_proc_Y(procY, tmp.getAddr32(tmpBounds.left(), tmpBounds.top()), tmp.rowBytesAsPixels(), &dst, height, tmpBounds); } else if (width > 0) { call_proc_X(procX, inputBM, &dst, width, srcBounds); } else if (height > 0) { call_proc_Y(procY, inputBM.getAddr32(srcBounds.left(), srcBounds.top()), inputBM.rowBytesAsPixels(), &dst, height, srcBounds); } offset->fX = bounds.left(); offset->fY = bounds.top(); return SkSpecialImage::MakeFromRaster(source->internal_getProxy(), SkIRect::MakeWH(bounds.width(), bounds.height()), dst, &source->props()); }
sk_sp<SkSpecialImage> SkDisplacementMapEffect::onFilterImage(SkSpecialImage* source, const Context& ctx, SkIPoint* offset) const { SkIPoint colorOffset = SkIPoint::Make(0, 0); sk_sp<SkSpecialImage> color(this->filterInput(1, source, ctx, &colorOffset)); if (!color) { return nullptr; } SkIPoint displOffset = SkIPoint::Make(0, 0); sk_sp<SkSpecialImage> displ(this->filterInput(0, source, ctx, &displOffset)); if (!displ) { return nullptr; } const SkIRect srcBounds = SkIRect::MakeXYWH(colorOffset.x(), colorOffset.y(), color->width(), color->height()); // Both paths do bounds checking on color pixel access, we don't need to // pad the color bitmap to bounds here. SkIRect bounds; if (!this->applyCropRect(ctx, srcBounds, &bounds)) { return nullptr; } SkIRect displBounds; displ = this->applyCropRect(ctx, displ.get(), &displOffset, &displBounds); if (!displ) { return nullptr; } if (!bounds.intersect(displBounds)) { return nullptr; } const SkIRect colorBounds = bounds.makeOffset(-colorOffset.x(), -colorOffset.y()); SkVector scale = SkVector::Make(fScale, fScale); ctx.ctm().mapVectors(&scale, 1); #if SK_SUPPORT_GPU if (source->isTextureBacked()) { GrContext* context = source->getContext(); sk_sp<GrTexture> colorTexture(color->asTextureRef(context)); sk_sp<GrTexture> displTexture(displ->asTextureRef(context)); if (!colorTexture || !displTexture) { return nullptr; } GrSurfaceDesc desc; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fWidth = bounds.width(); desc.fHeight = bounds.height(); desc.fConfig = kSkia8888_GrPixelConfig; SkAutoTUnref<GrTexture> dst(context->textureProvider()->createApproxTexture(desc)); if (!dst) { return nullptr; } GrPaint paint; SkMatrix offsetMatrix = GrCoordTransform::MakeDivByTextureWHMatrix(displTexture.get()); offsetMatrix.preTranslate(SkIntToScalar(colorOffset.fX - displOffset.fX), SkIntToScalar(colorOffset.fY - displOffset.fY)); paint.addColorFragmentProcessor( GrDisplacementMapEffect::Create(fXChannelSelector, fYChannelSelector, scale, displTexture.get(), offsetMatrix, colorTexture.get(), SkISize::Make(color->width(), color->height())))->unref(); paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); SkMatrix matrix; matrix.setTranslate(-SkIntToScalar(colorBounds.x()), -SkIntToScalar(colorBounds.y())); SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(dst->asRenderTarget())); if (!drawContext) { return nullptr; } drawContext->drawRect(GrClip::WideOpen(), paint, matrix, SkRect::Make(colorBounds)); offset->fX = bounds.left(); offset->fY = bounds.top(); return SkSpecialImage::MakeFromGpu(SkIRect::MakeWH(bounds.width(), bounds.height()), kNeedNewImageUniqueID_SpecialImage, dst); } #endif SkBitmap colorBM, displBM; if (!color->getROPixels(&colorBM) || !displ->getROPixels(&displBM)) { return nullptr; } if ((colorBM.colorType() != kN32_SkColorType) || (displBM.colorType() != kN32_SkColorType)) { return nullptr; } SkAutoLockPixels colorLock(colorBM), displLock(displBM); if (!colorBM.getPixels() || !displBM.getPixels()) { return nullptr; } SkImageInfo info = SkImageInfo::MakeN32(bounds.width(), bounds.height(), colorBM.alphaType()); SkBitmap dst; if (!dst.tryAllocPixels(info)) { return nullptr; } SkAutoLockPixels dstLock(dst); computeDisplacement(fXChannelSelector, fYChannelSelector, scale, &dst, displBM, colorOffset - displOffset, colorBM, colorBounds); offset->fX = bounds.left(); offset->fY = bounds.top(); return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), dst); }
static void TestBitmapCopy(skiatest::Reporter* reporter) { static const Pair gPairs[] = { { SkBitmap::kNo_Config, "00000000" }, { SkBitmap::kA1_Config, "01000000" }, { SkBitmap::kA8_Config, "00101110" }, { SkBitmap::kIndex8_Config, "00111110" }, { SkBitmap::kRGB_565_Config, "00101110" }, { SkBitmap::kARGB_4444_Config, "00101110" }, { SkBitmap::kARGB_8888_Config, "00101110" }, // TODO: create valid RLE bitmap to test with // { SkBitmap::kRLE_Index8_Config, "00101111" } }; const int W = 20; const int H = 33; for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) { for (size_t j = 0; j < SK_ARRAY_COUNT(gPairs); j++) { SkBitmap src, dst; SkColorTable* ct = NULL; src.setConfig(gPairs[i].fConfig, W, H); if (SkBitmap::kIndex8_Config == src.config() || SkBitmap::kRLE_Index8_Config == src.config()) { ct = init_ctable(); } src.allocPixels(ct); ct->safeRef(); init_src(src); bool success = src.copyTo(&dst, gPairs[j].fConfig); bool expected = gPairs[i].fValid[j] != '0'; if (success != expected) { SkString str; str.printf("SkBitmap::copyTo from %s to %s. expected %s returned %s", gConfigName[i], gConfigName[j], boolStr(expected), boolStr(success)); reporter->reportFailed(str); } bool canSucceed = src.canCopyTo(gPairs[j].fConfig); if (success != canSucceed) { SkString str; str.printf("SkBitmap::copyTo from %s to %s. returned %s canCopyTo %s", gConfigName[i], gConfigName[j], boolStr(success), boolStr(canSucceed)); reporter->reportFailed(str); } if (success) { REPORTER_ASSERT(reporter, src.width() == dst.width()); REPORTER_ASSERT(reporter, src.height() == dst.height()); REPORTER_ASSERT(reporter, dst.config() == gPairs[j].fConfig); test_isOpaque(reporter, src, dst.config()); if (src.config() == dst.config()) { SkAutoLockPixels srcLock(src); SkAutoLockPixels dstLock(dst); REPORTER_ASSERT(reporter, src.readyToDraw()); REPORTER_ASSERT(reporter, dst.readyToDraw()); const char* srcP = (const char*)src.getAddr(0, 0); const char* dstP = (const char*)dst.getAddr(0, 0); REPORTER_ASSERT(reporter, srcP != dstP); REPORTER_ASSERT(reporter, !memcmp(srcP, dstP, src.getSize())); } // test extractSubset { SkBitmap subset; SkIRect r; r.set(1, 1, 2, 2); if (src.extractSubset(&subset, r)) { REPORTER_ASSERT(reporter, subset.width() == 1); REPORTER_ASSERT(reporter, subset.height() == 1); SkBitmap copy; REPORTER_ASSERT(reporter, subset.copyTo(©, subset.config())); REPORTER_ASSERT(reporter, copy.width() == 1); REPORTER_ASSERT(reporter, copy.height() == 1); REPORTER_ASSERT(reporter, copy.rowBytes() <= 4); SkAutoLockPixels alp0(subset); SkAutoLockPixels alp1(copy); // they should both have, or both not-have, a colortable bool hasCT = subset.getColorTable() != NULL; REPORTER_ASSERT(reporter, (copy.getColorTable() != NULL) == hasCT); } } } else { // dst should be unchanged from its initial state REPORTER_ASSERT(reporter, dst.config() == SkBitmap::kNo_Config); REPORTER_ASSERT(reporter, dst.width() == 0); REPORTER_ASSERT(reporter, dst.height() == 0); } } } }
sk_sp<SkSpecialImage> SkBlurImageFilter::onFilterImage(SkSpecialImage* source, const Context& ctx, SkIPoint* offset) const { SkIPoint inputOffset = SkIPoint::Make(0, 0); sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset)); if (!input) { return nullptr; } SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY, input->width(), input->height()); SkIRect dstBounds; if (!this->applyCropRect(this->mapContext(ctx), inputBounds, &dstBounds)) { return nullptr; } if (!inputBounds.intersect(dstBounds)) { return nullptr; } const SkVector sigma = map_sigma(fSigma, ctx.ctm()); #if SK_SUPPORT_GPU if (input->peekTexture() && input->peekTexture()->getContext()) { if (0 == sigma.x() && 0 == sigma.y()) { offset->fX = inputBounds.x(); offset->fY = inputBounds.y(); return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(), -inputOffset.y())); } GrTexture* inputTexture = input->peekTexture(); offset->fX = dstBounds.fLeft; offset->fY = dstBounds.fTop; inputBounds.offset(-inputOffset); dstBounds.offset(-inputOffset); SkRect inputBoundsF(SkRect::Make(inputBounds)); SkAutoTUnref<GrTexture> tex(SkGpuBlurUtils::GaussianBlur(inputTexture->getContext(), inputTexture, false, source->props().allowSRGBInputs(), SkRect::Make(dstBounds), &inputBoundsF, sigma.x(), sigma.y())); if (!tex) { return nullptr; } return SkSpecialImage::MakeFromGpu(source->internal_getProxy(), SkIRect::MakeWH(dstBounds.width(), dstBounds.height()), kNeedNewImageUniqueID_SpecialImage, tex, &source->props()); } #endif int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX; int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY; get_box3_params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX); get_box3_params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY); if (kernelSizeX < 0 || kernelSizeY < 0) { return nullptr; } if (kernelSizeX == 0 && kernelSizeY == 0) { offset->fX = inputBounds.x(); offset->fY = inputBounds.y(); return input->makeSubset(inputBounds.makeOffset(-inputOffset.x(), -inputOffset.y())); } SkPixmap inputPixmap; if (!input->peekPixels(&inputPixmap)) { return nullptr; } if (inputPixmap.colorType() != kN32_SkColorType) { return nullptr; } SkImageInfo info = SkImageInfo::Make(dstBounds.width(), dstBounds.height(), inputPixmap.colorType(), inputPixmap.alphaType()); SkBitmap tmp, dst; if (!tmp.tryAllocPixels(info) || !dst.tryAllocPixels(info)) { return nullptr; } SkAutoLockPixels tmpLock(tmp), dstLock(dst); offset->fX = dstBounds.fLeft; offset->fY = dstBounds.fTop; SkPMColor* t = tmp.getAddr32(0, 0); SkPMColor* d = dst.getAddr32(0, 0); int w = dstBounds.width(), h = dstBounds.height(); const SkPMColor* s = inputPixmap.addr32(inputBounds.x() - inputOffset.x(), inputBounds.y() - inputOffset.y()); inputBounds.offset(-dstBounds.x(), -dstBounds.y()); dstBounds.offset(-dstBounds.x(), -dstBounds.y()); SkIRect inputBoundsT = SkIRect::MakeLTRB(inputBounds.top(), inputBounds.left(), inputBounds.bottom(), inputBounds.right()); SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width()); int sw = int(inputPixmap.rowBytes() >> 2); /** * * In order to make memory accesses cache-friendly, we reorder the passes to * use contiguous memory reads wherever possible. * * For example, the 6 passes of the X-and-Y blur case are rewritten as * follows. Instead of 3 passes in X and 3 passes in Y, we perform * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X, * then 1 pass in X transposed to Y on write. * * +----+ +----+ +----+ +---+ +---+ +---+ +----+ * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB | * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+ * +---+ +---+ +---+ * * In this way, two of the y-blurs become x-blurs applied to transposed * images, and all memory reads are contiguous. */ if (kernelSizeX > 0 && kernelSizeY > 0) { SkOpts::box_blur_xx(s, sw, inputBounds, t, kernelSizeX, lowOffsetX, highOffsetX, w, h); SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX, highOffsetX, lowOffsetX, w, h); SkOpts::box_blur_xy(d, w, dstBounds, t, kernelSizeX3, highOffsetX, highOffsetX, w, h); SkOpts::box_blur_xx(t, h, dstBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w); SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w); SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w); } else if (kernelSizeX > 0) { SkOpts::box_blur_xx(s, sw, inputBounds, d, kernelSizeX, lowOffsetX, highOffsetX, w, h); SkOpts::box_blur_xx(d, w, dstBounds, t, kernelSizeX, highOffsetX, lowOffsetX, w, h); SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX3, highOffsetX, highOffsetX, w, h); } else if (kernelSizeY > 0) { SkOpts::box_blur_yx(s, sw, inputBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w); SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w); SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w); } return SkSpecialImage::MakeFromRaster(source->internal_getProxy(), SkIRect::MakeWH(dstBounds.width(), dstBounds.height()), dst, &source->props()); }
sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* source, const Context& ctx, SkIPoint* offset) const { SkIPoint inputOffset = SkIPoint::Make(0, 0); sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset)); if (!input) { return nullptr; } const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), input->width(), input->height()); SkIRect bounds; if (!this->applyCropRect(ctx, inputBounds, &bounds)) { return nullptr; } SkScalar invInset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1; SkScalar invXZoom = fSrcRect.width() / bounds.width(); SkScalar invYZoom = fSrcRect.height() / bounds.height(); #if SK_SUPPORT_GPU if (source->isTextureBacked()) { GrContext* context = source->getContext(); sk_sp<GrTexture> inputTexture(input->asTextureRef(context)); SkASSERT(inputTexture); offset->fX = bounds.left(); offset->fY = bounds.top(); bounds.offset(-inputOffset); SkScalar yOffset = inputTexture->origin() == kTopLeft_GrSurfaceOrigin ? fSrcRect.y() : inputTexture->height() - fSrcRect.height() * inputTexture->height() / bounds.height() - fSrcRect.y(); int boundsY = inputTexture->origin() == kTopLeft_GrSurfaceOrigin ? bounds.y() : inputTexture->height() - bounds.height(); SkRect effectBounds = SkRect::MakeXYWH( SkIntToScalar(bounds.x()) / inputTexture->width(), SkIntToScalar(boundsY) / inputTexture->height(), SkIntToScalar(inputTexture->width()) / bounds.width(), SkIntToScalar(inputTexture->height()) / bounds.height()); // SRGBTODO: Handle sRGB here sk_sp<GrFragmentProcessor> fp(GrMagnifierEffect::Create( inputTexture.get(), effectBounds, fSrcRect.x() / inputTexture->width(), yOffset / inputTexture->height(), invXZoom, invYZoom, bounds.width() * invInset, bounds.height() * invInset)); if (!fp) { return nullptr; } return DrawWithFP(context, std::move(fp), bounds); } #endif SkBitmap inputBM; if (!input->getROPixels(&inputBM)) { return nullptr; } if ((inputBM.colorType() != kN32_SkColorType) || (fSrcRect.width() >= inputBM.width()) || (fSrcRect.height() >= inputBM.height())) { return nullptr; } SkAutoLockPixels alp(inputBM); SkASSERT(inputBM.getPixels()); if (!inputBM.getPixels() || inputBM.width() <= 0 || inputBM.height() <= 0) { return nullptr; } const SkImageInfo info = SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()); SkBitmap dst; if (!dst.tryAllocPixels(info)) { return nullptr; } SkAutoLockPixels dstLock(dst); SkColor* dptr = dst.getAddr32(0, 0); int dstWidth = dst.width(), dstHeight = dst.height(); for (int y = 0; y < dstHeight; ++y) { for (int x = 0; x < dstWidth; ++x) { SkScalar x_dist = SkMin32(x, dstWidth - x - 1) * invInset; SkScalar y_dist = SkMin32(y, dstHeight - y - 1) * invInset; SkScalar weight = 0; static const SkScalar kScalar2 = SkScalar(2); // To create a smooth curve at the corners, we need to work on // a square twice the size of the inset. if (x_dist < kScalar2 && y_dist < kScalar2) { x_dist = kScalar2 - x_dist; y_dist = kScalar2 - y_dist; SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) + SkScalarSquare(y_dist)); dist = SkMaxScalar(kScalar2 - dist, 0); weight = SkMinScalar(SkScalarSquare(dist), SK_Scalar1); } else { SkScalar sqDist = SkMinScalar(SkScalarSquare(x_dist), SkScalarSquare(y_dist)); weight = SkMinScalar(sqDist, SK_Scalar1); } SkScalar x_interp = SkScalarMul(weight, (fSrcRect.x() + x * invXZoom)) + (SK_Scalar1 - weight) * x; SkScalar y_interp = SkScalarMul(weight, (fSrcRect.y() + y * invYZoom)) + (SK_Scalar1 - weight) * y; int x_val = SkTPin(bounds.x() + SkScalarFloorToInt(x_interp), 0, inputBM.width() - 1); int y_val = SkTPin(bounds.y() + SkScalarFloorToInt(y_interp), 0, inputBM.height() - 1); *dptr = *inputBM.getAddr32(x_val, y_val); dptr++; } } offset->fX = bounds.left(); offset->fY = bounds.top(); return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), dst); }
DEF_TEST(BitmapCopy, reporter) { static const bool isExtracted[] = { false, true }; for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) { SkBitmap srcOpaque, srcPremul; setup_src_bitmaps(&srcOpaque, &srcPremul, gPairs[i].fColorType); for (size_t j = 0; j < SK_ARRAY_COUNT(gPairs); j++) { SkBitmap dst; bool success = srcPremul.copyTo(&dst, gPairs[j].fColorType); bool expected = gPairs[i].fValid[j] != '0'; if (success != expected) { ERRORF(reporter, "SkBitmap::copyTo from %s to %s. expected %s " "returned %s", gColorTypeName[i], gColorTypeName[j], boolStr(expected), boolStr(success)); } bool canSucceed = srcPremul.canCopyTo(gPairs[j].fColorType); if (success != canSucceed) { ERRORF(reporter, "SkBitmap::copyTo from %s to %s. returned %s " "canCopyTo %s", gColorTypeName[i], gColorTypeName[j], boolStr(success), boolStr(canSucceed)); } if (success) { REPORTER_ASSERT(reporter, srcPremul.width() == dst.width()); REPORTER_ASSERT(reporter, srcPremul.height() == dst.height()); REPORTER_ASSERT(reporter, dst.colorType() == gPairs[j].fColorType); test_isOpaque(reporter, srcOpaque, srcPremul, dst.colorType()); if (srcPremul.colorType() == dst.colorType()) { SkAutoLockPixels srcLock(srcPremul); SkAutoLockPixels dstLock(dst); REPORTER_ASSERT(reporter, srcPremul.readyToDraw()); REPORTER_ASSERT(reporter, dst.readyToDraw()); const char* srcP = (const char*)srcPremul.getAddr(0, 0); const char* dstP = (const char*)dst.getAddr(0, 0); REPORTER_ASSERT(reporter, srcP != dstP); REPORTER_ASSERT(reporter, !memcmp(srcP, dstP, srcPremul.getSize())); REPORTER_ASSERT(reporter, srcPremul.getGenerationID() == dst.getGenerationID()); } else { REPORTER_ASSERT(reporter, srcPremul.getGenerationID() != dst.getGenerationID()); } } else { // dst should be unchanged from its initial state REPORTER_ASSERT(reporter, dst.colorType() == kUnknown_SkColorType); REPORTER_ASSERT(reporter, dst.width() == 0); REPORTER_ASSERT(reporter, dst.height() == 0); } } // for (size_t j = ... // Tests for getSafeSize(), getSafeSize64(), copyPixelsTo(), // copyPixelsFrom(). // for (size_t copyCase = 0; copyCase < SK_ARRAY_COUNT(isExtracted); ++copyCase) { // Test copying to/from external buffer. // Note: the tests below have hard-coded values --- // Please take care if modifying. // Tests for getSafeSize64(). // Test with a very large configuration without pixel buffer // attached. SkBitmap tstSafeSize; tstSafeSize.setInfo(SkImageInfo::Make(100000000U, 100000000U, gPairs[i].fColorType, kPremul_SkAlphaType)); int64_t safeSize = tstSafeSize.computeSafeSize64(); if (safeSize < 0) { ERRORF(reporter, "getSafeSize64() negative: %s", gColorTypeName[tstSafeSize.colorType()]); } bool sizeFail = false; // Compare against hand-computed values. switch (gPairs[i].fColorType) { case kUnknown_SkColorType: break; case kAlpha_8_SkColorType: case kIndex_8_SkColorType: if (safeSize != 0x2386F26FC10000LL) { sizeFail = true; } break; case kRGB_565_SkColorType: case kARGB_4444_SkColorType: if (safeSize != 0x470DE4DF820000LL) { sizeFail = true; } break; case kN32_SkColorType: if (safeSize != 0x8E1BC9BF040000LL) { sizeFail = true; } break; default: break; } if (sizeFail) { ERRORF(reporter, "computeSafeSize64() wrong size: %s", gColorTypeName[tstSafeSize.colorType()]); } int subW = 2; int subH = 2; // Create bitmap to act as source for copies and subsets. SkBitmap src, subset; SkColorTable* ct = nullptr; if (kIndex_8_SkColorType == src.colorType()) { ct = init_ctable(); } int localSubW; if (isExtracted[copyCase]) { // A larger image to extract from. localSubW = 2 * subW + 1; } else { // Tests expect a 2x2 bitmap, so make smaller. localSubW = subW; } // could fail if we pass kIndex_8 for the colortype if (src.tryAllocPixels(SkImageInfo::Make(localSubW, subH, gPairs[i].fColorType, kPremul_SkAlphaType))) { // failure is fine, as we will notice later on } SkSafeUnref(ct); // Either copy src or extract into 'subset', which is used // for subsequent calls to copyPixelsTo/From. bool srcReady = false; // Test relies on older behavior that extractSubset will fail on // kUnknown_SkColorType if (kUnknown_SkColorType != src.colorType() && isExtracted[copyCase]) { // The extractedSubset() test case allows us to test copy- // ing when src and dst mave possibly different strides. SkIRect r; r.set(1, 0, 1 + subW, subH); // 2x2 extracted bitmap srcReady = src.extractSubset(&subset, r); } else { srcReady = src.copyTo(&subset); } // Not all configurations will generate a valid 'subset'. if (srcReady) { // Allocate our target buffer 'buf' for all copies. // To simplify verifying correctness of copies attach // buf to a SkBitmap, but copies are done using the // raw buffer pointer. const size_t bufSize = subH * SkColorTypeMinRowBytes(src.colorType(), subW) * 2; SkAutoTMalloc<uint8_t> autoBuf (bufSize); uint8_t* buf = autoBuf.get(); SkBitmap bufBm; // Attach buf to this bitmap. bool successExpected; // Set up values for each pixel being copied. Coordinates coords(subW * subH); for (int x = 0; x < subW; ++x) for (int y = 0; y < subH; ++y) { int index = y * subW + x; SkASSERT(index < coords.length); coords[index]->fX = x; coords[index]->fY = y; } writeCoordPixels(subset, coords); // Test #1 //////////////////////////////////////////// const SkImageInfo info = SkImageInfo::Make(subW, subH, gPairs[i].fColorType, kPremul_SkAlphaType); // Before/after comparisons easier if we attach buf // to an appropriately configured SkBitmap. memset(buf, 0xFF, bufSize); // Config with stride greater than src but that fits in buf. bufBm.installPixels(info, buf, info.minRowBytes() * 2); successExpected = false; // Then attempt to copy with a stride that is too large // to fit in the buffer. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes() * 3) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, 1.5*maxRowBytes)", reporter); // Test #2 //////////////////////////////////////////// // This test should always succeed, but in the case // of extracted bitmaps only because we handle the // issue of getSafeSize(). Without getSafeSize() // buffer overrun/read would occur. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, subset.rowBytes()); successExpected = subset.getSafeSize() <= bufSize; REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize)", reporter); // Test #3 //////////////////////////////////////////// // Copy with different stride between src and dst. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, subset.rowBytes()+1); successExpected = true; // Should always work. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, subset.rowBytes()+1) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, rowBytes+1)", reporter); // Test #4 //////////////////////////////////////////// // Test copy with stride too small. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, info.minRowBytes()); successExpected = false; // Request copy with stride too small. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes()-1) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, rowBytes()-1)", reporter); #if 0 // copyPixelsFrom is gone // Test #5 //////////////////////////////////////////// // Tests the case where the source stride is too small // for the source configuration. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, info.minRowBytes()); writeCoordPixels(bufBm, coords); REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, bufSize, 1) == false); // Test #6 /////////////////////////////////////////// // Tests basic copy from an external buffer to the bitmap. // If the bitmap is "extracted", this also tests the case // where the source stride is different from the dest. // stride. // We've made the buffer large enough to always succeed. bufBm.installPixels(info, buf, info.minRowBytes()); writeCoordPixels(bufBm, coords); REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, bufSize, bufBm.rowBytes()) == true); reportCopyVerification(bufBm, subset, coords, "copyPixelsFrom(buf, bufSize)", reporter); // Test #7 //////////////////////////////////////////// // Tests the case where the source buffer is too small // for the transfer. REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, 1, subset.rowBytes()) == false); #endif } } // for (size_t copyCase ... } }
sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialImage* source, const Context& ctx, SkIPoint* offset) const { SkIPoint inputOffset = SkIPoint::Make(0, 0); sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset)); if (!input) { return nullptr; } SkIRect bounds; input = this->applyCropRect(this->mapContext(ctx), input.get(), &inputOffset, &bounds); if (!input) { return nullptr; } #if SK_SUPPORT_GPU // Note: if the kernel is too big, the GPU path falls back to SW if (source->isTextureBacked() && fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE) { GrContext* context = source->getContext(); sk_sp<GrTexture> inputTexture(input->asTextureRef(context)); SkASSERT(inputTexture); offset->fX = bounds.left(); offset->fY = bounds.top(); bounds.offset(-inputOffset); // SRGBTODO: handle sRGB here sk_sp<GrFragmentProcessor> fp(GrMatrixConvolutionEffect::Make(inputTexture.get(), bounds, fKernelSize, fKernel, fGain, fBias, fKernelOffset, convert_tilemodes(fTileMode), fConvolveAlpha)); if (!fp) { return nullptr; } return DrawWithFP(context, std::move(fp), bounds, ctx.outputProperties()); } #endif SkBitmap inputBM; if (!input->getROPixels(&inputBM)) { return nullptr; } if (inputBM.colorType() != kN32_SkColorType) { return nullptr; } if (!fConvolveAlpha && !inputBM.isOpaque()) { inputBM = unpremultiply_bitmap(inputBM); } SkAutoLockPixels alp(inputBM); if (!inputBM.getPixels()) { return nullptr; } const SkImageInfo info = SkImageInfo::MakeN32(bounds.width(), bounds.height(), inputBM.alphaType()); SkBitmap dst; if (!dst.tryAllocPixels(info)) { return nullptr; } SkAutoLockPixels dstLock(dst); offset->fX = bounds.fLeft; offset->fY = bounds.fTop; bounds.offset(-inputOffset); SkIRect interior = SkIRect::MakeXYWH(bounds.left() + fKernelOffset.fX, bounds.top() + fKernelOffset.fY, bounds.width() - fKernelSize.fWidth + 1, bounds.height() - fKernelSize.fHeight + 1); SkIRect top = SkIRect::MakeLTRB(bounds.left(), bounds.top(), bounds.right(), interior.top()); SkIRect bottom = SkIRect::MakeLTRB(bounds.left(), interior.bottom(), bounds.right(), bounds.bottom()); SkIRect left = SkIRect::MakeLTRB(bounds.left(), interior.top(), interior.left(), interior.bottom()); SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(), bounds.right(), interior.bottom()); this->filterBorderPixels(inputBM, &dst, top, bounds); this->filterBorderPixels(inputBM, &dst, left, bounds); this->filterInteriorPixels(inputBM, &dst, interior, bounds); this->filterBorderPixels(inputBM, &dst, right, bounds); this->filterBorderPixels(inputBM, &dst, bottom, bounds); return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), dst); }