1 /* 2 * Copyright (C) 2023 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.tools.common.traces.surfaceflinger 18 19 import android.tools.common.Rotation 20 import android.tools.common.datatypes.Matrix33 21 import android.tools.common.datatypes.RectF 22 import android.tools.common.withCache 23 import kotlin.js.JsExport 24 import kotlin.js.JsName 25 26 /** 27 * Wrapper for TransformProto (frameworks/native/services/surfaceflinger/layerproto/common.proto) 28 * 29 * This class is used by flicker and Winscope 30 */ 31 @JsExport 32 class Transform 33 private constructor(@JsName("type") val type: Int?, @JsName("matrix") val matrix: Matrix33) { 34 35 /** 36 * Returns true if the applying the transform on an an axis aligned rectangle results in another 37 * axis aligned rectangle. 38 */ 39 @JsName("isSimpleRotation") 40 val isSimpleRotation: Boolean = !(type?.isFlagSet(ROT_INVALID_VAL) ?: true) 41 42 /** 43 * The transformation matrix is defined as the product of: | cos(a) -sin(a) | \/ | X 0 | | 44 * sin(a) cos(a) | /\ | 0 Y | 45 * 46 * where a is a rotation angle, and X and Y are scaling factors. A transformation matrix is 47 * invalid when either X or Y is zero, as a rotation matrix is valid for any angle. When either 48 * X or Y is 0, then the scaling matrix is not invertible, which makes the transformation matrix 49 * not invertible as well. A 2D matrix with components | A B | is not invertible if and only if 50 * AD - BC = 0. 51 * 52 * ``` 53 * | C D | 54 * ``` 55 * 56 * This check is included above. 57 */ 58 @JsName("isValid") 59 val isValid: Boolean 60 get() { 61 // determinant of transform 62 return matrix.dsdx * matrix.dtdy != matrix.dtdx * matrix.dsdy 63 } 64 65 @JsName("isScaling") 66 val isScaling: Boolean 67 get() = type?.isFlagSet(SCALE_VAL) ?: false 68 @JsName("isTranslating") 69 val isTranslating: Boolean 70 get() = type?.isFlagSet(TRANSLATE_VAL) ?: false 71 @JsName("isRotating") 72 val isRotating: Boolean 73 get() = type?.isFlagSet(ROTATE_VAL) ?: false 74 getRotationnull75 fun getRotation(): Rotation { 76 if (type == null) { 77 return Rotation.ROTATION_0 78 } 79 80 return when { 81 type.isFlagClear(SCALE_VAL or ROTATE_VAL or TRANSLATE_VAL) -> Rotation.ROTATION_0 82 type.isFlagSet(ROT_90_VAL) -> Rotation.ROTATION_90 83 type.isFlagSet(FLIP_V_VAL or FLIP_H_VAL) -> Rotation.ROTATION_180 84 type.isFlagSet(ROT_90_VAL or FLIP_V_VAL or FLIP_H_VAL) -> Rotation.ROTATION_270 85 else -> Rotation.ROTATION_0 86 } 87 } 88 89 private val typeFlags: Array<String> 90 get() { 91 if (type == null) { 92 return arrayOf("IDENTITY") 93 } 94 95 val result = mutableListOf<String>() 96 97 if (type.isFlagClear(SCALE_VAL or ROTATE_VAL or TRANSLATE_VAL)) { 98 result.add("IDENTITY") 99 } 100 101 if (type.isFlagSet(SCALE_VAL)) { 102 result.add("SCALE") 103 } 104 105 if (type.isFlagSet(TRANSLATE_VAL)) { 106 result.add("TRANSLATE") 107 } 108 109 when { 110 type.isFlagSet(ROT_INVALID_VAL) -> result.add("ROT_INVALID") 111 type.isFlagSet(ROT_90_VAL or FLIP_V_VAL or FLIP_H_VAL) -> result.add("ROT_270") 112 type.isFlagSet(FLIP_V_VAL or FLIP_H_VAL) -> result.add("ROT_180") 113 else -> { 114 if (type.isFlagSet(ROT_90_VAL)) { 115 result.add("ROT_90") 116 } 117 if (type.isFlagSet(FLIP_V_VAL)) { 118 result.add("FLIP_V") 119 } 120 if (type.isFlagSet(FLIP_H_VAL)) { 121 result.add("FLIP_H") 122 } 123 } 124 } 125 126 if (result.isEmpty()) { 127 throw RuntimeException("Unknown transform type $type") 128 } 129 130 return result.toTypedArray() 131 } 132 133 @JsName("prettyPrint") prettyPrintnull134 fun prettyPrint(): String { 135 val transformType = typeFlags.joinToString("|") 136 137 if (isSimpleTransform(type)) { 138 return transformType 139 } 140 141 return "$transformType $matrix" 142 } 143 144 @JsName("getTypeAsString") getTypeAsStringnull145 fun getTypeAsString(): String { 146 return typeFlags.joinToString("|") 147 } 148 toStringnull149 override fun toString(): String = prettyPrint() 150 151 @JsName("apply") 152 fun apply(bounds: RectF?): RectF { 153 return multiplyRect(matrix, bounds ?: RectF.EMPTY) 154 } 155 156 private data class Vec2(val x: Float, val y: Float) 157 multiplyRectnull158 private fun multiplyRect(matrix: Matrix33, rect: RectF): RectF { 159 // |dsdx dsdy tx| | left, top | 160 // matrix = |dtdx dtdy ty| rect = | | 161 // |0 0 1 | | right, bottom | 162 163 val leftTop = multiplyVec2(matrix, rect.left, rect.top) 164 val rightTop = multiplyVec2(matrix, rect.right, rect.top) 165 val leftBottom = multiplyVec2(matrix, rect.left, rect.bottom) 166 val rightBottom = multiplyVec2(matrix, rect.right, rect.bottom) 167 168 return RectF.from( 169 left = arrayOf(leftTop.x, rightTop.x, leftBottom.x, rightBottom.x).minOrNull() ?: 0f, 170 top = arrayOf(leftTop.y, rightTop.y, leftBottom.y, rightBottom.y).minOrNull() ?: 0f, 171 right = arrayOf(leftTop.x, rightTop.x, leftBottom.x, rightBottom.x).minOrNull() ?: 0f, 172 bottom = arrayOf(leftTop.y, rightTop.y, leftBottom.y, rightBottom.y).minOrNull() ?: 0f 173 ) 174 } 175 multiplyVec2null176 private fun multiplyVec2(matrix: Matrix33, x: Float, y: Float): Vec2 { 177 // |dsdx dsdy tx| | x | 178 // |dtdx dtdy ty| x | y | 179 // |0 0 1 | | 1 | 180 return Vec2( 181 matrix.dsdx * x + matrix.dsdy * y + matrix.tx, 182 matrix.dtdx * x + matrix.dtdy * y + matrix.ty 183 ) 184 } 185 equalsnull186 override fun equals(other: Any?): Boolean { 187 if (this === other) return true 188 if (other !is Transform) return false 189 190 if (type != other.type) return false 191 if (matrix != other.matrix) return false 192 if (isSimpleRotation != other.isSimpleRotation) return false 193 194 return true 195 } 196 hashCodenull197 override fun hashCode(): Int { 198 var result = type ?: 0 199 result = 31 * result + matrix.hashCode() 200 result = 31 * result + isSimpleRotation.hashCode() 201 return result 202 } 203 204 companion object { 205 @JsName("EMPTY") 206 val EMPTY: Transform <lambda>null207 get() = withCache { Transform(type = null, matrix = Matrix33.EMPTY) } 208 /* transform type flags */ 209 @JsName("TRANSLATE_VAL") const val TRANSLATE_VAL = 0x0001 210 @JsName("ROTATE_VAL") const val ROTATE_VAL = 0x0002 211 @JsName("SCALE_VAL") const val SCALE_VAL = 0x0004 212 213 /* orientation flags */ 214 @JsName("FLIP_H_VAL") const val FLIP_H_VAL = 0x0100 // (1 << 0 << 8) 215 @JsName("FLIP_V_VAL") const val FLIP_V_VAL = 0x0200 // (1 << 1 << 8) 216 @JsName("ROT_90_VAL") const val ROT_90_VAL = 0x0400 // (1 << 2 << 8) 217 @JsName("ROT_INVALID_VAL") const val ROT_INVALID_VAL = 0x8000 // (0x80 << 8) 218 219 @JsName("isSimpleTransform") isSimpleTransformnull220 fun isSimpleTransform(type: Int?): Boolean { 221 return type?.isFlagClear(ROT_INVALID_VAL or SCALE_VAL) ?: false 222 } 223 isFlagClearnull224 fun Int.isFlagClear(bits: Int): Boolean { 225 return this and bits == 0 226 } 227 isFlagSetnull228 fun Int.isFlagSet(bits: Int): Boolean { 229 return this and bits == bits 230 } 231 232 @JsName("from") <lambda>null233 fun from(type: Int?, matrix: Matrix33): Transform = withCache { Transform(type, matrix) } 234 } 235 } 236