aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gm/alpha_image.cpp39
-rw-r--r--include/config/SkUserConfigManual.h4
-rw-r--r--src/core/SkBlitter.cpp16
-rw-r--r--src/core/SkBlitter.h3
-rw-r--r--src/core/SkDraw.cpp131
-rw-r--r--src/core/SkDraw.h7
6 files changed, 200 insertions, 0 deletions
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/include/config/SkUserConfigManual.h b/include/config/SkUserConfigManual.h
index 5f11d96168..528e406431 100644
--- a/include/config/SkUserConfigManual.h
+++ b/include/config/SkUserConfigManual.h
@@ -32,4 +32,8 @@
#define SK_DISABLE_DAA // skbug.com/6886
#define SK_ABORT(...) __android_log_assert(nullptr, "skia", ##__VA_ARGS__)
+
+ // TODO (b/239048372): Remove this flag when we can safely migrate apps to the
+ // new behavior.
+ #define SK_SUPPORT_LEGACY_ALPHA_BITMAP_AS_COVERAGE
#endif // SkUserConfigManual_DEFINED
diff --git a/src/core/SkBlitter.cpp b/src/core/SkBlitter.cpp
index f68f8eea9b..b910d06b41 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<uint8_t> 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 dc5fe8ede9..c87948cc3d 100644
--- a/src/core/SkDraw.h
+++ b/src/core/SkDraw.h
@@ -98,6 +98,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,
@@ -117,6 +121,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<SkBlender> blender,
const SkPaint& paint,