diff --git a/gm/alpha_image.cpp b/gm/alpha_image.cpp index 95e23f8971..6edafe13f2 100644 --- a/gm/alpha_image.cpp +++ b/gm/alpha_image.cpp @@ -17,6 +17,7 @@ #include "include/core/SkPaint.h" #include "include/core/SkRefCnt.h" #include "include/core/SkShader.h" +#include "tools/Resources.h" static SkBitmap make_alpha_image(int w, int h) { SkBitmap bm; @@ -84,3 +85,41 @@ DEF_SIMPLE_GM(alpha_image_alpha_tint, canvas, 152, 80) { paint.setShader(image->makeShader(SkSamplingOptions())); canvas->drawRect({ 0, 0, 64, 64 }, paint); } + +#if defined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) +// For a long time, the CPU backend treated A8 bitmaps as coverage, rather than alpha. This was +// inconsistent with the GPU backend (skbug.com/9692). When this was fixed, it altered behavior +// for some Android apps (b/231400686). This GM verifies that our Android framework workaround +// produces the old result (mandrill with a round-rect border). +DEF_SIMPLE_GM(alpha_bitmap_is_coverage_ANDROID, canvas, 128, 128) { + SkBitmap maskBitmap; + maskBitmap.allocPixels(SkImageInfo::MakeA8(128, 128)); + { + SkCanvas maskCanvas(maskBitmap); + maskCanvas.clear(SK_ColorWHITE); + + SkPaint maskPaint; + maskPaint.setAntiAlias(true); + maskPaint.setColor(SK_ColorWHITE); + maskPaint.setBlendMode(SkBlendMode::kClear); + maskCanvas.drawRoundRect({0, 0, 128, 128}, 16, 16, maskPaint); + } + + SkBitmap offscreenBitmap; + offscreenBitmap.allocN32Pixels(128, 128); + { + SkCanvas offscreenCanvas(offscreenBitmap); + offscreenCanvas.drawImage(GetResourceAsImage("images/mandrill_128.png"), 0, 0); + + SkPaint clearPaint; + clearPaint.setAntiAlias(true); + clearPaint.setBlendMode(SkBlendMode::kClear); + // At tip-of-tree (or at any time on the GPU backend), this draw produces full coverage, + // completely erasing the mandrill. With the workaround enabled, the alpha border is treated + // as coverage, so we only apply kClear to those pixels, just erasing the outer border. + offscreenCanvas.drawImage(maskBitmap.asImage(), 0, 0, SkSamplingOptions{}, &clearPaint); + } + + canvas->drawImage(offscreenBitmap.asImage(), 0, 0); +} +#endif diff --git a/src/core/SkBlitter.cpp b/src/core/SkBlitter.cpp index a52dc9a00c..c2659b49c5 100644 --- a/src/core/SkBlitter.cpp +++ b/src/core/SkBlitter.cpp @@ -273,6 +273,22 @@ void SkBlitter::blitMask(const SkMask& mask, const SkIRect& clip) { /////////////////////// these are not virtual, just helpers +#if defined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) +void SkBlitter::blitMaskRegion(const SkMask& mask, const SkRegion& clip) { + if (clip.quickReject(mask.fBounds)) { + return; + } + + SkRegion::Cliperator clipper(clip, mask.fBounds); + + while (!clipper.done()) { + const SkIRect& cr = clipper.rect(); + this->blitMask(mask, cr); + clipper.next(); + } +} +#endif + void SkBlitter::blitRectRegion(const SkIRect& rect, const SkRegion& clip) { SkRegion::Cliperator clipper(clip, rect); diff --git a/src/core/SkBlitter.h b/src/core/SkBlitter.h index 0ed7cd6286..ec58c5ab89 100644 --- a/src/core/SkBlitter.h +++ b/src/core/SkBlitter.h @@ -132,6 +132,9 @@ public: } ///@name non-virtual helpers +#if defined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) + void blitMaskRegion(const SkMask& mask, const SkRegion& clip); +#endif void blitRectRegion(const SkIRect& rect, const SkRegion& clip); void blitRegion(const SkRegion& clip); ///@} diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp index 60d18e92bb..30cbaf207a 100644 --- a/src/core/SkDraw.cpp +++ b/src/core/SkDraw.cpp @@ -705,6 +705,39 @@ void SkDraw::drawRect(const SkRect& prePaintRect, const SkPaint& paint, } } +#if defined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) +void SkDraw::drawDevMask(const SkMask& srcM, const SkPaint& paint) const { + if (srcM.fBounds.isEmpty()) { + return; + } + + const SkMask* mask = &srcM; + + SkMask dstM; + if (paint.getMaskFilter() && + as_MFB(paint.getMaskFilter()) + ->filterMask(&dstM, srcM, fMatrixProvider->localToDevice(), nullptr)) { + mask = &dstM; + } + SkAutoMaskFreeImage ami(dstM.fImage); + + SkAutoBlitterChoose blitterChooser(*this, nullptr, paint); + SkBlitter* blitter = blitterChooser.get(); + + SkAAClipBlitterWrapper wrapper; + const SkRegion* clipRgn; + + if (fRC->isBW()) { + clipRgn = &fRC->bwRgn(); + } else { + wrapper.init(*fRC, blitter); + clipRgn = &wrapper.getRgn(); + blitter = wrapper.getBlitter(); + } + blitter->blitMaskRegion(*mask, *clipRgn); +} +#endif + static SkScalar fast_len(const SkVector& vec) { SkScalar x = SkScalarAbs(vec.fX); SkScalar y = SkScalarAbs(vec.fY); @@ -925,6 +958,94 @@ void SkDraw::drawPath(const SkPath& origSrcPath, const SkPaint& origPaint, this->drawDevPath(*devPathPtr, *paint, drawCoverage, customBlitter, doFill); } +#if defined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) +void SkDraw::drawBitmapAsMask(const SkBitmap& bitmap, const SkSamplingOptions& sampling, + const SkPaint& paint) const { + SkASSERT(bitmap.colorType() == kAlpha_8_SkColorType); + + // nothing to draw + if (fRC->isEmpty()) { + return; + } + + SkMatrix ctm = fMatrixProvider->localToDevice(); + if (SkTreatAsSprite(ctm, bitmap.dimensions(), sampling, paint)) + { + int ix = SkScalarRoundToInt(ctm.getTranslateX()); + int iy = SkScalarRoundToInt(ctm.getTranslateY()); + + SkPixmap pmap; + if (!bitmap.peekPixels(&pmap)) { + return; + } + SkMask mask; + mask.fBounds.setXYWH(ix, iy, pmap.width(), pmap.height()); + mask.fFormat = SkMask::kA8_Format; + mask.fRowBytes = SkToU32(pmap.rowBytes()); + // fImage is typed as writable, but in this case it is used read-only + mask.fImage = (uint8_t*)pmap.addr8(0, 0); + + this->drawDevMask(mask, paint); + } else { // need to xform the bitmap first + SkRect r; + SkMask mask; + + r.setIWH(bitmap.width(), bitmap.height()); + ctm.mapRect(&r); + r.round(&mask.fBounds); + + // set the mask's bounds to the transformed bitmap-bounds, + // clipped to the actual device and further limited by the clip bounds + { + SkASSERT(fDst.bounds().contains(fRC->getBounds())); + SkIRect devBounds = fDst.bounds(); + devBounds.intersect(fRC->getBounds().makeOutset(1, 1)); + // need intersect(l, t, r, b) on irect + if (!mask.fBounds.intersect(devBounds)) { + return; + } + } + + mask.fFormat = SkMask::kA8_Format; + mask.fRowBytes = SkAlign4(mask.fBounds.width()); + size_t size = mask.computeImageSize(); + if (0 == size) { + // the mask is too big to allocated, draw nothing + return; + } + + // allocate (and clear) our temp buffer to hold the transformed bitmap + SkAutoTMalloc storage(size); + mask.fImage = storage.get(); + memset(mask.fImage, 0, size); + + // now draw our bitmap(src) into mask(dst), transformed by the matrix + { + SkBitmap device; + device.installPixels(SkImageInfo::MakeA8(mask.fBounds.width(), mask.fBounds.height()), + mask.fImage, mask.fRowBytes); + + SkCanvas c(device); + // need the unclipped top/left for the translate + c.translate(-SkIntToScalar(mask.fBounds.fLeft), + -SkIntToScalar(mask.fBounds.fTop)); + c.concat(ctm); + + // We can't call drawBitmap, or we'll infinitely recurse. Instead + // we manually build a shader and draw that into our new mask + SkPaint tmpPaint; + tmpPaint.setAntiAlias(paint.isAntiAlias()); + tmpPaint.setDither(paint.isDither()); + SkPaint paintWithShader = make_paint_with_image(tmpPaint, bitmap, sampling); + SkRect rr; + rr.setIWH(bitmap.width(), bitmap.height()); + c.drawRect(rr, paintWithShader); + } + this->drawDevMask(mask, paint); + } +} +#endif + static bool clipped_out(const SkMatrix& m, const SkRasterClip& c, const SkRect& srcR) { SkRect dstR; @@ -998,6 +1119,16 @@ void SkDraw::drawBitmap(const SkBitmap& bitmap, const SkMatrix& prematrix, SkDraw draw(*this); draw.fMatrixProvider = &matrixProvider; + // For a long time, the CPU backend treated A8 bitmaps as coverage, rather than alpha. This was + // inconsistent with the GPU backend (skbug.com/9692). When this was fixed, it altered behavior + // for some Android apps (b/231400686). Thus: keep the old behavior in the framework. +#if defined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) + if (bitmap.colorType() == kAlpha_8_SkColorType && !paint->getColorFilter()) { + draw.drawBitmapAsMask(bitmap, sampling, *paint); + return; + } +#endif + SkPaint paintWithShader = make_paint_with_image(*paint, bitmap, sampling); const SkRect srcBounds = SkRect::MakeIWH(bitmap.width(), bitmap.height()); if (dstBounds) { diff --git a/src/core/SkDraw.h b/src/core/SkDraw.h index d76cff9833..860ed6ec01 100644 --- a/src/core/SkDraw.h +++ b/src/core/SkDraw.h @@ -102,6 +102,10 @@ public: SkMask* mask, SkMask::CreateMode mode, SkStrokeRec::InitStyle style); +#if defined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) + void drawDevMask(const SkMask& mask, const SkPaint&) const; +#endif + enum RectType { kHair_RectType, kFill_RectType, @@ -121,6 +125,9 @@ public: SkPoint* strokeSize); private: +#if defined(SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE) + void drawBitmapAsMask(const SkBitmap&, const SkSamplingOptions&, const SkPaint&) const; +#endif void drawFixedVertices(const SkVertices* vertices, sk_sp blender, const SkPaint& paint,