From ac33a487516196e9f1cf830e78313806e6daf777 Mon Sep 17 00:00:00 2001 From: Derek Sollenberger Date: Mon, 22 Apr 2019 16:28:09 -0400 Subject: [PATCH] Fix fading edge effect from impacting neighboring pixels Bug: 129117085 Test: skia unit tests and test cases described in the bug Change-Id: Ieaa7c831dd6298ac0565e6f1837b1c1dbd4545da --- core/java/android/view/View.java | 66 ++++++++++++++-------- core/jni/android_graphics_Canvas.cpp | 6 ++ graphics/java/android/graphics/Canvas.java | 13 +++++ libs/hwui/DisplayListOps.in | 3 +- libs/hwui/RecordingCanvas.cpp | 13 +++++ libs/hwui/RecordingCanvas.h | 2 + libs/hwui/SkiaCanvas.cpp | 13 +++++ libs/hwui/SkiaCanvas.h | 1 + libs/hwui/hwui/Canvas.h | 1 + 9 files changed, 95 insertions(+), 23 deletions(-) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 58429f44e932e..bcd570ade7241 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22013,23 +22013,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } saveCount = canvas.getSaveCount(); + int topSaveCount = -1; + int bottomSaveCount = -1; + int leftSaveCount = -1; + int rightSaveCount = -1; int solidColor = getSolidColor(); if (solidColor == 0) { if (drawTop) { - canvas.saveUnclippedLayer(left, top, right, top + length); + topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length); } if (drawBottom) { - canvas.saveUnclippedLayer(left, bottom - length, right, bottom); + bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom); } if (drawLeft) { - canvas.saveUnclippedLayer(left, top, left + length, bottom); + leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom); } if (drawRight) { - canvas.saveUnclippedLayer(right - length, top, right, bottom); + rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom); } } else { scrollabilityCache.setFadeColor(solidColor); @@ -22046,21 +22050,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final Matrix matrix = scrollabilityCache.matrix; final Shader fade = scrollabilityCache.shader; - if (drawTop) { - matrix.setScale(1, fadeHeight * topFadeStrength); - matrix.postTranslate(left, top); + // must be restored in the reverse order that they were saved + if (drawRight) { + matrix.setScale(1, fadeHeight * rightFadeStrength); + matrix.postRotate(90); + matrix.postTranslate(right, top); fade.setLocalMatrix(matrix); p.setShader(fade); - canvas.drawRect(left, top, right, top + length, p); - } + if (solidColor == 0) { + canvas.restoreUnclippedLayer(rightSaveCount, p); - if (drawBottom) { - matrix.setScale(1, fadeHeight * bottomFadeStrength); - matrix.postRotate(180); - matrix.postTranslate(left, bottom); - fade.setLocalMatrix(matrix); - p.setShader(fade); - canvas.drawRect(left, bottom - length, right, bottom, p); + } else { + canvas.drawRect(right - length, top, right, bottom, p); + } } if (drawLeft) { @@ -22069,16 +22071,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); - canvas.drawRect(left, top, left + length, bottom, p); + if (solidColor == 0) { + canvas.restoreUnclippedLayer(leftSaveCount, p); + } else { + canvas.drawRect(left, top, left + length, bottom, p); + } } - if (drawRight) { - matrix.setScale(1, fadeHeight * rightFadeStrength); - matrix.postRotate(90); - matrix.postTranslate(right, top); + if (drawBottom) { + matrix.setScale(1, fadeHeight * bottomFadeStrength); + matrix.postRotate(180); + matrix.postTranslate(left, bottom); fade.setLocalMatrix(matrix); p.setShader(fade); - canvas.drawRect(right - length, top, right, bottom, p); + if (solidColor == 0) { + canvas.restoreUnclippedLayer(bottomSaveCount, p); + } else { + canvas.drawRect(left, bottom - length, right, bottom, p); + } + } + + if (drawTop) { + matrix.setScale(1, fadeHeight * topFadeStrength); + matrix.postTranslate(left, top); + fade.setLocalMatrix(matrix); + p.setShader(fade); + if (solidColor == 0) { + canvas.restoreUnclippedLayer(topSaveCount, p); + } else { + canvas.drawRect(left, top, right, top + length, p); + } } canvas.restoreToCount(saveCount); diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index 7a8c5c8a7b36d..ebc6cd7dec6fe 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -106,6 +106,11 @@ static jint saveUnclippedLayer(jlong canvasHandle, jint l, jint t, jint r, jint return reinterpret_cast(get_canvas(canvasHandle)->saveUnclippedLayer(l, t, r, b)); } +static void restoreUnclippedLayer(jlong canvasHandle, jint saveCount, jlong paintHandle) { + Paint* paint = reinterpret_cast(paintHandle); + get_canvas(canvasHandle)->restoreUnclippedLayer(saveCount, *paint); +} + static bool restore(jlong canvasHandle) { Canvas* canvas = get_canvas(canvasHandle); if (canvas->getSaveCount() <= 1) { @@ -668,6 +673,7 @@ static const JNINativeMethod gMethods[] = { {"nSaveLayer","(JFFFFJI)I", (void*) CanvasJNI::saveLayer}, {"nSaveLayerAlpha","(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha}, {"nSaveUnclippedLayer","(JIIII)I", (void*) CanvasJNI::saveUnclippedLayer}, + {"nRestoreUnclippedLayer","(JIJ)V", (void*) CanvasJNI::restoreUnclippedLayer}, {"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount}, {"nRestore","(J)Z", (void*) CanvasJNI::restore}, {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount}, diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index d4d5ae7bdddbe..5648b854db408 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -558,6 +558,16 @@ public class Canvas extends BaseCanvas { return nSaveUnclippedLayer(mNativeCanvasWrapper, left, top, right, bottom); } + /** + * @hide + * @param saveCount The save level to restore to. + * @param paint This is copied and is applied to the area within the unclipped layer's + * bounds (i.e. equivalent to a drawPaint()) before restore() is called. + */ + public void restoreUnclippedLayer(int saveCount, Paint paint) { + nRestoreUnclippedLayer(mNativeCanvasWrapper, saveCount, paint.getNativeInstance()); + } + /** * Helper version of saveLayer() that takes 4 values rather than a RectF. * @@ -1398,6 +1408,9 @@ public class Canvas extends BaseCanvas { @CriticalNative private static native int nSaveUnclippedLayer(long nativeCanvas, int l, int t, int r, int b); @CriticalNative + private static native void nRestoreUnclippedLayer(long nativeCanvas, int saveCount, + long nativePaint); + @CriticalNative private static native boolean nRestore(long canvasHandle); @CriticalNative private static native void nRestoreToCount(long canvasHandle, int saveCount); diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in index 14e3a32817a01..2deb5657c877c 100644 --- a/libs/hwui/DisplayListOps.in +++ b/libs/hwui/DisplayListOps.in @@ -26,7 +26,8 @@ X(ClipPath) X(ClipRect) X(ClipRRect) X(ClipRegion) -X(DrawPaint) +X(DrawPaint) +X(DrawBehind) X(DrawPath) X(DrawRect) X(DrawRegion) diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 85947665839a7..e58fbbe8e6673 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -20,6 +20,7 @@ #include "SkAndroidFrameworkUtils.h" #include "SkCanvas.h" +#include "SkCanvasPriv.h" #include "SkData.h" #include "SkDrawShadowInfo.h" #include "SkImage.h" @@ -187,6 +188,12 @@ struct DrawPaint final : Op { SkPaint paint; void draw(SkCanvas* c, const SkMatrix&) const { c->drawPaint(paint); } }; +struct DrawBehind final : Op { + static const auto kType = Type::DrawBehind; + DrawBehind(const SkPaint& paint) : paint(paint) {} + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { SkCanvasPriv::DrawBehind(c, paint); } +}; struct DrawPath final : Op { static const auto kType = Type::DrawPath; DrawPath(const SkPath& path, const SkPaint& paint) : path(path), paint(paint) {} @@ -565,6 +572,9 @@ void DisplayListData::clipRegion(const SkRegion& region, SkClipOp op) { void DisplayListData::drawPaint(const SkPaint& paint) { this->push(0, paint); } +void DisplayListData::drawBehind(const SkPaint& paint) { + this->push(0, paint); +} void DisplayListData::drawPath(const SkPath& path, const SkPaint& paint) { this->push(0, path, paint); } @@ -834,6 +844,9 @@ void RecordingCanvas::onClipRegion(const SkRegion& region, SkClipOp op) { void RecordingCanvas::onDrawPaint(const SkPaint& paint) { fDL->drawPaint(paint); } +void RecordingCanvas::onDrawBehind(const SkPaint& paint) { + fDL->drawBehind(paint); +} void RecordingCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { fDL->drawPath(path, paint); } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 3a76ca1137a57..7269bcad3d7a2 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -89,6 +89,7 @@ private: void clipRegion(const SkRegion&, SkClipOp); void drawPaint(const SkPaint&); + void drawBehind(const SkPaint&); void drawPath(const SkPath&, const SkPaint&); void drawRect(const SkRect&, const SkPaint&); void drawRegion(const SkRegion&, const SkPaint&); @@ -157,6 +158,7 @@ public: void onClipRegion(const SkRegion&, SkClipOp) override; void onDrawPaint(const SkPaint&) override; + void onDrawBehind(const SkPaint&) override; void onDrawPath(const SkPath&, const SkPaint&) override; void onDrawRect(const SkRect&, const SkPaint&) override; void onDrawRegion(const SkRegion&, const SkPaint&) override; diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 834069988f422..6ea6af8f29358 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -191,6 +192,18 @@ int SkiaCanvas::saveUnclippedLayer(int left, int top, int right, int bottom) { return SkAndroidFrameworkUtils::SaveBehind(mCanvas, &bounds); } +void SkiaCanvas::restoreUnclippedLayer(int restoreCount, const SkPaint& paint) { + + while (mCanvas->getSaveCount() > restoreCount + 1) { + this->restore(); + } + + if (mCanvas->getSaveCount() == restoreCount + 1) { + SkCanvasPriv::DrawBehind(mCanvas, *filterPaint(paint)); + this->restore(); + } +} + class SkiaCanvas::Clip { public: Clip(const SkRect& rect, SkClipOp op, const SkMatrix& m) diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 3fe2bce06b412..bbe91eb2fbc4b 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -69,6 +69,7 @@ public: virtual int save(SaveFlags::Flags flags) override; virtual void restore() override; virtual void restoreToCount(int saveCount) override; + virtual void restoreUnclippedLayer(int saveCount, const SkPaint& paint) override; virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, SaveFlags::Flags flags) override; diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 11e8579a481fa..ac8db216b0593 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -191,6 +191,7 @@ public: virtual int save(SaveFlags::Flags flags) = 0; virtual void restore() = 0; virtual void restoreToCount(int saveCount) = 0; + virtual void restoreUnclippedLayer(int saveCount, const SkPaint& paint) = 0; virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, SaveFlags::Flags flags) = 0;