-
-
Couldn't load subscription status.
- 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 6 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)) delete traceOut.source; | ||||||||||||||||||
archmoj 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.
Outdated
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.
Outdated
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'; | ||||||||||||||||||
|
||||||||||||||||||
| colormodel: { | |
| valType: 'enumerated', | |
| values: cm, | |
| dflt: 'rgb', | |
| role: 'info', | |
| editType: 'calc', | |
| description: 'Color model used to map the numerical color components described in `z` into colors.' | |
| }, |
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 calc and plot?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering also what happens if one pass a jpeg (often has no alpha channel) instead of a png?
In that case the default should be rgba or simply rgb?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codepen for jpeg: https://codepen.io/MojtabaSamimi/pen/zYqKOKy
This seems correct to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking more about this automatic default behaviour.
I still believe we should respect colormodel to be defined by the user.
And we must remove colormodel.dflt and set it during supplyDefaults.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I still believe we should respect colormodel to be defined by the user.
Do you mean supporting colormodel hsl with say a PNG image? That seems like a weird thing to do and support. Also, it would be slow: now we would have to read all the pixels one by one and convert them into the new colorspace. cc @archmoj
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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.
Outdated
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
|
||
|
|
||
| var p = 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); | ||
| resolve(); | ||
| }; | ||
| sel.attr('xlink:href', trace.source); | ||
| } | ||
| }) | ||
| .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.
Outdated
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.
Outdated
Show resolved
Hide resolved
|
||
| sizeImage(displayImage); | ||
| displayImage.attr('xlink:href', href); | ||
| } | ||
| }); | ||
|
|
||
| gd._promises.push(p); | ||
| }); | ||
| }; | ||
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.