I am having a problem with updating the canvas. The code is hosted at http://ssarangi.github.com/nombre/. The problem is I am loading a red image and after the loading is done I convert the image to Yellow and want to re-render it to the canvas.
// Turn to Yellow
function updateImage() {
// Update the image
var ctx = canvas2d.getContext("2d");
var width = canvas2d.width;
var height = canvas2d.height;
var pixels = ctx.getImageData(0, 0, width, height);
for (var x = 0; x < width; ++x) {
for (var y = 0; y < height; ++y) {
var offset = (y * width + x) * 4;
pixels.data[offset] = 0;
pixels.data[offset+1] = 255;
pixels.data[offset+2] = 255;
pixels.data[offset+3] = 255;
}
}
ctx.putImageData(pixels, 0, 0);
}
However, the ctx doesn't update the pixels. However, doing the same thing on a simple html page did work for me. This example enables WebGL though. Could someone point me to the right direction.
Thanks.
Nothing is wrong with the code provided, except that it claims to make yellow pixels when it in fact makes teal pixels. Here it is modified and working, making yellow pixels:
http://jsfiddle.net/TCf6f/
The code on your webpage works when you set a breakpoint in the chrome debugger, specifically it turns the canvas teal as your code specifies. It does not seem to work when there isn't a breakpoint, and I imagine the size of the CanvasPixelArray is to blame. What happens with a smaller canvas?
Whats weirder, sometimes you can set a breakpoint and see the operation partially working, like here:
With earlier breakpoints lead to the entire thing being the correct teal.
If a smaller canvas works, try modifying the pixel array in 4 or 8 chunks and see what happens.
Related
I'm trying to make it appear as though movement on my <canvas> creates motion trails. In order to do this, instead of clearing the canvas between frames I reduce the opacity of the existing content by replacing a clearRect call with something like this:
// Redraw the canvas's contents at lower opacity. The 'copy' blend
// mode keeps only the new content, discarding what was previously
// there. That way we don't have to use a second canvas when copying
// data
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = 0.98;
ctx.drawImage(canvas, 0, 0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
However, since setting globalAlpha multiplies alpha values, the alpha values of the trail can approach zero but will never actually reach it. This means that graphics never quite fade, leaving traces like these on the canvas that do not fade even after thousands of frames have passed over several minutes:
To combat this, I've been subtracting alpha values pixel-by-pixel instead of using globalAlpha. Subtraction guarantees that the pixel opacity will reach zero.
// Reduce opacity of each pixel in canvas
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Iterates, hitting only the alpha values of each pixel.
for (let i = 3; i < data.length; i += 4) {
// Use 0 if the result of subtraction would be less than zero.
data[i] = Math.max(data[i] - (0.02 * 255), 0);
}
ctx.putImageData(imageData, 0, 0);
This fixes the problem, but it's extremely slow since I'm manually changing each pixel value and then using the expensive putImageData() method.
Is there a more performant way to subtract, rather than multiplying, the opacity of pixels being drawn on the canvas?
Unfortunately there is nothing we can do about it except from manually iterating over the pixels to clear low-value alpha pixels like you do already.
The problem is related to integer math and rounding (more details at this link, from the answer).
There are blending modes such as "luminosity" (and to a certain degree "multiply") which can be used to subtract luma, the problem is it works on the entire surface contrary to composite modes which only works on alpha - there is no equivalent in composite operations. So this won't help here.
There is also a new luma mask via CSS but the problem is that the image source (which in theory could've been manipulated using for example contrast) has to be updated every frame and basically, the performance would be very bad.
Workaround
One workaround is to use "particles". That is, instead of using a feedback-loop instead log and store the path points, then redraw all logged points every frame. Using a max value and reusing that to set alpha can work fine in many cases.
This simple example is just a proof-of-concept and can be implemented in various ways in regards to perhaps pre-populated arrays, order of drawing, alpha value calculations and so forth. But I think you'll get the idea.
var ctx = c.getContext("2d");
var cx = c.width>>1, cy = c.height>>1, r = c.width>>2, o=c.width>>3;
var particles = [], max = 50;
ctx.fillStyle = "#fff";
(function anim(t) {
var d = t * 0.002, x = cx + r * Math.cos(d), y = cy + r * Math.sin(d);
// store point and trim array when reached max
particles.push({x: x, y: y});
if (particles.length > max) particles.shift();
// clear frame as usual
ctx.clearRect(0,0,c.width,c.height);
// redraw all particles at a log. alpha, except last which is drawn full
for(var i = 0, p, a; p = particles[i++];) {
a = i / max * 0.6;
ctx.globalAlpha = i === max ? 1 : a*a*a;
ctx.fillRect(p.x-o, p.y-o, r, r); // or image etc.
}
requestAnimationFrame(anim);
})();
body {background:#037}
<canvas id=c width=400 height=400></canvas>
I found this issue https://code.google.com/p/android/issues/detail?id=17565 and it seems that it is not going to get solved soon, so I would like to know, how can I draw on canvas without using the putImageData()?
var imgCanvas = document.createElement("canvas");
var ctx = imgCanvas.getContext("2d");
var imageObj.src = 'img/someImage.png';
imageObj.onload = function(){
ctx.draw(imageObj,0,0);
var imageData = ctx.getImageData(0,0,30,30);
var data = imageData.data;
for(var i = 0, n = data.length; i < n; i += 4) {
data[i]+=20;//red
data[i+1]-=20;//green
data[i+2]-=20;//blue
data[i+3]=0; //alpha, но я не трогаю этого параметра
}
ctx.putImageData(imageData,0,0);
}
For instance, in normal browsers, the result of changing a pixel rgba=[100,100,100,200] with the above script should be rgba=[120,80,80,200], but on android's 4.2 default browser the result is not the expected, is something weird like rgba=[153,102,102,200]. Anyway, the point is that there is an issue and I would like to know, how can I change one image's rgba parameters without using the putImageData() method?. If there is not a way with canvas, I would like to know, how can solve this another way? I need to change the image color dinamically, and I need this to work on android (I am using phonegap and the result is the same as in the default android browser). Thank you!
If you want to change the full image color by a factor (for example, add 10 to r, 30 to g and 4 to b), you could use globalComposite operations:
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing
Check 'lighter' and 'darker' ops.
If you want to set a different color / factor to each pixel, i think that putImageData is the way to go...
Hope it helps.
I am using CreateJS to add graphics (shapes) and bitmaps to my stage, and add that to my HTML5 canvas.
After moving the circle graphic around the screen (20px in size), there was severe lag after a little while.
I followed this article to figure out performance issues: http://blog.toggl.com/2013/05/6-performance-tips-for-html-canvas-and-createjs/
So I tried caching... now when I press the keys, the circle does not move. Am I caching incorrectly?
world = new createjs.Container();
segment = new createjs.Shape();
segment.graphics.beginFill("red").drawCircle(0, 0, 20);
segment.x = 100;
segment.y = 100;
segment2 = new createjs.Shape();
segment2.graphics.beginFill("black").drawCircle(0, 0, 20);
segment2.x = 150;
segment2.y = 150;
ContainerOfPeople = new createjs.Container();
ContainerOfPeople.addChild(segment, segment2);
world.addChild(ContainerOfPeople); //add container of people to world container (which will contain all objects in a container)
world.cache(0, 0, 1000, 1000); //cache all objects within world container
stage.addChild(world);
Edit:
If I don't cache the tiles after creating the map, I can see them rendered to the canvas:
function createWorld() {
background = new createjs.Container();
for (var y = 0; y < mapWidth; y++) {
for (var x = 0; x < mapHeight; x++) {
var tile = new createjs.Bitmap('images/tile.png');
tile.x = x * 28;
tile.y = y * 30;
background.addChild(tile);
}
}
//background.cache(0, 0, mapWidth, mapHeight);
stage.addChild(background);
}
If I do cache the background container of tile children, it won't render
function createWorld() {
background = new createjs.Container();
for (var y = 0; y < mapWidth; y++) {
for (var x = 0; x < mapHeight; x++) {
var tile = new createjs.Bitmap('images/tile.png');
tile.x = x * 28;
tile.y = y * 30;
background.addChild(tile);
}
}
background.cache(0, 0, mapWidth, mapHeight);
stage.addChild(background);
}
Why?
You wouldn't want to cache the whole world if you are animating/moving its child objects. Think of caching as taking a snapshot of the DisplayObject and all of its children. While an item is cached, you won't see any changes you make to the children until you update the cache again as explained in the EaselJS docs:
http://www.createjs.com/Docs/EaselJS/classes/DisplayObject.html#method_cache
cache ( x y width height [scale=1] )
Defined in cache:735
Draws the display object into a new canvas, which is then used for subsequent draws. For complex content that does not change frequently (ex. a Container with many children that do not move, or a complex vector Shape), this can provide for much faster rendering because the content does not need to be re-rendered each tick. The cached display object can be moved, rotated, faded, etc freely, however if its content changes, you must manually update the cache by calling updateCache() or cache() again. You must specify the cache area via the x, y, w, and h parameters. This defines the rectangle that will be rendered and cached using this display object's coordinates.
To expand on the explanation, let's say you have a game character that is a Container made up of 6 child shapes, 2 arms, 2 legs, a body and a head. During gameplay, the character's arms and legs flail around. In this scenario, you DON'T want to cache the character as you would be forced to update the cache each time the arms and legs moved, removing any performance gain from caching.
However, let's say once the character dies, he freezes in a dead position, and alpha fades off the screen. In this case you WOULD cache the character. This is because alpha animations become increasingly CPU intensive with the greater number of shapes it has to consider. By caching the character, you are effectively telling the CPU to tween just one shape instead of 6. You can then uncache once your alpha tween is complete and you want to return the player for round 2.
Update
easeljs loads images asynchronously when they are first referenced. Because you aren't preloading your image, the image data isn't yet loaded into memory when you are caching your background.
http://jsfiddle.net/8EvUX/
Here's a fiddle where the image is embedded as a base64 encoded string and is therefore available to cache immediately to prove the caching works as expected. My suggestion would be to use the preloadjs library to load your image first before adding it to the stage.
I want to do some manual anti-aliasing on some text on a canvas. I know how to iterate over the image / colour data, but not exactly how to anti-alias.
From googling around a bit it seems like to do anti-aliasing I need to have an original image which I use as my sample, then pass over the colour data, then for each pixel take an average of the surrounding pixels, then copy this new value into the data for my anti-aliased image.
The bit I'm not sure about is exactly how to 'take an average' of the surrounding pixels.
I have done a jsFiddle to demostrate what I have done so far. As you will see I am copying the image data from the original canvas, making it negative, then putting it into the second canvas.
If I am being more specific in what I am struggling with, it is how exactly do you figure out what the surrounding pixels are in a loop which only has one iterator? And also is the average just a case of adding the nearest pixels colour vals to the current pixels vals, then dividing by the number of pixels?
This is the loop in which I wish to manipulate the data:
var imgData = originalContext.getImageData(0, 0, width, height);
var aliasedData = originalContext.createImageData(width, height);
aliasedData.data.set(imgData.data);
for (var i = 0; i < imgData.data.length; i += 4) {
// just make the data negative to show something is happening
aliasedData.data[i] = 255 - imgData.data[i];
aliasedData.data[i + 1] = 255 - imgData.data[i + 1];
aliasedData.data[i + 2] = 255 - imgData.data[i + 2];
// need to get an average of surrounding pixels here
}
aliasedContext.putImageData(aliasedData, 0, 0);
I've got an ipad webapp I'm working on where you can draw on a canvas and save it. Like a more basic paint program. I need to be able to upload and image to the background and draw on it. Right now that wouldn't be too difficult since I got the drawing functionality and it wouldn't be hard to just print the image to the background and draw on it. The problem I'm having is that it also needs to have manageable layers. This means it needs to support alpha pixels.
So what I've done is written a panel class that when paint is called it moves down through it's child panels and paints their buffered images to the temp image. Then I take that and paint it over the parent- continueing until the image is flattened to a temporary image.
This works fine- especially on a desktop. But to accomplish this I had to write the putImageData code from scratch which loops through the array of pixels and paints them taking the alpha in account. Like so-
var offset = (canvasW*4*y)+x*4;
for(var r = 0; r < newHeight; r++)
{
var lineOffset = (size.width*4 - columns)*r + offset;
for(var c = 0; c < columns; c+=4)
{
var start = (r*columns)+c;
var destStart = start+lineOffset;
var red = imageData[start];
var green = imageData[start+1];
var blue = imageData[start+2];
var alpha = imageData[start+3];
var destRed = canvasData[destStart];
var destGreen = canvasData[destStart+1];
var destBlue = canvasData[destStart+2];
var destAlpha = canvasData[destStart+3];
var opacity = alpha/255;
var destOpacity = destAlpha/255;
var invOpacity = 1-opacity;
var newRed = Math.abs(red - ((red-destRed)*invOpacity));
var newGreen = Math.abs(green - ((green-destGreen)*invOpacity));
var newBlue = Math.abs(blue - ((blue-destBlue)*invOpacity));
canvasData[start+lineOffset] = newRed;
canvasData[start+lineOffset+1] = newGreen;
canvasData[start+lineOffset+2] = newBlue;
canvasData[start+lineOffset+3] = 255;
}
}
This takes about 50 miliseconds per layer. Not very good for a desktop. Takes a whopping 1200 miliseconds for the ipad! So I tested it with the original putImageData (which doesn't support alpha) and it was still not very impressive but it's the best I got I'm thinking.
So here is my problem. I know there is an overal opacity for drawing with canvases but it needs to be able to draw some pixels completely opaque and some completely transparent. Is there an putImageData that includes opacity?
If not any recommendations on how I can accomplish this?
As #Jeffrey Sweeney mentioned, try stacking canvases on top of each other. For one of my Javascript library, CInk (search there for z-index), I did the same thing.
I had one container div, which I stuffed with many canvas DOMs to mimic the layers. All canvas DOMs are absolutely positioned and their z-index define the order of the layers. In your case you will have to apply style at specific layers to set its opacity.