Most efficient way to store a section of a canvas? - javascript

I'm making an application, and I need to store "snapshots" of the canvas so I can use them later.
I'm currently turning the canvas into a dataUrl, creating a 'new Image()' with it, and storing it into an array. But this turns out to be quite slow, and kind of freezes the application for a split second when it happens.
There's one problem I'm aware of; it saves the entire canvas, while I only need to save a small section of it.
So, is there any faster way to save the current canvas, or is there any way to turn only a small section into a dataUrl without creating new canvases every time?

You can create a canvas the size of the area you want to save:
var save_area = document.createElement("canvas");
save_area.width = x1 - x0;
save_area.height = y1 - y0;
Then you draw the original canvas into the save area, translated by (-x0, -y0) so that the part you're interested in is copied in save_area pixels.
save_area.getContext("2d").drawImage(original_canvas, -x0, -y0);
To restore just draw back the save_area into the canvas after first clearing the affected portion (this is needed to handle transparency correctly).
original_canvas_context.clearRect(x0, y0, x1-x0, y1-y0);
original_canvas_context.drawImage(save_area, x0, y0);
You can see a working example in http://jsfiddle.net/Lhmrswch/2/

Related

readPixels from WebGL2RenderContext returns only black pixels

So there is a game online that uses WebGL2 and WebAssembly. My goal is to interact with the game by script. It uses pointers internally which makes it hard to read data from the game data. That's why I decided to go over the UI using the WebGL context. I'm very new to WebGL, graphics and rendering in general and have no idea what I'm actually doing.
I've found the canvas and can execute methods on it. My first step is to take screenshots of areas using WebGL that I may use to analyze parts of the UI. For that I'm using WebGLRenderingContext#readPixels. Here's a snippet reading the whole canvas and saving its' pixels as RGBA:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("webgl2");
const pixels = new Uint8Array(ctx.drawingBufferWidth * ctx.drawingBufferHeight * 4);
ctx.readPixels(0, 0, ctx.drawingBufferWidth, ctx.drawingBufferHeight, ctx.RGBA, ctx.UNSIGNED_BYTE, pixels)
// Returns only black / white
pixels.findIndex(pixel => pixel !== 0 && pixel !== 255); // -1
So in this case, there are only black pixels, all 4-tuples equal (0,0,0,255). A method to draw those pixels in a temporary canvas and download its' ImageData as png creates a black image.
What's the reason behind this and how can I fix it?
For performance reasons the WebGl's drawing buffer gets cleared after drawing. https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2
Any calls to readPixels() will just return empty data.
To keep it's content you need to set the preserveDrawingBuffer flag to true wen getting the drawing context via the getContext("webgl") function.
So change this
const ctx = canvas.getContext("webgl2");
to
const ctx = canvas.getContext("webgl2", {preserveDrawingBuffer: true});

Resizing images using createjs / easeljs

I'd like to dynamically downsize some images on my canvas using createjs, and then store the smaller images to be displayed when zooming out of the canvas for performance reasons. Right now, I'm using the following code:
var bitmap = createjs.Bitmap('somefile.png');
// wait for bitmap to load (using preload.js etc.)
var oc = document.createElement('canvas');
var octx = oc.getContext('2d');
oc.width = bitmap.image.width*0.5;
oc.height = bitmap.image.height*0.5;
octx.drawImage(bitmap.image, 0, 0, oc.width, oc.height);
var dataUrl = oc.toDataURL('image/png'); // very expensive
var smallBitmap = new createjs.Bitmap(dataUrl);
This works, but:
The toDataURL operation is very expensive when converting to image/png and too slow to use in practice (and I can't convert to the faster image/jpeg due to the insufficient quality of the output for all settings I tried)
Surely there must be a way to downsize the image without having to resort to separate canvas code, and then do a conversion manually to draw onto the createjs Bitmap object??
I've also tried:
octx.drawImage(bitmap.image, 0, 0, oc.width, oc.height);
var smallBitmap = new createjs.Bitmap(oc);
But although very fast, this doesn't seem to actually work (and in any case I'm having to create a separate canvas element every time to facilitate this.)
I'm wondering if there is a way that I can use drawImage to draw a downsampled version of the bitmap into a createjs Bitmap instance directly without having to go via a separate canvas object or do a conversion to string?
If I understand correctly, internally this is how the createjs cache property works (i.e. uses drawImage internally to write into the DisplayObject) but I'm unable to figure out how use it myself.
You have tagged this post with createjs and easeljs, but your examples show plain Canvas context usage for scaling.
You can use the scale parameter on Bitmap.cache() to get the result you want, then reuse the cacheCanvas as necessary.
// This will create a half-size cache (50%)
// But scale it back up for you when it displays on the stage
var bmp = new createjs.Bitmap(img);
bmp.cache(0, 0, img.width, img.height, 0.5);
// Pull out the generated cache and use it in a new Bitmap
// This will display at the new scaled size.
var bmp2 = new createjs.Bitmap(bmp.cacheCanvas);
// Un-cache the first one to reset it if you want
bmp.uncache();
Here is a fiddle to see it in action: http://jsfiddle.net/lannymcnie/ofdsyn7g/
Note that caching just uses another canvas with a drawImage to scale it down. I definitely would stay away from toDataURL, as it not performant at all.

Image data in JS

I want to know if I've understood this correctly.
I loop my map and load sprites on the map.
So I decided to store the pixel information in an array so that when I click with my mouse I check if its in a pixel array range and get the id related to it (effectively being pixel accurate for detecting what object was clicked?)
This is my thinking process:
I draw the sprite:
ctx.drawImage(castle[id], abposx, abposy - (imgheight/2));
myImageData[sdata[i][j][1]] =
ctx.getImageData(abposx, abposy, castle[id].width, castle[id].height);
Then some how with left click, check if mouse x and mouse y is inbetween the range of the arrays and return the value of myImageData?
Or have I misunderstood what getImageData is about?
getImageData gives you all of the pixel data for an image. Basically you only need to use getImageData if you are doing any sort of pixel manipulation with the image, like changing its hue/color, or applying a filter, or need specific data, such as the r/g/b, or alpha values. In order to check for pixel perfect collisions you an do something like the following:
var imageData = ctx.getImageData(x, y, 1, 1);
if(imageData.data[3] !== 0){
// you have a collision!
}
imageData.data[0-3] holds an array of data, 0-2 are the color values r/g/b, and 3 is the alpha value. So we assume if the alpha is 0, it must be a transparent portion. Also note, in the example and fiddle I am grabbing the data from the canvas itself, so if there was an image behind it that wasnt transparent it would count as not being transparent. The best way to do it if you have many images that overlap is to keep a copy of the image by itself offscreen somewhere and do a translation of the coordinates to get the position on the image. Heres a good MDN Article explaining getImageData as well.
Live Demo

Hand-create images in javascript and draw with respect to the alpha channel?

I'm currently trying to create a page with dynamically generated images, which are not shapes, drawn into a canvas to create an animation.
The first thing I tried was the following:
//create plenty of those:
var imageArray = ctx.createImageData(0,0,16,8);
//fill them with RGBA values...
//then draw them
ctx.putImageData(imageArray,x,y);
The problem is that the images are overlapping and that putImageData simply... puts the data in the context, with no respect to the alpha channel as specified in the w3c:
pixels in the canvas are replaced wholesale, with no composition, alpha blending, no shadows, etc.
So I thought, well how can I use Images and not ImageDatas?
I tried to find a way to actually put the ImageData object back into an image but it appears it can only be put in a canvas context. So, as a last resort, I tried to use the toDataURL() method of a 16x8 canvas(the size of my images) and to stick the result as src of my ~600 images.
The result was beautiful, but was eating up 100% of my CPU...(which it did not with putImageData, ~5% cpu) My guess is that for some unknown reason the image is re-loaded from the image/png data URI each time it is drawn... but that would be plain weird... no? It also seems to take a lot more RAM than my previous technique.
So, as a result, I have no idea how to achieve my goal.
How can I dynamically create alpha-channelled images in javascript and then draw them at an appreciable speed on a canvas?
Is the only real alternative using a Java applet?
Thanks for your time.
Not knowing, what you really want to accomplish:
Did you have a look at the drawImage-method of the rendering-context?
Basically, it does the composition (as specified by the globalCompositeOperation-property) for you -- and it allows you to pass in a canvas element as the source.
So could probably do something along the lines of:
var offScreenContext = document.getCSSCanvasContext( "2d", "synthImage", width, height);
var pixelBuffer = offScreenContext.createImageData( tileWidth, tileHeight );
// do your image synthesis and put the updated buffer back into the context:
offScreenContext.putImageData( pixelBuffer, 0, 0, tileOriginX, tileOriginY, tileWidth, tileHeight );
// assuming 'ctx' is the context of the canvas that actually gets drawn on screen
ctx.drawImage(
offScreenContext.canvas, // => the synthesized image
tileOriginX, tileOriginY, tileWidth, tileHeight, // => frame of offScreenContext that get's drawn
originX, originY, tileWidth, tileHeight // => frame of ctx to draw in
);
Assuming that you have an animation you want to loop over, this has the added benefit of only having to generate the frames once into some kind of sprite-map so that in subsequent iterations you'll only ever need to call ctx.drawImage() -- at the expense of an increased memory footprint of course...
Why don't you use SVG?
If you have to use canvas, maybe you could implement drawing an image on a canvas yourself?
var red = oldred*(1-alpha)+imagered*alpha
...and so on...
getCSSCanvasContext seems to be WebKit only, but you could also create an offscreen canvas like this:
var canvas = document.createElement('canvas')
canvas.setAttribute('width',300);//use whatever you like for width and height
canvas.setAttribute('height',200);
Which you can then draw to and draw onto another canvas with the drawImage method.

Get image line co-ordinates for canvas

I just started to work with canvas.
I need to simulate some image in pure canvas.
image => tool => [1, 20, 80, 45.....] => canvas => canvas render image
some picuture coordinates this picture but rendered(created) via canvas
Are there any tools that help to get image lines coordinates (to map)?
So, next, I could just use them, and get a pure canvas image.
If I understood your comment correctly, you either want to draw an image onto a canvas, or convert it to vector data and then draw that on the canvas.
Drawing an image on a canvas
This is by far the simplest solution. Converting raster images to vector data is a complicated process involving advanced algorithms, and still it's not perfect.
Rendering an image on a canvas is actually very simple:
// Get the canvas element on the page (<canvas id="canvas"> in the HTML)
var ctx = document.getElementById('canvas').getContext('2d');
// Create a new image object which will hold the image data that you want to
// render.
var img = new Image();
// Use the onload event to make the code in the function execute when the image
// has finished loading.
img.onload = function () {
// You can use all standard canvas operations, of course. In this case, the
// rotate function to rotate the image 45 degrees.
ctx.rotate(Math.PI / 4);
// Draw image at (0, 0)
ctx.drawImage(img, 0, 0);
}
// Tell the image object to load an image.
img.src = 'my_image.png';
Converting a raster image to vector data
This is a complicated process, so I won't give you the whole walkthrough. First of all, you can give up on trying to implement this yourself right now, because it requires a lot of work. However, there are applications and services that do this for you:
http://vectormagic.com/home
Works great, but you will have to pay for most of the functionality
How to convert SVG files to other image formats
A good list of applications that can do this for you
After this, you can store the vector data as SVG and use the SVG rendering that some browsers have, or a library such as SVGCanvas to render SVG onto a canvas. You can probably use that library to convert the resulting image to a list of context operations instead of converting from SVG every time.

Categories