How to scale an image for a canvas pattern? - javascript

I want to fill a Canvas with an Image and scale it to a certain width beforehand.
I am trying to achieve an effect where an image in the foreground of the canvas can be erased with the mouse to view an image in the background. This is why I need to use a pattern to fill my canvas instead of just using drawImage(). Everything works apart from the scaling of the foreground image. Here is my code for generating the pattern:
var blueprint_background = new Image();
blueprint_background.src = "myfunurl";
blueprint_background.width = window.innerWidth;
blueprint_background.onload = function(){
var pattern = context.createPattern(this, "no-repeat");
context.fillStyle = pattern;
context.fillRect(0, 0, window.innerWidth, 768);
context.fill();
};
This does exactly what it should do, except that the image keeps its original size.
As you see, I want the image to scale to window.innerWidth (which has the value 1920 when logging it).
If needed, I can provide the rest of the code, but since the error is most likely in this snippet, I decided not to post the rest.
EDIT: Here is my full code with the suggested changes. The front ground image now displays over the full width, however the erasing does not work anymore.
JavaScript (Note that I use jQuery instead of $):
jQuery(document).ready(function() {
var cwidth = window.innerWidth;
var cheight = 768;
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(canvas, fillColor) {
var ctx = canvas.context;
canvas.isDrawing = true;
jQuery('#canvas').children().css('position:absolute; top: ' + jQuery('#Top_bar').height() + 'px');
// 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();
};
// bind mouse events
canvas.onmousemove = function(e) {
if (!canvas.isDrawing) {
return;
}
var x = e.pageX - this.offsetLeft;
var y = e.pageY - jQuery('#Top_bar').outerHeight();
var radius = 30;
var fillColor = '#ff0000';
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
};
}
var container = document.getElementById('canvas');
jQuery('#canvas').css('position:absolute; top: ' + jQuery('#Top_bar').height() + 'px');
var canvas = createCanvas(container, cwidth, cheight);
init(canvas, '#ddd');
var fgimg = document.getElementById("fgimg");
fgimg.width = cwidth;
var context = canvas.node.getContext("2d");
let canvasP = document.getElementById("pattern");
canvasP.width = window.innerWidth;
canvasP.height = 768;
let ctxP = canvasP.getContext("2d");
ctxP.drawImage( fgimg, 0, 0,window.innerWidth,768 );
context.fillStyle = context.createPattern(canvasP,"no-repeat");
context.fillRect(0,0, canvas.width, canvas.height);
});
CSS:
#canvas {
background:url(http://ulmke-web.de/wp-content/uploads/2019/01/Header-6.jpg);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
width: 100%;
height: 768px;
}
HTML:
<div id="canvas">
<canvas id="pattern">
</div>
<div style="display:none">
<img id="fgimg" src=" http://ulmke-web.de/wp-content/uploads/2019/01/Header-5.jpg">
</div>

I would use two canvases. On the first one you draw your image and you use this canvas as an image to create the pattern. In order to scale the image you scale the size of the first canvas #pattern in my example.
For example you can do this for a 10/10 image:
canvasP.width = 10;
canvasP.height = 10;
ctxP.drawImage( redpoint, 2.5, 2.5 );
or you can do this for a 20/20 image:
canvasP.width = 20;
canvasP.height = 20;
ctxP.drawImage( redpoint, 5, 5,10,10 );
Furthermore, in my example I'm adding a little margin around the image.
let canvasP = document.getElementById("pattern");
if (canvasP && canvasP.getContext) {
let ctxP = canvasP.getContext("2d");
/*canvasP.width = 10;
canvasP.height = 10;
ctxP.drawImage( redpoint, 2.5, 2.5 ); */
canvasP.width = 20;
canvasP.height = 20;
ctxP.drawImage( redpoint, 5, 5,10,10 );
}
let canvas1 = document.getElementById("canvas");
if (canvas1 && canvas1.getContext) {
let ctx1 = canvas1.getContext("2d");
if (ctx1) {
ctx1.fillStyle = ctx1.createPattern(canvasP,"repeat");
ctx1.fillRect(0,0, canvas1.width, canvas1.height);
}
}
canvas{border:1px solid}
<img id="redpoint" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO 9TXL0Y4OHwAAAABJRU5ErkJggg==">
<canvas id="pattern"></canvas>
<canvas id="canvas"></canvas>
I hope it helps.

Related

Saturate image where mouse is hovering

I'm trying to have an image on my website become saturated at the same location the mouse is. When the mouse moves the saturation effect goes with it, and the area previously hovered over becomes grayscale again. I'm thinking this effect could be accomplished using saturate(), however I haven't had any success with it. Additionally, I would like the effect to be circular without hard edges similar to this.
Example of what it would look like (orange arrow indicating where the mouse is).
Any help or insight would be appreciated, thanks!
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content= "width=device-width, initial-scale=1.0" />
</head>
<script>
const size = 250;
var radius = 30;
var rad = Math.PI / 180;
var canvas = document.querySelector("canvas")
var ctx = canvas.getContext("2d");
canvas.width = size;
canvas.height = size;
var image = new Image();
image.onload = demo
image.src = "https://picsum.photos/250"
function draw_circle(x, y, radius) {
ctx.clearRect(0, 0, size, size);
ctx.drawImage(image, 0, 0); // image to change
ctx.globalCompositeOperation = "saturation";
ctx.beginPath();
ctx.fillStyle = "hsl(0,100%,50%)"; // saturation at 100%
ctx.arc(x, y, radius, 0, 360 * rad, false);
ctx.fill()
ctx.closePath();
ctx.globalCompositeOperation = "source-over"; // restore default comp
}
function demo() {
ctx.drawImage(image, 0, 0); // image to change
canvas.addEventListener('mousemove', function(ev) {
var cx = ev.offsetX
var cy = ev.offsetY
draw_circle(cx, cy, radius)
})
}
</script>
<canvas></canvas>
</html>
Using a canvas we can try. Here's a start inspired by How can I adjust the huse, saturation, and lightness of in image in HTML5 Canvas?.
const size = 250;
var radius = 30;
var rad = Math.PI / 180;
var canvas = document.querySelector("canvas")
var ctx = canvas.getContext("2d");
canvas.width = size;
canvas.height = size;
var image = new Image();
image.onload = demo
image.src = "https://picsum.photos/250"
function draw_circle(x, y, radius) {
ctx.clearRect(0, 0, size, size);
ctx.drawImage(image, 0, 0); // image to change
ctx.globalCompositeOperation = "saturation";
ctx.beginPath();
ctx.fillStyle = "hsl(0,100%,50%)"; // saturation at 100%
ctx.arc(x, y, radius, 0, 360 * rad, false);
ctx.fill()
ctx.closePath();
ctx.globalCompositeOperation = "source-over"; // restore default comp
}
function demo() {
ctx.drawImage(image, 0, 0); // image to change
canvas.addEventListener('mousemove', function(ev) {
var cx = ev.offsetX
var cy = ev.offsetY
draw_circle(cx, cy, radius)
})
}
<canvas></canvas>
This is a simple answer (change the logic of the program as you want):
<!DOCTYPE html>
<html>
<head>
<style>
div.relative {
position: relative;
width: 200px;
height: 150px;
}
.image {
width: 100%;
height: 100%;
}
</style>
<script>
const width = 50;
const height = 50;
function create() {
const element = document.createElement("div");
element.id = "filtered";
element.style.width = `${width}px`;
element.style.height = `${height}px`;
element.style.borderRadius = "50%";
element.style.position = "absolute";
element.style.backgroundColor = "red";
element.style.opacity = "0.2";
element.style.zIndex = "2";
return element;
}
function changePos(e) {
x = e.clientX;
y = e.clientY;
let element = document.getElementById("filtered");
if (!element) {
element = create();
document.getElementById("focusArea").appendChild(element);
}
element.style.left = `${x - width / 2}px`;
element.style.top = `${y - height / 2}px`;
}
function removeElement() {
if (document.getElementById("filtered")) {
document.getElementById("filtered").remove();
}
}
</script>
</head>
<body>
<div
id="focusArea"
onmouseleave="removeElement()"
onmousemove="changePos(event)"
class="relative"
>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/800px-Image_created_with_a_mobile_phone.png"
class="image"
/>
</div>
</body>
</html>

Rotate an image on top of another canvas image

Currently, I am making a game and in need of making the image rotate toward the cursor. I am using node but the image is in a js tag in the HTML file that uses ctx to draw the image.
If I put a ctx.rotate(angle); pretty much anywhere it will rotate everything; player, map, etc. I need help so that only the player is rotated
this is a simplified version of my code:
<canvas id="ctx" width="200" height="200"></canvas>
<script>
//game
var ctx = document.getElementById("ctx").getContext("2d");
var WIDTH = 200;
var HEIGHT = 200;
var Img = {};
//player
Img.player = new Image();
Img.player.src = '/client/img/player.png';
var Player = function(/*node*/){
ctx.drawImage(Img.player, ...);
}
//map
Img.map = new Image();
Img.map.src = '/client/img/map.png';
//display everything
setInterval(function(){
ctx.clearRect(0,0,200,200);
drawMap();
for(var i in Player.list)
Player.list[i].draw();
},1000/60);
//functions
//move map so that player is always in the middle
var drawMap= function(){
var x = WIDTH/2 - Player.list[/*node*/].x;
var y = HEIGHT/2 - Player.list[/*node*/].y;
ctx.drawImage(Img.map,x,y);
}
</script>
Here's an example of what you may be looking for
const ctx = document.getElementById("ctx").getContext("2d");
const WIDTH = 500,
HEIGHT = 500;
document.getElementById("ctx").height = HEIGHT;
document.getElementById("ctx").width = WIDTH;
var Player = {
x: 50,
y: 55,
angle: 0
}
document.addEventListener("mousemove", (event) => {
var x = event.clientX - Player.x,
y = event.clientY- Player.y,
angle = Math.atan2(y,x);
Player.angle = angle
})
function draw() {
window.requestAnimationFrame(draw);
ctx.clearRect(0, 0, WIDTH, HEIGHT);
ctx.save();
ctx.translate(Player.x, Player.y);
ctx.rotate(Player.angle);
ctx.translate(-Player.x, -Player.y);
ctx.fillRect(Player.x, Player.y, 20, 20);
ctx.restore();
ctx.fillRect(150, 50, 20, 20);
}
draw();
<canvas id="ctx"></canvas>
jsfiddle here
Hope this helps!

globalCompositeOperation on 2 canvases

I have 2 layered canvases, and one has a circle that i would like to see at all times. the circle follows the mouse at all times. the circle is on top, but when the other canvas gets something drawn on it (with similar color), the circle gets really hard to see. I have tried using globalCompositeOperation, but it does not work, the circle still disappears.
here is some code:
//pos is the mouse position {x:x,y:y}
ctx2.beginPath()
ctx2.globalCompositeOperation = "xor";
ctx1.globalCompositeOperation = "xor";
ctx2.shadowColor = brush.color
ctx2.shadowBlur = 1
ctx2.clearRect(0, 0, brush.prePos.x + brush.size*2, brush.prePos.y + brush.size*2)
ctx2.arc(pos.x, pos.y, brush.size / 4, 0, Math.PI*2)
ctx2.stroke()
ctx2.closePath()
Since you have 2 overlapping canvas elements you can use the CSS mix-blend-mode. I hope this is what you need.
const canvas = document.getElementById("canvas");
const canvas1 = document.getElementById("canvas1");
const ctx = canvas.getContext("2d");
const ctx1 = canvas1.getContext("2d");
let cw = canvas.width = canvas1.width = 300,
cx = cw / 2;
let ch = canvas.height = canvas1.height = 300,
cy = ch / 2;
let m;
ctx.fillStyle = "red"
ctx.beginPath();
ctx.fillRect(10,10,200,100);
canvas1.addEventListener("mousemove",(evt)=>{
m = oMousePos(canvas1, evt);
ctx1.clearRect(0,0,cw,ch);
ctx1.beginPath();
ctx1.arc(m.x,m.y,20,0,2*Math.PI);
ctx1.fillStyle = "red"
ctx1.fill();
})
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
canvas{border:1px solid; position:absolute; top:0;left:0;}
#canvas1{mix-blend-mode: difference;}
<canvas id="canvas"></canvas>
<canvas id="canvas1"></canvas>

Drawing multiple offscreen canvases into onscreen canvas

I am having an issue when I'm trying to render multiple offscreen canvases into onscreen canvas. I do get one offscreen canvas rendered but the problem is that there should be two other rendered before. In other words, only last canvas is rendered. The expected result would be three overlapping rectangles (or squares :) in red, green and blue. Here's the code:
function rectangle(color) {
var offScreenCanvas = document.createElement('canvas');
var offScreenCtx = offScreenCanvas.getContext('2d');
var width = offScreenCanvas.width = 150;
var height = offScreenCanvas.height = 150;
switch(color) {
case 1:
offScreenCtx.fillStyle='rgb(255,0,0)';
break;
case 2:
offScreenCtx.fillStyle='rgb(0,255,0)';
break;
case 3:
offScreenCtx.fillStyle='rgb(0,0,255)';
break;
}
offScreenCtx.fillRect(0,0,width,height);
return offScreenCanvas;
}
function draw(offScreenCanvas, x , y) {
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
ctx.drawImage(offScreenCanvas, x, y);
}
var images = [];
var color = 1;
for (var i=0; i<3; i++) {
var img = new rectangle(color);
images.push(img);
color++;
}
var x = 0;
var y = 0;
for (var i = 0; i < images.length; i++) {
draw(images[i], x, y);
x += 100;
y += 100;
}
I did some searching and it seems that I'm not the first with this issue, but I could not get this working properly.
Setting canvas height or width clears the canvas.
The problem with your code is that you are causing the onscreen canvas to be cleared when you set it size in the function draw
Setting the canvas size, even if that size is the same, will cause the canvas context to reset and clear the canvas. All the other canvases are rendered, but erased when you set the onscreen canvas size.
Your draw function
function draw(offScreenCanvas, x , y) {
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
// The cause of the problem ===================================
// Either one of the following lines will clear the canvas
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
//=============================================================
ctx.drawImage(offScreenCanvas, x, y);
}
To avoid this just set the canvas size once. If you need to resize the canvas and keep its content you first need to create a copy of the canvas, then resize it, then render the copy back to the original.
Demo shows 5 offscreen canvases being rendered onto one onscreen canvas.
const colours = ['#f00', '#ff0', '#0f0', '#0ff', '#00f'];
const ctx = can.getContext('2d');
can.width = innerWidth - 4; // sub 4 px for border
can.height = innerHeight - 4;
function createCanvas(color, i) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 150;
canvas.height = 150;
ctx.font = "24px arial";
ctx.fillStyle = color;
ctx.fillRect(0, i * 30, canvas.width, 30);
ctx.fillStyle = "black";
ctx.fillText("Canvas "+i,10,(i + 0.75) * 30);
return canvas;
}
colours.forEach((c, i) => {
ctx.drawImage(createCanvas(c, i), 0, 0);
})
canvas {
border: 2px solid black;
position : absolute;
top : 0px;
left : 0px;
}
<canvas id="can"></canvas>

Use Clip Function to with Gradient Effect with JavaScript on Canvas

I'm trying to use the clip() function in canvas to create this effect, as pictured: there is a background image, and when your mouse hover on it, part of the image is shown. I got it to work as a circle, but I want this gradient effect you see the picture. How do I achieve that?
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="./assets/stylesheet/normalize.css">
<link rel="stylesheet" type="text/css" href="./assets/stylesheet/style.css">
</head>
<body>
<canvas id="canvas" width="2000" height="1200"></canvas>
<script>
var can = document.getElementById('canvas');
var ctx = can.getContext('2d');
can.addEventListener('mousemove', function(e) {
var mouse = getMouse(e, can);
redraw(mouse);
}, false);
function redraw(mouse) {
console.log('a');
can.width = can.width;
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.rect(0,0,2000,1200);
ctx.arc(mouse.x, mouse.y, 200, 0, Math.PI*2, true)
ctx.clip();
ctx.fillRect(0,0,2000,1200);
}
var img = new Image();
img.onload = function() {
redraw({x: 0, y: 0})
}
img.src = 'http://placekitten.com/2000/1000';
function getMouse(e, canvas) {
var element = canvas,
offsetX = 0,
offsetY = 0,
mx, my;
// Compute the total offset. It's possible to cache this if you want
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
return {
x: mx,
y: my
};
}
</script>
USING a RADIAL gradient
There are many ways to do that but the simplest is a gradient with an alpha.
First you need to define the size of the circle you wish to show.
var cirRadius = 300;
Then the location (canvas coordinates) where this circle will be centered
var posX = 100;
var posY = 100;
Now define the rgb colour
var RGB = [0,0,0] ; // black
Then an array of alpha values to define what is transparent
var alphas = [0,0,0.2,0.5,1]; // zero is transparent;
Now all you do is render the background image
// assume ctx is context and image is loaded
ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height); // fill the canvas
Then create the gradient with it centered at the position you want and the second circle at the radius you want. The first 3 numbers define the center and radius of the start of the gradient, the last 3 define the center and radius of the end
var grad = ctx.createRadialGradient(posX,posY,0,posX,posY,cirRadius);
Now add the colour stops using the CSS color string rgba(255,255,255,1) where the last is the alpha value from 0 to 1.
var len = alphas.length-1;
alphas.forEach((a,i) => {
grad.addColorStop(i/len,`rgba(${RGB[0]},${RGB[1]},${RGB[2]},${a})`);
});
or for legacy browsers that do not support arrow functions or template strings
var i,len = alphas.length;
for(i = 0; i < len; i++){
grad.addColorStop(i / (len - 1), "rgba(" + RGB[0] + "," + RGB[1] + "," + RGB[2] + "," + alphas[i] + ")");
}
Then set the fill style to the gradient
ctx.fillStyle = grad;
then just fill a rectangle covering the image
ctx.fillRect(0,0,ctx.canvas.width,ctx.canvas.height);
And you are done.
By setting the position with via a mouse event and then doing the above steps 60times a second using window.requestAnimationFrame you can get the effect you are looking for in real time.
Here is an example
// create a full screen canvas
var canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 10;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
// var to hold context
var ctx;
// load an image
var image = new Image();
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
// add resize event
var resize = function(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
// add mouse event. Because it is full screen no need to bother with offsets
var mouse = function(event){
posX = event.clientX;
posY = event.clientY;
}
// incase the canvas size is changed
window.addEventListener("resize",resize);
// listen to the mouse move
canvas.addEventListener("mousemove",mouse)
// Call resize as that gets our context
resize();
// define the gradient
var cirRadius = 300;
var posX = 100; // this will be set by the mouse
var posY = 100;
var RGB = [0,0,0] ; // black any values from 0 to 255
var alphas = [0,0,0.2,0.5,0.9,0.95,1]; // zero is transparent one is not
// the update function
var update = function(){
if(ctx){ // make sure all is in order..
if(image.complete){ // draw the image when it is ready
ctx.drawImage(image,0,0,canvas.width,canvas.height)
}else{ // while waiting for image clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
}
// create gradient
var grad = ctx.createRadialGradient(posX,posY,0,posX,posY,cirRadius);
// add colour stops
var len = alphas.length-1;
alphas.forEach((a,i) => {
grad.addColorStop(i/len,`rgba(${RGB[0]},${RGB[1]},${RGB[2]},${a})`);
});
// set fill style to gradient
ctx.fillStyle = grad;
// render that gradient
ctx.fillRect(0,0,canvas.width,canvas.height);
}
requestAnimationFrame(update); // keep doing it till cows come home.
}
// start it all happening;
requestAnimationFrame(update);

Categories