Merge "Optimize luminance calculation for rotation animation"

This commit is contained in:
Riddle Hsu
2022-10-24 01:03:38 +00:00
committed by Android (Google) Code Review
6 changed files with 109 additions and 201 deletions

View File

@@ -41,12 +41,16 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorSpace;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageReader;
import android.os.SystemProperties;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionOldType;
import android.view.WindowManager.TransitionType;
@@ -59,9 +63,11 @@ import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.window.ScreenCapture;
import com.android.internal.R;
import java.nio.ByteBuffer;
import java.util.List;
/** @hide */
@@ -1262,4 +1268,90 @@ public class TransitionAnimation {
return set;
}
/** Returns whether the hardware buffer passed in is marked as protected. */
public static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
return (hardwareBuffer.getUsage() & HardwareBuffer.USAGE_PROTECTED_CONTENT)
== HardwareBuffer.USAGE_PROTECTED_CONTENT;
}
/** Returns the luminance in 0~1. */
public static float getBorderLuma(SurfaceControl surfaceControl, int w, int h) {
final ScreenCapture.ScreenshotHardwareBuffer buffer =
ScreenCapture.captureLayers(surfaceControl, new Rect(0, 0, w, h), 1);
if (buffer != null) {
return getBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
}
return 0;
}
/** Returns the luminance in 0~1. */
public static float getBorderLuma(HardwareBuffer hwBuffer, ColorSpace colorSpace) {
if (hwBuffer == null) {
return 0;
}
final int format = hwBuffer.getFormat();
// Only support RGB format in 4 bytes. And protected buffer is not readable.
if (format != HardwareBuffer.RGBA_8888 || hasProtectedContent(hwBuffer)) {
return 0;
}
final ImageReader ir = ImageReader.newInstance(hwBuffer.getWidth(), hwBuffer.getHeight(),
format, 1 /* maxImages */);
ir.getSurface().attachAndQueueBufferWithColorSpace(hwBuffer, colorSpace);
final Image image = ir.acquireLatestImage();
if (image == null || image.getPlaneCount() < 1) {
return 0;
}
final Image.Plane plane = image.getPlanes()[0];
final ByteBuffer buffer = plane.getBuffer();
final int width = image.getWidth();
final int height = image.getHeight();
final int pixelStride = plane.getPixelStride();
final int rowStride = plane.getRowStride();
final int sampling = 10;
final int[] borderLumas = new int[(width + height) * 2 / sampling];
// Grab the top and bottom borders.
int i = 0;
for (int x = 0, size = width - sampling; x < size; x += sampling) {
borderLumas[i++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
borderLumas[i++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
}
// Grab the left and right borders.
for (int y = 0, size = height - sampling; y < size; y += sampling) {
borderLumas[i++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
borderLumas[i++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
}
ir.close();
// Get "mode" by histogram.
final int[] histogram = new int[256];
int maxCount = 0;
int mostLuma = 0;
for (int luma : borderLumas) {
final int count = ++histogram[luma];
if (count > maxCount) {
maxCount = count;
mostLuma = luma;
}
}
return mostLuma / 255f;
}
/** Returns the luminance of the pixel in 0~255. */
private static int getPixelLuminance(ByteBuffer buffer, int x, int y, int pixelStride,
int rowStride) {
final int color = buffer.getInt(y * rowStride + x * pixelStride);
// The buffer from ImageReader is always in native order (little-endian), so extract the
// color components in reversed order.
final int r = color & 0xff;
final int g = (color >> 8) & 0xff;
final int b = (color >> 16) & 0xff;
// Approximation of WCAG 2.0 relative luminance.
return ((r * 8) + (g * 22) + (b * 2)) >> 5;
}
}

View File

@@ -16,8 +16,6 @@
package com.android.wm.shell.transition;
import static android.hardware.HardwareBuffer.RGBA_8888;
import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
import static android.util.RotationUtils.deltaRotation;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
@@ -37,8 +35,6 @@ import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageReader;
import android.util.Slog;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -50,12 +46,11 @@ import android.window.ScreenCapture;
import android.window.TransitionInfo;
import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
/**
* This class handles the rotation animation when the device is rotated.
@@ -173,7 +168,7 @@ class ScreenRotationAnimation {
t.setBuffer(mScreenshotLayer, hardwareBuffer);
t.show(mScreenshotLayer);
if (!isCustomRotate()) {
mStartLuma = getMedianBorderLuma(hardwareBuffer, colorSpace);
mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, colorSpace);
}
}
@@ -404,93 +399,6 @@ class ScreenRotationAnimation {
mTransactionPool.release(t);
}
/**
* Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the
* luminance at the borders of the bitmap
* @return the average luminance of all the pixels at the borders of the bitmap
*/
private static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) {
// Cannot read content from buffer with protected usage.
if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888
|| hasProtectedContent(hardwareBuffer)) {
return 0;
}
ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(),
hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1);
ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace);
Image image = ir.acquireLatestImage();
if (image == null || image.getPlanes().length == 0) {
return 0;
}
Image.Plane plane = image.getPlanes()[0];
ByteBuffer buffer = plane.getBuffer();
int width = image.getWidth();
int height = image.getHeight();
int pixelStride = plane.getPixelStride();
int rowStride = plane.getRowStride();
float[] borderLumas = new float[2 * width + 2 * height];
// Grab the top and bottom borders
int l = 0;
for (int x = 0; x < width; x++) {
borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
}
// Grab the left and right borders
for (int y = 0; y < height; y++) {
borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
}
// Cleanup
ir.close();
// Oh, is this too simple and inefficient for you?
// How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians
Arrays.sort(borderLumas);
return borderLumas[borderLumas.length / 2];
}
/**
* @return whether the hardwareBuffer passed in is marked as protected.
*/
private static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
}
private static float getPixelLuminance(ByteBuffer buffer, int x, int y,
int pixelStride, int rowStride) {
int offset = y * rowStride + x * pixelStride;
int pixel = 0;
pixel |= (buffer.get(offset) & 0xff) << 16; // R
pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G
pixel |= (buffer.get(offset + 2) & 0xff); // B
pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
return Color.valueOf(pixel).luminance();
}
/**
* Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
* @see #getMedianBorderLuma(HardwareBuffer, ColorSpace)
*/
private static float getLumaOfSurfaceControl(Rect bounds, SurfaceControl surfaceControl) {
if (surfaceControl == null) {
return 0;
}
Rect crop = new Rect(0, 0, bounds.width(), bounds.height());
ScreenCapture.ScreenshotHardwareBuffer buffer =
ScreenCapture.captureLayers(surfaceControl, crop, 1);
if (buffer == null) {
return 0;
}
return getMedianBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
}
private static void applyColor(int startColor, int endColor, float[] rgbFloat,
float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,

View File

@@ -55,6 +55,7 @@ import android.view.animation.Transformation;
import android.window.ScreenCapture;
import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.display.DisplayControl;
import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -246,7 +247,7 @@ class ScreenRotationAnimation {
HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"ScreenRotationAnimation#getMedianBorderLuma");
mStartLuma = RotationAnimationUtils.getMedianBorderLuma(hardwareBuffer,
mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
screenshotBuffer.getColorSpace());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -489,8 +490,8 @@ class ScreenRotationAnimation {
return false;
}
if (!mStarted) {
mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(),
mDisplayContent.getWindowingLayer());
mEndLuma = TransitionAnimation.getBorderLuma(mDisplayContent.getWindowingLayer(),
finalWidth, finalHeight);
startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
exitAnim, enterAnim);
}

View File

@@ -83,11 +83,11 @@ import android.window.TransitionInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.utils.RotationAnimationUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -2190,7 +2190,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
changeInfo.mSnapshot = snapshotSurface;
if (isDisplayRotation) {
// This isn't cheap, so only do it for display rotations.
changeInfo.mSnapshotLuma = RotationAnimationUtils.getMedianBorderLuma(
changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace());
}
SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();

View File

@@ -16,24 +16,11 @@
package com.android.server.wm.utils;
import static android.hardware.HardwareBuffer.RGBA_8888;
import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
import android.graphics.Color;
import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageReader;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.ScreenCapture;
import java.nio.ByteBuffer;
import java.util.Arrays;
/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/
@@ -46,89 +33,6 @@ public class RotationAnimationUtils {
return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
}
/**
* Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the
* luminance at the borders of the bitmap
* @return the average luminance of all the pixels at the borders of the bitmap
*/
public static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) {
// Cannot read content from buffer with protected usage.
if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888
|| hasProtectedContent(hardwareBuffer)) {
return 0;
}
ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(),
hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1);
ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace);
Image image = ir.acquireLatestImage();
if (image == null || image.getPlanes().length == 0) {
return 0;
}
Image.Plane plane = image.getPlanes()[0];
ByteBuffer buffer = plane.getBuffer();
int width = image.getWidth();
int height = image.getHeight();
int pixelStride = plane.getPixelStride();
int rowStride = plane.getRowStride();
float[] borderLumas = new float[2 * width + 2 * height];
// Grab the top and bottom borders
int l = 0;
for (int x = 0; x < width; x++) {
borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
}
// Grab the left and right borders
for (int y = 0; y < height; y++) {
borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
}
// Cleanup
ir.close();
// Oh, is this too simple and inefficient for you?
// How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians
Arrays.sort(borderLumas);
return borderLumas[borderLumas.length / 2];
}
private static float getPixelLuminance(ByteBuffer buffer, int x, int y,
int pixelStride, int rowStride) {
int offset = y * rowStride + x * pixelStride;
int pixel = 0;
pixel |= (buffer.get(offset) & 0xff) << 16; // R
pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G
pixel |= (buffer.get(offset + 2) & 0xff); // B
pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
return Color.valueOf(pixel).luminance();
}
/**
* Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
* @see #getMedianBorderLuma(HardwareBuffer, ColorSpace)
*/
public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) {
if (surfaceControl == null) {
return 0;
}
Point size = new Point();
display.getSize(size);
Rect crop = new Rect(0, 0, size.x, size.y);
ScreenCapture.ScreenshotHardwareBuffer buffer =
ScreenCapture.captureLayers(surfaceControl, crop, 1);
if (buffer == null) {
return 0;
}
return RotationAnimationUtils.getMedianBorderLuma(buffer.getHardwareBuffer(),
buffer.getColorSpace());
}
public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) {
switch (rotation) {
case Surface.ROTATION_0:

View File

@@ -26,8 +26,10 @@ import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.hardware.HardwareBuffer;
import android.view.Surface;
import android.platform.test.annotations.Presubmit;
import android.view.Surface;
import com.android.internal.policy.TransitionAnimation;
import org.junit.Before;
import org.junit.Test;
@@ -52,7 +54,8 @@ public class RotationAnimationUtilsTest {
public void blackLuma() {
Bitmap swBitmap = createBitmap(0);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(0, borderLuma, 0);
}
@@ -60,7 +63,7 @@ public class RotationAnimationUtilsTest {
public void whiteLuma() {
Bitmap swBitmap = createBitmap(1);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(1, borderLuma, 0);
}
@@ -68,7 +71,7 @@ public class RotationAnimationUtilsTest {
public void unevenBitmapDimens() {
Bitmap swBitmap = createBitmap(1, BITMAP_WIDTH + 1, BITMAP_HEIGHT + 1);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(1, borderLuma, 0);
}
@@ -77,7 +80,7 @@ public class RotationAnimationUtilsTest {
Bitmap swBitmap = createBitmap(1);
setBorderLuma(swBitmap, 0);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(0, borderLuma, 0);
}
@@ -86,7 +89,7 @@ public class RotationAnimationUtilsTest {
Bitmap swBitmap = createBitmap(0);
setBorderLuma(swBitmap, 1);
HardwareBuffer hb = swBitmapToHardwareBuffer(swBitmap);
float borderLuma = RotationAnimationUtils.getMedianBorderLuma(hb, mColorSpace);
float borderLuma = TransitionAnimation.getBorderLuma(hb, mColorSpace);
assertEquals(1, borderLuma, 0);
}