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
This commit is contained in:
@@ -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<maskHeight; py++) {
|
||||
for (int px=0; px<maskWidth; px++) {
|
||||
double shading = calcPixelShading(cx, cy, px, py, radius);
|
||||
pixels[maskWidth*py + px] =
|
||||
Color.argb(255 - (int)Math.round(255.0*shading), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap transparency = Bitmap.createBitmap(pixels, maskWidth, maskHeight,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
|
||||
c.drawBitmap(transparency,
|
||||
(float)mMaskThickness,
|
||||
(float)((mScreenSize.y - maskHeight) / 2),
|
||||
mPaint);
|
||||
|
||||
mSurface.unlockCanvasAndPost(c);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user