blob: 308bec95606234650e0259a035daaf5ce8f94338 [file] [log] [blame]
<!-- The <multi-zoom-sk> custom element declaration.
This element is a view that allows zooming into images.
It displays the three images, left, top and diff, and then allows zooming and
panning over various combinations of the images. There is a single pixel in
the center of the zoomed view which is highlighted, and its information is
displayed on the zoom dialog.
There are three images that it deals with, called left, top, and diff.
Attributes:
None
Events:
None
Methods:
setDetails(details) - Sets the information the zoom dialog needs, which
is an object of the following form, where each member is a
URL of the full size image.
{
leftImgUrl: "...",
middleImgUrl: "...",
rightImgUrl: "...",
llabel: "Left Label",
rlabel: "Right Label"
}
Mailboxes:
None
-->
<link rel="import" href="bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/paper-dialog/paper-dialog.html">
<link rel="import" href="bower_components/paper-button/paper-button.html">
<link rel="import" href="bower_components/paper-dialog-scrollable/paper-dialog-scrollable.html">
<link rel="import" href="bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel=import href="../common/imp/canvas-layers.html">
<link rel=import href="../common/imp/crosshair.html">
<link rel=import href="../common/imp/zoom.html">
<link rel="import" href="shared-styles.html">
<dom-module id="multi-zoom-sk">
<style include="iron-flex iron-flex-alignment shared-styles">
img, .imgContainer {
width: 128px;
}
.imgContainer, .zoomContainer {
padding: 4px;
border: solid lightgray 1px;
margin: 4px;
}
.zoomContainer {
width: 270px;
height: 270px;
padding: 0;
margin: 0;
}
.imgColumn {
width: 12em;
}
.selectorWrapper {
font-size: 16px;
}
.imagesWrap {
margin-bottom: 2em;
}
.showBold {
font-weight: bold;
}
.multiZoomWrapper {
min-width: 20em;
min-height: 20em;
padding: 2em;
}
.legendContainer {
margin: 2em;
font-size: 90%;
}
</style>
<template>
<div class="multiZoomWrapper">
<div class="horizontal layout">
<div class="vertical layout" hidden="{{_noImage(details)}}">
<div class="horizontal layout wrap imagesWrap">
<div class="vertical layout imgColumn">
<div class="imgContainer">
<canvas-layers-sk layers='["crosshairLeft"]' id="layersLeft">
<img id="left_img" src$="{{details.leftImgUrl}}"/>
</canvas-layers-sk>
<crosshair-sk x="{{_x}}"
y="{{_y}}"
target="layersLeft"
name="crosshairLeft"
update_on="click">
</crosshair-sk>
</div>
<div class="selectorWrapper">
<paper-toggle-button checked={{_leftActive}}>
<span class$="{{_boldClass(_currSeq, 'left')}}">{{details.llabel}}</span>
</paper-toggle-button>
</div>
</div>
<div class="layout vertical imgColumn" hidden="{{_hideImg(details.middleImgUrl)}}">
<div class="imgContainer">
<canvas-layers-sk layers='["crosshairMiddle"]' id="layersMiddle">
<img id="middle_img" src$="{{details.middleImgUrl}}"/>
</canvas-layers-sk>
<crosshair-sk x="{{_x}}"
y="{{_y}}"
target="layersMiddle"
name="crosshairMiddle"
update_on="click">
</crosshair-sk>
</div>
<div class="selectorWrapper">
<paper-toggle-button checked={{_middleActive}}>
<span class$="{{_boldClass(_currSeq, 'middle')}}">Diff</span>
</paper-toggle-button>
</div>
</div>
<div class="layout vertical imgColumn" hidden="{{_hideImg(details.rightImgUrl)}}">
<div class="imgContainer">
<canvas-layers-sk layers='["crosshairRight"]' id="layersRight">
<img id="right_img" src$="{{details.rightImgUrl}}"/>
</canvas-layers-sk>
<crosshair-sk x="{{_x}}"
y="{{_y}}"
target="layersRight"
name="crosshairRight"
update_on="click">
</crosshair-sk>
</div>
<div class="selectorWrapper">
<paper-toggle-button checked={{_rightActive}}>
<span class$="{{_boldClass(_currSeq, 'right')}}">{{details.rlabel}}</span>
</paper-toggle-button>
</div>
</div>
</div>
<div class="layout horizontal wrap">
<div class="layout vertical zoomContainer">
<zoom-sk hidden$="{{_hideMe(_currSeq, 'left')}}"
source="left_img"
pixels="{{_pixels}}"
id="zoomLeft"
x="{{_x}}"
y="{{_y}}">
</zoom-sk>
<zoom-sk hidden$="{{_hideMe(_currSeq, 'middle')}}"
source="middle_img"
pixels="{{_pixels}}"
id="zoomMiddle"
x="{{_x}}"
y="{{_y}}">
</zoom-sk>
<zoom-sk hidden$="{{_hideMe(_currSeq, 'right')}}"
source="right_img"
pixels="{{_pixels}}"
id="zoomRight"
x="{{_x}}"
y="{{_y}}">
</zoom-sk>
</div>
</div>
</div>
<div class="vertical layout">
<div class="legendContainer">
<table border="0" cellspacing="5" cellpadding="5">
<tr><th>Coord</th><td><pre>({{_x}}, {{_y}})</pre><td></tr>
<tr><th>{{details.llabel}}</th><td><pre>{{_centerPixel.zoomLeft}}</pre><td></tr>
<tr hidden="{{_hideImg(details.middleImgUrl)}}"><th>Diff</th><td><pre>{{_centerPixel.zoomMiddle}}</pre><td></tr>
<tr hidden="{{_hideImg(details.rightImgUrl)}}"><th>{{details.rlabel}}</th><td><pre>{{_centerPixel.zoomRight}}</pre><td></tr>
</table>
<div class="horizontal layout">
<table border="0" cellspacing="5" cellpadding="5">
<tr><th>Color Distance</th><th>RGBA</th><th>Alpha</th></tr>
<tr><td>1-1 </td><td><div style="background :#fdd0a2">&nbsp;</div> </td><td><div style="background :#c6dbef">&nbsp;</div></td></tr>
<tr><td>2-5 </td><td><div style="background :#fdae6b">&nbsp;</div> </td><td><div style="background :#9ecae1">&nbsp;</div></td></tr>
<tr><td>6-15 </td><td><div style="background :#fd8d3c">&nbsp;</div> </td><td><div style="background :#6baed6">&nbsp;</div></td></tr>
<tr><td>16-46 </td><td><div style="background :#f16913">&nbsp;</div> </td><td><div style="background :#4292c6">&nbsp;</div></td></tr>
<tr><td>47-140 </td><td><div style="background :#d94801">&nbsp;</div> </td><td><div style="background :#2171b5">&nbsp;</div></td></tr>
<tr><td>141-420 </td><td><div style="background :#a63603">&nbsp;</div> </td><td><div style="background :#08519c">&nbsp;</div></td></tr>
<tr><td>421-1024 </td><td><div style="background :#7f2704">&nbsp;</div> </td><td></td></tr>
</table>
<table border="0" cellspacing="5" cellpadding="5" id=nav>
<tr><th colspan=2>Naviation</th></tr>
<tr><th>H</th><td>Left</td></tr>
<tr><th>J</th><td>Down</td></tr>
<tr><th>K</th><td>Up</td></tr>
<tr><th>L</th><td>Right</td></tr>
<tr><th>A</th><td>Zoom In</td></tr>
<tr><th>Z</th><td>Zoom Out</td></tr>
<tr><th>U</th><td>Jump To Largest Diff</td></tr>
<tr><th>M</th><td>Manual Toggle</td></tr>
</table>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
Polymer({
is: "multi-zoom-sk",
properties:{
details: {
type: Object,
value: function() { return {}; }
},
_currSeq: '',
_leftActive: {
type: Boolean,
value: true,
observer: "_updateTiming"
},
_middleActive: {
type: Boolean,
value: false,
observer: "_updateTiming"
},
_rightActive: {
type: Boolean,
value: true,
observer: "_updateTiming"
},
_x : {
type: Number,
value: 0,
notify: true
},
_y : {
type: Number,
value: 0,
notify: true
},
_pixels : {
type: Number,
value: 64,
notify: true
},
_centerPixel: {
type: Object,
value: function() { return {}; }
}
},
ready: function(){
this._seq = [];
this.listen(this.$.zoomLeft, 'zoom-point', '_handleZoomPixels');
this.listen(this.$.zoomMiddle, 'zoom-point', '_handleZoomPixels');
this.listen(this.$.zoomRight, 'zoom-point', '_handleZoomPixels');
this.listen(document, 'keydown', '_handleKeyDown');
},
setDetails: function(details) {
details = sk.object.shallowCopy(details);
details.llabel = details.llabel || "Left";
details.rlabel = details.rlabel || "Right";
this.set('_leftActive', true);
this.set('_middleActive', false);
this.set('_rightActive', true);
this.set('_x', 0);
this.set('_y', 0);
this.set('details', details);
this.set('_pixels', 64);
this._updateTiming();
this._showNext(true);
this._active = true;
this.$.zoomLeft.notifyResize();
this.$.zoomMiddle.notifyResize();
this.$.zoomRight.notifyResize();
},
clear: function(details) {
this.set('details', {});
this._cancelAsync();
this._active = false;
},
_cancelAsync: function() {
if (this._asyncHandle) {
this.cancelAsync(this._asyncHandle);
this._asyncHandle = null;
}
},
_showNext: function(triggerAsync) {
var currSeq = '';
if (this._seq.length > 0) {
var idx = this._seq.indexOf(this._currSeq);
if (idx < 0) {
idx = 0;
} else {
idx = (idx+1) % this._seq.length;
}
currSeq = this._seq[idx];
}
this.set('_currSeq', currSeq);
if (triggerAsync) {
this._asyncHandle = this.async(function() {
this._showNext(true);
}.bind(this), 500);
}
},
_updateTiming: function() {
var seq = [];
if (this._leftActive) {
seq.push("left");
}
if (this._middleActive && this.details.middleImgUrl && (this.details.middleImgUrl != '')) {
seq.push('middle');
}
if (this._rightActive && this.details.rightImgUrl && (this.details.rigthImgUrl != '')) {
seq.push('right');
}
this._seq = seq;
},
_handleZoomPixels: function(ev) {
ev.stopPropagation();
this.set('_centerPixel.' + ev.target.id, ev.detail.rgb + ' ' + ev.detail.hex);
// Always calculate the middle value.
var lc = this.$.zoomLeft.getPixelColor(ev.detail.x, ev.detail.y);
var rc = this.$.zoomRight.getPixelColor(ev.detail.x, ev.detail.y);
var color = [
Math.abs(lc[0]-rc[0]),
Math.abs(lc[1]-rc[1]),
Math.abs(lc[2]-rc[2]),
Math.abs(lc[3]-rc[3])
];
var diffStr = sk.colorRGB(color, 0, true) + ' ' + sk.colorHex(color, 0);
this.set("_centerPixel.zoomMiddle", diffStr);
},
_handleKeyDown: function(e) {
if (!this._active) {
return;
}
var c = String.fromCharCode(e.keyCode);
switch (c) {
case "J":
this.set('_y', this._y+1);
break;
case "K":
this.set('_y', this._y-1);
break;
case "H":
this.set('_x', this._x-1);
break;
case "L":
this.set('_x', this._x+1);
break;
case "A":
this.set('_pixels', Math.max(this._pixels/2, 8));
break;
case "Z":
var size = this.$.zoomLeft.ctx.canvas.width;
var maxPixels = Math.pow(2, Math.floor(Math.log(size)/Math.log(2))-1);
this.set('_pixels', Math.min(this._pixels*2, maxPixels));
break;
case "U":
this._moveToLargestDiff();
break;
case "M":
this._manualToggle();
break;
}
if ("JKHLAZUM".indexOf(c) != -1 ) {
e.stopPropagation();
}
},
_moveToLargestDiff: function() {
if (!this.details || !this.details.middleImgUrl) {
return;
}
var leftColors = this.$.zoomLeft.getImageData();
var rightColors = this.$.zoomRight.getImageData();
var width = leftColors.width;
var height = leftColors.height;
var lc = leftColors.data;
var rc = rightColors.data;
var max = 0;
var dx = 0;
var dy = 0
for (var x=0; x<width; x++) {
for (var y=0; y<height; y++) {
var offset = (y*width+x)*4; // Offset into the colors array.
var dist = this._colorDist(
rc[offset+0]-lc[offset+0],
rc[offset+1]-lc[offset+1],
rc[offset+2]-lc[offset+2],
rc[offset+3]-lc[offset+3]
);
if (dist > max) {
max = dist;
dx = x;
dy = y;
}
}
}
this._x = dx;
this._y = dy;
},
// colorDist returns the distance of a color from (0, 0, 0, 0) using a
// crude square distance per channel.
_colorDist: function(r, g, b, a) {
return r*r + g*g + b*b + a*a;
},
_manualToggle: function() {
if (!this.details || !this.details.middleImgUrl) {
return;
}
this._cancelAsync();
this._showNext(false);
},
_hideMe: function(currSeq, pos) {
return currSeq != pos;
},
_boldClass: function(currSeq, pos) {
return (currSeq==pos) ? 'showBold' : '';
},
_hideImg: function(url) {
return !url;
},
_noImage: function(details) {
return !(details && details.leftImgUrl);
}
});
</script>
</dom-module>