Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  reftest-analyzer.xhtml   Sprache: unbekannt

 
<?xml version="1.0" encoding="UTF-8"?>
<!-- -*- Mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
<!-- vim: set shiftwidth=2 tabstop=2 autoindent expandtab: -->
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!--

Features to add:
* make the left and right parts of the viewer independently scrollable
* make the test list filterable
** default to only showing unexpecteds
* add other ways to highlight differences other than circling?
* add zoom/pan to images
* Add ability to load log via XMLHttpRequest (also triggered via URL param)
* color the test list based on pass/fail and expected/unexpected/random/skip
* ability to load multiple logs ?
** rename them by clicking on the name and editing
** turn the test list into a collapsing tree view
** move log loading into popup from viewer UI

-->
<!DOCTYPE html>
<html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Reftest analyzer</title>
  <style type="text/css"><![CDATA[

  html, body { margin: 0; }
  html { padding: 0; }
  body { padding: 4px; }

  #pixelarea, #itemlist, #images { position: absolute; }
  #itemlist, #images { overflow: auto; }
  #pixelarea { top: 0; left: 0; width: 320px; height: 84px; overflow: visible }
  #itemlist { top: 84px; left: 0; width: 320px; bottom: 0; }
  #images { top: 0; bottom: 0; left: 320px; right: 0; }

  #leftpane { width: 320px; }
  #images { position: fixed; top: 10px; left: 340px; }

  form#imgcontrols { margin: 0; display: block; }

  #itemlist > table { border-collapse: collapse; }
  #itemlist > table > tbody > tr > td { border: 1px solid; padding: 1px; }
  #itemlist td.activeitem { background-color: yellow; }

  /*
  #itemlist > table > tbody > tr.pass > td.url { background: lime; }
  #itemlist > table > tbody > tr.fail > td.url { background: red; }
  */

  #magnification > svg { display: block; width: 84px; height: 84px; }

  #pixelinfo { font: small sans-serif; position: absolute; width: 200px; left: 84px; }
  #pixelinfo table { border-collapse: collapse; }
  #pixelinfo table th { white-space: nowrap; text-align: left; padding: 0; }
  #pixelinfo table td { font-family: monospace; padding: 0 0 0 0.25em; }

  #pixelhint { display: inline; color: #88f; cursor: help; }
  #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; }
  #pixelhint:hover { color: #000; }
  #pixelhint:hover > * { display: block; }
  #pixelhint p { margin: 0; }
  #pixelhint p + p { margin-top: 1em; }

  ]]></style>
  <script type="text/javascript"><![CDATA[

let heatmapCanvas = null;
let heatmapUMouse;
let gl = null;

function heatmap_render_setup(canvas) {
  gl = canvas.getContext('webgl', {antialias: false, depth: false, preserveDrawingBuffer:false});

  const vertices = [
    0, 0,
    1, 0,
    0, 1,
    1, 1,
  ];

  const vertexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

  const vsCode =
    `
    attribute vec2 a_vertCoord;
    varying   vec2 v_texCoord;
    void main(void) {
      gl_Position = vec4(2.0 * a_vertCoord - 1.0, 0.0, 1.0);
      v_texCoord = a_vertCoord;
    }`;

  const VS = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(VS, vsCode);
  gl.compileShader(VS);

  const psCode =
    `
    precision mediump float;
    uniform vec2 heatmapUMouse;
    varying vec2 v_texCoord;
    uniform sampler2D u_image1, u_image2;
    void main(void) {
      vec2 dxy = abs(heatmapUMouse - gl_FragCoord.xy);
      if(dxy.x < 1.0 || dxy.y < 1.0) {  // crosshair
        gl_FragColor = vec4( 1.0, 1.0, 0.5, 1.0 );
        return;
      }

      vec3 img1 = texture2D(u_image1, v_texCoord).rgb;
      vec3 img2 = texture2D(u_image2, v_texCoord).rgb;

      bool is_top  = gl_FragCoord.y > float(heatmapUMouse.y);
      bool is_left = gl_FragCoord.x < float(heatmapUMouse.x);

      vec3 rgb;
      if(is_top) {
        if(is_left) {
          rgb = img1;
        } else {
          rgb = img2;
        }
      } else {
        vec3 diff = abs(img1 - img2);
        if(is_left) {
          rgb = diff;
        } else {
          float max_diff = max(diff.r, max(diff.g, diff.b));
          if(max_diff == 0.0) {
            rgb = vec3(0.0, 0.0, 0.2);
          } else {
            // some arbitrary colorization -- transition from green to red
            // with some contrast tweaks to make red stand out a bit more
            // at about 0.5'ish
            rgb = vec3( pow(max_diff, 0.5), pow(1.0 - max_diff, 3.0), 0.0 );
          }
        }
      }

      gl_FragColor = vec4( rgb, 1.0 );
    }`;

  const FS = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(FS, psCode);
  gl.compileShader(FS);

  const program = gl.createProgram();
  gl.attachShader(program, VS);
  gl.attachShader(program, FS);
  gl.linkProgram(program);
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error('Link failed: ' + gl.getProgramInfoLog(program));
    console.error('vs info-log: ' + gl.getShaderInfoLog(VS));
    console.error('fs info-log: ' + gl.getShaderInfoLog(FS));
    return;  // don't assign heatmapCanvas
  }
  gl.useProgram(program);

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

  const coord = gl.getAttribLocation(program, "a_vertCoord");
  gl.vertexAttribPointer(coord, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(coord);

  heatmapUMouse = gl.getUniformLocation(program, "heatmapUMouse");

  gl.uniform1i(gl.getUniformLocation(program, 'u_image1'), 0);
  gl.uniform1i(gl.getUniformLocation(program, 'u_image2'), 1);

  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
  heatmapCanvas = canvas;
}

function heatmap_change_image(index, image) {
  if (heatmapCanvas === null) {
    return;
  }
  const texture = gl.createTexture();
  gl.activeTexture(gl.TEXTURE0 + index);
  gl.bindTexture  (gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texImage2D   (gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
}

function heatmap_render(mouse_x, mouse_y) {
  if (heatmapCanvas === null) {
    return;
  }

  gl.uniform2f(heatmapUMouse, mouse_x, mouse_y);

  // the canvas resizes as user selects different reftests
  gl.viewport(0, 0, heatmapCanvas.width, heatmapCanvas.height);

  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

function heatmap_on_mousemove(mousemove_event) {
  if (heatmapCanvas === null) {
    return;
  }
  const rect = heatmapCanvas.getBoundingClientRect();
  let x = mousemove_event.clientX - rect.left;
  let y = mousemove_event.clientY - rect.top;
  x = x * heatmapCanvas.width  / heatmapCanvas.clientWidth;
  y = y * heatmapCanvas.height / heatmapCanvas.clientHeight;

  // mouse has Y == 0 at the top, GL has it at the bottom:
  const flip_y = heatmapCanvas.height-1 - y;
  heatmap_render(x, flip_y);

  return { x:x, y:y };
}

  ]]></script>

  <script type="text/javascript"><![CDATA[

var XLINK_NS = "http://www.w3.org/1999/xlink";
var SVG_NS = "http://www.w3.org/2000/svg";
var IMAGE_NOT_AVAILABLE = "data:image/png;base64,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";

var gPhases = null;

var gIDCache = {};

var gMagPixPaths = [];     // 2D array of array-of-two <path> objects used in the pixel magnifier
var gMagWidth = 5;         // number of zoomed in pixels to show horizontally
var gMagHeight = 5;        // number of zoomed in pixels to show vertically
var gMagZoom = 16;         // size of the zoomed in pixels
var gImage1Data;           // ImageData object for the reference image
var gImage2Data;           // ImageData object for the test output image
var gFlashingPixels = [];  // array of <path> objects that should be flashed due to pixel color mismatch
var gParams;

function ID(id) {
  if (!(id in gIDCache))
    gIDCache[id] = document.getElementById(id);
  return gIDCache[id];
}

function hash_parameters() {
  var result = { };
  var params = window.location.hash.substr(1).split(/[&;]/);
  for (var i = 0; i < params.length; i++) {
    var parts = params[i].split("=");
    result[parts[0]] = unescape(unescape(parts[1]));
  }
  return result;
}

function load() {
  gPhases = [ ID("entry"), ID("loading"), ID("viewer") ];
  build_mag();
  gParams = hash_parameters();
  if (gParams.log) {
    show_phase("loading");
    process_log(gParams.log);
  } else if (gParams.logurl) {
    show_phase("loading");
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState === 4) {
        process_log(req.responseText);
      }
    };
    req.open('GET', gParams.logurl, true);
    req.send();
  }
  window.addEventListener('keypress', handle_keyboard_shortcut);
  ID("image1").addEventListener('error', image_load_error);
  ID("image2").addEventListener('error', image_load_error);
}

function image_load_error(e) {
  e.target.setAttributeNS(XLINK_NS, "xlink:href", IMAGE_NOT_AVAILABLE);
}

function build_mag() {
  var mag = ID("mag");

  var r = document.createElementNS(SVG_NS, "rect");
  r.setAttribute("x", gMagZoom * -gMagWidth / 2);
  r.setAttribute("y", gMagZoom * -gMagHeight / 2);
  r.setAttribute("width", gMagZoom * gMagWidth);
  r.setAttribute("height", gMagZoom * gMagHeight);
  mag.appendChild(r);

  mag.setAttribute("transform", "translate(" + (gMagZoom * (gMagWidth / 2) + 1) + "," + (gMagZoom * (gMagHeight / 2) + 1) + ")");

  for (var x = 0; x < gMagWidth; x++) {
    gMagPixPaths[x] = [];
    for (var y = 0; y < gMagHeight; y++) {
      var p1 = document.createElementNS(SVG_NS, "path");
      p1.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "h" + -gMagZoom + "v" + gMagZoom);
      p1.setAttribute("stroke", "black");
      p1.setAttribute("stroke-width", "1px");
      p1.setAttribute("fill", "#aaa");

      var p2 = document.createElementNS(SVG_NS, "path");
      p2.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "v" + gMagZoom + "h" + -gMagZoom);
      p2.setAttribute("stroke", "black");
      p2.setAttribute("stroke-width", "1px");
      p2.setAttribute("fill", "#888");

      mag.appendChild(p1);
      mag.appendChild(p2);
      gMagPixPaths[x][y] = [p1, p2];
    }
  }

  var flashedOn = false;
  setInterval(function() {
    flashedOn = !flashedOn;
    flash_pixels(flashedOn);
  }, 500);
}

function show_phase(phaseid) {
  for (var i in gPhases) {
    var phase = gPhases[i];
    phase.style.display = (phase.id == phaseid) ? "" : "none";
  }

  if (phase == "viewer")
    ID("images").style.display = "none";
}

function fileentry_changed() {
  show_phase("loading");
  var input = ID("fileentry");
  var files = input.files;
  if (files.length > 0) {
    // Only handle the first file; don't handle multiple selection.
    // The parts of the log we care about are ASCII-only.  Since we
    // can ignore lines we don't care about, best to read in as
    // iso-8859-1, which guarantees we don't get decoding errors.
    var fileReader = new FileReader();
    fileReader.onload = function(e) {
      var log = null;

      log = e.target.result;

      if (log)
        process_log(log);
      else
        show_phase("entry");
    }
    fileReader.readAsText(files[0], "iso-8859-1");
  }
  // So the user can process the same filename again (after
  // overwriting the log), clear the value on the form input so we
  // will always get an onchange event.
  input.value = "";
}

function log_pasted() {
  show_phase("loading");
  var entry = ID("logentry");
  var log = entry.value;
  entry.value = "";
  process_log(log);
}

var gTestItems;

function process_log(contents) {
  var lines = contents.split(/[\r\n]+/);
  gTestItems = [];
  for (var j in lines) {
    var line = lines[j];
    // Ignore duplicated output in logcat.
    if (line.match(/I\/Gecko.*?REFTEST/))
      continue;
    var match = line.match(/^(?:.*? (?:INFO|ERROR) -\s+)?(?:REFTEST\s+)?(.*)$/);
    if (!match)
      continue;
    line = match[1];
    match = line.match(/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL|TEST-DEBUG-INFO)(\(EXPECTED RANDOM\)|) \| ([^\|]+) \|(.*)/);
    if (match) {
      var state = match[1];
      var random = match[2];
      var url = match[3];
      var extra = match[4];
      gTestItems.push(
        {
          pass: !state.match(/DEBUG-INFO$|FAIL$/),
          // only one of the following three should ever be true
          unexpected: !!state.match(/^TEST-UNEXPECTED/),
          random: (random == "(EXPECTED RANDOM)"),
          skip: (extra == " (SKIP)"),
          url: url,
          images: [],
          imageLabels: []
        });
      continue;
    }
    match = line.match(/IMAGE([^:]*): (data:.*)$/);
    if (match) {
      var item = gTestItems[gTestItems.length - 1];
      item.images.push(match[2]);
      item.imageLabels.push(match[1]);
    }
  }

  build_viewer();
}

function build_viewer() {
  if (gTestItems.length == 0) {
    show_phase("entry");
    return;
  }

  var cell = ID("itemlist");
  while (cell.childNodes.length > 0)
    cell.removeChild(cell.childNodes[cell.childNodes.length - 1]);

  var table = document.createElement("table");
  var tbody = document.createElement("tbody");
  table.appendChild(tbody);

  for (var i in gTestItems) {
    var item = gTestItems[i];

    // optional url filter for only showing unexpected results
    if (parseInt(gParams.only_show_unexpected) && !item.unexpected)
      continue;

    // XXX regardless skip expected pass items until we have filtering UI
    if (item.pass && !item.unexpected)
      continue;

    var tr = document.createElement("tr");
    var rowclass = item.pass ? "pass" : "fail";
    var td;
    var text;

    td = document.createElement("td");
    text = "";
    if (item.unexpected) { text += "!"; rowclass += " unexpected"; }
    if (item.random) { text += "R"; rowclass += " random"; }
    if (item.skip) { text += "S"; rowclass += " skip"; }
    td.appendChild(document.createTextNode(text));
    tr.appendChild(td);

    td = document.createElement("td");
    td.id = "item" + i;
    td.className = "url";
    // Only display part of URL after "/mozilla/".
    var match = item.url.match(/\/mozilla\/(.*)/);
    text = document.createTextNode(match ? match[1] : item.url);
    if (item.images.length > 0) {
      var a = document.createElement("a");
      a.href = "javascript:show_images(" + i + ")";
      a.appendChild(text);
      td.appendChild(a);
    } else {
      td.appendChild(text);
    }
    tr.appendChild(td);

    tbody.appendChild(tr);
  }

  cell.appendChild(table);

  show_phase("viewer");
}

function get_image_data(src, whenReady) {
  var img = new Image();
  img.onload = function() {
    var canvas = document.createElement("canvas");
    canvas.width = img.naturalWidth;
    canvas.height = img.naturalHeight;

    var ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);

    whenReady(ctx.getImageData(0, 0, img.naturalWidth, img.naturalHeight));
  };
  img.src = src;
}

function sync_svg_size(imageData) {
  // We need the size of the 'svg' and its 'image' elements to match the size
  // of the ImageData objects that we're going to read pixels from or else our
  // magnify() function will be very broken.
  ID("svg").setAttribute("width", imageData.width);
  ID("svg").setAttribute("height", imageData.height);
}

function sync_heatmap_size(imageData) {
  ID("heat_canvas").setAttribute("width" , imageData.width);
  ID("heat_canvas").setAttribute("height", imageData.height);
}

function show_images(i) {
  var item = gTestItems[i];
  var cell = ID("images");

  // Remove activeitem class from any existing elements
  var activeItems = document.querySelectorAll(".activeitem");
  for (var activeItemIdx = activeItems.length; activeItemIdx-- != 0;) {
    activeItems[activeItemIdx].classList.remove("activeitem");
  }

  ID("item" + i).classList.add("activeitem");
  ID("image1").style.display = "";
  ID("image2").style.display = "none";
  show_diff_none();
  ID("imgcontrols").reset();
  ID("diffcontrols").reset();

  ID("image1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
  // Making the href be #image1 doesn't seem to work
  ID("feimage1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
  if (item.images.length == 1) {
    ID("imgcontrols").style.display = "none";
  } else {
    ID("imgcontrols").style.display = "";

    ID("image2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);
    // Making the href be #image2 doesn't seem to work
    ID("feimage2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);

    ID("label1").textContent = 'Image ' + item.imageLabels[0];
    ID("label2").textContent = 'Image ' + item.imageLabels[1];
  }

  cell.style.display = "";

  get_image_data(item.images[0], function(data) { gImage1Data = data; sync_svg_size(gImage1Data); sync_heatmap_size(gImage1Data); heatmap_change_image(0, gImage1Data); });
  get_image_data(item.images[1], function(data) { gImage2Data = data; heatmap_change_image(1, gImage2Data); });
}

function show_image(i) {
  if (i == 1) {
    ID("image1").style.display = "";
    ID("image2").style.display = "none";
  } else {
    ID("image1").style.display = "none";
    ID("image2").style.display = "";
  }
}

function handle_keyboard_shortcut(event) {
  switch (event.charCode) {
  case 49: // "1" key
    document.getElementById("radio1").checked = true;
    show_image(1);
    break;
  case 50: // "2" key
    document.getElementById("radio2").checked = true;
    show_image(2);
    break;
  case 100: // "d" key
    document.getElementById("radio_diff_circle").click();
    break;
  case 104: // "h" key
    document.getElementById("radio_diff_heatmap").click();
    break;
  case 112: // "p" key
    shift_images(-1);
    break;
  case 110: // "n" key
    shift_images(1);
    break;
  }
}

function shift_images(dir) {
  var activeItem = document.querySelector(".activeitem");
  if (!activeItem) {
    return;
  }
  for (var elm = activeItem; elm; elm = elm.parentElement) {
    if (elm.tagName != "tr") {
      continue;
    }
    elm = dir > 0 ? elm.nextElementSibling : elm.previousElementSibling;
    if (elm) {
      elm.getElementsByTagName("a")[0].click();
    }
    return;
  }
}

function show_diff_none() {
  ID("svg")        .style.display = "";
  ID("diffrect")   .style.display = "none";
  ID("heat_canvas").style.display = "none";
}

function show_diff_circle() {
  ID("svg")        .style.display = "";
  ID("diffrect")   .style.display = "";
  ID("heat_canvas").style.display = "none";
}

function show_diff_heatmap() {
  ID("svg")        .style.display = "none";
  ID("diffrect")   .style.display = "none";
  ID("heat_canvas").style.display = "";

  if (heatmapCanvas === null) {
    canvas = document.getElementById('heat_canvas');
    heatmap_render_setup(canvas);
    heatmap_change_image(0, gImage1Data);
    heatmap_change_image(1, gImage2Data);
    heatmap_render(0, 0);

    window.addEventListener('mousemove', e => {
      var { x: x, y: y } = heatmap_on_mousemove(e);
      magnify_around(Math.floor(x), Math.floor(y));
    });
  }
}

function flash_pixels(on) {
  var stroke = on ? "red" : "black";
  var strokeWidth = on ? "2px" : "1px";
  for (var i = 0; i < gFlashingPixels.length; i++) {
    gFlashingPixels[i].setAttribute("stroke", stroke);
    gFlashingPixels[i].setAttribute("stroke-width", strokeWidth);
  }
}

function cursor_point(evt) {
  var m = evt.target.getScreenCTM().inverse();
  var p = ID("svg").createSVGPoint();
  p.x = evt.clientX;
  p.y = evt.clientY;
  p = p.matrixTransform(m);
  return { x: Math.floor(p.x), y: Math.floor(p.y) };
}

function hex2(i) {
  return (i < 16 ? "0" : "") + i.toString(16);
}

function canvas_pixel_as_hex(data, x, y) {
  var offset = (y * data.width + x) * 4;
  var r = data.data[offset];
  var g = data.data[offset + 1];
  var b = data.data[offset + 2];
  return "#" + hex2(r) + hex2(g) + hex2(b);
}

function hex_as_rgb(hex) {
  return "rgb(" + [parseInt(hex.substring(1, 3), 16), parseInt(hex.substring(3, 5), 16), parseInt(hex.substring(5, 7), 16)] + ")";
}

function magnify(evt) {
  var { x: x, y: y } = cursor_point(evt);
  magnify_around(x, y);
}

function magnify_around(x, y) {
  if (x < 0 || y < 0 || x >= gImage1Data.width || y >= gImage1Data.height) {
    return;
  }
  var centerPixelColor1, centerPixelColor2;

  var dx_lo = -Math.floor(gMagWidth / 2);
  var dx_hi = Math.floor(gMagWidth / 2);
  var dy_lo = -Math.floor(gMagHeight / 2);
  var dy_hi = Math.floor(gMagHeight / 2);

  flash_pixels(false);
  gFlashingPixels = [];
  for (var j = dy_lo; j <= dy_hi; j++) {
    for (var i = dx_lo; i <= dx_hi; i++) {
      var px = x + i;
      var py = y + j;
      var p1 = gMagPixPaths[i + dx_hi][j + dy_hi][0];
      var p2 = gMagPixPaths[i + dx_hi][j + dy_hi][1];
      // Here we just use the dimensions of gImage1Data since we expect test
      // and reference to have the same dimensions.
      if (px < 0 || py < 0 || px >= gImage1Data.width || py >= gImage1Data.height) {
        p1.setAttribute("fill", "#aaa");
        p2.setAttribute("fill", "#888");
      } else {
        var color1 = canvas_pixel_as_hex(gImage1Data, x + i, y + j);
        var color2 = canvas_pixel_as_hex(gImage2Data, x + i, y + j);
        p1.setAttribute("fill", color1);
        p2.setAttribute("fill", color2);
        if (color1 != color2) {
          gFlashingPixels.push(p1, p2);
          p1.parentNode.appendChild(p1);
          p2.parentNode.appendChild(p2);
        }
        if (i == 0 && j == 0) {
          centerPixelColor1 = color1;
          centerPixelColor2 = color2;
        }
      }
    }
  }
  flash_pixels(true);
  show_pixelinfo(x, y, centerPixelColor1, hex_as_rgb(centerPixelColor1), centerPixelColor2, hex_as_rgb(centerPixelColor2));
}

function show_pixelinfo(x, y, pix1rgb, pix1hex, pix2rgb, pix2hex) {
  var pixelinfo = ID("pixelinfo");
  ID("coords").textContent = [x, y];
  ID("pix1hex").textContent = pix1hex;
  ID("pix1rgb").textContent = pix1rgb;
  ID("pix2hex").textContent = pix2hex;
  ID("pix2rgb").textContent = pix2rgb;
}

  ]]></script>

</head>
<body onload="load()">

<div id="entry">

<h1>Reftest analyzer: load reftest log</h1>

<p>Either paste your log into this textarea:<br />
<textarea cols="80" rows="10" id="logentry"/><br/>
<input type="button" value="Process pasted log" onclick="log_pasted()" /></p>

<p>... or load it from a file:<br/>
<input type="file" id="fileentry" onchange="fileentry_changed()" />
</p>
</div>

<div id="loading" style="display:none">Loading log...</div>

<div id="viewer" style="display:none">
  <div id="pixelarea">
    <div id="pixelinfo">
      <table>
        <tbody>
          <tr><th>Pixel at:</th><td colspan="2" id="coords"/></tr>
          <tr><th>Image 1:</th><td id="pix1rgb"></td><td id="pix1hex"></td></tr>
          <tr><th>Image 2:</th><td id="pix2rgb"></td><td id="pix2hex"></td></tr>
        </tbody>
      </table>
      <div>
        <div id="pixelhint">★
          <div>
            <p>Move the mouse over the reftest image on the right to show
            magnified pixels on the left.  The color information above is for
            the pixel centered in the magnified view.</p>
            <p>Image 1 is shown in the upper triangle of each pixel and Image 2
            is shown in the lower triangle.</p>
          </div>
        </div>
      </div>
    </div>
    <div id="magnification">
      <svg xmlns="http://www.w3.org/2000/svg" width="84" height="84" shape-rendering="optimizeSpeed">
        <g id="mag"/>
      </svg>
    </div>
  </div>
  <div id="itemlist"></div>
  <div id="images" style="display:none">
    <form id="imgcontrols">
    <input id="radio1" type="radio" name="which" value="0" onchange="show_image(1)" checked="checked" /><label id="label1" title="1" for="radio1">Image 1</label>
    <input id="radio2" type="radio" name="which" value="1" onchange="show_image(2)"                   /><label id="label2" title="2" for="radio2">Image 2</label>
    </form>

    <form id="diffcontrols">
      Differences:
      <input id="radio_diff_none"    name="diff" value="0" type="radio" onchange="show_diff_none()" checked="checked"/>
        <label for="radio_diff_none">None</label>
      <input id="radio_diff_circle"  name="diff" value="1" type="radio" onchange="show_diff_circle()" />
        <label for="radio_diff_circle">Circle</label>
      <input id="radio_diff_heatmap" name="diff" value="2" type="radio" onchange="show_diff_heatmap()" />
        <label for="radio_diff_heatmap">Heatmap</label>
    </form>

    <canvas width="800" height="1000" id="heat_canvas" style="display:none;"></canvas>

    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800" height="1000" id="svg">
      <defs>
        <!-- use sRGB to avoid loss of data -->
        <filter id="showDifferences" x="0%" y="0%" width="100%" height="100%"
                style="color-interpolation-filters: sRGB">
          <feImage id="feimage1" result="img1" xlink:href="#image1" />
          <feImage id="feimage2" result="img2" xlink:href="#image2" />
          <!-- inv1 and inv2 are the images with RGB inverted -->
          <feComponentTransfer result="inv1" in="img1">
            <feFuncR type="linear" slope="-1" intercept="1" />
            <feFuncG type="linear" slope="-1" intercept="1" />
            <feFuncB type="linear" slope="-1" intercept="1" />
          </feComponentTransfer>
          <feComponentTransfer result="inv2" in="img2">
            <feFuncR type="linear" slope="-1" intercept="1" />
            <feFuncG type="linear" slope="-1" intercept="1" />
            <feFuncB type="linear" slope="-1" intercept="1" />
          </feComponentTransfer>
          <!-- w1 will have non-white pixels anywhere that img2
               is brighter than img1, and w2 for the reverse.
               It would be nice not to have to go through these
               intermediate states, but feComposite
               type="arithmetic" can't transform the RGB channels
               and leave the alpha channel untouched. -->
          <feComposite result="w1" in="img1" in2="inv2" operator="arithmetic" k2="1" k3="1" />
          <feComposite result="w2" in="img2" in2="inv1" operator="arithmetic" k2="1" k3="1" />
          <!-- c1 will have non-black pixels anywhere that img2
               is brighter than img1, and c2 for the reverse -->
          <feComponentTransfer result="c1" in="w1">
            <feFuncR type="linear" slope="-1" intercept="1" />
            <feFuncG type="linear" slope="-1" intercept="1" />
            <feFuncB type="linear" slope="-1" intercept="1" />
          </feComponentTransfer>
          <feComponentTransfer result="c2" in="w2">
            <feFuncR type="linear" slope="-1" intercept="1" />
            <feFuncG type="linear" slope="-1" intercept="1" />
            <feFuncB type="linear" slope="-1" intercept="1" />
          </feComponentTransfer>
          <!-- c will be nonblack (and fully on) for every pixel+component where there are differences -->
          <feComposite result="c" in="c1" in2="c2" operator="arithmetic" k2="255" k3="255" />
          <!-- a will be opaque for every pixel with differences and transparent for all others -->
          <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" />

          <!-- a, dilated by 1 pixel -->
          <feMorphology result="dila1" in="a" operator="dilate" radius="1" />
          <!-- a, dilated by 2 pixels -->
          <feMorphology result="dila2" in="dila1" operator="dilate" radius="1" />

          <!-- all the pixels in the 2-pixel dilation of a but not in the 1-pixel dilation, to highlight the diffs -->
          <feComposite result="highlight" in="dila2" in2="dila1" operator="out" />

          <feFlood result="red" flood-color="red" />
          <feComposite result="redhighlight" in="red" in2="highlight" operator="in" />
          <feFlood result="black" flood-color="black" flood-opacity="0.5" />
          <feMerge>
            <feMergeNode in="black" />
            <feMergeNode in="redhighlight" />
          </feMerge>
        </filter>
      </defs>
      <g onmousemove="magnify(evt)">
        <image x="0" y="0" width="100%" height="100%" id="image1" />
        <image x="0" y="0" width="100%" height="100%" id="image2" />
      </g>
      <rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
    </svg>
  </div>
</div>

</body>
</html>

[ Dauer der Verarbeitung: 0.4 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge