+
+ `;
}
updated(props) {
@@ -112,6 +264,8 @@ export class TiffViewer extends LitElement {
const imageData = new ImageData(new Uint8ClampedArray(rgba), firstImage.width, firstImage.height);
ctx.putImageData(imageData, 0, 0);
+ this.imageData = imageData;
+
this.dispatchEvent(new CustomEvent('image-loaded', {
bubbles: true,
composed: true,
@@ -133,6 +287,34 @@ export class TiffViewer extends LitElement {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
+
+ _onMouseEnter(event) {
+ if (this.disableMouseHandlers || !this.magnifierEnabled || !this.imageData) return;
+ this.canvasRect = event.target.getBoundingClientRect();
+ }
+
+ _onMouseLeave(event) {
+ if (this.disableMouseHandlers || !this.magnifierEnabled) return;
+ const magnifier = this.shadowRoot.getElementById('magnifier');
+ magnifier.hide();
+ }
+
+ _onMouseMove(event) {
+ if (this.disableMouseHandlers || !this.magnifierEnabled || !this.imageData || !this.canvasRect) return;
+
+ const rect = this.canvasRect;
+ const scaleX = this.imageData.width / rect.width;
+ const scaleY = this.imageData.height / rect.height;
+
+ const mouseX = event.clientX - rect.left;
+ const mouseY = event.clientY - rect.top;
+
+ const imageX = Math.floor(mouseX * scaleX);
+ const imageY = Math.floor(mouseY * scaleY);
+
+ const magnifier = this.shadowRoot.getElementById('magnifier');
+ magnifier.updateMagnifier(this.imageData, imageX, imageY, event.clientX, event.clientY, rect);
+ }
}
customElements.define('tiff-viewer', TiffViewer);
${v1}
-
`;
diff --git a/test/renderdiff/src/viewer_html/tiff-viewer.js b/test/renderdiff/src/viewer_html/tiff-viewer.js
index 1c587dfe03..dc5a36e71b 100644
--- a/test/renderdiff/src/viewer_html/tiff-viewer.js
+++ b/test/renderdiff/src/viewer_html/tiff-viewer.js
@@ -14,11 +14,154 @@
import { LitElement, html, css } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js";
+class ImageMagnifier extends LitElement {
+ static styles = css`
+ :host {
+ position: absolute;
+ pointer-events: none;
+ z-index: 1000;
+ display: none;
+ }
+
+ :host([visible]) {
+ display: block;
+ }
+
+ .magnifier {
+ width: 150px;
+ height: 150px;
+ border: 2px solid #000;
+ border-radius: 75px;
+ background: white;
+ box-shadow: 0 4px 8px rgba(0,0,0,0.3);
+ position: relative;
+ }
+
+ .magnifier-canvas {
+ width: 100%;
+ height: 100%;
+ border-radius: 73px;
+ }
+
+ .pixel-info {
+ position: absolute;
+ top: -65px;
+ left: 0;
+ background: rgba(0,0,0,0.8);
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-family: monospace;
+ font-size: 11px;
+ white-space: pre-line;
+ max-width: 250px;
+ }
+ `;
+
+ static properties = {
+ visible: {type: Boolean, reflect: true},
+ };
+
+ constructor() {
+ super();
+ this.visible = false;
+ }
+
+ render() {
+ return html`
+
-
+
+
+
${v2}
-
+ Difference Multiplier
+ Difference Multiplier: 1
+
+
+
+
+
+ `;
+ }
+
+ updateMagnifier(imageData, imageX, imageY, mouseX, mouseY, canvasRect, zoomFactor = 8, originalImageData = null) {
+ if (!imageData) return;
+
+ if (imageX < 0 || imageX >= imageData.width || imageY < 0 || imageY >= imageData.height) {
+ this.visible = false;
+ return;
+ }
+
+ const pixelIndex = (imageY * imageData.width + imageX) * 4;
+ const r = imageData.data[pixelIndex];
+ const g = imageData.data[pixelIndex + 1];
+ const b = imageData.data[pixelIndex + 2];
+ const a = imageData.data[pixelIndex + 3];
+
+ let pixelInfoText = `RGBA(${r}, ${g}, ${b}, ${a})\n@ (${imageX}, ${imageY})`;
+
+ // If original image data is provided, show the unmultiplied values too
+ if (originalImageData) {
+ const origR = originalImageData.data[pixelIndex];
+ const origG = originalImageData.data[pixelIndex + 1];
+ const origB = originalImageData.data[pixelIndex + 2];
+ const origA = originalImageData.data[pixelIndex + 3];
+ pixelInfoText = `Diff: RGBA(${origR}, ${origG}, ${origB}, ${origA})\nDisplay: RGBA(${r}, ${g}, ${b}, ${a})\n@ (${imageX}, ${imageY})`;
+ }
+
+ const magnifierSize = 150;
+ const sourceSize = magnifierSize / zoomFactor;
+ const halfSource = sourceSize / 2;
+
+ const sourceX = Math.max(0, Math.min(imageData.width - sourceSize, imageX - halfSource));
+ const sourceY = Math.max(0, Math.min(imageData.height - sourceSize, imageY - halfSource));
+
+ const magnifierCanvas = this.shadowRoot.getElementById('magnifierCanvas');
+ const pixelInfo = this.shadowRoot.getElementById('pixelInfo');
+
+ magnifierCanvas.width = magnifierSize;
+ magnifierCanvas.height = magnifierSize;
+ const magnifierCtx = magnifierCanvas.getContext('2d');
+
+ magnifierCtx.imageSmoothingEnabled = false;
+
+ const tempCanvas = document.createElement('canvas');
+ tempCanvas.width = imageData.width;
+ tempCanvas.height = imageData.height;
+ const tempCtx = tempCanvas.getContext('2d');
+ tempCtx.putImageData(imageData, 0, 0);
+
+ magnifierCtx.drawImage(
+ tempCanvas,
+ sourceX, sourceY, sourceSize, sourceSize,
+ 0, 0, magnifierSize, magnifierSize
+ );
+
+ const centerX = magnifierSize / 2;
+ const centerY = magnifierSize / 2;
+ magnifierCtx.strokeStyle = 'red';
+ magnifierCtx.lineWidth = 1;
+ magnifierCtx.beginPath();
+ magnifierCtx.moveTo(centerX - 5, centerY);
+ magnifierCtx.lineTo(centerX + 5, centerY);
+ magnifierCtx.moveTo(centerX, centerY - 5);
+ magnifierCtx.lineTo(centerX, centerY + 5);
+ magnifierCtx.stroke();
+
+ // Position relative to the TiffViewer container
+ this.style.left = (mouseX - magnifierSize / 2) + 'px';
+ this.style.top = (mouseY - magnifierSize / 2) + 'px';
+
+ pixelInfo.textContent = pixelInfoText;
+
+ this.visible = true;
+ }
+
+ hide() {
+ this.visible = false;
+ }
+}
+
+customElements.define('image-magnifier', ImageMagnifier);
+
// Generated by Gemini with some modifications
export class TiffViewer extends LitElement {
static styles = css`
:host {
display: block;
+ position: relative;
}
canvas {
border: 1px solid #ccc;
@@ -31,6 +174,8 @@ export class TiffViewer extends LitElement {
fileurl: {type: String, attribute: 'fileurl'},
name: {type: String, attribute: 'name'},
failedToFetch: {type: Boolean },
+ magnifierEnabled: {type: Boolean, attribute: 'magnifier-enabled'},
+ disableMouseHandlers: {type: Boolean, attribute: 'disable-mouse-handlers'},
};
constructor() {
@@ -38,13 +183,20 @@ export class TiffViewer extends LitElement {
this.fileurl = null;
this.name = null;
this.failedToFetch = false;
+ this.magnifierEnabled = false;
+ this.disableMouseHandlers = false;
+ this.imageData = null;
+ this.canvasRect = null;
}
render() {
if (this.failedToFetch) {
return html``;
}
- return html``;
+ return html`
+
+