| /** |
| * @module modules/resources-sk |
| * @description A view of the shared images that are present in an MSKP file. |
| * Contains a scrollable area suitable for viewing images with transparency |
| * and an image selection function so different parts of the app can send you |
| * to an image here, or you can pick one and view details about it. |
| */ |
| import { html, TemplateResult } from 'lit-html'; |
| import { define } from '../../../elements-sk/modules/define'; |
| import { ElementDocSk } from '../element-doc-sk/element-doc-sk'; |
| import { DefaultMap } from '../default-map'; |
| |
| import { SkpDebugPlayer } from '../debugger'; |
| import { |
| JumpCommandEventDetail, |
| JumpCommandEvent, |
| JumpInspectLayerEvent, |
| ToggleBackgroundEventDetail, |
| InspectLayerEventDetail, |
| ToggleBackgroundEvent, |
| BackgroundStyle, |
| } from '../events'; |
| |
| interface ImageItem { |
| // The image indices provided from the debugger are contiguous |
| index: number; |
| width: number; |
| height: number; |
| pngUri: string; |
| // A map from commands to lists of frames |
| uses: DefaultMap<number, number[]>; |
| // An image use map for every layer id. |
| layeruses: DefaultMap<number, DefaultMap<number, number[]>>; |
| } |
| |
| function displayName(item: ImageItem): string { |
| return `${item.index} (${item.width}, ${item.height})`; |
| } |
| |
| export class ResourcesSk extends ElementDocSk { |
| private static template = (ele: ResourcesSk) => html` <p> |
| ${ele.list.length} images were stored in this file. Note that image |
| indices here are file indices, which corresponded 1:1 to the gen ids that |
| the images had during recording. If an image appears more than once, that |
| indicates there were multiple copies of it in memory at record time. These |
| indices appear in the 'imageIndex' field of commands using them. This |
| metadata is only recorded for multi frame (mskp) files from android, so |
| nothing is shown here for other skps. All images in SKPs are serialized as |
| PNG regardless of their original encoding. |
| </p> |
| <div class="main-box ${ele.backdropStyle}"> |
| ${ele.list.map((item: ImageItem) => ResourcesSk.templateImage(ele, item))} |
| </div> |
| <div class="selection-detail"> |
| Selected: |
| ${ele.selection !== null |
| ? ResourcesSk.templateSelectionDetail(ele, ele.list[ele.selection]) |
| : 'none'} |
| </div>`; |
| |
| private static templateImage = ( |
| ele: ResourcesSk, |
| item: ImageItem |
| ) => html` <div class="image-box"> |
| <span |
| class="resource-name ${ele.textContrast()}" |
| @click=${ |
| () => { |
| ele.selectItem(item.index); |
| } /* makes it easier to select 1x1 images */ |
| }> |
| ${displayName(item)} </span |
| ><br /> |
| <img |
| src="${item.pngUri}" |
| id="res-img-${item.index}" |
| class="outline-on-hover ${ele.selection === item.index |
| ? 'selected-image' |
| : ''}" |
| @click=${() => { |
| ele.selectItem(item.index); |
| }} /> |
| </div>`; |
| |
| private static templateSelectionDetail = ( |
| ele: ResourcesSk, |
| item: ImageItem |
| ) => html` <b>${item.index}</b><br /> |
| size: (${item.width}, ${item.height})<br /> |
| Usage in top-level skp |
| ${item.uses.size > 0 |
| ? html`<table class="usage-table"> |
| ${ele.usageTable(item.uses)} |
| </table>` |
| : html`<p> |
| No uses found in any drawImage* commands. May occur in shaders. |
| </p>`} |
| Usage in offscreen buffers |
| ${item.layeruses.size > 0 |
| ? html`<table class="usage-table"> |
| ${ele.layerUsageTable(item.layeruses)} |
| </table>` |
| : html`<p>Not used</p>`}`; |
| |
| private list: ImageItem[] = []; |
| |
| private selection: number | null = null; |
| |
| private backdropStyle: BackgroundStyle = 'light-checkerboard'; |
| |
| constructor() { |
| super(ResourcesSk.template); |
| } |
| |
| connectedCallback(): void { |
| super.connectedCallback(); |
| this._render(); |
| |
| this.addDocumentEventListener(ToggleBackgroundEvent, (e) => { |
| this.backdropStyle = ( |
| e as CustomEvent<ToggleBackgroundEventDetail> |
| ).detail.mode; |
| this._render(); |
| }); |
| } |
| |
| reset(): void { |
| this.list = []; |
| this.selection = null; |
| this._render(); |
| } |
| |
| // Load resource data for current file from player and build the array that |
| // drives the resource grid template |
| // To be called once after file load. |
| // resources-sk will not save any reference to the player. |
| update(player: SkpDebugPlayer): void { |
| // At the time of this writing, only MSKP files recorded on android have the necessary metadata |
| // to show shared image use across the file. |
| |
| const imageCount = player.getImageCount(); |
| this.list = []; |
| for (let i = 0; i < imageCount; i++) { |
| const info = player.getImageInfo(i); |
| this.list.push({ |
| index: i, |
| width: info.width, |
| height: info.height, |
| pngUri: player.getImageResource(i), |
| // this will be populated below |
| uses: new DefaultMap<number, number[]>(() => []), |
| // one use map for every layer. |
| layeruses: new DefaultMap<number, DefaultMap<number, number[]>>( |
| () => new DefaultMap<number, number[]>(() => []) |
| ), |
| }); |
| } |
| |
| // Collect uses at top level |
| for (let fp = 0; fp < player.getFrameCount(); fp++) { |
| const oneFrameUseMap = player.imageUseInfo(fp, -1); |
| for (const [imageIdStr, listOfCommands] of Object.entries( |
| oneFrameUseMap |
| )) { |
| const id = parseInt(imageIdStr, 10); |
| for (const com of listOfCommands) { |
| this.list[id].uses.get(com).push(fp); |
| } |
| } |
| } |
| |
| // collect uses for every layer |
| const keys = player.getLayerKeys(); |
| for (const key of keys) { |
| const useMap = player.imageUseInfo(key.frame, key.nodeId); |
| for (const [imageIdStr, listOfCommands] of Object.entries(useMap)) { |
| const id = parseInt(imageIdStr, 10); |
| for (const com of listOfCommands) { |
| this.list[id].layeruses.get(key.nodeId).get(com).push(key.frame); |
| } |
| } |
| } |
| |
| this._render(); |
| } |
| |
| selectItem(i: number, scroll: boolean = false): void { |
| this.selection = i; |
| this._render(); |
| if (scroll) { |
| this.querySelector<HTMLImageElement>(`#res-img-${i}`)?.scrollIntoView({ |
| block: 'nearest', |
| }); |
| } |
| } |
| |
| private textContrast() { |
| if (this.backdropStyle === 'light-checkerboard') { |
| return 'dark-text'; |
| } |
| return 'light-text'; |
| } |
| |
| // Supply a non-negative nodeId to make jump actions go to a particular layer |
| private usageTable( |
| uses: Map<number, number[]>, |
| nodeId: number = -1 |
| ): TemplateResult[] { |
| const out: TemplateResult[] = []; |
| uses.forEach((frames: number[], key: number) => { |
| out.push(html` <tr> |
| <td class="command-cell">Command <b>${key}</b> on frames:</td> |
| ${frames.map( |
| (f: number) => html` <td |
| title="Jump to command ${key} frame ${f} ${nodeId >= 0 |
| ? `on layer ${nodeId}` |
| : ''}" |
| class="clickable-cell" |
| @click=${() => { |
| this.jump(f, key, nodeId); |
| }}> |
| ${f} |
| </td>` |
| )} |
| </tr>`); |
| }); |
| return out; |
| } |
| |
| private layerUsageTable( |
| layeruses: DefaultMap<number, DefaultMap<number, number[]>> |
| ): TemplateResult[] { |
| const out: TemplateResult[] = []; |
| layeruses.forEach( |
| (usemap: DefaultMap<number, number[]>, nodeid: number) => { |
| out.push(html` <tr> |
| <td class="layer-cell"><b>Layer ${nodeid}</b></td> |
| </tr> |
| ${this.usageTable(usemap, nodeid)}`); |
| } |
| ); |
| return out; |
| } |
| |
| private jump(frame: number, command: number, nodeId: number): void { |
| // Note that the app may already be in the inspector for a layer. |
| // even for non-layer jumps, this is an appropriate way to get there, |
| // becuase it will close the inspector if needed. |
| // debugger-page-sk will move the frame when handling this event. |
| this.dispatchEvent( |
| new CustomEvent<InspectLayerEventDetail>(JumpInspectLayerEvent, { |
| detail: { id: nodeId, frame: frame }, |
| bubbles: true, |
| }) |
| ); |
| this.dispatchEvent( |
| new CustomEvent<JumpCommandEventDetail>(JumpCommandEvent, { |
| detail: { unfilteredIndex: command }, |
| bubbles: true, |
| }) |
| ); |
| } |
| } |
| |
| define('resources-sk', ResourcesSk); |