1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm.utils;
18 
19 import static android.hardware.HardwareBuffer.RGBA_8888;
20 import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
21 
22 import android.graphics.Color;
23 import android.graphics.ColorSpace;
24 import android.graphics.Matrix;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.hardware.HardwareBuffer;
28 import android.media.Image;
29 import android.media.ImageReader;
30 import android.view.Display;
31 import android.view.Surface;
32 import android.view.SurfaceControl;
33 
34 import java.nio.ByteBuffer;
35 import java.util.Arrays;
36 
37 
38 /** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/
39 public class RotationAnimationUtils {
40 
41     /**
42      * @return whether the hardwareBuffer passed in is marked as protected.
43      */
hasProtectedContent(HardwareBuffer hardwareBuffer)44     public static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
45         return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
46     }
47 
48     /**
49      * Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the
50      * luminance at the borders of the bitmap
51      * @return the average luminance of all the pixels at the borders of the bitmap
52      */
getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace)53     public static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) {
54         // Cannot read content from buffer with protected usage.
55         if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888
56                 || hasProtectedContent(hardwareBuffer)) {
57             return 0;
58         }
59 
60         ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(),
61                 hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1);
62         ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace);
63         Image image = ir.acquireLatestImage();
64         if (image == null || image.getPlanes().length == 0) {
65             return 0;
66         }
67 
68         Image.Plane plane = image.getPlanes()[0];
69         ByteBuffer buffer = plane.getBuffer();
70         int width = image.getWidth();
71         int height = image.getHeight();
72         int pixelStride = plane.getPixelStride();
73         int rowStride = plane.getRowStride();
74         float[] borderLumas = new float[2 * width + 2 * height];
75 
76         // Grab the top and bottom borders
77         int l = 0;
78         for (int x = 0; x < width; x++) {
79             borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
80             borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
81         }
82 
83         // Grab the left and right borders
84         for (int y = 0; y < height; y++) {
85             borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
86             borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
87         }
88 
89         // Cleanup
90         ir.close();
91 
92         // Oh, is this too simple and inefficient for you?
93         // How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians
94         Arrays.sort(borderLumas);
95         return borderLumas[borderLumas.length / 2];
96     }
97 
getPixelLuminance(ByteBuffer buffer, int x, int y, int pixelStride, int rowStride)98     private static float getPixelLuminance(ByteBuffer buffer, int x, int y,
99             int pixelStride, int rowStride) {
100         int offset = y * rowStride + x * pixelStride;
101         int pixel = 0;
102         pixel |= (buffer.get(offset) & 0xff) << 16;     // R
103         pixel |= (buffer.get(offset + 1) & 0xff) << 8;  // G
104         pixel |= (buffer.get(offset + 2) & 0xff);       // B
105         pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
106         return Color.valueOf(pixel).luminance();
107     }
108 
109     /**
110      * Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
111      * @see #getMedianBorderLuma(HardwareBuffer, ColorSpace)
112      */
getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl)113     public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) {
114         if (surfaceControl ==  null) {
115             return 0;
116         }
117 
118         Point size = new Point();
119         display.getSize(size);
120         Rect crop = new Rect(0, 0, size.x, size.y);
121         SurfaceControl.ScreenshotHardwareBuffer buffer =
122                 SurfaceControl.captureLayers(surfaceControl, crop, 1);
123         if (buffer == null) {
124             return 0;
125         }
126 
127         return RotationAnimationUtils.getMedianBorderLuma(buffer.getHardwareBuffer(),
128                 buffer.getColorSpace());
129     }
130 
createRotationMatrix(int rotation, int width, int height, Matrix outMatrix)131     public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) {
132         switch (rotation) {
133             case Surface.ROTATION_0:
134                 outMatrix.reset();
135                 break;
136             case Surface.ROTATION_90:
137                 outMatrix.setRotate(90, 0, 0);
138                 outMatrix.postTranslate(height, 0);
139                 break;
140             case Surface.ROTATION_180:
141                 outMatrix.setRotate(180, 0, 0);
142                 outMatrix.postTranslate(width, height);
143                 break;
144             case Surface.ROTATION_270:
145                 outMatrix.setRotate(270, 0, 0);
146                 outMatrix.postTranslate(0, width);
147                 break;
148         }
149     }
150 }
151