In a canvas I created a 2d context. In that context... with a function... I'm able to create some 'circle objects'. Now, what I want, is to get the ImageData of a single circle object instead of the image data of the whole context.
In the code below, you can see my wish commented out.
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
var circle = function (X,Y) {
var that = this;
that.X = X;
that.Y = Y;
that.clicked = function(e) {
//
//
//!!!!!!!!!!!!!!
// Code below works fine, on context level
imgData = ctx.getImageData(e.pageX, e.pageY, 1, 1);
//
// Code below is at the level of the circle, that's what I want, but isn't working
imgData = that.getImageData(e.pageX, e.pageY, 1, 1);
//!!!!!!!!!!!!!!
//
//
alert(imgData.data[3]);
}
that.draw = function () {
ctx.save();
ctx.translate(that.X, that.Y);
ctx.fillStyle = '#33cc33';
ctx.beginPath();
ctx.arc(0, 0, 50, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
var circles = new Array();
circles.push(new circle(50,50));
document.addEventListener('click',function() {
circles.forEach(function(circ,index){
circ.clicked();
});
})
So, how do I get the image data on specific objects?
edit:
I understand that I need to draw the circle first, I do that later in my code, but what if I've got a background rect in the context, when I click next to the circle, it will get the imageData of the background rect, when I want to return the 0 value of the alpha rgba.
To this you need to log all your drawings as a "shadow canvas". The most common way is to create shape objects and store them in for example an array:
Draw the shape on canvas
Log its type, position, dimension, colors and orientation and store as an object and push that object to the array
When you need to get an isolated shape or object as an image:
Get mouse position (if you want to click on the object to select it)
Iterate the array of objects to see which object is "hit"
Create a temporary canvas of the dimension of that shape
Draw in the shape into the temporary canvas
Extract the data as an image (ctx.getImageData(x, y, w, h) or canvas.toDataURL())
When you need to resize your canvas you simply iterate all the objects and redraw them. You can even serialize your data for storage using this method.
An example of an object can be:
function Rectangle(x, y, w, h, fill, stroke) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.fill = fill;
this.stroke = stroke;
}
You can extend this object to render it self to canvas as well as giving you a bitmap of itself isolated from the other shapes. Add this to the above code:
Rectangle.prototype.render = function(ctx) {
if (this.fill) { /// fill only if fill is defined
ctx.fillStyle = this.fill;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
if (this.stroke) { /// stroke only if stroke is defined
ctx.strokeStyle = this.stroke;
ctx.strokeRect(this.x, this.y, this.width, this.height);
}
}
Rectangle.prototype.toBitmap = function() {
var tcanvas = document.createElement('canvas'), /// create temp canvas
tctx = tcanvas.getContext('2d'); /// temp context
tcanvas.width = this.width; /// set width = shape width
tcanvas.height = this.height;
tctx.translate(-this.x, -this.y); /// make sure shape is drawn at origin
this.render(tcxt); /// render itself to temp context
return tcanvas.toDataURL(); /// return image (or use getImageData)
}
You simply draw your shapes, create the object based on the positions etc:
var rect = new Rectangle(x, y, w, h, fillColor, strokeColor);
myShapeArray.push(rect);
When you need to render the shapes:
for(var i = 0, shape; shape = myShapeArray[i++];)
shape.render(ctx);
And when you need to get its bitmap (you retrieved its index in advance with the mouse click):
var image = myShapeArray[index].toBitmap();
And of course: you can make similar objects for circles, lines etc.
Hope this helps!
Remember that Canvas is a bitmap graphics tool. Anything you draw into a single context becomes part and parcel of the same object. You can't get separate image data for each "object" you used to draw on that canvas... it's painted ... flattened ... into those pixel positions for that bitmap as soon as you hit draw().
The only way you could do something like what you are looking for would be to create separate canvas contexts that you overlay on top of each other. This would be better handled by utilizing a library such as KineticJS (http://www.html5canvastutorials.com/kineticjs/html5-canvas-events-tutorials-introduction-with-kineticjs/). The only other option would be to use an object oriented drawing tool such as SVG, (through Raphael.js, for example: http://raphaeljs.com) which does preserve separate objects in the the graphics space.
For reference about getImageData, see http://www.html5canvastutorials.com/advanced/html5-canvas-get-image-data-tutorial/
You can use trigonometry instead of trying to locate your colors with getImageData.
For example, if you have a circle defined like this:
var centerX=150;
var centerY=150;
var radius=20;
var circleColor="red";
Then you can test if any x,y is inside that circle like this:
// returns true if x,y is inside the red circle
isXYinCircle(140,140,centerX,centerY,radius);
function isXYinCircle(x,y,cx,cy,r){
var dx=x-cx;
var dy=y-cy;
return(dx*dx+dy*dy<=r*r);
}
If the x,y is inside that red circle then you know the color at x,y is "red"
If you have multiple overlapping circles you can test each circle in increasing z-index order. The last circle that reports x,y inside will be the color at x,y.
It is because that is not a CanvasGraphicsContext. Try:
that.draw();
imgData = ctx.getImageData(e.pageX, e.pageY, 1, 1);
At first, I create my 2 canvas elements. 1 to display, 1 to calculate the pixeldata.
var c = document.getElementById('canvas');
var c2 = document.getElementById('canvas2');
var ctx = c.getContext('2d');
var ctx2 = c2.getContext('2d');
var width = window.innerWidth,
height = window.innerHeight;
c.width = ctx.width = c2.width = ctx2.width = width;
c.height = ctx.height = c2.height = ctx2.height = height;
Than I make my function to create an image
function Afbeelding(src, X, Y, W, H) {
var that = this;
that.X = X;
that.Y = Y;
that.W = W;
that.H = H;
that.onClick = function () { };
that.image = new Image(that.W, that.H);
that.image.src = src;
that.draw = function (context) {
context = (typeof context != 'undefined') ? context : ctx;
context.save();
context.translate(that.X, that.Y);
context.drawImage(that.image, 0, 0, that.W, that.H);
context.restore();
}
When a document.click event is fired, the next function (inside the Afbeelding function) will be called:
that.clicked = function (e) {
if ((e.pageX > that.X - (that.W / 2) && e.pageX < that.X + (that.W / 2)) && (e.pageY > that.Y - (that.H / 2) && e.pageY < that.Y + (that.H / 2))) {
if (that.isNotTransparent(e)) {
that.onClick();
}
}
}
This function (also inside the Afbeelding function) is used to check the pixel for transparancy.
that.isNotTransparent = function (e) {
var result = false;
ctx2.clearRect(0, 0, width, height);
that.draw(ctx2);
var imgData = ctx2.getImageData(e.pageX, e.pageY, 1, 1);
ctx2.clearRect(0, 0, width, height);
if (imgData.data[3] > 0) {
result = true;
}
return result;
}
}
And all below is to lauch the things up above.
var items = new Array();
var afb = new Afbeelding();
afb.draw();
afb.onClick = function () {
alert('clicked');
}
items.push(afb);
document.addEventListener('mousedown', function (e) {
items.forEach(function (item, index) {
item.clicked(e);
});
});
Related
I am creating a game using the HTML5 Canvas element, and as one of the visual effects I would like to create a glow (like a light) effect. Previously for glow effects I found solutions involving creating shadows of shapes, but these require a solid shape or object to cast the shadow. What I am looking for is a way to create something like an ambient light glow with a source location but no object at the position.
Something I have thought of was to define a centerpoint x and y and create hundreds of concentric circles, each 1px larger than the last and each with a very low opacity, so that together they create a solid center and a transparent edge. However, this is very computationally heavy and does not seem elegant at all, as the resulting glow looks awkward.
While this is all that I am asking of and I would be more than happy to stop here, bonus points if your solution is A) computationally light, B) modifiable to create a focused direction of light, or even better, C) if there was a way to create an "inverted" light system in which the entire screen is darkened by a mask and the shade is lifted where there is light.
I have done several searches, but none have turned up any particularly illuminating results.
So I'm not quite sure what you want, but I hope the following snippet will help.
Instead of creating a lot of concentric circles, create one radialGradient.
Then you can combine this radial gradient with some blending, and even filters to modify the effect as you wish.
var img = new Image();
img.onload = init;
img.src = "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg";
var ctx = c.getContext('2d');
var gradCtx = c.cloneNode().getContext('2d');
var w, h;
var ratio;
function init() {
w = c.width = gradCtx.canvas.width = img.width;
h = c.height = gradCtx.canvas.height = img.height;
draw(w / 2, h / 2)
updateGradient();
c.onmousemove = throttle(handleMouseMove);
}
function updateGradient() {
var grad = gradCtx.createRadialGradient(w / 2, h / 2, w / 8, w / 2, h / 2, 0);
grad.addColorStop(0, 'transparent');
grad.addColorStop(1, 'white');
gradCtx.fillStyle = grad;
gradCtx.filter = "blur(5px)";
gradCtx.fillRect(0, 0, w, h);
}
function handleMouseMove(evt) {
var rect = c.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
draw(x, y);
}
function draw(x, y) {
ctx.clearRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(gradCtx.canvas, x - w / 2, y - h / 2);
ctx.globalCompositeOperation = 'lighten';
ctx.fillRect(0, 0, w, h);
}
function throttle(callback) {
var active = false; // a simple flag
var evt; // to keep track of the last event
var handler = function() { // fired only when screen has refreshed
active = false; // release our flag
callback(evt);
}
return function handleEvent(e) { // the actual event handler
evt = e; // save our event at each call
if (!active) { // only if we weren't already doing it
active = true; // raise the flag
requestAnimationFrame(handler); // wait for next screen refresh
};
}
}
<canvas id="c"></canvas>
I'm developing web app using canvas and I made three. canvas, canvas_panorama and canvas_image.
First one is something like main canvas, conteiner for the others. canvas_panorama is a background for canvas_image.
After canvas is right clicked, I'm computing angle to rotate canvas_image:
function getAngle( e, pw /*canvas*/ ){
var offset = pw.offset();
var center_x = (offset.left) + ($(pw).width() / 2);
var center_y = (offset.top) + ($(pw).height() / 2);
var mouse_x = e.pageX;
var mouse_y = e.pageY;
var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y);
angle = radians;
}
After I have an angle I'm trying to rotate canvas_image like this:
function redraw(){
var p1 = ctx.transformedPoint(0,0);
var p2 = ctx.transformedPoint(canvas.width,canvas.height);
ctx.clearRect( p1.x, p1.y, p2.x-p1.x, p2.y-p1.y );
canvas_image_ctx.drawImage(image_img, 0, 0, 150, 150);
canvas_panorama_ctx.drawImage(panorama_img, 0, 0, 600, 300);
canvas_panorama_ctx.drawImage(canvas_image, 20, 20);
// rotate panorama_img around its center
// x = x + 0.5 * width
// y = y + 0.5 * height
canvas_panorama_ctx.translate(95, 95);
canvas_panorama_ctx.rotate(angle);
// translate to back
canvas_panorama_ctx.translate(-95, -95);
ctx.drawImage(canvas_panorama, 0, 0);
}
But this rotates both canvas_image and canvas_panorama. It should only rotate canvas_image
JSFiddle to show you my problem
I think you are confusing yourself with this idea of multiple canvases.
Once in the drawImage() method, every of your canvases are just images, and could be just one or even just plain shapes.
Transformation methods do apply to the canvas' context's matrix, and will have effect only if you do some drawing operations when they are set.
Note : To reset your context matrix, you can either use save(); and restore() methods which will also save all other properties of your context, so if you only need to reset the transform, then it's preferred to simply reset the transformation matrix to its default : ctx.setTransform(1,0,0,1,0,0).
Here is a simplified example to make things clearer :
var ctx = canvas.getContext('2d');
// a single shape, with the border of the context matrix
var drawRect = function(){
ctx.beginPath();
ctx.rect(10, 10, 50, 20);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.stroke();
};
// set the color of our shapes
var gradient = ctx.createLinearGradient(0,0,70,0);
gradient.addColorStop(0,"green");
gradient.addColorStop(1,"yellow");
ctx.fillStyle = gradient;
// here comes the actual drawings
//we don't have modified the transform matrix yet
ctx.strokeStyle = "green";
drawRect();
// here we translate of 100px then we do rotate the context of 45deg
ctx.translate(100, 0)
ctx.rotate(Math.PI/4)
ctx.strokeStyle = "red";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
// here we move of 150px to the right and 25px to the bottom
ctx.translate(150, 25)
ctx.strokeStyle = "blue";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
<canvas id="canvas" width="500" height="200"></canvas>
In your code, you are setting the transformations on the canvas that does represent your image, and you do draw every of your canvases at each call.
What you want instead, is to set the transformation on the main canvas only, and draw the non-transformed image :
var main_ctx = canvas.getContext('2d');
var img_canvas = canvas.cloneNode();
var bg_canvas = canvas.cloneNode();
var angle = 0;
// draw on the main canvas, and only on the main canvas
var drawToMain = function(){
// first clear the canvas
main_ctx.clearRect(0,0,canvas.width, canvas.height);
// draw the background image
main_ctx.drawImage(bg_canvas, 0,0);
// do the transforms
main_ctx.translate(img_canvas.width/2, img_canvas.height/2);
main_ctx.rotate(angle);
main_ctx.translate(-img_canvas.width/2, -img_canvas.height/2);
// draw the img with the transforms applied
main_ctx.drawImage(img_canvas, 0,0);
// reset the transforms
main_ctx.setTransform(1,0,0,1,0,0);
};
// I changed the event to a simple onclick
canvas.onclick = function(e){
e.preventDefault();
angle+=Math.PI/8;
drawToMain();
}
// a dirty image loader
var init = function(){
var img = (this.src.indexOf('lena')>0);
var this_canvas = img ? img_canvas : bg_canvas;
this_canvas.width = this.width;
this_canvas.height = this.height;
this_canvas.getContext('2d').drawImage(this, 0,0);
if(!--toLoad){
drawToMain();
}
};
var toLoad = 2;
var img = new Image();
img.onload = init;
img.src = "http://pgmagick.readthedocs.org/en/latest/_images/lena_scale.jpg";
var bg = new Image();
bg.onload = init;
bg.src = 'http://www.fnordware.com/superpng/pnggradHDrgba.png';
<canvas id="canvas" width="500" height="300"></canvas>
I want to put an image instead of red rectangles in my function that draws enemies.
function drawEnemy(x,y){
ctx.fillStyle= "red";
ctx.fillRect(x,y,50,50);
}
This is the interval/loop im trying to run it in:
var timer = setInterval(function(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle= "yellow";
ctx.fillRect(xPos,yPos,10,150);
drawPikachu(x,y);
drawEnemy(enemyX, enemyY);
yPos -= moveY;
enemyY -= enemyMoveY;
if(enemyY > drawSurface.height ||
collisionCheck(enemyX,enemyY,enemySize,xPos,yPos,30)) {
enemyY = -50;
enemyX = Math.ceil(Math.random()*(drawSurface.width-50));
}
},50)
;
A simple Google, found a seemingly suitable answer:
window.onload = function() {
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var img=document.getElementById("scream");
ctx.drawImage(img,10,10);
};
Source: http://www.w3schools.com/tags/canvas_drawimage.asp
You just have to get a ref to an image, and draw it using the canvas context.
you need a context reference to draw it:
// prepare your enemy sprite once
var enemyImg = document.getElementById('enemy');
function drawEnemy(x, y, ctx) {
// desired dimensions of the enemy sprite
var width = 100
var height = 100
// draw enemy sprite
ctx.drawImage(enemyImg, x, y, width, height);
}
I have this little JavaScript canvas snippet:
(function() {
// Creates a new canvas element and appends it as a child
// to the parent element, and returns the reference to
// the newly created canvas element
function createCanvas(parent, width, height) {
var canvas = {};
canvas.node = document.createElement('canvas');
canvas.context = canvas.node.getContext('2d');
canvas.node.width = width || 100;
canvas.node.height = height || 100;
parent.appendChild(canvas.node);
return canvas;
}
function init(container, width, height, fillColor) {
var canvas = createCanvas(container, width, height);
var ctx = canvas.context;
// define a custom fillCircle method
ctx.fillCircle = function(x, y, radius, fillColor) {
this.fillStyle = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI * 2, false);
this.fill();
};
ctx.clearTo = function(fillColor) {
ctx.fillStyle = fillColor;
ctx.fillRect(0, 0, width, height);
};
ctx.clearTo(fillColor || "#ddd");
// bind mouse events
canvas.node.onmousemove = function(e) {
if (!canvas.isDrawing) {
return;
}
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
var radius = 10; // or whatever
var fillColor = '#ff0000';
ctx.fillCircle(x, y, radius, fillColor);
};
canvas.node.onmousedown = function(e) {
canvas.isDrawing = true;
};
canvas.node.onmouseup = function(e) {
canvas.isDrawing = false;
};
}
var container = document.getElementById('canvas');
init(container, 200, 200, '#ddd');
})();
Now it takes the div with the id of canvas, and creates a little HTML5 canvas inside it, where the user can basically draw with their mouse. NOW thats nice... but what if I had to save whats inside the canvas with canvas.toDataURL()? As far as I know, I would have to take the canvas, and do the canvas.toDataURL() to save it to an image. But- that needs an ID right? SOOOO when I created the canvas in the createCanvas function, how would I make an id accompanying it, so I could do something like document.getElementById("idofcanvas").toDataURL() ?
Thank you in advance for your help! :)
But- that needs an ID right?
It doesn't need an ID, but an ID won't hurt (so long as it's actually unique ;-)). Any method to select a DOM element will work here. If there's only one canvas on the page, this would suffice, for example:
var canvas = document.getElementsByTagName('canvas')[0];
var imageData = canvas.toDataURL();
When I created the canvas in the createCanvas function, how would I make an id accompanying it?
You'd just set the ID property of the canvas element before returning it. Remember, you need to ensure that this value is unique!
function createCanvas(parent, width, height) {
var canvas = {};
canvas.node = document.createElement('canvas');
canvas.node.id = /* some unique value here */;
canvas.context = canvas.node.getContext('2d');
canvas.node.width = width || 100;
canvas.node.height = height || 100;
parent.appendChild(canvas.node);
return canvas;
}
How do you blend two arrays of pixel data to create one image? with the option of using different blending modes?
Pixastic is a special framework for advanced use of canvas, here are blending examples: http://www.pixastic.com/lib/docs/actions/blend/
If you would like do this alone, you can extract pixel data from 2 images, blend it with a mathematical equation, and put into a canvas. Here is information how to get and put pixel data from/to canvas:
http://ajaxian.com/archives/canvas-image-data-optimization-tip
Update:
Simple example with alpha blending of 2 images in proportion 50-50.
(Images borrowed from http://www.pixastic.com/sample/Butterfly.jpg and http://www.pixastic.com/sample/Flower.jpg )
<img src="Butterfly.jpg" id="img1">
<img src="Flower.jpg" id="img2">
<p>Blended image<br><canvas id="canvas"></canvas></p>
<script>
window.onload = function () {
var img1 = document.getElementById('img1');
var img2 = document.getElementById('img2');
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = img1.width;
var height = img1.height;
canvas.width = width;
canvas.height = height;
var pixels = 4 * width * height;
context.drawImage(img1, 0, 0);
var image1 = context.getImageData(0, 0, width, height);
var imageData1 = image1.data;
context.drawImage(img2, 0, 0);
var image2 = context.getImageData(0, 0, width, height);
var imageData2 = image2.data;
while (pixels--) {
imageData1[pixels] = imageData1[pixels] * 0.5 + imageData2[pixels] * 0.5;
}
image1.data = imageData1;
context.putImageData(image1, 0, 0);
};
</script>
I have created a separate, lightweight, open-source library for perform Photoshop-style blend modes from one HTML Canvas context to another: context-blender. Here's the sample usage:
// Might be an 'offscreen' canvas
var over = someCanvas.getContext('2d');
var under = anotherCanvas.getContext('2d');
over.blendOnto( under, 'screen', {destX:30,destY:15} );
See the README for more information.
I am tasked with recreating this java applet using JavaScript (must be tablet friendly, and work in all modern browsers > IE8).
I am creating images using: var image1 = new Image(); and then setting source: img.src = "some path";
So, from pepkin88 I see that the following function will blend two images by combining their pixel array data, overriding previous data from the first image with the new blended data, and finally putting the new data on the canvas resulting in a blended image:
window.onload = function () {
var img1 = document.getElementById('img1');
var img2 = document.getElementById('img2');
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = img1.width;
var height = img1.height;
canvas.width = width;
canvas.height = height;
var pixels = 4 * width * height;
context.drawImage(img1, 0, 0);
var image1 = context.getImageData(0, 0, width, height);
var imageData1 = image1.data;
context.drawImage(img2, 0, 0);
var image2 = context.getImageData(0, 0, width, height);
var imageData2 = image2.data;
while (pixels--) {
imageData1[pixels] = imageData1[pixels] * 0.5 + imageData2[pixels] * 0.5;
}
image1.data = imageData1;
context.putImageData(image1, 0, 0); };
HOWEVER, if you viewed the java applet that I'm responsible for recreating, you see that blending happens in real-time continuously as you drag the image around with the pointer the images are constantly blending based on their overlapped regions..
SO, I'm looking to modify the code to account for this, and I continually have the x, y, positions of images drawn (based on top left corner), and the w, h of all images stays static:
the following snippets don't include everything I'm doing, just what I sense is important for you to know
//Rectangle Class from Java converted to JS
function Rectangle(x, y, width, height, src) {
this.x = x;
this.y = y;
this.w = width;
this.h = height;
this.img = new Image();
this.img.src = src;
}
//Stores instance in rect array
rect[0] = new Rectangle(1, (height - 111)/2, 150, 105, "images/mMain.png");
//Draw method that's called
Rectangle.prototype.draw = function(ctx) {
//this.checkBound();
ctx.drawImage(this.img, this.x, this.y, this.w, this.h);
prepareMix(this.img, this.x, this.y, this.w, this.h);
}
So, I'm working on a prepareMix function that receives image info and uses it to get and store image data:
function prepareMix(src, x, y, w, h) {
pixels = 4 * w * h;
var image = mtx.getImageData(x, y, w, h);
var imgData = image.data;
}
Made a list of what to do:
Sense the overlapping
Get and Store the overlapping image data
Mix the overlapping region data arrays
Replace the overlapping image data with the blended data
Put the new data on the canvas
1. Sense the Overlapping:
Plan: Store image positions and compare positions data to know whether or not overlapping is occurring.
IF overlapping is TRUE, which two images is it true for? Distinguish these images that're overlapping from other images so that methods can be called on them.
js, css, html, and images in zip here BOX