From c84731ae3b6744b7f3a0c7a39dc3a1d0d3f436a3 Mon Sep 17 00:00:00 2001 From: Matthew Bouyack Date: Tue, 2 Aug 2016 11:58:26 -0700 Subject: [PATCH] DO NOT MERGE Improved anti-aliasing for circular display mask Calculate the alpha value for each pixel in the display mask based on the area of overlap between the circle and that pixel. This works around a bug in Skia that is fixed in master. Change-Id: Ieb8df0d6f46f07cff1dbc368d74938ec699e08cf --- .../server/wm/CircularDisplayMask.java | 144 +++++++++++++++++- 1 file changed, 139 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java index ae41541310799..0a9b33404b37a 100644 --- a/services/core/java/com/android/server/wm/CircularDisplayMask.java +++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java @@ -21,6 +21,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -85,12 +86,115 @@ class CircularDisplayMask { mSurfaceControl = ctrl; mDrawNeeded = true; mPaint = new Paint(); - mPaint.setAntiAlias(true); - mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mScreenOffset = screenOffset; mMaskThickness = maskThickness; } + static private double distanceFromCenterSquared(double x, double y) { + return x*x + y*y; + } + + static private double distanceFromCenter(double x, double y) { + return Math.sqrt(distanceFromCenterSquared(x, y)); + } + + static private double verticalLineIntersectsCircle(double x, double radius) { + return Math.sqrt(radius*radius - x*x); + } + + static private double horizontalLineIntersectsCircle(double y, double radius) { + return Math.sqrt(radius*radius - y*y); + } + + static private double triangleArea(double width, double height) { + return width * height / 2.0; + } + + static private double trapezoidArea(double width, double height1, double height2) { + return width * (height1 + height2) / 2.0; + } + + static private double areaUnderChord(double radius, double chordLength) { + double isocelesHeight = Math.sqrt(radius*radius - chordLength * chordLength / 4.0); + double areaUnderIsoceles = isocelesHeight * chordLength / 2.0; + double halfAngle = Math.asin(chordLength / (2.0 * radius)); + double areaUnderArc = halfAngle * radius * radius; + + return areaUnderArc - triangleArea(chordLength, isocelesHeight); + } + + // Returns the fraction of the pixel at (px, py) covered by + // the circle with center (cx, cy) and radius 'radius' + static private double calcPixelShading(double cx, double cy, double px, + double py, double radius) { + // Translate so the center is at the origin + px -= cx; + py -= cy; + + // Reflect across the axis so the point is in the first quadrant + px = Math.abs(px); + py = Math.abs(py); + + // One more transformation which simplifies the logic later + if (py > px) { + double temp; + + temp = px; + px = py; + py = temp; + } + + double left = px - 0.5; + double right = px + 0.5; + double bottom = py - 0.5; + double top = py + 0.5; + + if (distanceFromCenterSquared(left, bottom) > radius*radius) { + return 0.0; + } + + if (distanceFromCenterSquared(right, top) < radius*radius) { + return 1.0; + } + + // Check if only the bottom-left corner of the pixel is inside the circle + if (distanceFromCenterSquared(left, top) > radius*radius) { + double triangleWidth = horizontalLineIntersectsCircle(bottom, radius) - left; + double triangleHeight = verticalLineIntersectsCircle(left, radius) - bottom; + double chordLength = distanceFromCenter(triangleWidth, triangleHeight); + + return triangleArea(triangleWidth, triangleHeight) + + areaUnderChord(radius, chordLength); + + } + + // Check if only the top-right corner of the pixel is outside the circle + if (distanceFromCenterSquared(right, bottom) < radius*radius) { + double triangleWidth = right - horizontalLineIntersectsCircle(top, radius); + double triangleHeight = top - verticalLineIntersectsCircle(right, radius); + double chordLength = distanceFromCenter(triangleWidth, triangleHeight); + + return 1 - triangleArea(triangleWidth, triangleHeight) + + areaUnderChord(radius, chordLength); + } + + // It must be that the top-left and bottom-left corners are inside the circle + double trapezoidWidth1 = horizontalLineIntersectsCircle(top, radius) - left; + double trapezoidWidth2 = horizontalLineIntersectsCircle(bottom, radius) - left; + double chordLength = distanceFromCenter(1, trapezoidWidth2 - trapezoidWidth1); + double shading = trapezoidArea(1.0, trapezoidWidth1, trapezoidWidth2) + + areaUnderChord(radius, chordLength); + + // When top >= 0 and bottom <= 0 it's possible for the circle to intersect the pixel 4 times. + // If so, remove the area of the section which crosses the right-hand edge. + if (top >= 0 && bottom <= 0 && radius > right) { + shading -= areaUnderChord(radius, 2 * verticalLineIntersectsCircle(right, radius)); + } + + return shading; + } + private void drawIfNeeded() { if (!mDrawNeeded || !mVisible || mDimensionsUnequal) { return; @@ -123,11 +227,41 @@ class CircularDisplayMask { break; } - int circleRadius = mScreenSize.x / 2; c.drawColor(Color.BLACK); - // The radius is reduced by mMaskThickness to provide an anti aliasing effect on the display edges. - c.drawCircle(circleRadius, circleRadius, circleRadius - mMaskThickness, mPaint); + int maskWidth = mScreenSize.x - 2*mMaskThickness; + int maskHeight; + + // Don't render the whole mask if it is partly offscreen. + if (maskWidth > mScreenSize.y) { + maskHeight = mScreenSize.y; + } else { + // To ensure the mask can be properly centered on the canvas the + // bitmap dimensions must have the same parity as those of the canvas. + maskHeight = mScreenSize.y - ((mScreenSize.y - maskWidth) & ~1); + } + + double cx = (maskWidth - 1.0) / 2.0; + double cy = (maskHeight - 1.0) / 2.0; + double radius = maskWidth / 2.0; + int[] pixels = new int[maskWidth * maskHeight]; + + for (int py=0; py