Draw Vectors to a Bitmap in EaselJS - javascript

I'm working on an EaselJS program that continually draws thousands of vectors. Needless to say, after a short amount of time, performance drops. To solve this in Flash, I would have created a Bitmap behind the vectors and periodically draw the vectors to that Bitmap and remove them from the display list.
I'm having trouble doing this in EaselJS, even though it seems like all the pieces are in place. How can I create an empty Bitmap() and draw the contents of my Container() to it?

I think you can just put everything in a container and cache it.
var vectorContainer=new Container;
for (var i = 0;i<numVectors;i++){
var vector=new Shape();
//draw whatever vector you want to vector.graphics
vectorContainer.addChild(vector);
}
vectorContainer.cache(someX,someY,someW,someH);
However get the newest untagged build as the current release has a bug and doesn't cache container properly.

I've also been adding the 'snapToPixel = true' to vectors because I've read it is supposed to help in some browsers. So, in your example code above, I would add the following line right before the last line where you do your caching.
vectorContainer.snapToPixel = true;
Check out this demo of cache and snap to pixel settings to see a live example.
In my particular browser set up, the snap to pixel does not actually change the performance, but in my reading of the docs it seems like it should.

Related

FabricJS HiDPI getImageData

I'm still gathering the pieces needed to understand a web 2 print editor in HTML5. I'm using FabricJS and a bunch of other 3rd party tools to make it a possibility.
My latest roadblock is my Samsung Chromebook Pro, which has a HiDPI/retina display. The window.devicePixelRatio is different, and I'm trying to figure out how to cope with this higher DPI using FabricJS. I followed what everybody else has been saying is successful, but it doesn't seem to work or help me. Plus my canvas becomes extremely large, and objects are no longer movable.
I'm also using another 3rd party library, PetitoJPEG, which I am using to encode the raw pixel data from the FabricJS canvas.
Because of the different devicePixelRatio from my other laptop, running the encoder causes the red dot on the right side of the canvas to not show on the proof image.
I'm wondering how I can encode the entirety of the canvas on a HiDPI display.
Because of all the 3rd party assets, I'm willing to provide a direct link instead of a JSFiddle: http://xbit.x10host.com/editor2.php
I think I understand what the scaling method people are saying is successful does, but I'm wondering if it isn't working properly because of my CSS transform on the canvas container. I've tried adjusting scaling and the canvas widths/heights and CSS width/heights through the console to no avail though.
Any help would be appreciated. I'll paste the snippet that everybody is saying is working below.
Note: My Samsung Chromebook Pro is logging 26562 bytes written, and my Lenovo x230 Windows laptop is logging 27586 bytes.
if( window.devicePixelRatio !== 1 ){
var c = canvas.getElement(); // canvas = fabric.Canvas
var w = c.width, h = c.height;
// Scale the canvas up by two for retina
// just like for an image
c.setAttribute('width', w*window.devicePixelRatio);
c.setAttribute('height', h*window.devicePixelRatio);
// then use css to bring it back to regular size
// or set it here
c.setAttribute('style', 'width="'+w+'"; height="'+h+'";')
// or jQuery $(c).css('width', w);
// $(c).css('width', w);
// $(c).css('height', h);
// finally set the scale of the context
c.getContext('2d').scale(window.devicePixelRatio, window.devicePixelRatio);
canvas.renderAll();
}
I know the above chunk of code seems to work for most people, but isn't the following line incorrect?
c.setAttribute('style', 'width="'+w+'"; height="'+h+'";')
That isn't how you define inline styles. You need a ":" instead of "=" and there's no unit of measurement, so I'm wondering how this works.
Here is a screenshot of the new isolation page I've created to more effectively isolate the issue: And here is the direct URL: http://xbit.x10host.com/isolation.php
You can see how using the method everybody else is saying works isn't working. The CSS property values are invalid. Maybe I'm just confused, but even if I set the CSS width and height correctly, am I using the 1125x675 for getImageData or the new 4500x1200? Using the latter causes the proof image to be too large; the 4500x1200 contains all the data I want but then there's all this black space. Below is a screenshot of that result.
Now, the 2250x1350 (output using the goto method with correct inline CSS syntax) seems to be correct, but I thought the canvas fix was supposed to help to output the same as my other laptop. Would this mean people with HiDPI displays will get higher quality proof images (and prints) than people who don't have a HiDPI display? I want the dimensions and pixel count to the be same regardless of device DPI.
I've found out how to make images the same number of pixels regardless of DPI.
It involves using an off-screen canvas and re-drawing the on-screen canvas onto it at the desired width and height, and re-encoding the off-screen canvas at 300dpi so that regardless of screen resolution, the output file will always be the same.

How multiple canvas elements affect performance?

I'm trying to make a simple game (view from above) on canvas. Please tell me, which is faster:
1) Draw everything on one <canvas> and calculate the areas that need to be redrawn.
2) Draw certain parts of the scene on different <canvas> elements and update each only if necessary. In the it variant, probably, I can also use a partial redraw.
For example, I could draw a map on one element, enemies on the second, and a cursor and etc on the third. When moving enemies, I could only redraw the second canvas.
Please explain in as much detail as possible which option is better and why. And could you please advise books that can deepen my knowledge on this issue? For beginners.
Most canvas games will use a sort of layering that you are describing. The furthest forward layer would be the HUD, either by a seperate canvas or HTML elements and this would only change if something happened to it, such as someone upgrading their player. Then after that it depends on how they draw the canvas. The simplest way would be to simply draw the portions of the map and obstacles are on the screen.
Use one canvas, trust me. If you have a sprite that does nothing but sit there it is still getting drawn 60 times a second so it makes no difference if it is moving or not changing or not if it exists on screen it is getting drawn regardless of change.

JavaScript canvas game development

Ive been having a really baffling slow down issue with the game I am working on probably because I am unsure how to handle graphics (most likely responsible for the slow down) in javascript without using a third party framework like Phaser/ImapactJS/EaselJS etc)*. The following is the low down on how I am approaching my graphics. I would be very thankful for some tips or methods on how to do this right.
My game is tile based - using tiles designed at 500x500 px because I want them to display decently on high definition devices.
I am using a spritesheet to load all (most of) my tiles before the main loop is run. This image is roughly 4000 x 4000 (keeping it below 4096 because the GPU cant handle texture sizes larger than that).
I then use the drawImage function to cycle through and draw each tile on a part of the canvas using information (w, h, x, y) stored in the tile array. I do this on every cycle of the main loop using my drawMap function.
The map is currently 6x6 tiles in size
A character spritesheet is also loaded and drawn on to the canvas after the map has been drawn. The character displays a different frame of the animation on every cycle of the main loop. There are sets of animations for the character each contained in the same spritesheet.
The character sprite sheet is roughly 4000x3500
The character is roughly 350x250 px
Other objects also use the same sprite sheet. Currently there is only one object.
Possibly helpful questions:
Am I using too many spritesheets or too few?
Should I only draw something if it's coordinates are in bounds of the screen?
How should I go about garbage collection? Do I need to set image objects to null when no longer in use?
Thanks in advance for input. I would just like to know if I am going about it the right way and pick your brains as how to speed it up enough.
*Note that I plan to port the JS game to cocoonJS which provides graphics acceleration for the canvas element on mobile.
** If interested please visit my Patreon page for fun!
You have asked lots of questions here, I'll address the ones I've run into.
I would like to start out by saying very clearly,
Use a profiler
Find out whether each thing you are advised to do, by anybody, is making an improvement. Unless we work on your code, we can only give you theories on how to optimise it.
How should I go about garbage collection? Do I need to set image objects to null when no longer in use?
If you are no longer using an object, setting its reference to null will probably mean it gets garbage collected. Having nulls around is not necessarily good but this is not within the scope of this question.
For high performance applications, you want to avoid too much allocation and therefore too much garbage collection activity. See what your profiler says - the chrome profiler can tell you how much CPU time the garbage collector is taking up. You might be OK at the moment.
I then use the drawImage function to cycle through and draw each tile on a part of the canvas using information (w, h, x, y) stored in the tile array. I do this on every cycle of the main loop using my drawMap function.
This is quite slow - instead consider drawing the current on screen tiles to a background canvas, and then only drawing areas which were previously obscured.
For example, if your player walks to the left, there is going to be a lot of tiles on the left hand side of the screen which have come into view; you will need to draw the background buffer onto the screen, offset to account for the movement, and then draw the missing tiles.
My game is tile based - using tiles designed at 500x500 px because I want them to display decently on high definition devices
If I interpret this right, your tiles are 500x500px in diameter, and you are drawing a small number of these on screen. and then for devices without such a high resolution, the canvas renderer is going to be scaling these down. You really want to be drawing pixels 1:1 on each device.
Would you be able, instead, to have a larger number of smaller tiles on screen - thereby avoiding the extra drawing at the edges? Its likely that the tiles around the edges will sometimes draw only a few pixels of one edge, and the rest of the image will be cropped anyway, so why not break them up further?
Should I only draw something if it's coordinates are in bounds of the screen?
Yes, this is a very common and good optimisation to take. You'll find it makes a big difference.
Am I using too many spritesheets or too few?
I have found that when I have a small number of sprite sheets, the big performance hit is when I frequently switch between them. If during one draw phase, you draw all your characters from character_sheet.png and then draw all the plants from plant_sheet.png you'll be ok. Switching between them can cause lots of trouble and you'll see a slow down. You will know this is happening if your profiler tells you that drawImage is taking a big proportion of your frame.

maximum number of svg elements for the browser in a map

I am creating a map with leaflet and d3. A lot of circles will be plotted on a map. In terms of browser compatibility, there is an expected limit of how many svg elements the browser can render. In terms of user experience however, I would prefer that the user can see as many elements on the map as possible (otherwise the user might need to zoom in and out constantly and would need to wait for the ajax to return data). There will be some optimisation that I need to consider (user waiting time user vs. server query load vs. what the browser can handle).
See plot, there is a limit right now on the number of points that the server returns and thus only a portion of the map is filled.
The browser cannot handle a fully filled map here and the user would need to wait too long for the server response as well.
I suppose the problem that I am faced with needs to be solved by answering two questions:
Is there a standard in terms of what the average browser can handle in terms of number of simple svg shapes (circles) on a map?
What is the best technique to show as many shapes on the map as possible?
I'm considering the following points but I am unsure if it will help;
use squares instead of circles
use the leaflet API instead of the D3
Speaking in general terms, neither of the points you're considering will help. In both cases, the amount of processing to be done / information to display by the browser will be approximately the same.
Regarding your first question, not that I'm aware of. There are huge variations between browsers and platforms (especially if you consider mobile devices as well) and an average would be almost meaningless. Furthermore, this is changing constantly. I've found that up to about 1000 simple shapes are usually not a problem.
To show as many shapes as possible on the map, I would pre-render them into bitmap tiles and then use either the leaflet API or something like d3.geo.tile (example here) to overlay it on the actual map. This way you can easily scale to millions of points.
Although you can only render ~2-5k SVG elements before you start to see noticeable slowdown (depending on size, use of gradient fills, etc.), you can store and manipulate much larger datasets client-side. You can often handle tens or hundreds of thousands of data points efficiently in SVG: the trick is to be very selective about what you actually render, and to use techniques like debouncing to redraw only when necessary.
(For very large datasets: yes, you'll need to either aggregate/subsample points or pre-render.)
With this in mind, one technique I've used for d3 maps in particular is to use d3.geom.quadtree() to dynamically cull points as the user pans/zooms. More specifically, I avoid drawing points that are either
outside the current map bounding box (since these aren't visible at all), or
too close to other points (since these add visual clutter and are hard to interact with anyways).
In JS-ish pseudocode, this would look roughly like:
function getIndicesToDraw(data, r, bbox) {
var indicesToDraw = [];
var Q = d3.geom.quadtree();
// set bounds in pixel space
for (var i = 0; i < data.length; i++) {
var d = data[i];
var p = getPointForDatum(d);
if (isInsideBoundingBox(bbox, p) && !hasPointWithinRadius(Q, r, p)) {
Q.add(p);
indicesToDraw.push(i);
}
}
return indicesToDraw;
}
function redraw(svg, data, r, bbox) {
var indicesToDraw = getIndicesToDraw(data, r, bbox);
var points = svg.selectAll('.data-point')
.data(indicesToDraw, function(i) { return i; });
// draw new points for points.enter()
points.exit().remove();
// update positions of points (or SVG transforms, etc.)
}
This is a question of cartography as much as it is of technology. Just because you can put thousands of points on a map doesn't mean you should. You should ask yourself if your user needs to see as many points as possible at once, and will understand the data better as a result of it. Will seeing this many points confuse and overwhelm the user, or will it help them accomplish their desired task? Is there any way you could filter the data so that all points do not need to be drawn at once?
The most common solution to this problem is to use clusters, usually with something like Leaflet MarkerCluster. In the past while using D3 and Leaflet I have created a sort of heat map by creating an arbitrary grid, assigning each point to a bin, and applying a color ramp to the grid. However, getting back to your original concern, it is rather computationally intensive.
Depending on which platforms you want to support, be aware that phones and tablets do not always handle SVGs very well, especially while drawing thousands of features.
Another place for potential performance gain is in the delivery of the point geometry. I'm not sure how you are currently loading them, but using a spatially indexed PostGIS table and selecting by bounding box is often quite quick, but because you are drawing points and not polygons, you could even get away with loading them into the browser via CSV, which is substantially smaller than a GeoJSON or Topojson.
I would load all the data at once but only draw circles in the view port that are large enough. On zoom or pan, remove the circles that shouldn't be shown and check if previously hidden circles should be added.
You may use canvas + three.js or webgl where may join another map and 10k animation created 3d models or native code svg some elements, and intercative live time animation in one scene very good. I`m tested for fun this methode.Sry, my bad engl. Another puths - i think may used dlsl shader, opengl+ wasm and so on

HTML Canvas: mouse click hit test by using ghost canvas - anti-aliasing troubles

I'm writing an JavaScript application that is drawing arbitrarily shaped objects on a HTML canvas. The user should be able to select any of the objects by clicking on them.
To make this an O(1) operation I'm using a shadow canvas, i.e. a not displayed canvas that has exactly the same size, where each object drawn on the normal canvas is also drawn there - but with a color that represents it's ID.
So a simple ghostContex.getImageData() together with the mouse click coordinates gives me the color at that pixel and thus the ID of the clicked object.
All of that is working fine - except when I click on the exact border of an object.
As it's drawn with anti-aliasing on the ghost canvas I get a wrong color (as that color is a mixture between the correct ID and the ID of the object under it that was drawn before...). This wrong color is representing a wrong ID and thus I'm selecting a totally different object :(
How can I solve that problem?
Note #1: I'm already using the translate(0.5, 0.5) trick to prevent most anti-aliasing
Note #2: I was trying to write this application with SVG before, but especially this object selection was extremely slow as I guess it's been too many objects for the collision detection. That's the main reason why I want a O(1) approach now... Oh, and this way I can easily draw a much bigger line on the ghost canvas than the line is drawn on the normal canvas to make picking much easier.
Note #3: Relevant browsers are Firefox, Chrome, Android 2.3+ native and iOS native
The reason why I couldn't accept any answer here is quite easy and quite sad: it doesn't exist... :(
The antialiasing can not be switched of, the standard has no method for that. But the standard does have a hit test function (http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas_CR/#hit-regions) that would do exactly what is needed here. Even in a nice way that would hide the nasty details for the developer - but it's not implemented in any of the browsers right now.
And implementation was looking to be far away till impossible (see e.g. comment #6 at https://code.google.com/p/chromium/issues/detail?id=328961). But apparently it gained momentum during the last month...
So what can be done in the mean time? What did I do?
In my code I could implement for each shape a isPointInShape() method. So I use the ghost canvas trick to get a shape and verify with the isPointInShape() that I really selected the correct shape. This helps in the anti aliased pixels not to pick a wrong shape (just think of clicking on the border of shape #2 where we had a 50% antialias transparency - this would tell you wrongly a selection of shape #1...).
If implementing a generic isPointInShape() is quite hard for your shape you could try a trick that I was reading of somewhere else (I didn't try it tough, so I haven't tested it...):
Create an additional ghost canvas of size 1x1 pixel that is positioned exactly on the mouse position. Then draw the shape of interest - when the A of the RGBA is changed, this shape does belong to that pixel.
I know this is an old post, but recently I had a similar issue. The way I solved the "seam of two colors" problem was doing a 10x10 pixel sampling of the secondary canvas instead of a single pixel. I then stringified the RGB values and used these as keys in a map that mapped to the object that color represents. So initially with the 1 pixel sampling I used the map immediately to determine the associated object but antialiasing created halfway colors that didn't exist in the map. The 10x10 method solves this problem by looping through the 100 RGB values returned and creating a "counting map." This map uses the stringified colors and maps them to a count, but only includes valid colors from the first map in the count. So you end up with a map saying you counted 65 red pixels and 23 blue pixels (where the remaining 12 pixels were some weird anti-alias hybrid). In the same loop where I was counting the colors I also maintained a variable for current max count and current color associated with that max count (to avoid looping through this new map again). Now at the end you have the color that was counted the most in that 10x10 sampling and can use that to map back to the object associated with it. You will only get an undefined result if no valid colors were found in the 10x10 sample which you can reasonably assume means the "background" was clicked.
I made up the name ghost context! Are you using my old tutorial? :)
In that old tutorial I do not clear the ghost context after each object is drawn to it. In your case, to fix your issue, you may need to clear after testing each object on the ghost context.
Make sure of course that you are translating the ghost context and normal context by precisely the same amounts. (and translating them back, or resetting the transformation, afterwards).

Categories

Resources