-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Introduces attribute source
to image traces
#5075
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
1c893e2
d45496d
f03c7ae
2a687f2
aaf7cbc
e693446
130593e
09eb1d0
9b8a843
2ba0998
a2ea331
4f807c5
7dd3b68
f2caed8
d91e0c1
fe1d208
2960e55
3717cbe
b586a86
d73f4c4
18133c9
19ce21d
74574b6
8c736f7
3b75921
66c58f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -11,13 +11,19 @@ | |||||||||||||||||
var Lib = require('../../lib'); | ||||||||||||||||||
var attributes = require('./attributes'); | ||||||||||||||||||
var constants = require('./constants'); | ||||||||||||||||||
var dataUri = require('../../snapshot/helpers').IMAGE_URL_PREFIX; | ||||||||||||||||||
|
||||||||||||||||||
module.exports = function supplyDefaults(traceIn, traceOut) { | ||||||||||||||||||
function coerce(attr, dflt) { | ||||||||||||||||||
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); | ||||||||||||||||||
} | ||||||||||||||||||
coerce('source'); | ||||||||||||||||||
if(traceOut.source && !traceOut.source.match(dataUri)) traceOut.source = null; | ||||||||||||||||||
antoinerg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
traceOut._isFromSource = !!traceOut.source; | ||||||||||||||||||
|
||||||||||||||||||
var z = coerce('z'); | ||||||||||||||||||
if(z === undefined || !z.length || !z[0] || !z[0].length) { | ||||||||||||||||||
traceOut._isFromZ = !(z === undefined || !z.length || !z[0] || !z[0].length); | ||||||||||||||||||
if(!traceOut._isFromZ && !traceOut._isFromSource) { | ||||||||||||||||||
archmoj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
traceOut.visible = false; | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
|
@@ -26,10 +32,16 @@ module.exports = function supplyDefaults(traceIn, traceOut) { | |||||||||||||||||
coerce('y0'); | ||||||||||||||||||
coerce('dx'); | ||||||||||||||||||
coerce('dy'); | ||||||||||||||||||
var colormodel = coerce('colormodel'); | ||||||||||||||||||
|
||||||||||||||||||
coerce('zmin', constants.colormodel[colormodel].min); | ||||||||||||||||||
coerce('zmax', constants.colormodel[colormodel].max); | ||||||||||||||||||
if(traceOut._isFromZ) { | ||||||||||||||||||
coerce('colormodel'); | ||||||||||||||||||
archmoj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
coerce('zmin', constants.colormodel[traceOut.colormodel].min); | ||||||||||||||||||
coerce('zmax', constants.colormodel[traceOut.colormodel].max); | ||||||||||||||||||
} else if(traceOut._isFromSource) { | ||||||||||||||||||
traceOut.colormodel = 'rgba'; | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... plotly.js/src/traces/image/attributes.js Lines 33 to 40 in 3781f48
Or even better if you not set this parameter this way. What about adding an underscore variable e.g. traceOut._colormodel = 'rgba'; here and then var colormodel = trace._colormodel || trace.colormodel; in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wondering also what happens if one pass a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codepen for This seems correct to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking more about this if(traceOut._isFromZ) {
coerce('colormodel', 'rgb');
...
} else if(traceOut._isFromSource) {
coerce('colormodel', isJPEG(traceOut.source) ? 'rgb' : 'rgba');
...
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Do you mean supporting colormodel There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||
traceOut.zmin = constants.colormodel[traceOut.colormodel].min; | ||||||||||||||||||
traceOut.zmax = constants.colormodel[traceOut.colormodel].max; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
coerce('text'); | ||||||||||||||||||
coerce('hovertext'); | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,14 +13,24 @@ var Lib = require('../../lib'); | |
var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); | ||
var constants = require('./constants'); | ||
|
||
function compatibleAxis(ax) { | ||
return ax.type === 'linear' && | ||
// y axis must be reversed but x axis mustn't be | ||
((ax.range[1] > ax.range[0]) === (ax._id.charAt(0) === 'x')); | ||
} | ||
|
||
module.exports = function plot(gd, plotinfo, cdimage, imageLayer) { | ||
var xa = plotinfo.xaxis; | ||
var ya = plotinfo.yaxis; | ||
|
||
var supportsPixelatedImage = !Lib.isSafari() && !gd._context._exportedPlot; | ||
antoinerg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Lib.makeTraceGroups(imageLayer, cdimage, 'im').each(function(cd) { | ||
var plotGroup = d3.select(this); | ||
var cd0 = cd[0]; | ||
var trace = cd0.trace; | ||
var fastImage = supportsPixelatedImage && trace._isFromSource && compatibleAxis(xa) && compatibleAxis(ya); | ||
trace._fastImage = fastImage; | ||
archmoj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
var z = cd0.z; | ||
var x0 = cd0.x0; | ||
|
@@ -66,11 +76,14 @@ module.exports = function plot(gd, plotinfo, cdimage, imageLayer) { | |
} | ||
|
||
// Reduce image size when zoomed in to save memory | ||
var extra = 0.5; // half the axis size | ||
left = Math.max(-extra * xa._length, left); | ||
right = Math.min((1 + extra) * xa._length, right); | ||
top = Math.max(-extra * ya._length, top); | ||
bottom = Math.min((1 + extra) * ya._length, bottom); | ||
if(!fastImage) { | ||
var extra = 0.5; // half the axis size | ||
archmoj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
left = Math.max(-extra * xa._length, left); | ||
right = Math.min((1 + extra) * xa._length, right); | ||
top = Math.max(-extra * ya._length, top); | ||
bottom = Math.min((1 + extra) * ya._length, bottom); | ||
} | ||
|
||
var imageWidth = Math.round(right - left); | ||
var imageHeight = Math.round(bottom - top); | ||
|
||
|
@@ -82,48 +95,118 @@ module.exports = function plot(gd, plotinfo, cdimage, imageLayer) { | |
return; | ||
} | ||
|
||
// Draw each pixel | ||
var canvas = document.createElement('canvas'); | ||
canvas.width = imageWidth; | ||
canvas.height = imageHeight; | ||
var context = canvas.getContext('2d'); | ||
|
||
var ipx = function(i) {return Lib.constrain(Math.round(xa.c2p(x0 + i * dx) - left), 0, imageWidth);}; | ||
var jpx = function(j) {return Lib.constrain(Math.round(ya.c2p(y0 + j * dy) - top), 0, imageHeight);}; | ||
|
||
var fmt = constants.colormodel[trace.colormodel].fmt; | ||
var c; | ||
for(i = 0; i < cd0.w; i++) { | ||
var ipx0 = ipx(i); var ipx1 = ipx(i + 1); | ||
if(ipx1 === ipx0 || isNaN(ipx1) || isNaN(ipx0)) continue; | ||
for(var j = 0; j < cd0.h; j++) { | ||
var jpx0 = jpx(j); var jpx1 = jpx(j + 1); | ||
if(jpx1 === jpx0 || isNaN(jpx1) || isNaN(jpx0) || !z[j][i]) continue; | ||
c = trace._scaler(z[j][i]); | ||
if(c) { | ||
context.fillStyle = trace.colormodel + '(' + fmt(c).join(',') + ')'; | ||
} else { | ||
// Return a transparent pixel | ||
context.fillStyle = 'rgba(0,0,0,0)'; | ||
// Create a new canvas and draw magnified pixels on it | ||
function drawMagnifiedPixelsOnCanvas(readPixel) { | ||
var colormodel = trace.colormodel; | ||
var canvas = document.createElement('canvas'); | ||
canvas.width = imageWidth; | ||
canvas.height = imageHeight; | ||
var context = canvas.getContext('2d'); | ||
|
||
var ipx = function(i) {return Lib.constrain(Math.round(xa.c2p(x0 + i * dx) - left), 0, imageWidth);}; | ||
archmoj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var jpx = function(j) {return Lib.constrain(Math.round(ya.c2p(y0 + j * dy) - top), 0, imageHeight);}; | ||
archmoj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
var fmt = constants.colormodel[colormodel].fmt; | ||
var c; | ||
for(i = 0; i < cd0.w; i++) { | ||
var ipx0 = ipx(i); var ipx1 = ipx(i + 1); | ||
if(ipx1 === ipx0 || isNaN(ipx1) || isNaN(ipx0)) continue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why we need to test for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not sure at the moment. However, this PR didn't change this logic: I simply moved it into a function. Could we maybe revisit this topic in a later PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But they still be called in the case of non-fast-image! if(ipx1 === ipx0 || (
!hasSource && (isNaN(ipx1) || isNaN(ipx0))
)) continue; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not confident about this change. Checking for NaN might be necessary even when the image is defined via cc @archmoj There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright. Let's skip this for now. |
||
for(var j = 0; j < cd0.h; j++) { | ||
var jpx0 = jpx(j); var jpx1 = jpx(j + 1); | ||
if(jpx1 === jpx0 || isNaN(jpx1) || isNaN(jpx0) || !readPixel(i, j)) continue; | ||
c = trace._scaler(readPixel(i, j)); | ||
if(c) { | ||
context.fillStyle = colormodel + '(' + fmt(c).join(',') + ')'; | ||
} else { | ||
// Return a transparent pixel | ||
context.fillStyle = 'rgba(0,0,0,0)'; | ||
} | ||
context.fillRect(ipx0, jpx0, ipx1 - ipx0, jpx1 - jpx0); | ||
} | ||
context.fillRect(ipx0, jpx0, ipx1 - ipx0, jpx1 - jpx0); | ||
} | ||
|
||
return canvas; | ||
} | ||
|
||
function sizeImage(image) { | ||
image.attr({ | ||
height: imageHeight, | ||
width: imageWidth, | ||
x: left, | ||
y: top | ||
}); | ||
} | ||
|
||
var data = (trace._isFromSource && !fastImage) ? [cd, {hidden: true}] : [cd]; | ||
var image3 = plotGroup.selectAll('image') | ||
.data(cd); | ||
.data(data); | ||
|
||
image3.enter().append('svg:image').attr({ | ||
xmlns: xmlnsNamespaces.svg, | ||
preserveAspectRatio: 'none' | ||
}); | ||
|
||
image3.attr({ | ||
height: imageHeight, | ||
width: imageWidth, | ||
x: left, | ||
y: top, | ||
'xlink:href': canvas.toDataURL('image/png') | ||
if(fastImage) sizeImage(image3); | ||
image3.exit().remove(); | ||
|
||
// Pixelated image rendering | ||
// http://phrogz.net/tmp/canvas_image_zoom.html | ||
// https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering | ||
image3 | ||
.attr('style', 'image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -o-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated;'); | ||
antoinerg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
new Promise(function(resolve) { | ||
if(trace._isFromZ) { | ||
resolve(); | ||
} else if(trace._isFromSource) { | ||
// Transfer image to a canvas to access pixel information | ||
trace._canvas = trace._canvas || document.createElement('canvas'); | ||
trace._canvas.width = w; | ||
trace._canvas.height = h; | ||
var context = trace._canvas.getContext('2d'); | ||
|
||
var sel; | ||
if(fastImage) { | ||
// Use the displayed image | ||
sel = image3; | ||
} else { | ||
// Use the hidden image | ||
sel = d3.select(image3[0][1]); | ||
} | ||
|
||
var image = sel.node(); | ||
image.onload = function() { | ||
context.drawImage(image, 0, 0); | ||
// we need to wait for the image to be loaded in order to redraw it from the canvas | ||
if(!fastImage) resolve(); | ||
}; | ||
sel.attr('xlink:href', trace.source); | ||
if(fastImage) resolve(); | ||
} | ||
}) | ||
.then(function() { | ||
if(!fastImage) { | ||
var canvas; | ||
if(trace._isFromZ) { | ||
canvas = drawMagnifiedPixelsOnCanvas(function(i, j) {return z[j][i];}); | ||
} else if(trace._isFromSource) { | ||
var context = trace._canvas.getContext('2d'); | ||
var data = context.getImageData(0, 0, w, h).data; | ||
canvas = drawMagnifiedPixelsOnCanvas(function(i, j) { | ||
var index = 4 * (j * w + i); | ||
return [ | ||
data[index + 0], | ||
archmoj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data[index + 1], | ||
data[index + 2], | ||
data[index + 3] | ||
]; | ||
}); | ||
} | ||
var href = canvas.toDataURL('image/png'); | ||
var displayImage = d3.select(image3[0][0]); | ||
archmoj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
sizeImage(displayImage); | ||
displayImage.attr('xlink:href', href); | ||
} | ||
}); | ||
}); | ||
}; |
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.