1/* 2 * Copyright (C) 2024 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 17import {assertDefined} from 'common/assert_utils'; 18import {IDENTITY_MATRIX, TransformMatrix} from 'common/geometry_types'; 19import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 20 21export enum TransformType { 22 EMPTY = 0x0, 23 TRANSLATE_VAL = 0x0001, 24 ROTATE_VAL = 0x0002, 25 SCALE_VAL = 0x0004, 26 FLIP_H_VAL = 0x0100, 27 FLIP_V_VAL = 0x0200, 28 ROT_90_VAL = 0x0400, 29 ROT_INVALID_VAL = 0x8000, 30} 31 32export class Transform { 33 static EMPTY = new Transform(TransformType.EMPTY, IDENTITY_MATRIX); 34 35 constructor(public type: TransformType, public matrix: TransformMatrix) {} 36 37 static from( 38 transformNode: PropertyTreeNode, 39 position?: PropertyTreeNode, 40 ): Transform { 41 if (transformNode.getAllChildren().length === 0) return Transform.EMPTY; 42 43 const transformType = transformNode.getChildByName('type')?.getValue() ?? 0; 44 const matrixNode = transformNode.getChildByName('matrix'); 45 46 if (matrixNode) { 47 return new Transform(transformType, { 48 dsdx: assertDefined(matrixNode.getChildByName('dsdx')).getValue(), 49 dtdx: assertDefined(matrixNode.getChildByName('dtdx')).getValue(), 50 tx: assertDefined(matrixNode.getChildByName('tx')).getValue(), 51 dsdy: assertDefined(matrixNode.getChildByName('dsdy')).getValue(), 52 dtdy: assertDefined(matrixNode.getChildByName('dtdy')).getValue(), 53 ty: assertDefined(matrixNode.getChildByName('ty')).getValue(), 54 }); 55 } 56 57 const x = position?.getChildByName('x')?.getValue() ?? 0; 58 const y = position?.getChildByName('y')?.getValue() ?? 0; 59 60 if (TransformUtils.isSimpleTransform(transformType)) { 61 return TransformUtils.getDefaultTransform(transformType, x, y); 62 } 63 64 return new Transform(transformType, { 65 dsdx: transformNode.getChildByName('dsdx')?.getValue() ?? 0, 66 dtdx: transformNode.getChildByName('dtdx')?.getValue() ?? 0, 67 tx: x, 68 dsdy: transformNode.getChildByName('dsdy')?.getValue() ?? 0, 69 dtdy: transformNode.getChildByName('dtdy')?.getValue() ?? 0, 70 ty: y, 71 }); 72 } 73} 74 75export class TransformUtils { 76 static isValidTransform(transform: Transform): boolean { 77 return ( 78 transform.matrix.dsdx * transform.matrix.dtdy !== 79 transform.matrix.dtdx * transform.matrix.dsdy 80 ); 81 } 82 83 static isSimpleRotation(type: TransformType | undefined): boolean { 84 return !(type 85 ? TransformUtils.isFlagSet(type, TransformType.ROT_INVALID_VAL) 86 : false); 87 } 88 89 static getTypeFlags(type: TransformType): string { 90 const typeFlags: string[] = []; 91 92 if ( 93 TransformUtils.isFlagClear( 94 type, 95 TransformType.SCALE_VAL | 96 TransformType.ROTATE_VAL | 97 TransformType.TRANSLATE_VAL, 98 ) 99 ) { 100 typeFlags.push('IDENTITY'); 101 } 102 103 if (TransformUtils.isFlagSet(type, TransformType.SCALE_VAL)) { 104 typeFlags.push('SCALE'); 105 } 106 107 if (TransformUtils.isFlagSet(type, TransformType.TRANSLATE_VAL)) { 108 typeFlags.push('TRANSLATE'); 109 } 110 111 if (TransformUtils.isFlagSet(type, TransformType.ROT_INVALID_VAL)) { 112 typeFlags.push('ROT_INVALID'); 113 } else if ( 114 TransformUtils.isFlagSet( 115 type, 116 TransformType.ROT_90_VAL | 117 TransformType.FLIP_V_VAL | 118 TransformType.FLIP_H_VAL, 119 ) 120 ) { 121 typeFlags.push('ROT_270'); 122 } else if ( 123 TransformUtils.isFlagSet( 124 type, 125 TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL, 126 ) 127 ) { 128 typeFlags.push('ROT_180'); 129 } else { 130 if (TransformUtils.isFlagSet(type, TransformType.ROT_90_VAL)) { 131 typeFlags.push('ROT_90'); 132 } 133 if (TransformUtils.isFlagSet(type, TransformType.FLIP_V_VAL)) { 134 typeFlags.push('FLIP_V'); 135 } 136 if (TransformUtils.isFlagSet(type, TransformType.FLIP_H_VAL)) { 137 typeFlags.push('FLIP_H'); 138 } 139 } 140 141 if (typeFlags.length === 0) { 142 throw Error(`Unknown transform type ${type}`); 143 } 144 return typeFlags.join('|'); 145 } 146 147 static getDefaultTransform( 148 type: TransformType, 149 x: number, 150 y: number, 151 ): Transform { 152 // IDENTITY 153 if (!type) { 154 return new Transform(type, { 155 dsdx: 1, 156 dtdx: 0, 157 tx: x, 158 dsdy: 0, 159 dtdy: 1, 160 ty: y, 161 }); 162 } 163 164 // ROT_270 = ROT_90|FLIP_H|FLIP_V 165 if ( 166 TransformUtils.isFlagSet( 167 type, 168 TransformType.ROT_90_VAL | 169 TransformType.FLIP_V_VAL | 170 TransformType.FLIP_H_VAL, 171 ) 172 ) { 173 return new Transform(type, { 174 dsdx: 0, 175 dtdx: -1, 176 tx: x, 177 dsdy: 1, 178 dtdy: 0, 179 ty: y, 180 }); 181 } 182 183 // ROT_180 = FLIP_H|FLIP_V 184 if ( 185 TransformUtils.isFlagSet( 186 type, 187 TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL, 188 ) 189 ) { 190 return new Transform(type, { 191 dsdx: -1, 192 dtdx: 0, 193 tx: x, 194 dsdy: 0, 195 dtdy: -1, 196 ty: y, 197 }); 198 } 199 200 // ROT_90 201 if (TransformUtils.isFlagSet(type, TransformType.ROT_90_VAL)) { 202 return new Transform(type, { 203 dsdx: 0, 204 dtdx: 1, 205 tx: x, 206 dsdy: -1, 207 dtdy: 0, 208 ty: y, 209 }); 210 } 211 212 // IDENTITY 213 if ( 214 TransformUtils.isFlagClear( 215 type, 216 TransformType.SCALE_VAL | TransformType.ROTATE_VAL, 217 ) 218 ) { 219 return new Transform(type, { 220 dsdx: 1, 221 dtdx: 0, 222 tx: x, 223 dsdy: 0, 224 dtdy: 1, 225 ty: y, 226 }); 227 } 228 229 throw new Error(`Unknown transform type ${type}`); 230 } 231 232 static isSimpleTransform(type: TransformType): boolean { 233 return TransformUtils.isFlagClear( 234 type, 235 TransformType.ROT_INVALID_VAL | TransformType.SCALE_VAL, 236 ); 237 } 238 239 private static isFlagSet(type: TransformType, bits: number): boolean { 240 type = type || 0; 241 return (type & bits) === bits; 242 } 243 244 private static isFlagClear(type: TransformType, bits: number): boolean { 245 return (type & bits) === 0; 246 } 247} 248