1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/tools/reftest/reftest-analyzer.xhtml Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,579 @@ 1.4 +<?xml version="1.0" encoding="UTF-8"?> 1.5 +<!-- -*- Mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- --> 1.6 +<!-- vim: set shiftwidth=2 tabstop=2 autoindent expandtab: --> 1.7 +<!-- This Source Code Form is subject to the terms of the Mozilla Public 1.8 + - License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> 1.10 +<!-- 1.11 + 1.12 +Features to add: 1.13 +* make the left and right parts of the viewer independently scrollable 1.14 +* make the test list filterable 1.15 +** default to only showing unexpecteds 1.16 +* add other ways to highlight differences other than circling? 1.17 +* add zoom/pan to images 1.18 +* Add ability to load log via XMLHttpRequest (also triggered via URL param) 1.19 +* color the test list based on pass/fail and expected/unexpected/random/skip 1.20 +* ability to load multiple logs ? 1.21 +** rename them by clicking on the name and editing 1.22 +** turn the test list into a collapsing tree view 1.23 +** move log loading into popup from viewer UI 1.24 + 1.25 +--> 1.26 +<!DOCTYPE html> 1.27 +<html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml"> 1.28 +<head> 1.29 + <title>Reftest analyzer</title> 1.30 + <style type="text/css"><![CDATA[ 1.31 + 1.32 + html, body { margin: 0; } 1.33 + html { padding: 0; } 1.34 + body { padding: 4px; } 1.35 + 1.36 + #pixelarea, #itemlist, #images { position: absolute; } 1.37 + #itemlist, #images { overflow: auto; } 1.38 + #pixelarea { top: 0; left: 0; width: 320px; height: 84px; overflow: visible } 1.39 + #itemlist { top: 84px; left: 0; width: 320px; bottom: 0; } 1.40 + #images { top: 0; bottom: 0; left: 320px; right: 0; } 1.41 + 1.42 + #leftpane { width: 320px; } 1.43 + #images { position: fixed; top: 10px; left: 340px; } 1.44 + 1.45 + form#imgcontrols { margin: 0; display: block; } 1.46 + 1.47 + #itemlist > table { border-collapse: collapse; } 1.48 + #itemlist > table > tbody > tr > td { border: 1px solid; padding: 1px; } 1.49 + 1.50 + /* 1.51 + #itemlist > table > tbody > tr.pass > td.url { background: lime; } 1.52 + #itemlist > table > tbody > tr.fail > td.url { background: red; } 1.53 + */ 1.54 + 1.55 + #magnification > svg { display: block; width: 84px; height: 84px; } 1.56 + 1.57 + #pixelinfo { font: small sans-serif; position: absolute; width: 200px; left: 84px; } 1.58 + #pixelinfo table { border-collapse: collapse; } 1.59 + #pixelinfo table th { white-space: nowrap; text-align: left; padding: 0; } 1.60 + #pixelinfo table td { font-family: monospace; padding: 0 0 0 0.25em; } 1.61 + 1.62 + #pixelhint { display: inline; color: #88f; cursor: help; } 1.63 + #pixelhint > * { display: none; position: absolute; margin: 8px 0 0 8px; padding: 4px; width: 400px; background: #ffa; color: black; box-shadow: 3px 3px 2px #888; z-index: 1; } 1.64 + #pixelhint:hover { color: #000; } 1.65 + #pixelhint:hover > * { display: block; } 1.66 + #pixelhint p { margin: 0; } 1.67 + #pixelhint p + p { margin-top: 1em; } 1.68 + 1.69 + ]]></style> 1.70 + <script type="text/javascript"><![CDATA[ 1.71 + 1.72 +var XLINK_NS = "http://www.w3.org/1999/xlink"; 1.73 +var SVG_NS = "http://www.w3.org/2000/svg"; 1.74 +var IMAGE_NOT_AVAILABLE = "data:image/png;base64,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"; 1.75 + 1.76 +var gPhases = null; 1.77 + 1.78 +var gIDCache = {}; 1.79 + 1.80 +var gMagPixPaths = []; // 2D array of array-of-two <path> objects used in the pixel magnifier 1.81 +var gMagWidth = 5; // number of zoomed in pixels to show horizontally 1.82 +var gMagHeight = 5; // number of zoomed in pixels to show vertically 1.83 +var gMagZoom = 16; // size of the zoomed in pixels 1.84 +var gImage1Data; // ImageData object for the reference image 1.85 +var gImage2Data; // ImageData object for the test output image 1.86 +var gFlashingPixels = []; // array of <path> objects that should be flashed due to pixel color mismatch 1.87 + 1.88 +function ID(id) { 1.89 + if (!(id in gIDCache)) 1.90 + gIDCache[id] = document.getElementById(id); 1.91 + return gIDCache[id]; 1.92 +} 1.93 + 1.94 +function hash_parameters() { 1.95 + var result = { }; 1.96 + var params = window.location.hash.substr(1).split(/[&;]/); 1.97 + for (var i = 0; i < params.length; i++) { 1.98 + var parts = params[i].split("="); 1.99 + result[parts[0]] = unescape(unescape(parts[1])); 1.100 + } 1.101 + return result; 1.102 +} 1.103 + 1.104 +function load() { 1.105 + gPhases = [ ID("entry"), ID("loading"), ID("viewer") ]; 1.106 + build_mag(); 1.107 + var params = hash_parameters(); 1.108 + if (params.log) { 1.109 + ID("logentry").value = params.log; 1.110 + log_pasted(); 1.111 + } 1.112 + window.addEventListener('keypress', maybe_load_image, false); 1.113 + ID("image1").addEventListener('error', image_load_error, false); 1.114 + ID("image2").addEventListener('error', image_load_error, false); 1.115 +} 1.116 + 1.117 +function image_load_error(e) { 1.118 + e.target.setAttributeNS(XLINK_NS, "xlink:href", IMAGE_NOT_AVAILABLE); 1.119 +} 1.120 + 1.121 +function build_mag() { 1.122 + var mag = ID("mag"); 1.123 + 1.124 + var r = document.createElementNS(SVG_NS, "rect"); 1.125 + r.setAttribute("x", gMagZoom * -gMagWidth / 2); 1.126 + r.setAttribute("y", gMagZoom * -gMagHeight / 2); 1.127 + r.setAttribute("width", gMagZoom * gMagWidth); 1.128 + r.setAttribute("height", gMagZoom * gMagHeight); 1.129 + mag.appendChild(r); 1.130 + 1.131 + mag.setAttribute("transform", "translate(" + (gMagZoom * (gMagWidth / 2) + 1) + "," + (gMagZoom * (gMagHeight / 2) + 1) + ")"); 1.132 + 1.133 + for (var x = 0; x < gMagWidth; x++) { 1.134 + gMagPixPaths[x] = []; 1.135 + for (var y = 0; y < gMagHeight; y++) { 1.136 + var p1 = document.createElementNS(SVG_NS, "path"); 1.137 + p1.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "h" + -gMagZoom + "v" + gMagZoom); 1.138 + p1.setAttribute("stroke", "black"); 1.139 + p1.setAttribute("stroke-width", "1px"); 1.140 + p1.setAttribute("fill", "#aaa"); 1.141 + 1.142 + var p2 = document.createElementNS(SVG_NS, "path"); 1.143 + p2.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "v" + gMagZoom + "h" + -gMagZoom); 1.144 + p2.setAttribute("stroke", "black"); 1.145 + p2.setAttribute("stroke-width", "1px"); 1.146 + p2.setAttribute("fill", "#888"); 1.147 + 1.148 + mag.appendChild(p1); 1.149 + mag.appendChild(p2); 1.150 + gMagPixPaths[x][y] = [p1, p2]; 1.151 + } 1.152 + } 1.153 + 1.154 + var flashedOn = false; 1.155 + setInterval(function() { 1.156 + flashedOn = !flashedOn; 1.157 + flash_pixels(flashedOn); 1.158 + }, 500); 1.159 +} 1.160 + 1.161 +function show_phase(phaseid) { 1.162 + for (var i in gPhases) { 1.163 + var phase = gPhases[i]; 1.164 + phase.style.display = (phase.id == phaseid) ? "" : "none"; 1.165 + } 1.166 + 1.167 + if (phase == "viewer") 1.168 + ID("images").style.display = "none"; 1.169 +} 1.170 + 1.171 +function fileentry_changed() { 1.172 + show_phase("loading"); 1.173 + var input = ID("fileentry"); 1.174 + var files = input.files; 1.175 + if (files.length > 0) { 1.176 + // Only handle the first file; don't handle multiple selection. 1.177 + // The parts of the log we care about are ASCII-only. Since we 1.178 + // can ignore lines we don't care about, best to read in as 1.179 + // iso-8859-1, which guarantees we don't get decoding errors. 1.180 + var fileReader = new FileReader(); 1.181 + fileReader.onload = function(e) { 1.182 + var log = null; 1.183 + 1.184 + log = e.target.result; 1.185 + 1.186 + if (log) 1.187 + process_log(log); 1.188 + else 1.189 + show_phase("entry"); 1.190 + } 1.191 + fileReader.readAsText(files[0], "iso-8859-1"); 1.192 + } 1.193 + // So the user can process the same filename again (after 1.194 + // overwriting the log), clear the value on the form input so we 1.195 + // will always get an onchange event. 1.196 + input.value = ""; 1.197 +} 1.198 + 1.199 +function log_pasted() { 1.200 + show_phase("loading"); 1.201 + var entry = ID("logentry"); 1.202 + var log = entry.value; 1.203 + entry.value = ""; 1.204 + process_log(log); 1.205 +} 1.206 + 1.207 +var gTestItems; 1.208 + 1.209 +function process_log(contents) { 1.210 + var lines = contents.split(/[\r\n]+/); 1.211 + gTestItems = []; 1.212 + for (var j in lines) { 1.213 + var line = lines[j]; 1.214 + var match = line.match(/^.*?REFTEST (.*)$/); 1.215 + if (!match) 1.216 + continue; 1.217 + line = match[1]; 1.218 + match = line.match(/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL|TEST-DEBUG-INFO)(\(EXPECTED RANDOM\)|) \| ([^\|]+) \|(.*)/); 1.219 + if (match) { 1.220 + var state = match[1]; 1.221 + var random = match[2]; 1.222 + var url = match[3]; 1.223 + var extra = match[4]; 1.224 + gTestItems.push( 1.225 + { 1.226 + pass: !state.match(/DEBUG-INFO$|FAIL$/), 1.227 + // only one of the following three should ever be true 1.228 + unexpected: !!state.match(/^TEST-UNEXPECTED/), 1.229 + random: (random == "(EXPECTED RANDOM)"), 1.230 + skip: (extra == " (SKIP)"), 1.231 + url: url, 1.232 + images: [] 1.233 + }); 1.234 + continue; 1.235 + } 1.236 + match = line.match(/^ IMAGE[^:]*: (.*)$/); 1.237 + if (match) { 1.238 + var item = gTestItems[gTestItems.length - 1]; 1.239 + item.images.push(match[1]); 1.240 + } 1.241 + } 1.242 + 1.243 + build_viewer(); 1.244 +} 1.245 + 1.246 +function build_viewer() { 1.247 + if (gTestItems.length == 0) { 1.248 + show_phase("entry"); 1.249 + return; 1.250 + } 1.251 + 1.252 + var cell = ID("itemlist"); 1.253 + while (cell.childNodes.length > 0) 1.254 + cell.removeChild(cell.childNodes[cell.childNodes.length - 1]); 1.255 + 1.256 + var table = document.createElement("table"); 1.257 + var tbody = document.createElement("tbody"); 1.258 + table.appendChild(tbody); 1.259 + 1.260 + for (var i in gTestItems) { 1.261 + var item = gTestItems[i]; 1.262 + 1.263 + // XXX skip expected pass items until we have filtering UI 1.264 + if (item.pass && !item.unexpected) 1.265 + continue; 1.266 + 1.267 + var tr = document.createElement("tr"); 1.268 + var rowclass = item.pass ? "pass" : "fail"; 1.269 + var td; 1.270 + var text; 1.271 + 1.272 + td = document.createElement("td"); 1.273 + text = ""; 1.274 + if (item.unexpected) { text += "!"; rowclass += " unexpected"; } 1.275 + if (item.random) { text += "R"; rowclass += " random"; } 1.276 + if (item.skip) { text += "S"; rowclass += " skip"; } 1.277 + td.appendChild(document.createTextNode(text)); 1.278 + tr.appendChild(td); 1.279 + 1.280 + td = document.createElement("td"); 1.281 + td.className = "url"; 1.282 + // Only display part of URL after "/mozilla/". 1.283 + var match = item.url.match(/\/mozilla\/(.*)/); 1.284 + text = document.createTextNode(match ? match[1] : item.url); 1.285 + if (item.images.length > 0) { 1.286 + var a = document.createElement("a"); 1.287 + a.href = "javascript:show_images(" + i + ")"; 1.288 + a.appendChild(text); 1.289 + td.appendChild(a); 1.290 + } else { 1.291 + td.appendChild(text); 1.292 + } 1.293 + tr.appendChild(td); 1.294 + 1.295 + tbody.appendChild(tr); 1.296 + } 1.297 + 1.298 + cell.appendChild(table); 1.299 + 1.300 + show_phase("viewer"); 1.301 +} 1.302 + 1.303 +function get_image_data(src, whenReady) { 1.304 + var img = new Image(); 1.305 + img.onload = function() { 1.306 + var canvas = document.createElement("canvas"); 1.307 + canvas.width = 800; 1.308 + canvas.height = 1000; 1.309 + 1.310 + var ctx = canvas.getContext("2d"); 1.311 + ctx.drawImage(img, 0, 0); 1.312 + 1.313 + whenReady(ctx.getImageData(0, 0, 800, 1000)); 1.314 + }; 1.315 + img.src = src; 1.316 +} 1.317 + 1.318 +function show_images(i) { 1.319 + var item = gTestItems[i]; 1.320 + var cell = ID("images"); 1.321 + 1.322 + ID("image1").style.display = ""; 1.323 + ID("image2").style.display = "none"; 1.324 + ID("diffrect").style.display = "none"; 1.325 + ID("imgcontrols").reset(); 1.326 + 1.327 + ID("image1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]); 1.328 + // Making the href be #image1 doesn't seem to work 1.329 + ID("feimage1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]); 1.330 + if (item.images.length == 1) { 1.331 + ID("imgcontrols").style.display = "none"; 1.332 + } else { 1.333 + ID("imgcontrols").style.display = ""; 1.334 + 1.335 + ID("image2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]); 1.336 + // Making the href be #image2 doesn't seem to work 1.337 + ID("feimage2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]); 1.338 + } 1.339 + 1.340 + cell.style.display = ""; 1.341 + 1.342 + get_image_data(item.images[0], function(data) { gImage1Data = data }); 1.343 + get_image_data(item.images[1], function(data) { gImage2Data = data }); 1.344 +} 1.345 + 1.346 +function show_image(i) { 1.347 + if (i == 1) { 1.348 + ID("image1").style.display = ""; 1.349 + ID("image2").style.display = "none"; 1.350 + } else { 1.351 + ID("image1").style.display = "none"; 1.352 + ID("image2").style.display = ""; 1.353 + } 1.354 +} 1.355 + 1.356 +function maybe_load_image(event) { 1.357 + switch (event.charCode) { 1.358 + case 49: // "1" key 1.359 + document.getElementById("radio1").checked = true; 1.360 + show_image(1); 1.361 + break; 1.362 + case 50: // "2" key 1.363 + document.getElementById("radio2").checked = true; 1.364 + show_image(2); 1.365 + break; 1.366 + } 1.367 +} 1.368 + 1.369 +function show_differences(cb) { 1.370 + ID("diffrect").style.display = cb.checked ? "" : "none"; 1.371 +} 1.372 + 1.373 +function flash_pixels(on) { 1.374 + var stroke = on ? "red" : "black"; 1.375 + var strokeWidth = on ? "2px" : "1px"; 1.376 + for (var i = 0; i < gFlashingPixels.length; i++) { 1.377 + gFlashingPixels[i].setAttribute("stroke", stroke); 1.378 + gFlashingPixels[i].setAttribute("stroke-width", strokeWidth); 1.379 + } 1.380 +} 1.381 + 1.382 +function cursor_point(evt) { 1.383 + var m = evt.target.getScreenCTM().inverse(); 1.384 + var p = ID("svg").createSVGPoint(); 1.385 + p.x = evt.clientX; 1.386 + p.y = evt.clientY; 1.387 + p = p.matrixTransform(m); 1.388 + return { x: Math.floor(p.x), y: Math.floor(p.y) }; 1.389 +} 1.390 + 1.391 +function hex2(i) { 1.392 + return (i < 16 ? "0" : "") + i.toString(16); 1.393 +} 1.394 + 1.395 +function canvas_pixel_as_hex(data, x, y) { 1.396 + var offset = (y * data.width + x) * 4; 1.397 + var r = data.data[offset]; 1.398 + var g = data.data[offset + 1]; 1.399 + var b = data.data[offset + 2]; 1.400 + return "#" + hex2(r) + hex2(g) + hex2(b); 1.401 +} 1.402 + 1.403 +function hex_as_rgb(hex) { 1.404 + return "rgb(" + [parseInt(hex.substring(1, 3), 16), parseInt(hex.substring(3, 5), 16), parseInt(hex.substring(5, 7), 16)] + ")"; 1.405 +} 1.406 + 1.407 +function magnify(evt) { 1.408 + var { x: x, y: y } = cursor_point(evt); 1.409 + var centerPixelColor1, centerPixelColor2; 1.410 + 1.411 + var dx_lo = -Math.floor(gMagWidth / 2); 1.412 + var dx_hi = Math.floor(gMagWidth / 2); 1.413 + var dy_lo = -Math.floor(gMagHeight / 2); 1.414 + var dy_hi = Math.floor(gMagHeight / 2); 1.415 + 1.416 + flash_pixels(false); 1.417 + gFlashingPixels = []; 1.418 + for (var j = dy_lo; j <= dy_hi; j++) { 1.419 + for (var i = dx_lo; i <= dx_hi; i++) { 1.420 + var px = x + i; 1.421 + var py = y + j; 1.422 + var p1 = gMagPixPaths[i + dx_hi][j + dy_hi][0]; 1.423 + var p2 = gMagPixPaths[i + dx_hi][j + dy_hi][1]; 1.424 + if (px < 0 || py < 0 || px >= 800 || py >= 1000) { 1.425 + p1.setAttribute("fill", "#aaa"); 1.426 + p2.setAttribute("fill", "#888"); 1.427 + } else { 1.428 + var color1 = canvas_pixel_as_hex(gImage1Data, x + i, y + j); 1.429 + var color2 = canvas_pixel_as_hex(gImage2Data, x + i, y + j); 1.430 + p1.setAttribute("fill", color1); 1.431 + p2.setAttribute("fill", color2); 1.432 + if (color1 != color2) { 1.433 + gFlashingPixels.push(p1, p2); 1.434 + p1.parentNode.appendChild(p1); 1.435 + p2.parentNode.appendChild(p2); 1.436 + } 1.437 + if (i == 0 && j == 0) { 1.438 + centerPixelColor1 = color1; 1.439 + centerPixelColor2 = color2; 1.440 + } 1.441 + } 1.442 + } 1.443 + } 1.444 + flash_pixels(true); 1.445 + show_pixelinfo(x, y, centerPixelColor1, hex_as_rgb(centerPixelColor1), centerPixelColor2, hex_as_rgb(centerPixelColor2)); 1.446 +} 1.447 + 1.448 +function show_pixelinfo(x, y, pix1rgb, pix1hex, pix2rgb, pix2hex) { 1.449 + var pixelinfo = ID("pixelinfo"); 1.450 + ID("coords").textContent = [x, y]; 1.451 + ID("pix1hex").textContent = pix1hex; 1.452 + ID("pix1rgb").textContent = pix1rgb; 1.453 + ID("pix2hex").textContent = pix2hex; 1.454 + ID("pix2rgb").textContent = pix2rgb; 1.455 +} 1.456 + 1.457 + ]]></script> 1.458 + 1.459 +</head> 1.460 +<body onload="load()"> 1.461 + 1.462 +<div id="entry"> 1.463 + 1.464 +<h1>Reftest analyzer: load reftest log</h1> 1.465 + 1.466 +<p>Either paste your log into this textarea:<br /> 1.467 +<textarea cols="80" rows="10" id="logentry"/><br/> 1.468 +<input type="button" value="Process pasted log" onclick="log_pasted()" /></p> 1.469 + 1.470 +<p>... or load it from a file:<br/> 1.471 +<input type="file" id="fileentry" onchange="fileentry_changed()" /> 1.472 +</p> 1.473 +</div> 1.474 + 1.475 +<div id="loading" style="display:none">Loading log...</div> 1.476 + 1.477 +<div id="viewer" style="display:none"> 1.478 + <div id="pixelarea"> 1.479 + <div id="pixelinfo"> 1.480 + <table> 1.481 + <tbody> 1.482 + <tr><th>Pixel at:</th><td colspan="2" id="coords"/></tr> 1.483 + <tr><th>Image 1:</th><td id="pix1rgb"></td><td id="pix1hex"></td></tr> 1.484 + <tr><th>Image 2:</th><td id="pix2rgb"></td><td id="pix2hex"></td></tr> 1.485 + </tbody> 1.486 + </table> 1.487 + <div> 1.488 + <div id="pixelhint">★ 1.489 + <div> 1.490 + <p>Move the mouse over the reftest image on the right to show 1.491 + magnified pixels on the left. The color information above is for 1.492 + the pixel centered in the magnified view.</p> 1.493 + <p>Image 1 is shown in the upper triangle of each pixel and Image 2 1.494 + is shown in the lower triangle.</p> 1.495 + </div> 1.496 + </div> 1.497 + </div> 1.498 + </div> 1.499 + <div id="magnification"> 1.500 + <svg xmlns="http://www.w3.org/2000/svg" width="84" height="84" shape-rendering="optimizeSpeed"> 1.501 + <g id="mag"/> 1.502 + </svg> 1.503 + </div> 1.504 + </div> 1.505 + <div id="itemlist"></div> 1.506 + <div id="images" style="display:none"> 1.507 + <form id="imgcontrols"> 1.508 + <label title="1"><input id="radio1" type="radio" name="which" value="0" onchange="show_image(1)" checked="checked" />Image 1</label> 1.509 + <label title="2"><input id="radio2" type="radio" name="which" value="1" onchange="show_image(2)" />Image 2</label> 1.510 + <label><input type="checkbox" onchange="show_differences(this)" />Circle differences</label> 1.511 + </form> 1.512 + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800px" height="1000px" viewBox="0 0 800 1000" id="svg"> 1.513 + <defs> 1.514 + <!-- use sRGB to avoid loss of data --> 1.515 + <filter id="showDifferences" x="0%" y="0%" width="100%" height="100%" 1.516 + style="color-interpolation-filters: sRGB"> 1.517 + <feImage id="feimage1" result="img1" xlink:href="#image1" /> 1.518 + <feImage id="feimage2" result="img2" xlink:href="#image2" /> 1.519 + <!-- inv1 and inv2 are the images with RGB inverted --> 1.520 + <feComponentTransfer result="inv1" in="img1"> 1.521 + <feFuncR type="linear" slope="-1" intercept="1" /> 1.522 + <feFuncG type="linear" slope="-1" intercept="1" /> 1.523 + <feFuncB type="linear" slope="-1" intercept="1" /> 1.524 + </feComponentTransfer> 1.525 + <feComponentTransfer result="inv2" in="img2"> 1.526 + <feFuncR type="linear" slope="-1" intercept="1" /> 1.527 + <feFuncG type="linear" slope="-1" intercept="1" /> 1.528 + <feFuncB type="linear" slope="-1" intercept="1" /> 1.529 + </feComponentTransfer> 1.530 + <!-- w1 will have non-white pixels anywhere that img2 1.531 + is brighter than img1, and w2 for the reverse. 1.532 + It would be nice not to have to go through these 1.533 + intermediate states, but feComposite 1.534 + type="arithmetic" can't transform the RGB channels 1.535 + and leave the alpha channel untouched. --> 1.536 + <feComposite result="w1" in="img1" in2="inv2" operator="arithmetic" k2="1" k3="1" /> 1.537 + <feComposite result="w2" in="img2" in2="inv1" operator="arithmetic" k2="1" k3="1" /> 1.538 + <!-- c1 will have non-black pixels anywhere that img2 1.539 + is brighter than img1, and c2 for the reverse --> 1.540 + <feComponentTransfer result="c1" in="w1"> 1.541 + <feFuncR type="linear" slope="-1" intercept="1" /> 1.542 + <feFuncG type="linear" slope="-1" intercept="1" /> 1.543 + <feFuncB type="linear" slope="-1" intercept="1" /> 1.544 + </feComponentTransfer> 1.545 + <feComponentTransfer result="c2" in="w2"> 1.546 + <feFuncR type="linear" slope="-1" intercept="1" /> 1.547 + <feFuncG type="linear" slope="-1" intercept="1" /> 1.548 + <feFuncB type="linear" slope="-1" intercept="1" /> 1.549 + </feComponentTransfer> 1.550 + <!-- c will be nonblack (and fully on) for every pixel+component where there are differences --> 1.551 + <feComposite result="c" in="c1" in2="c2" operator="arithmetic" k2="255" k3="255" /> 1.552 + <!-- a will be opaque for every pixel with differences and transparent for all others --> 1.553 + <feColorMatrix result="a" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0" /> 1.554 + 1.555 + <!-- a, dilated by 1 pixel --> 1.556 + <feMorphology result="dila1" in="a" operator="dilate" radius="1" /> 1.557 + <!-- a, dilated by 2 pixels --> 1.558 + <feMorphology result="dila2" in="dila1" operator="dilate" radius="1" /> 1.559 + 1.560 + <!-- all the pixels in the 2-pixel dilation of a but not in the 1-pixel dilation, to highlight the diffs --> 1.561 + <feComposite result="highlight" in="dila2" in2="dila1" operator="out" /> 1.562 + 1.563 + <feFlood result="red" flood-color="red" /> 1.564 + <feComposite result="redhighlight" in="red" in2="highlight" operator="in" /> 1.565 + <feFlood result="black" flood-color="black" flood-opacity="0.5" /> 1.566 + <feMerge> 1.567 + <feMergeNode in="black" /> 1.568 + <feMergeNode in="redhighlight" /> 1.569 + </feMerge> 1.570 + </filter> 1.571 + </defs> 1.572 + <g onmousemove="magnify(evt)"> 1.573 + <image x="0" y="0" width="100%" height="100%" id="image1" /> 1.574 + <image x="0" y="0" width="100%" height="100%" id="image2" /> 1.575 + </g> 1.576 + <rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" /> 1.577 + </svg> 1.578 + </div> 1.579 +</div> 1.580 + 1.581 +</body> 1.582 +</html>