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