1 /* 2 * Copyright (C) 2020 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 android.util; 18 19 import static android.view.Surface.ROTATION_0; 20 import static android.view.Surface.ROTATION_180; 21 import static android.view.Surface.ROTATION_270; 22 import static android.view.Surface.ROTATION_90; 23 24 import android.annotation.Dimension; 25 import android.graphics.Insets; 26 import android.graphics.Matrix; 27 import android.graphics.Point; 28 import android.graphics.PointF; 29 import android.graphics.Rect; 30 import android.view.Surface.Rotation; 31 import android.view.SurfaceControl; 32 33 /** 34 * A class containing utility methods related to rotation. 35 * 36 * @hide 37 */ 38 public class RotationUtils { 39 40 /** 41 * Rotates an Insets according to the given rotation. 42 */ rotateInsets(Insets insets, @Rotation int rotation)43 public static Insets rotateInsets(Insets insets, @Rotation int rotation) { 44 if (insets == null || insets == Insets.NONE) { 45 return insets; 46 } 47 Insets rotated; 48 switch (rotation) { 49 case ROTATION_0: 50 rotated = insets; 51 break; 52 case ROTATION_90: 53 rotated = Insets.of( 54 insets.top, 55 insets.right, 56 insets.bottom, 57 insets.left); 58 break; 59 case ROTATION_180: 60 rotated = Insets.of( 61 insets.right, 62 insets.bottom, 63 insets.left, 64 insets.top); 65 break; 66 case ROTATION_270: 67 rotated = Insets.of( 68 insets.bottom, 69 insets.left, 70 insets.top, 71 insets.right); 72 break; 73 default: 74 throw new IllegalArgumentException("unknown rotation: " + rotation); 75 } 76 return rotated; 77 } 78 79 /** 80 * Rotates bounds as if parentBounds and bounds are a group. The group is rotated from 81 * oldRotation to newRotation. This assumes that parentBounds is at 0,0 and remains at 0,0 after 82 * rotation. The bounds will be at the same physical position in parentBounds. 83 * 84 * Only 'inOutBounds' is mutated. 85 */ rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int oldRotation, @Rotation int newRotation)86 public static void rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int oldRotation, 87 @Rotation int newRotation) { 88 rotateBounds(inOutBounds, parentBounds, deltaRotation(oldRotation, newRotation)); 89 } 90 91 /** 92 * Rotates inOutBounds together with the parent for a given rotation delta. This assumes that 93 * the parent starts at 0,0 and remains at 0,0 after the rotation. The inOutBounds will remain 94 * at the same physical position within the parent. 95 * 96 * Only 'inOutBounds' is mutated. 97 */ rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight, @Rotation int rotation)98 public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight, 99 @Rotation int rotation) { 100 final int origLeft = inOutBounds.left; 101 final int origTop = inOutBounds.top; 102 switch (rotation) { 103 case ROTATION_0: 104 return; 105 case ROTATION_90: 106 inOutBounds.left = inOutBounds.top; 107 inOutBounds.top = parentWidth - inOutBounds.right; 108 inOutBounds.right = inOutBounds.bottom; 109 inOutBounds.bottom = parentWidth - origLeft; 110 return; 111 case ROTATION_180: 112 inOutBounds.left = parentWidth - inOutBounds.right; 113 inOutBounds.right = parentWidth - origLeft; 114 inOutBounds.top = parentHeight - inOutBounds.bottom; 115 inOutBounds.bottom = parentHeight - origTop; 116 return; 117 case ROTATION_270: 118 inOutBounds.left = parentHeight - inOutBounds.bottom; 119 inOutBounds.bottom = inOutBounds.right; 120 inOutBounds.right = parentHeight - inOutBounds.top; 121 inOutBounds.top = origLeft; 122 } 123 } 124 125 /** 126 * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta` 127 * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and 128 * remains at 0,0 after rotation. The bounds will be at the same physical position in 129 * parentBounds. 130 * 131 * Only 'inOutBounds' is mutated. 132 */ rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int rotation)133 public static void rotateBounds(Rect inOutBounds, Rect parentBounds, @Rotation int rotation) { 134 rotateBounds(inOutBounds, parentBounds.right, parentBounds.bottom, rotation); 135 } 136 137 /** @return the rotation needed to rotate from oldRotation to newRotation. */ 138 @Rotation deltaRotation(@otation int oldRotation, @Rotation int newRotation)139 public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) { 140 int delta = newRotation - oldRotation; 141 if (delta < 0) delta += 4; 142 return delta; 143 } 144 145 /** 146 * Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the 147 * bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left 148 * corner appropriately. 149 */ rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc, @Rotation int rotation)150 public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc, 151 @Rotation int rotation) { 152 // Note: the matrix values look inverted, but they aren't because our coordinate-space 153 // is actually left-handed. 154 // Note: setMatrix expects values in column-major order. 155 switch (rotation) { 156 case ROTATION_0: 157 t.setMatrix(sc, 1.f, 0.f, 0.f, 1.f); 158 break; 159 case ROTATION_90: 160 t.setMatrix(sc, 0.f, -1.f, 1.f, 0.f); 161 break; 162 case ROTATION_180: 163 t.setMatrix(sc, -1.f, 0.f, 0.f, -1.f); 164 break; 165 case ROTATION_270: 166 t.setMatrix(sc, 0.f, 1.f, -1.f, 0.f); 167 break; 168 } 169 } 170 171 /** 172 * Rotates a point CCW within a rectangle of size parentW x parentH with top/left at the 173 * origin as if the point is stuck to the rectangle. The rectangle is transformed such that 174 * it's top/left remains at the origin after the rotation. 175 */ rotatePoint(Point inOutPoint, @Rotation int rotation, int parentW, int parentH)176 public static void rotatePoint(Point inOutPoint, @Rotation int rotation, 177 int parentW, int parentH) { 178 int origX = inOutPoint.x; 179 switch (rotation) { 180 case ROTATION_0: 181 return; 182 case ROTATION_90: 183 inOutPoint.x = inOutPoint.y; 184 inOutPoint.y = parentW - origX; 185 return; 186 case ROTATION_180: 187 inOutPoint.x = parentW - inOutPoint.x; 188 inOutPoint.y = parentH - inOutPoint.y; 189 return; 190 case ROTATION_270: 191 inOutPoint.x = parentH - inOutPoint.y; 192 inOutPoint.y = origX; 193 } 194 } 195 196 /** 197 * Same as {@link #rotatePoint}, but for float coordinates. 198 */ rotatePointF(PointF inOutPoint, @Rotation int rotation, float parentW, float parentH)199 public static void rotatePointF(PointF inOutPoint, @Rotation int rotation, 200 float parentW, float parentH) { 201 float origX = inOutPoint.x; 202 switch (rotation) { 203 case ROTATION_0: 204 return; 205 case ROTATION_90: 206 inOutPoint.x = inOutPoint.y; 207 inOutPoint.y = parentW - origX; 208 return; 209 case ROTATION_180: 210 inOutPoint.x = parentW - inOutPoint.x; 211 inOutPoint.y = parentH - inOutPoint.y; 212 return; 213 case ROTATION_270: 214 inOutPoint.x = parentH - inOutPoint.y; 215 inOutPoint.y = origX; 216 } 217 } 218 219 /** 220 * Sets a matrix such that given a rotation, it transforms physical display 221 * coordinates to that rotation's logical coordinates. 222 * 223 * @param rotation the rotation to which the matrix should transform 224 * @param out the matrix to be set 225 */ transformPhysicalToLogicalCoordinates(@otation int rotation, @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out)226 public static void transformPhysicalToLogicalCoordinates(@Rotation int rotation, 227 @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) { 228 switch (rotation) { 229 case ROTATION_0: 230 out.reset(); 231 break; 232 case ROTATION_90: 233 out.setRotate(270); 234 out.postTranslate(0, physicalWidth); 235 break; 236 case ROTATION_180: 237 out.setRotate(180); 238 out.postTranslate(physicalWidth, physicalHeight); 239 break; 240 case ROTATION_270: 241 out.setRotate(90); 242 out.postTranslate(physicalHeight, 0); 243 break; 244 default: 245 throw new IllegalArgumentException("Unknown rotation: " + rotation); 246 } 247 } 248 } 249