Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit df86b40c authored by Riddle Hsu's avatar Riddle Hsu Committed by Android (Google) Code Review
Browse files

Merge "Optimize luminance calculation for rotation animation"

parents c9f364c0 f33d1634
Loading
Loading
Loading
Loading
+92 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
+2 −94
Original line number Diff line number Diff line
@@ -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,
+4 −3
Original line number Diff line number Diff line
@@ -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);
        }
+2 −2
Original line number Diff line number Diff line
@@ -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();
+0 −96
Original line number Diff line number Diff line
@@ -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:
Loading