| /* |
| * Copyright (C) 2024 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import {TransformType} from 'common/geometry/transform_utils'; |
| import {Timestamp} from 'common/time/time'; |
| import {TimeDuration} from 'common/time/time_duration'; |
| import {RawDataUtils} from 'parsers/raw_data_utils'; |
| import {CujType} from 'trace/cujs/cuj_type'; |
| import {PropertyTreeNode} from './property_tree_node'; |
| |
| const EMPTY_OBJ_STRING = '{empty}'; |
| const EMPTY_ARRAY_STRING = '[empty]'; |
| |
| function formatAsDecimal(value: number): string { |
| if (!Number.isInteger(value)) { |
| return value.toFixed(3).toString(); |
| } |
| return value.toString(); |
| } |
| |
| function formatAsHex(value: number, upperCase = false): string { |
| if (value < 0) { |
| value += Math.pow(2, 32); // convert to 2's complement |
| } |
| let hexValue = value.toString(16); |
| if (upperCase) { |
| hexValue = hexValue.toUpperCase(); |
| } |
| return '0x' + hexValue; |
| } |
| |
| interface PropertyFormatter { |
| format(node: PropertyTreeNode): string; |
| } |
| |
| class DefaultPropertyFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| const value = node.getValue(); |
| if (Array.isArray(value) && value.length === 0) { |
| return EMPTY_ARRAY_STRING; |
| } |
| |
| if (typeof value === 'number') { |
| return formatAsDecimal(value); |
| } |
| |
| if (value?.toString) return value.toString(); |
| |
| return `${value}`; |
| } |
| } |
| const DEFAULT_PROPERTY_FORMATTER = new DefaultPropertyFormatter(); |
| |
| class ColorFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| const rNode = node.getChildByName('r'); |
| const gNode = node.getChildByName('g'); |
| const bNode = node.getChildByName('b'); |
| const alphaNode = node.getChildByName('a'); |
| |
| const r = formatAsDecimal(rNode?.getValue() ?? 0); |
| const g = formatAsDecimal(gNode?.getValue() ?? 0); |
| const b = formatAsDecimal(bNode?.getValue() ?? 0); |
| const rgbString = `(${r}, ${g}, ${b})`; |
| if (rNode && gNode && bNode && !alphaNode) { |
| return rgbString; |
| } |
| |
| const alpha = formatAsDecimal(alphaNode?.getValue() ?? 0); |
| const alphaString = `alpha: ${alpha}`; |
| if (RawDataUtils.isEmptyObj(node)) { |
| return `${EMPTY_OBJ_STRING}, ${alphaString}`; |
| } |
| return `${rgbString}, ${alphaString}`; |
| } |
| } |
| const COLOR_FORMATTER = new ColorFormatter(); |
| |
| class RectFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| if (!RawDataUtils.isRect(node) || RawDataUtils.isEmptyObj(node)) { |
| return EMPTY_OBJ_STRING; |
| } |
| const left = formatAsDecimal(node.getChildByName('left')?.getValue() ?? 0); |
| const top = formatAsDecimal(node.getChildByName('top')?.getValue() ?? 0); |
| const right = formatAsDecimal( |
| node.getChildByName('right')?.getValue() ?? 0, |
| ); |
| const bottom = formatAsDecimal( |
| node.getChildByName('bottom')?.getValue() ?? 0, |
| ); |
| |
| return `(${left}, ${top}) - (${right}, ${bottom})`; |
| } |
| } |
| const RECT_FORMATTER = new RectFormatter(); |
| |
| class BufferFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| return `w: ${node.getChildByName('width')?.getValue() ?? 0}, h: ${ |
| node.getChildByName('height')?.getValue() ?? 0 |
| }, stride: ${node.getChildByName('stride')?.getValue()}, format: ${node |
| .getChildByName('format') |
| ?.getValue()}`; |
| } |
| } |
| const BUFFER_FORMATTER = new BufferFormatter(); |
| |
| class LayerIdFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| const value = node.getValue(); |
| return value === -1 || value === 0 ? 'none' : `${value}`; |
| } |
| } |
| const LAYER_ID_FORMATTER = new LayerIdFormatter(); |
| |
| class MatrixFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| const dsdx = formatAsDecimal(node.getChildByName('dsdx')?.getValue() ?? 0); |
| const dtdx = formatAsDecimal(node.getChildByName('dtdx')?.getValue() ?? 0); |
| const dtdy = formatAsDecimal(node.getChildByName('dtdy')?.getValue() ?? 0); |
| const dsdy = formatAsDecimal(node.getChildByName('dsdy')?.getValue() ?? 0); |
| const tx = node.getChildByName('tx'); |
| const ty = node.getChildByName('ty'); |
| if ( |
| dsdx === '0' && |
| dtdx === '0' && |
| dsdy === '0' && |
| dtdy === '0' && |
| !tx && |
| !ty |
| ) { |
| return 'null'; |
| } |
| const matrix22 = `dsdx: ${dsdx}, dtdx: ${dtdx}, dtdy: ${dtdy}, dsdy: ${dsdy}`; |
| if (!tx && !ty) { |
| return matrix22; |
| } |
| return ( |
| matrix22 + |
| `, tx: ${formatAsDecimal(tx?.getValue() ?? 0)}, ty: ${formatAsDecimal( |
| ty?.getValue() ?? 0, |
| )}` |
| ); |
| } |
| } |
| const MATRIX_FORMATTER = new MatrixFormatter(); |
| |
| class TransformFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| const type = node.getChildByName('type'); |
| return type !== undefined |
| ? TransformType.getTypeFlags(type.getValue() ?? 0) |
| : 'null'; |
| } |
| } |
| const TRANSFORM_FORMATTER = new TransformFormatter(); |
| |
| class SizeFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| return `${node.getChildByName('w')?.getValue() ?? 0} x ${ |
| node.getChildByName('h')?.getValue() ?? 0 |
| }`; |
| } |
| } |
| const SIZE_FORMATTER = new SizeFormatter(); |
| |
| class PositionFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| const x = formatAsDecimal(node.getChildByName('x')?.getValue() ?? 0); |
| const y = formatAsDecimal(node.getChildByName('y')?.getValue() ?? 0); |
| return `x: ${x}, y: ${y}`; |
| } |
| } |
| const POSITION_FORMATTER = new PositionFormatter(); |
| |
| class RegionFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| let res = 'SkRegion('; |
| node |
| .getChildByName('rect') |
| ?.getAllChildren() |
| .forEach((rectNode: PropertyTreeNode) => { |
| res += `(${rectNode.getChildByName('left')?.getValue() ?? 0}, ${ |
| rectNode.getChildByName('top')?.getValue() ?? 0 |
| }, ${rectNode.getChildByName('right')?.getValue() ?? 0}, ${ |
| rectNode.getChildByName('bottom')?.getValue() ?? 0 |
| })`; |
| }); |
| return res + ')'; |
| } |
| } |
| const REGION_FORMATTER = new RegionFormatter(); |
| |
| class EnumFormatter implements PropertyFormatter { |
| constructor( |
| private readonly valuesById: {[key: number]: string}, |
| private readonly overrideValue?: string, |
| ) {} |
| |
| format(node: PropertyTreeNode): string { |
| const value = node.getValue(); |
| if (typeof value === 'number' && this.valuesById[value]) { |
| return this.valuesById[value]; |
| } |
| if (typeof value === 'bigint' && this.valuesById[Number(value)]) { |
| return this.valuesById[Number(value)]; |
| } |
| return this.overrideValue ?? `${value}`; |
| } |
| } |
| |
| class FixedStringFormatter implements PropertyFormatter { |
| constructor(private readonly fixedStringValue: string) {} |
| |
| format(node: PropertyTreeNode): string { |
| return this.fixedStringValue; |
| } |
| } |
| |
| class TimestampNodeFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| const timestamp = node.getValue(); |
| if (timestamp instanceof Timestamp || timestamp instanceof TimeDuration) { |
| return timestamp.format(); |
| } |
| return 'null'; |
| } |
| } |
| const TIMESTAMP_NODE_FORMATTER = new TimestampNodeFormatter(); |
| |
| class CujTypeFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| const cujTypeId: string = `${node.getValue()}`; |
| let cujTypeString: string | undefined; |
| if (cujTypeId in CujType) { |
| cujTypeString = CujType[cujTypeId as keyof typeof CujType]; |
| } else { |
| cujTypeString = 'UNKNOWN'; |
| } |
| return `${cujTypeString} (${cujTypeId})`; |
| } |
| } |
| const CUJ_TYPE_FORMATTER = new CujTypeFormatter(); |
| |
| class HexFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| return formatAsHex(node.getValue() ?? 0); |
| } |
| } |
| const HEX_FORMATTER = new HexFormatter(); |
| |
| class UpperCaseFormatter implements PropertyFormatter { |
| format(node: PropertyTreeNode): string { |
| return node.getValue()?.toString().toUpperCase() ?? ''; |
| } |
| } |
| const UPPER_CASE_FORMATTER = new UpperCaseFormatter(); |
| |
| export { |
| BUFFER_FORMATTER, |
| COLOR_FORMATTER, |
| CUJ_TYPE_FORMATTER, |
| DEFAULT_PROPERTY_FORMATTER, |
| EMPTY_ARRAY_STRING, |
| EMPTY_OBJ_STRING, |
| EnumFormatter, |
| FixedStringFormatter, |
| HEX_FORMATTER, |
| LAYER_ID_FORMATTER, |
| MATRIX_FORMATTER, |
| POSITION_FORMATTER, |
| PropertyFormatter, |
| RECT_FORMATTER, |
| REGION_FORMATTER, |
| SIZE_FORMATTER, |
| TIMESTAMP_NODE_FORMATTER, |
| TRANSFORM_FORMATTER, |
| UPPER_CASE_FORMATTER, |
| formatAsHex, |
| }; |