consider this code:
var deSaturated = deSaturate(greyscaleCtx.getImageData(0, 0, canvasWidth, canvasHeight));
imageData comes from getImageData canvas function.
function deSaturate (imageData) {
var theData = imageData.data;
var dataLength = theData.length;
var i = dataLength-1;
var lightLevel;
// Iterate through each pixel, desaturating it
while ( i >= 0) {
// To find the desaturated value, average the brightness of the red, green, and blue values
theData[i] = theData[i+1] = theData[i+2] = (theData[i] + theData[i + 1] + theData[i + 2]) / 3;
// Fully opaque
theData[i+3] = 255;
// returning an average intensity of all pixels. Used for calibrating sensitivity based on room light level.
lightLevel += theData[i]; //combining the light level in the samefunction
i -= 4;
}
imageData.data = theData; //bring back theData into imageData.data - do I really need this?
var r = [lightLevel/dataLength,imageData]
return r;
}
during the writing and optimizing of this code I found out I don't really understand how js is treating for example "theData" variable. is working with it just a short way to reference imageData.data in which case I don't need the following code in the end:
imageData.data = theData
but then do I pay in degraded performance ( a lot of DOM I/O)?
or is doing theData = imageData.data actually copying the original array (represented as Uint8ClampedArray) and then I have to reassign the modified data to imageData.data.
I guess this is basic javascript, but I found contradictory code examples in MDN and other developer resources and I would really like to understand this properly.
thanks for the help!
Just ran a quick test:
var idata = ctx.getImageData(0,0,300,300);
var data = idata.data;
for(var i=0;i<data.length;i++){
data[i]=0;
}
ctx.putImageData(idata,0,0);
And that properly blanks out part of the screen as expected. However without putImageData nothing will happen. So changing the data object, whether stored in a different variable or not, will be reflected in that imageData object. However this will not affect the canvas until putImageData has been called.
So, yes, you can remove that final assignment and it will work as desired.
However I will warn that it is not a valid assumption that it is a Uint8ClampedArray. Yes, that is how Chrome handles it (last I checked), and it is indeed what the official specification uses. However some browsers have no notion of Uint8ClampedArray, while still supporting canvas through the now deprecated CanvasPixelArray.
So all you are guaranteed to get is something with the some array-like interface. I had to learn this the hard way when I tried to cache interesting features of image data by creating a new Uint8ClampedArray, which failed in some browsers.
See: https://developer.mozilla.org/en-US/docs/DOM/CanvasPixelArray
In javascript, assigning either an array or an object just assigns a reference to that array or object - it does not make a copy of the data. A copy is only made if you physically create a new array and copy the data over or call some function that is designed to do that for you.
So, if imageData.data is an array, then assigning it to theData just makes a shortcut for referring to the same data. It does not make a new copy of the data. Thus, after modifying the data pointed to by theData, you don't have to assign it back to imageData.data because there is only one copy of the data and both theData and imageData.data point already point to that same copy of the data.
So, in direct answer to your question, this line is unnecessary:
imageData.data = theData;
Related
In order to make maps, I need to import some values from csv to json directly in the code.
For loading json and csv files, I use an asynchronous operation with Promise object and I use two loops and a common key to add new properties on json file.
for (var i=0; i< fr[1].length;i++){
var csvId = fr[1][i].codgeo;
var csvValue1 = parseFloat(fr[1][i].value1);
var csvValue0 = parseFloat(fr[1][i].value0);
for (var j=0; j<topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8).features.length;j++){
var jsonId = topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8).features[j].properties.codgeo;
if (csvId === jsonId) {
topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8).features[j].properties.value1 = csvValue1;
topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8).features[j].properties.value0 = csvValue0;
break;
Everything is working but show up the map on the web takes time.
Is there a way to optimize the loading time of the map ?
Here is a sample of my code : https://plnkr.co/edit/ccwIQzlefAbd53qnjCX9?p=preview
I took your plunkr and added some timing points to it, ran it a bunch of times and got some data on where your script takes its time:
Here's a block with the logging.
I am pretty sure my bandwidth where I live is below average and has a ton of variability; the file load time showed a lot of variability for me, down to 500 milliseconds and up to 1800 milliseconds, everything else was consistent
Let's take a closer look a the data manipulation stage, which you include in your question:
for (var i=0; i< fr[1].length;i++){
var csvId = fr[1][i].codgeo;
var csvValue1 = parseFloat(fr[1][i].value1);
var csvValue0 = parseFloat(fr[1][i].value0);
for (var j=0; j<topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8).features.length;j++){
var jsonId = topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8).features[j].properties.codgeo;
if (csvId === jsonId) {
topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8).features[j].properties.value1 = csvValue1;
topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8).features[j].properties.value0 = csvValue0;
break;
The nested for statement runs approximately 5,151 times by my count. The parent for statement runs 101. These shouldn't change as your data is fixed. Why do these cycles take so long? Because you are calling topojson.feature() every for iteration:
If I isolate this line:
topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8)
We can see that this actually takes a few milliseconds alone.
Topojson.feature
Returns the GeoJSON Feature or FeatureCollection for the specified
object in the given topology. If the specified object is a
GeometryCollection, a FeatureCollection is returned, and each geometry
in the collection is mapped to a Feature. Otherwise, a Feature is
returned. The returned feature is a shallow copy of the source object:
they may share identifiers, bounding boxes, properties and
coordinates. (from the docs).
So, everytime we use topojson.feature we are essentially converting the topojson to geojson. We don't need to do this in the for loop. Let's do that once:
var featureCollection = topojson.feature(fr[0],fr[0].objects.dep_GEN_WGS84_UTF8);
//Merge csv & json
//Add properties from csv to json)
for (var i=0; i< fr[1].length;i++){
var csvId = fr[1][i].codgeo;
var csvValue1 = parseFloat(fr[1][i].value1);
var csvValue0 = parseFloat(fr[1][i].value0);
for (var j=0; j<featureCollection.features.length;j++){
var jsonId = featureCollection.features[j].properties.codgeo;
if (csvId === jsonId) {
featureCollection.features[j].properties.value1 = csvValue1;
featureCollection.features[j].properties.value0 = csvValue0;
break;
}
}
}
Of course, we have to update the portion of code that renders to use the featureCollection variable too, rather than the topojson
Let's take a look at timing now:
Here's an updated bl.ock based on the one above, also with timing points.
No, I didn't forget to include a time for manipulation, it just averaged 1.5 milliseconds for me. Yes, the variability in my bandwidth shows - but the time spent on other manipulation should be clearly less regardless of external factors
Further Enhancements
Preprojection of geometry, see this question/answer.
Simplification of geometry, see mapshaper.org (though I believe you have already done this).
Removal of non-necessary attributes from csv or topojson - are you really using the population field in the topojson, do you need both libgeo and libgeo_m in the topojson (eg: "libgeo":"Puy-de-Dôme","libgeo_m":"PUY-DE-DÔME")?
I have Image data of the canvas:
myImage = ctx.getImageData(0, 0, 640, 480);
I figured out, that i can create new Uint8Array and use set() to copy imagedata. This is working example:
var numBytes = width * height * 4;
var ptr= Module._malloc(numBytes);
var heapBytes= new Uint8Array(Module.HEAPU8.buffer, ptr, numBytes);
heapBytes.set(new Uint8Array(myImage.data));
_processImage(heapBytes.byteOffset, width, height);
myImage.data.set(heapBytes);
But, unfortunately every .set() operation is far more slower than processing image, and the code above is slower than JS implementation!
So, I want to process image without copying it. I can successfuly read and write the data directly to the heap this way:
Module.HEAPU8.set(myImage.data, myImage.data.byteOffset);
_processImage(myImage.data.byteOffset, width, height);
myImage.data.set(new Uint8ClampedArray(Module.HEAPU8.buffer , myImage.data.byteOffset , numBytes));
It's faster, but still the first .set() takes 17ms to execute.
The c++ function prototype is:
extern "C" {
int processImage(unsigned char *buffer, int width, int height)
{
}
}
Is there any way to pass the array to C++ without using set()? Just telling the c++ where the data is in memory, and allow to modify it?
Just telling the c++ where the data is in memory, and allow to modify it?
As of v1.34.12, Emscripten has a SPLIT_MEMORY option, where you can tell Emscripten to use an existing buffer as part of its memory space that is split up into evenly sized chunks
You could, for example, get the buffer from the canvas
var existingBuffer = myImage.data.buffer;
var bufferSize = existingBuffer.byteLength; // Must be equal to SPLIT_MEMORY
and then, modifying the example from the explanation of split memory, tell Emscripten to use this buffer as part of its memory space
var chunkIndex = 2; // For example
allocateSplitChunk(chunkIndex, existingBuffer);
and then pass a pointer to the chunk to your C++ function.
var pointerToImageInGlobalMemorySpace = chunkIndex * bufferSize;
_processImage(pointerToImageInGlobalMemorySpace, width, height);
However there are problems and limitations
The Emscripten memory space must be split into chunks exactly the size of the canvas image data buffer.
There are apparently serious performance implications for all of the Emscripten-compiled code, which might make this perform worse than your original code.
As far as I understand topojson.presimplify(JSON) in D3 adds Z coordinate to each point in the input topojson shape based on its significance, which then allows to use it for the dynamic simplification like in http://bl.ocks.org/mbostock/6245977
This method topojson.presimplify() takes quite a long time to execute on complicated maps, especially in Firefox which makes the browser unresponsive for few seconds.
Can it be baked directly into the topojson file via the command line as it is done with projections:
topojson --projection 'd3.geo.mercator().translate([0,0]).scale(1)' -o cartesian.topo.json spherical.topo.json
I found a workaround for this which is not completely as simple as I wanted but still achieves the same result.
After the topojson.presimplify(data) is called, data already holds the pre simplified geometry with added Z axis values.
Then I convert it to the JSON string and manually copy it to a new file with JSON.stringify(data)
Nevertheless these conversion to a JSON string has a problem with Infinity values which often occur for Z and with JSON.stringify method are converted to null. Also when there is a value for Z coordinate it is usually too precise and writing all decimal points takes too much space.
For that reason before converting data to a JSON string I trim the numbers:
// Simplifying the map
topojson.presimplify(data);
// Changing Infinity values to 0, limiting decimal points
var arcs = data.arcs;
for(var i1 = arcs.length; i1--;) {
var arc = arcs[i1];
for(var i2 = arc.length; i2--;) {
var v = arc[i2][2];
if(v === Infinity) arc[i2][2] = 0;
else {
arc[i2][2] = M.round(v * 1e9)/1e9;
}
}
}
This makes Infinity values to appear as exactly 0 and other values are trimmed to 9 decimal points which is enough for dynamic simplification to work properly.
Since such string is too long to easily print it for copying to the new json file it is much easier to store it in the localStorage of the browser:
localStorage.setItem(<object name>, JSON.stringify(data))
Then in Safari or Chrome open the developer console and in the tab Resources -> Local Storage -> <Website URL> the stored object can be found, copied and then pasted into a text editor.
Usually it is pasted as a <key> <value> pair, so one needs to remove from the beginning of the pasted string so that it starts from {.
Since Infinity values have been converted to 0, in the dynamic simplification function it should be taken into account so that points with Z = 0 are treated as Z = Infinity and are always plotted with any simplification area:
point: function(x, y, z) {
if (z===0 || z >= simplificationArea) {
this.stream.point(x, y);
}
}
An emulator I am working with internally stores a 1-dimensional framebuffer of RGB values. However, HTML5 canvas uses RGBA values when calling putImageData. In order to display the framebuffer, I currently loop through the RGB array and create a new RGBA array, in a manner similar to this.
This seems suboptimal. There has been much written on performing canvas draws quickly, but I'm still lost on how to improve my application performance. Is there any way to more quickly translate this RGB array to an RGBA array? The alpha channel will always be fully opaque. Also, is there any way to interface with a canvas so that it takes an array of RGB, not RGBA, values?
There's no way to use plain RGB, but the loop in that code could be optimised somewhat by removing repeated calculations, array deferences, etc.
In general you shouldn't use ctx.getImageData to obtain the destination buffer - you don't normally care what values are already there and should use ctx.createImageData instead. If at all possible, re-use the same raw buffer for every frame.
However, since you want to preset the alpha values to 0xff (they default to 0x00) and only need to do so once, it seems to be much most efficient to just fill the canvas and then fetch the raw values with getImageData.
ctx.fillStyle = '#ffffff'; // implicit alpha of 1
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
dest = ctx.getImageData(0, 0).data
and then for each frame for can just leave the alpha byte untouched:
var n = 4 * w * h;
var s = 0, d = 0;
while (d < n) {
dest[d++] = src[s++];
dest[d++] = src[s++];
dest[d++] = src[s++];
d++; // skip the alpha byte
}
You could also experiment with "loop unrolling" (i.e. repeating that four line block multiple times within the while loop) although results will vary across browsers.
Since it's very likely that your total number of pixels will be a multiple of four, just repeat the block another three times and then the while will only be evaluated for every four pixel copies.
Both ctx.createImageData and ctx.getImageData will create a buffer, the later (get) will be slower since it has also to copy the buffer.
This jsperf : http://jsperf.com/drawing-pixels-to-data
confirms that we have a like 33% slowdown on Chrome, and 16 times slower on Firefox (FFF seems to byte-copy when Chrome copy with 32 or 64 bits move).
i'll just recall that you can handle typed array of different types, and even create a view on the buffer (image.data.buffer).
So this may allow you to write the bytes 4 by 4.
var dest = ctx.createImageData(width, height);
var dest32 = new Int32Array(dest.data.buffer);
var i = 0, j=0, last = 3*width*height;
while (i<last) {
dest32[j] = src[i]<<24 + src[i+1] << 16
+ src[i+2] << 8 + 255;
i+=3;
j++;
}
You will see in this jsperf test i made that it is faster to
write using 32 bits integers :
http://jsperf.com/rgb-to-rgba-conversion-with-typed-arrays
notice that there is a big issue in those tests : since this test is
awfull in terms of garbage creation, accuracy is so-so.
Still after many launch, we see that we have around 50%
gain on write 4 vs write 1.
Edit : it might be worth to see if reading the source with a DataView wouldn't speed things up.
but the input array has to be a buffer (or have a buffer property like a Uint8Array).
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/DataView)
do not hesitate to update the fiddle with such a try.
Edit 2 :
I don't understand i re-ran the test and now write 4 is slower : ??? and after, faster again : -------
Anyway you have great interest in keeping the dest32 buffer under your hand and not
create a new one each time anyway, so since this test measure the Int32Array creation, it does not correspond to your use case.
I'm working with a database that has X and Y points per group, it's being used to draw outlines of images.
Right now in my web side this code is what I use to get the points:
var Drawing = $(XML).find('DrawingXML');
alert($(Drawing[1]).text());
Result:
<DrawingPoints>
<Point><X>1</X><Y>2</Y></Point>
<Point><X>2</X><Y>4</Y></Point>
<Point><X>3</X><Y>5</Y></Point>
<Point><X>2</X><Y>2</Y></Point>
<Point><X>0</X><Y>4</Y></Point>
</DrawingPoints>
Using the .replace() call only changes one item so it's usable for something like this:
.replace("</DrawingPoints>","");
but if I want to replace all 'Point' tags I'm out of luck.
My goal is to use the canvas feature to draw the points out so I want it to be parsed like this:
ctx.beginPath();
ctx.moveTo(1,2);
ctx.lineTo(2,4);
ctx.lineTo(3,5);
ctx.lineTo(2,2);
ctx.lineTo(0,4);
ctx.stroke();
I'm not going to use this with IE browsers just Safari/Chrome, if that helps out.
In this case you'll probably save an awful lot of brainache by using a library instead of writing your own code.
I reckon d3 does what you need:
d3.xml
d3.geo.path
Check out this question/answer. It's not Prototype specific and should help you here.
How to parse XML string with Prototype?
Get all your X and Y values at once:
var points = {};
points.X = Array();
points.Y = Array();
var ix = 0;
$(XML).find('DrawingXML DrawingPoints Point X').each(function()
{
points.X[ix++] = $(this).text();
});
$(XML).find('DrawingXML DrawingPoints Point Y').each(function()
{
points.Y[ix++] = $(this).text();
});
This might not be exact, I didn't test it and my Javascript is a bit rusty, but you get the idea.