Draw an image on top of another image with an animated element - javascript

Initially what I am trying to do is place a blurry image on top of the same image that is clear. Then having another element(div box) that animates across the blurry image showing the clear image. Thus giving the effect like it is a pair of glasses.. Now I KNOW this can be achieved through html5 canvas, however I am not very knowledgable with this api and how to achieve it. There are two things that I am struggling with doing.
The first thing would be to change out the grey fillColor that gets erased so you can see the image behind it, with the blurry image. Now I have attempted to perform the drawImage() function. But cannot seem to get it to work with the current code that I have. Any suggestions or examples would be greatly appreciated.
The second thing would be to have the animating div do the 'erasing' instead of the user performing a mouse down functionality.
Third and last thing (more worried about just getting the first two figured out), would be for the image to go back to the reblur image after the div has moved.
I am open to ideas of better approaches as well. I greatly appreciate any time spent assisting myself.
animateLense();
function animateLense() {
$("#lense").animate({
left: 200,
top: 150
}, 2000 );
}
(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.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
};
canvas.node.onmousedown = function(e) {
canvas.isDrawing = true;
};
canvas.node.onmouseup = function(e) {
canvas.isDrawing = false;
};
// bind mouse end
}
var container = document.getElementById('canvas');
init(container, 300, 250, '#ddd');
})();
.canvas {
position: absolute;
width: 300px;
height: 250px;
left: 0px;
top: 0px;
background: url("http://www.atlantisbahamas.com/media/Things%20To%20Do/Water%20Park/Beaches/Gallery/waterpark_covebeach.jpg");
background-size: 300px, 250px
}
.lense {
position: absolute;
height: 50px;
width: 50px;
border: 2px black solid;
top: 0;
left: 0;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<div class="canvas" id="canvas">
<div class="lense" id="lense"></div>
</div>
</body>
</html>

Just draw two images in the same location.
Draw the second one on top of the first one with:
ctx.globalAlpha = 0.5; // Set to animated value
Animate globalAlpha.
Note: You don't draw images inside <canvas> using HTML elements. You have to load them up via JavaScript and use the drawImage method.

Related

Keep canvas object in one place against changing size of the entire canvas

l was wondering about the logic required in-order to position a canvas object, in this instance it is a image, in the bottom right corner of the canvas persistently. l essentially need help with the canvas object staying in one place without it moving up,down,left,right due to the resizable canvas.
l also have tested using event listeners to constantly update the position of the canvas object depending on the window size of the canvas but l believe it would end up becoming an inefficient solution.
To see it working, full screen/expand the code snippet and change your window size by double clicking your browser's tab or simply resize it, then you should be able to see it in action.
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var the_button = document.getElementById("the_button");
var the_background = document.getElementById("the_background");
var button_imageX = 115;
var button_imageY = 85;
window.onload = function() {
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
ctx.drawImage(the_button, button_imageX, button_imageY, 130, 75);
}
initialize();
function initialize() {
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
}
function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
ctx.drawImage(the_button, button_imageX, button_imageY, 130, 75);
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
redraw();
}
html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
display: block;
}
<html>
<canvas id="c"></canvas>
<img style="display: none;" id="the_button" src="https://i.imgur.com/wO7Wc2w.png" />
<img style="display: none;" id="the_background" src="https://img.freepik.com/free-photo/hand-painted-watercolor-background-with-sky-clouds-shape_24972-1095.jpg?size=626&ext=jpg" />
</html>
This is all about rectangle geometry.
Basically the culprit is this line that is present at 2 different places.
ctx.drawImage(the_button, button_imageX, button_imageY, 130, 75);
When you want the button to be a certain fixed position when you resize, you should provide fixed values as the 2nd and 3rd params for the drawImage function.
Change it to this,
ctx.drawImage(the_button, button_imageX, canvas.height - button_imageY, 130, 75);
The 2nd param button_imageX is already fixed.
But the 3rd param is the value of y from the top of the canvas where your button should be. Not from the bottom. You must be confused with cartesian coordinate system.
So if you want your button to be always a fixed position from bottom. you should canvas.height.
So canvas.height - button_imageY means that the top left point of your button will always be button_imageY pixels above the bottom of the canvas.
You might also want to consider the height of the button, so the new 3rd parameter will become canvas.height - button_imageY - button_height
Check the snippet for full code.
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var the_button = document.getElementById("the_button");
var the_background = document.getElementById("the_background");
var button_imageX = 25;
var button_imageY = 25;
window.onload = function() {
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
drawButton();
}
initialize();
function initialize() {
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
}
function drawButton() {
ctx.drawImage(the_button, button_imageX, canvas.height - button_imageY - 75, 130, 75);
}
function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
drawButton();
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
redraw();
}
html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
display: block;
}
<html>
<canvas id="c"></canvas>
<img style="display: none;" id="the_button" src="https://i.imgur.com/wO7Wc2w.png" />
<img style="display: none;" id="the_background" src="https://img.freepik.com/free-photo/hand-painted-watercolor-background-with-sky-clouds-shape_24972-1095.jpg?size=626&ext=jpg" />
</html>

Canvas: create semi-transparent overlay over entire canvas except one window

I have a rectangular canvas with an image painted on it. As the user moves the cursor over the canvas, I'd like to make the canvas semitransparent except for a small rectangle around the cursor, which I'd like to retain the underlying image content in full opacity.
So my question in brief is how to "mute" the canvas except for a small rectangle.
My first thought was to create a semitransparent overlay over the entire canvas, then just paint the rectangular region that should be full opacity again, but this makes the background disappear while I want to retain it at 0.2 opacity:
var elem = document.querySelector('canvas');
var ctx = elem.getContext('2d');
elem.width = 400;
elem.height = 300;
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, elem.width, elem.height);
elem.addEventListener('mousemove', function(e) {
ctx.globalAlpha = 0.2;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, elem.width, elem.height);
var x = e.clientX;
var y = e.clientY;
var d = 30;
ctx.fillStyle = '#ff0000';
ctx.fillRect(x-d, y-d, d*2, d*2);
})
<canvas/>
Does anyone know the most performant way to mute the background to 0.2 opacity while retaining the rectangle around the cursor at full opacity? Any pointers would be super helpful!
Here's the two canvas method:
var elemA = document.querySelector('#a');
var elemB = document.querySelector('#b');
var ctx = elemA.getContext('2d');
var bctx = elemB.getContext('2d');
elemA.width = elemB.width = 400;
elemA.height = elemB.height = 300;
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, elemA.width, elemA.height);
elemB.addEventListener('mousemove', function(e) {
bctx.clearRect(0, 0, elemB.width, elemB.height);
var x = e.clientX;
var y = e.clientY;
var x0 = x-10;
var x1 = x+10;
var y0 = y-10;
var y1 = y+10;
// draw boxes; origin is top left
bctx.globalAlpha = 0.8;
bctx.fillStyle = '#ffffff';
bctx.fillRect(0, 0, elemA.width, y0); // top
bctx.fillRect(0, y0, x0, elemB.height+20); // left
bctx.fillRect(x0, y1, elemB.width+20, elemB.height); // bottom
bctx.fillRect(x1, y0, elemA.width, y1-y0); // right
})
* {
margin: 0;
}
#c {
position: relative;
}
#a, #b {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
#b {
z-index: 1;
opacity: 0.5;
}
<div id='c'>
<canvas id='a'></canvas>
<canvas id='b'></canvas>
</div>

Get full ImageData after Panning the image inside Cavnas

I have a situation where I need to get the full ImageData inside the canvas even after panning the image.
Thanks in Advance
Example in the fiddle: https://jsfiddle.net/z46cvm9h/
<canvas id="canvas"></canvas>
<button onclick="showImage()">
Show panned Image
</button>
<img id="image" src='' />
var context = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 300;
var global = {
scale : 1,
offset : {
x : 0,
y : 0,
},
};
var pan = {
start : {
x : null,
y : null,
},
offset : {
x : 0,
y : 0,
},
};
function draw() {
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(pan.offset.x, pan.offset.y);
context.beginPath();
context.rect(50, 50, 100, 100);
context.fillStyle = 'skyblue';
context.fill();
context.beginPath();
context.arc(350, 250, 50, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
}
draw();
canvas.addEventListener("mousedown", startPan);
canvas.addEventListener("mouseleave", endPan);
canvas.addEventListener("mouseup", endPan);
function startPan(e) {
canvas.addEventListener("mousemove", trackMouse);
canvas.addEventListener("mousemove", draw);
pan.start.x = e.clientX;
pan.start.y = e.clientY;
}
function endPan(e) {
canvas.removeEventListener("mousemove", trackMouse);
pan.start.x = null;
pan.start.y = null;
global.offset.x = pan.offset.x;
global.offset.y = pan.offset.y;
}
function trackMouse(e) {
var offsetX = e.clientX - pan.start.x;
var offsetY = e.clientY - pan.start.y;
pan.offset.x = global.offset.x + offsetX;
pan.offset.y = global.offset.y + offsetY;
}
function showImage(){
document.getElementById('image').src = canvas.toDataURL();
}
PS: JSFiddle is for demonstration, I can't put the exact scenario into JSFiddle. In the application, I pan the image and then use the mouse to draw something on the canvas using the mousedown and mousemove events. On the mouseup event, I need to have the ImageData with the full image I drew, but when using getImageData, I am only getting the image appearing on the canvas. The panned image is cropped to the canvas size.
As #Blindman67 suggested, you can make a second canvas.
Create an object to store the furthest away points in your image.
const imagePadding = {
top: 0,
right: 0,
bottom: 0,
left: 0
}
top shall be the highest point of the drawing, right shall be the point furthest to the right, and so on. For demonstration purposes, the padding is set equal to how far you have panned in each direction. You will want to set it equal to the shapes/points of the drawing that are furthest in each direction (up, right, down, and left--those are the directions I am talking about).
function panImage(e) {
pan.x += e.movementX
pan.y += e.movementY
drawPreview()
imagePadding.top = Math.max(imagePadding.top, pan.y)
imagePadding.left = Math.max(imagePadding.left, pan.x)
imagePadding.bottom = Math.max(imagePadding.bottom, -pan.y)
imagePadding.right = Math.max(imagePadding.right, -pan.x)
}
To draw the full image, you can make a function like this:
function drawFinalImage() {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const pannedImage = document.getElementById('image')
canvas.width = previewCanvas.width + imagePadding.left + imagePadding.right
canvas.height = previewCanvas.height + imagePadding.top + imagePadding.bottom
ctx.translate(imagePadding.left, imagePadding.top)
drawShapes(ctx)
pannedImage.src = canvas.toDataURL()
}
See the JSFiddle for a demo as well as how you can implement the code. Let me know if there is anything I should explain better.
You can use JSFiddle Console to adjust the padding. Type imagePadding.top = 200, press enter, and click the canvas to update the final image.

Cannot draw text in javascript

I am trying to display simple text on the screen via update() and setInterval() functions, but it's not working. Note that I create a canvas on which I want the text to be displayed. Please , check the code below.
var canvas, canvasContext;
window.onload = function() {
canvas = document.getElementById('theCanvas');
canvasContext = canvas.getContext('2d');
var fps = 30;
setInterval(update, 1000 / fps);
}
function update() {
drawText();
}
function drawText() {
canvasContext.fillStyle = 'black';
canvasContext.font = 'bold 40px Monospace';
canvasContext.fillText('POC ......', 150, 150);
}
#canvasHolder {
position: absolute;
left: 0px;
top: 0px;
}
#theCanvas {
background-color: lightgreen;
width: 100pc;
height: 100pc;
}
<div id="canvasHolder">
<canvas id="theCanvas"></canvas>
</div>
The issue is that the canvas is being stretched to fill the element. Both the canvas itself and the canvas element have a size. If you don't specify the size of the canvas (you haven't), it defaults to 300x150. When the size of the element and the canvas don't match, the canvas content is scaled to fit into the element.. So the text is being drawn, it's just really big; scroll to the right and down and you'll find it.
My guess is that you meant for the canvas and the element to be the same size. If so, you need to set the size of the canvas. You can do that with width and height attributes on the canvas element, but those values will be in pixels, not picas as in your CSS. So we do it dynamically by getting the computed width and height of the element (because they'll come back to us in pixels), and then setting those values for the width and height attributes of the canvas:
// Set the canvas size to match the element size
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
Updated snippet:
var canvas, canvasContext;
window.onload = function() {
canvas = document.getElementById('theCanvas');
// Set the canvas size to match the element size
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
canvasContext = canvas.getContext('2d');
canvasContext.canvas.width = canvas.width;
canvasContext.canvas.height = canvas.height;
var fps = 30;
setInterval(update, 1000 / fps);
};
function update() {
drawText();
}
function drawText() {
canvasContext.fillStyle = 'black';
canvasContext.font = 'bold 40px Monospace';
canvasContext.fillText('POC ......', 150, 150);
}
#canvasHolder {
position: absolute;
left: 0px;
top: 0px;
}
#theCanvas {
background-color: lightgreen;
width: 100pc;
height: 100pc;
}
<div id="canvasHolder">
<canvas id="theCanvas"></canvas>
</div>
Note that right now, you're just redrawing it at the same location every time, but I assume moving it was your next task...
The text is already rendered but if you want to animate your text then you have to change your draw function because each time you are drawing your text in the same position -
function drawText() {
canvasContext.fillStyle = 'black';
canvasContext.font = 'bold 40px Monospace';
canvasContext.fillText('POC ......', 150, 150); //here you have given the fixed position.
}
and you have to clear your canvas before rendering your text in new position-
please refer -
How to clear the canvas for redrawing

Drawing semi-transparent lines on mouse movement in HTML5 canvas

I'm trying to let users specify an area by painting over it with a "paint" tool that draws semi-transparent lines on a canvas. Its purpose is specifying a "mask" for an image that will be drawn below on the canvas.
This is what I tried so far:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasPos = canvas.getBoundingClientRect();
var dragging = false;
drawImage();
$(canvas).mousedown(mouseDown);
$(canvas).mouseup(mouseUp);
$(canvas).mousemove(mouseMove);
function drawImage() {
var img = new Image();
img.src = 'http://img2.timeinc.net/health/img/web/2013/03/slides/cat-allergies-400x400.jpg';
img.onload = function () {
ctx.drawImage(img, 0, 0);
};
}
function mouseDown(e) {
var pos = getCursorPosition(e);
dragging = true;
ctx.strokeStyle = 'rgba(0, 100, 0, 0.25)';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = 15;
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
function mouseUp(e) {
dragging = false;
}
function mouseMove(e) {
var pos, i;
if (!dragging) {
return;
}
pos = getCursorPosition(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
}
function getCursorPosition(e) {
return {
x: e.clientX - canvasPos.left,
y: e.clientY - canvasPos.top
};
}
Link to a jsfiddle of the above code: http://jsfiddle.net/s34PL/2/
The issue with this example code is that subsequent pixels that are drawn are making the opacity becomes less and less visible. I think it's because the line is 15 pixels wide (but I want it that wide though).
How can I solve this issue?
Thanks!
The problem is that you are drawing the whole path again and again:
function mouseMove(e) {
...
ctx.stroke(); // Draws whole path which begins where mouseDown happened.
}
You have to draw only the new segment of the path (http://jsfiddle.net/jF9a6/). And then ... you have the problem with the 15px width of the line.
So how to solve this? We have to draw the line at once as you did, but avoid painting on top of existing lines. Here is the code: http://jsfiddle.net/yfDdC/
The biggest change is the paths array. It contains yeah, paths :-) A path is an array of points stored in mouseDown and mouseMove functions. New path is created in mouseDown function:
paths.push([pos]); // Add new path, the first point is current pos.
In the mouseMove you add current mouse position to the last path in paths array and refreshs the image.
paths[paths.length-1].push(pos); // Append point tu current path.
refresh();
The refresh() function clears the whole canvas, draws the cat again and draws every path.
function refresh() {
// Clear canvas and draw the cat.
ctx.clearRect(0, 0, ctx.width, ctx.height);
if (globImg)
ctx.drawImage(globImg, 0, 0);
for (var i=0; i<paths.length; ++i) {
var path = paths[i];
if (path.length<1)
continue; // Need at least two points to draw a line.
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
...
for (var j=1; j<path.length; ++j)
ctx.lineTo(path[j].x, path[j].y);
ctx.stroke();
}
}
Alternative approach is to draw the paths solid and make the whole canvas transparent. Of course you have to move the image out of the canvas and stack it underneath. You can find the code here: http://jsfiddle.net/fP297/
<div style="position: relative; border: 1px solid black; width: 400px; height: 400px;">
<img src='cat.jpg' style="position: absolute; z-order: 1;">
<canvas id="canvas" width="400" height="400" style="position: absolute; z-order: 2; opacity: 0.25;"></canvas>
</div>
By drawing the lines solid you don't have to worry about drawing an area multiple times, so you don't have to worry about erasing the image and re-drawing everything.
I agree with Strix.
Here you will find an example based on his answer.
//
let mouseDownStartPosition: number | null = null;
let mouseDownLastPaintedPosition: number | null = null;
const drawRect = (canvas: HTMLCanvasElement, pixelX0 : number, pixelX1: number) => {
const context: CanvasRenderingContext2D = canvas.getContext('2d')!;
context.globalAlpha = 0.3;
context.fillStyle = "#bada55";
context.fillRect(pixelX0, 0, pixelX1 - pixelX0, context.canvas.height);
context.globalAlpha = 1.0;
}
const onCanvasMouseDown = (e: { clientX: number; }) => {
const canvas: HTMLCanvasElement = canvasRef.current!;
let rect = canvas.getBoundingClientRect();
mouseDownStartPosition = e.clientX - rect.left;
mouseDownLastPaintedPosition = mouseDownStartPosition;
}
const onCanvasMouseMove = (e: { clientX: number; }) => {
if (mouseDownLastPaintedPosition == null) return;
const canvas: HTMLCanvasElement = canvasRef.current!;
let rect = canvas.getBoundingClientRect();
const mouseCurrentPosition = e.clientX - rect.left;
drawRect(canvas, Math.min(mouseDownLastPaintedPosition, mouseCurrentPosition), Math.max(mouseDownLastPaintedPosition, mouseCurrentPosition));
mouseDownLastPaintedPosition = mouseCurrentPosition;
}
const onCanvasMouseUp = () => {
mouseDownStartPosition = null;
mouseDownLastPaintedPosition = null;
}
<MyCanvas
ref={canvasRef}
onMouseDown={onCanvasMouseDown}
onMouseMove={onCanvasMouseMove}
onMouseUp={onCanvasMouseUp}
/>

Categories