| /** |
| * @license |
| * Copyright (C) 2026 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 {html, css, LitElement, PropertyValues} from 'lit'; |
| import {customElement, property, state} from 'lit/decorators.js'; |
| import {ImageInfo} from '../gr-opacity-diff-mode/gr-opacity-diff-mode'; |
| import '../gr-opacity-diff-mode/gr-opacity-diff-mode'; |
| import '../gr-resemble-diff-mode/gr-resemble-diff-mode'; |
| |
| const DiffModes = { |
| OPACITY: 'opacity', |
| RESEMBLE: 'resemble', |
| }; |
| |
| @customElement('gr-image-diff-tool') |
| export class ImageDiffTool extends LitElement { |
| static override get styles() { |
| return [ |
| css` |
| :host { |
| background-color: var(--table-header-background-color, #fafafa); |
| display: block; |
| font-family: var(--font-family); |
| } |
| #header { |
| align-items: center; |
| border-bottom: 1px solid var(--border-color, #ddd); |
| border-top: 1px solid var(--border-color, #ddd); |
| display: inline-flex; |
| padding: 0.5em; |
| width: 100%; |
| } |
| h3 { |
| padding: 0 0.5em; |
| } |
| #dropdown { |
| background-color: var(--view-background-color); |
| border: 1px solid var(--border-color); |
| border-radius: 2px; |
| color: var(--primary-text-color); |
| font-size: var(--font-size-normal); |
| height: 2em; |
| margin-left: 1em; |
| padding: 0 0.15em; |
| } |
| .diffmode { |
| align-items: center; |
| display: flex; |
| justify-content: center; |
| } |
| ` |
| ]; |
| } |
| |
| @property({type: Object}) |
| baseImage?: ImageInfo; |
| |
| @property({type: Object}) |
| revisionImage?: ImageInfo; |
| |
| @property({type: Boolean, reflect: true}) |
| override hidden = false; |
| |
| @state() |
| protected showResembleMode = false; |
| |
| @state() |
| protected showOpacityMode = false; |
| |
| @state() |
| protected observeMode = ''; |
| |
| override render() { |
| return html` |
| <div id="header"> |
| <h3>Image diff</h3> |
| <select |
| .value=${this.observeMode} |
| @change=${this.handleSelectChange} |
| id="dropdown" |
| > |
| <option |
| value="resemble" |
| title="Scale the images to the same size and compute a diff with highlights" |
| >Highlight differences</option |
| > |
| <option |
| value="opacity" |
| title="Overlay the new image over the old and use an opacity control to view the differences" |
| >Onion skin</option |
| > |
| </select> |
| </div> |
| <div class="diffmode"> |
| ${this.showResembleMode |
| ? html` |
| <gr-resemble-diff-mode |
| .baseImage=${this.baseImage} |
| .revisionImage=${this.revisionImage} |
| ></gr-resemble-diff-mode> |
| ` |
| : ''} |
| </div> |
| <div class="diffmode"> |
| ${this.showOpacityMode |
| ? html` |
| <gr-opacity-diff-mode |
| .baseImage=${this.baseImage} |
| .revisionImage=${this.revisionImage} |
| ></gr-opacity-diff-mode> |
| ` |
| : ''} |
| </div> |
| `; |
| } |
| |
| override updated(changedProperties: PropertyValues) { |
| if (changedProperties.has('observeMode')) { |
| this.handleSelect(this.observeMode); |
| } |
| } |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| if (!this.baseImage || !this.revisionImage) { |
| // No need to show the diff tool if there are no images. |
| this.hidden = true; |
| } |
| const diff_mode = this.getMode(); |
| diff_mode === DiffModes.OPACITY |
| ? this.displayOpacityMode() |
| : this.displayResembleMode(); |
| } |
| |
| protected getMode() { |
| return window.localStorage.getItem('image-diff-mode'); |
| } |
| |
| protected setMode(mode: string) { |
| window.localStorage.setItem('image-diff-mode', mode); |
| } |
| |
| protected handleSelectChange(e: Event) { |
| this.observeMode = (e.target as HTMLSelectElement).value; |
| } |
| |
| protected handleSelect(mode: string) { |
| mode === DiffModes.OPACITY |
| ? this.displayOpacityMode() |
| : this.displayResembleMode(); |
| } |
| |
| protected displayResembleMode() { |
| this.observeMode = DiffModes.RESEMBLE; |
| this.showResembleMode = true; |
| this.showOpacityMode = false; |
| this.setMode(DiffModes.RESEMBLE); |
| } |
| |
| protected displayOpacityMode() { |
| this.observeMode = DiffModes.OPACITY; |
| this.showResembleMode = false; |
| this.showOpacityMode = true; |
| this.setMode(DiffModes.OPACITY); |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-image-diff-tool': ImageDiffTool; |
| } |
| } |