I want to use StackBlur.js on multiple canvas elements on the same page; however using the example provided by Zurb, I can only apply it to the first canvas element; it ignores the others.
Any idea how to make this code more universal to apply to every canvas element, not just to one?
The JS (by Zurb) is this:
$(function() {
// Change this value to adjust the amount of blur
var BLUR_RADIUS = 100;
var canvas = document.querySelector('[data-canvas]');
var canvasContext = canvas.getContext('2d');
var image = new Image();
image.src = document.querySelector('[data-canvas-image]').src;
var drawBlur = function() {
var w = canvas.width;
var h = canvas.height;
canvasContext.drawImage(image, 0, 0, w, h);
stackBlurCanvasRGBA('heroCanvas', 0, 0, w, h, BLUR_RADIUS);
};
image.onload = function() {
drawBlur();
}
});
The relevant HTML is this:
<canvas class="hero__background" id="heroCanvas" width="200" height="200" data-canvas></canvas>
<!-- Our image to be blurred -->
<img data-canvas-image style="display: none" src="path/to/image.jpg" />
Thank you!
Wrap their code in a loop over all elements with that attribute (in this case data-canvas) like so
$('[data-canvas]').each(function(){
var $el = $(this);
// insert their code here and change it to reference the correct image each time
});
So in the end you can refer to each of the canvases on the page individually, something like so:
$(function(){
// our iteration
$('[data-canvas]').each(function(){
var $el = $(this);
var BLUR_RADIUS = 100;
var canvas = this; // canvas is the element we're iterating over
var canvasContext = canvas.getContext('2d');
var image = new Image();
// I changed the line below of theirs to refer to the canvases attributes
// we're iterating over above
image.src = $el.attr('data-src');
var drawBlur = function() {
var w = canvas.width;
var h = canvas.height;
canvasContext.drawImage(image, 0, 0, w, h);
var id = $el.attr('id'); // use the id from the canvas
stackBlurCanvasRGBA( id, 0, 0, w, h, BLUR_RADIUS);
};
image.onload = function() {
drawBlur();
}
});
});
So now we don't need the images only their src attributes, which now live on the canvases asdata-src`. The the html can be as follows:
<canvas class="hero__background" id="heroCanvas1" data-src="/image/location.jpg" width="200" height="200" data-canvas></canvas>
<canvas class="hero__background" id="heroCanvas2" data-src="/image/location2.jpg" width="200" height="200" data-canvas></canvas>
Update: I realised that you're displaying info in the canvas, and so need more than one. code updated above.
Update 2: I realised that stack blur relies on an id for each call so you need to get that from each canvas in turn, updated code above
Aside: I would recommend iterating over the canvases by class not attribute, but I used it as that was what was in your html. I'd suggest adding a class to each and changing the iteration to something like so:
$('.blurImgCanvas').each(function(){....})
Related
I am trying to merge multiple frames from a GIF to convert them into spritesheet. I am somehow able to extract frames using libgif.js
here is my code. The Canvas in which i want to put all my images is empty and is at the end i dont know what is wrong with it.
<img hidden src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif" rel:auto_play="0"
rel:rubbable="0" />
<div id="frames">
</div>
<canvas id="myCanvas" style="border:1px solid #d3d3d3;">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/jsgif#1.0.2/libgif.js"></script>
<script>
$(document).ready(function () {
$('img').each(function (idx, img_tag) {
var total = 0;
if (/^.+\.gif$/.test($(img_tag).prop("src"))) {
var rub = new SuperGif({ gif: img_tag, progressbar_height: 0 });
rub.load(function () {
for (var i = 0; i < rub.get_length(); i++) {
total += 1;
rub.move_to(i);
var canvas = cloneCanvas(rub.get_canvas());
$("#frames").append('<img id = "' + i + '"src= "' + canvas + '">');
}
for (var i = 0; i <= total; i++) {
id = i.toString();
var img = document.getElementById(id);
window.onload = function () {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.drawImage(img, 10, 10);
}
}
});
}
});
});
function cloneCanvas(oldCanvas) {
var img = oldCanvas.toDataURL("image/png");
return img;
}
</script>
The main issue in your solution is that you are looping through all inserted frame-images and using window.onload with the expectation to draw images on the canvas. However, you are assigning windows.onload on the image elements after the images have already been attached to the dom in the previous (for i = 0) iteration, through $("#frames").append(.... This means that your onload handlers are getting registered after the event has already fired.
I rearranged your code into the snippet below to make sure that onload is registered before appending the image-frames into the document.
In this solution here I created the "spritesheet" by putting the image-frames vertically one-after-the-other. Tweak the solution accordingly to create your desired "spritesheet layout".
The tricky part is that you need to set a width and height for the canvas in a dynamic fashion, which means you have to wait for all image-frames to be loaded into the document and meassure their collective widths and heights.
The algorithm should be:
Collect all widths and heights of image-frames onload
Calculate the total canvas.width and canvas.height depending on
your desired layout (in my vertical-one-ofter-the-other solution
this means to use the maximal image width as canvas width and the
sum of all image heights as height. If you need a different
spritesheet layout then you need to adapt this logic differently)
Draw all the images on the canvas, which by this point has enough
width and height to fit all your gif-frame-images.
In my solution I did not implement this algorith, I rather hard-coded the canvas width and height after manually calculating the max-img-width and total-img-height for your particular gif example.
$(document).ready(function () {
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
let canvasWidth = 0;
let canvasHeight = 0;
$('img').each(function (idx, img_tag) {
var total = 0;
if (/^.+\.gif$/.test($(img_tag).prop("src"))) {
var rub = new SuperGif({ gif: img_tag, progressbar_height: 0 });
rub.load(function () {
for (let i = 0; i < rub.get_length(); i++) {
total += 1;
rub.move_to(i);
var canvas = cloneCanvas(rub.get_canvas());
const img = $('<img id = "' + i + '"src= "' + canvas + '">');
img.on('load', function() {
// draw the image
ctx.drawImage(this, 0, canvasHeight);
// extend canvas width, if newly loaded image exceeds current width
canvasWidth = $(this).width() > canvasWidth ? $(this).width() : canvasWidth;
// add canvas height by the newly loade image height value
canvasHeight += $(this).height();
// resize sprite-canvas to host the new image
canvas.width = canvasWidth;
canvas.height = canvasHeight;
});
$("#frames").append(img);
}
});
}
});
});
function cloneCanvas(oldCanvas) {
var img = oldCanvas.toDataURL("image/png");
return img;
}
Gif:
<img hidden src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif" rel:auto_play="0"
rel:rubbable="0" />
<hr />
Frames:
<div id="frames">
</div>
<hr />
Canvas:
<hr />
<canvas id="myCanvas" style="border:1px solid #d3d3d3;" width="400" height="17600">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/jsgif#1.0.2/libgif.js"></script>
I am trying to mirror a section of an image, into a below canvas, of exactly the same size, based on fixed values. For example, present it a picture of a map, and mirror below in the same position and the same size a building on that original map image. However, for some unknown reason, it resizes the mirrored section every time? The only botch fix for this is to apply a multiplier to all "d" variables in the draw image function, as seen here: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
Any ideas as to why this is?
Code (minus the multiplier, multipler avaliable if needed):
<!DOCTYPE html>
<html>
<body>
<img id="scream" src="image1.jpg" alt="The Scream" width="100%" height="100%">
<!--<canvas id="myCanvas" style="border:1px solid #d3d3d3;">-->
Your browser does not support the HTML5 canvas tag.
<script>
document.getElementById("scream").onload = function() {
var c = document.createElement('canvas');
var imgData = document.getElementById("scream");
c.width = imgData.width;
c.height = imgData.height;
var body = document.getElementsByTagName("body")[0];
body.appendChild(c);
var ctx = c.getContext("2d");
var img = document.getElementById("scream");
console.log(img);
//ctx.drawImage(img, 0, 0);
//ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
ctx.drawImage(img, 200, 200, 100, 143, 200, 200, 100*1.87, 143*1.87);
//console.log(ctx);
};
</script>
<p><strong>Note:</strong> The canvas tag is not supported in Internet
Explorer 8 and earlier versions.</p>
</body>
</html>
In order to mirror the image you need to translate the context ctx.translate(0, this.height) and then scale it ctx.scale(1,-1);. However on resize the canvas will stay the initial size while the image will adapt to the window size. If you need the canvas to adapt as well you will need to recalculate everything on resize.
var imgData = document.getElementById("scream");
imgData.onload = function() {
var c = document.createElement('canvas');
c.width = this.width;
c.height = this.height;
var body = document.getElementsByTagName("body")[0];
body.appendChild(c);
var ctx = c.getContext("2d");
ctx.translate(0, this.height);
ctx.scale(1,-1);
ctx.drawImage(this, 0, 0,this.width,this.height);
};
<img id="scream" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/imgres.jpg" alt="The Scream" width="100%" height="100%">
Next comes an example where the canvas size changes on resize. In this case you need to move all the code that draws the image inside a function to be called on resize.
Quote from MDN:
Since resize events can fire at a high rate, the event handler shouldn't execute computationally expensive operations such as DOM modifications. Instead, it is recommended to throttle the event using requestAnimationFrame, setTimeout or customEvent"
window.onload = function() {
var imgData = document.getElementById("scream");
var c = document.createElement("canvas");
var body = document.getElementsByTagName("body")[0];
body.appendChild(c);
var ctx = c.getContext("2d");
imgData.onload = function() {
init();
};
function init() {
c.width = imgData.width;
c.height = imgData.height;
ctx.translate(0, imgData.height);
ctx.scale(1, -1);
ctx.drawImage(imgData, 0, 0, imgData.width, imgData.height);
}
setTimeout(function() {
init();
addEventListener("resize", init, false);
}, 15);
};
<img id="scream" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/imgres.jpg" alt="The Scream" width="100%" height="100%">
This is because you are using the ImageElement's .width and .height properties. At getting, these should return the computed value of their corresponding attribute, in px.
In the markup, these attributes have both been set to the relative value 100%, which means their computed value will be relative to the ImageElement's parent's size.
So when you ask drawIamge to draw your image using these output size, it will indeed resize / stretch your image. To avoid it, you need to use the original size of the image, not of the ImageElement.
To do so, if all you need is to draw at the same size as the original image, then you can simply call the length 3 version of drawImage(source, x, y), which will use by default the source's original size.
But you'll still need to resize your canvas to the correct size before doing it, and to do this, you need to access the image's original size.
Fortunately, this original size of the image, is available from the ImageElement's naturalWidth and naturalHeight properties.
document.getElementById("scream").onload = function() {
var c = document.createElement('canvas');
var imgData = document.getElementById("scream");
console.log('computed width: ', imgData.width);
console.log('original width: ', imgData.naturalWidth);
c.width = imgData.naturalWidth;
c.height = imgData.naturalHeight;
var body = document.body;
body.appendChild(c);
var ctx = c.getContext("2d");
// the 3 length version is enough here
ctx.drawImage(imgData, 0, 0);
};
<img id="scream" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Edvard_Munch_-_The_Scream_-_Google_Art_Project.jpg/190px-Edvard_Munch_-_The_Scream_-_Google_Art_Project.jpg" alt="The Scream" width="100%" height="100%">
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<canvas id="spriteCanvas" width="500" height="500">
<img id="coin" width="440" height="40" src="coin.png">
</canvas>
</body>
</html>
I tried placing an image inside a canvas element, but it won't display on the browser. I know the image tag works because it's displayed if I place it outside of the canvas element. Also, if I inspect the canvas element, I can see that the image is inside, but its dimensions are 0 by 0. Can somebody explain why this isn't working?
EDIT: My original code added the image through javascript, but it wouldn't show on the canvas. It was giving me the same problems as above. But I just realized I was missing "onload".
original code:
var coinImage = new Image();
coinImage.src = "coin.png";
var sCanvas = document.getElementById('spriteCanvas');
function Sprite(canvas, width, height, image){
this.context = canvas.getContext("2d");
this.width = width;
this.height = height;
this.image = image;
}
Sprite.prototype.render = function() {
var that = this;
this.context.drawImage(that.image, 0, 0);
}
function init() {
var coin = new Sprite(sCanvas, 100, 100, coinImage);
coin.render();
}
init();
editted code:
var coinImage = new Image();
coinImage.src = "coin.png";
var sCanvas = document.getElementById('spriteCanvas');
function Sprite(canvas, width, height, image){
this.context = canvas.getContext("2d");
this.width = width;
this.height = height;
this.image = image;
}
Sprite.prototype.render = function() {
var that = this;
this.context.drawImage(that.image, 0, 0);
}
coinImage.onload = function () {
var coin = new Sprite(sCanvas, 100, 100, coinImage);
coin.render();
}
This isn't how a <canvas> tag works. If you want your image to appear in your canvas, you will have to use JavaScript to place the pixels of the image into your canvas.
<canvas>s are exactly what they state: canvases. They are an element for you to draw on programmatically. If you just want to display an image on a page, you don't need a canvas, you just need the <img> tag. In fact, elements should not be placed in <canvas>.
Take a look at CanvasRenderingContext2D.drawImage() and this tutorial: HTML5 Canvas Image Tutorial.
And this snippet:
var canvas = document.getElementById("painting"),
ctx = canvas.getContext("2d"),
image = new Image();
image.onload = function() {
ctx.drawImage(image, 30, 50);
};
image.src = "http://cdn.sstatic.net/Sites/stackoverflow/img/sprites.png?v=10a9e8743fb0";
<canvas id="painting" width="300" height="300"></canvas>
To draw an image on a canvas, use the following method:
drawImage(image,x,y)
If the image you want to draw is not in the DOM already you can load an image directly from a URL with a few lines of javascript.
function loadAndDrawImage(url)
{
// Create an image object. This is not attached to the DOM and is not part of the page.
var image = new Image();
// When the image has loaded, draw it to the canvas
image.onload = function()
{
// draw image...
}
// Now set the source of the image that we want to load
image.src = url;
}
loadAndDrawImage("http://www---.png");
I want to draw a copy of this image on top of it but further down, but the .onclick isn't working for my image object. I tested it already and it works perfectly fine with canvas.onclick but not with my image 'sticky'.
code is below:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext("2d");
var sticky = new Image();
sticky.src = "sticky.png";
sticky.onload = function() {
context.drawImage(sticky, 0, 0);
};
sticky.onclick = function() {
context.drawImage(sticky, 0, 100);
};
</script>
Your event needs to be on your canvas, not the image. Because when created, an image object isn't automatically added to the dom.
And when a canvas draw an image, it duplicates it, it copies pixels into itself.
So add your image to the dom, and listen for the click on your canvas.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext("2d");
var sticky = new Image();
var sticky2 = new Image();
sticky.src = "http://lorempixel.com/250/60/";
sticky2.src = "http://lorempixel.com/150/60/";
sticky.onload = function() {
context.drawImage(sticky, 0, 0);
};
canvas.onclick = function() {
context.drawImage(sticky2, 0, 100);
};
<canvas id="myCanvas"></canvas>
I have a problem displaying one canvas to another. I do everything according to this solution
<script>
var source = document.getElementById('hiddenCanvas');
var source_ctx = source.getContext('2d');
var img = new Image();
img.onload = function(){
source.width = img.width;
source.height = img.height;
source_ctx.drawImage(img, 0, 0, img.width, img.height);
}
img.src = 'icon.jpg';
var destination = document.getElementById('visibleCanvas');
var destin_ctx = destination.getContext('2d');
destin_ctx.drawImage(source, 0, 0);
</script>
Well, first canvas element displays picture correctly, but whatever I do, the second canvas does not want to display a picture.
<script>
function init()
{
var source = document.getElementById('hiddenCanvas');
var source_ctx = source.getContext('2d');
var destination = document.getElementById('visibleCanvas');
var destin_ctx = destination.getContext('2d');
var img = new Image();
img.onload = function(){
source.width = img.width;
source.height = img.height;
source_ctx.drawImage(img, 0, 0, img.width, img.height);
destin_ctx.drawImage(source, 0, 0);
}
img.src = 'arun.jpg';
}
</script>
</head>
<body onload="init();">
<canvas id="hiddenCanvas" />
<canvas id="visibleCanvas" />
Your code is not working because you are trying to access canvas element before it is loaded to dom
The way you've currently structured the code, img.onload is executed after the destin_ctx.drawImage line. That means that your program flow currently looks something like this:
Image is told to start loading
Destination canvas is drawn using (currently blank) source canvas
Image finishes loading
Image's onload executes, causing the source canvas to be drawn to. The destination canvas is NOT updated, because the destin_ctx.drawImage operation is a one-time copy.
What you need to do is move the destin_ctw.drawImage call to a place in the execution flow where you know the source canvas will definitely contain the appropriate contents. In this simple case, moving it to inside the image's onload would work.
Here's a full (but simplified) HTML file that works for me in Chromium, with a changed image url:
<script>
function load() {
var source = document.getElementById('hiddenCanvas');
var source_ctx = source.getContext('2d');
var destination = document.getElementById('visibleCanvas');
var destin_ctx = destination.getContext('2d');
var img = new Image();
img.onload = function(){
source.width = img.width;
source.height = img.height;
source_ctx.drawImage(img, 0, 0, img.width, img.height);
destin_ctx.drawImage(source, 0, 0);
}
img.src = 'ship360_32.png';
}
</script>
<body onload="load()">
<canvas id="hiddenCanvas"></canvas>
<canvas id="visibleCanvas"></canvas>
</body>
You are trying to draw the image from source before the image is loaded and the source even have the image.
Move the last draw operation inside the onload handler. Also remember to set the size for destination canvas:
var source = document.getElementById('hiddenCanvas');
var source_ctx = source.getContext('2d');
var destination = document.getElementById('visibleCanvas');
var destin_ctx = destination.getContext('2d');
var img = new Image();
img.onload = function(){
source.width = img.width;
source.height = img.height;
source_ctx.drawImage(img, 0, 0, img.width, img.height);
destination.width = source.width;
destination.height = source.height;
destin_ctx.drawImage(source, 0, 0);
}
img.src = 'icon.jpg';