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>
Related
I am creating a walkthrough video where the user can slide a UI slider across the screen, and the camera will walk through through a 3D space.
The video has been exported as jpg frames, and numbered 0 to 350.jpg.
I am pre-loading all of the frames first, and then applying the function to the slider change.
This is the canvas
<canvas id="walkthrough" width="1280" height="300"></canvas>
This is the function from the jQuery UI slider applying data.value
$("[data-slider]")
.each(function () {
var input = $(this);
$("<span>")
.addClass("output")
.insertAfter($(this));
})
.bind("slider:ready slider:changed", function (event, data) {
$(this)
.nextAll(".output:first")
.html(data.value.toFixed(3));
});
This is the image prelaod function
var totalImages = 50; // Wow, so many images for such a short clip
var images = new Array();
for(var i = 1; i < totalImages; i++) {
var filename = '/walkthrough/' + i + '.jpg'; // Filename of each image
var img = new Image;
img.src = filename;
images.push(img);
}
This is the function that should draw the image to canvas when the slider is changed
$("#my-input").bind("slider:changed", function (event, data) {
var currentimage = '/walkthrough/' + data.value + '.jpg';
var canvas = document.getElementById("walkthrough");
var context = canvas.getContext("2d");
context.drawImage(currentimage,10,10);
});
I have tried to adapt this code from an article I read here which is using the scroll position to draw the image instead of a data.value.
http://elikirk.com/canvas-based-scroll-controlled-backgroud-video-use-parallax-style-web-design/
I appreciate any help with this!
Here's a demo that uses a slider to change the image drawn on a canvas. Some notable differences from your code:
uses the native HTML5 slider instead of jQuery UI
use the input event instead of the change event to detect slider changes
access the slider value with event.target.value instead of data (which isn't defined on the input event)
use the slider value as an index into the array of pre-loaded images instead of a file path
var canvas = document.getElementById("canvas");
canvas.height = 150;
canvas.width = 400;
var totalImages = 72;
var videoFrames = [];
for (var i = 1; i <= totalImages; i++) {
var videoFrame = new Image;
var videoFrameUrl = 'http://rphv.net/walkthrough/tmp-' + i + '.gif';
videoFrame.src = videoFrameUrl;
videoFrames.push(videoFrame);
}
$("#my-input").on("input", function(event) {
var currentImage = videoFrames[event.target.value - 1];
var context = canvas.getContext("2d");
context.drawImage(currentImage, 0, 0);
});
html,
body {
height: 100%;
margin: 0 auto;
}
canvas {
height: 150px;
width: 400px;
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>
<br>
<input id="my-input" type="range" min="1" max="72" value="1" />
I'm creating something in Javascript/jQuery that will take an image, and reconstruct it as a multidimensional array. Due to the user being enabled to upload their own image, I need to get the image dimensions on the fly, and use them to scale the canvas accordingly, and draw the image.
Only issue is, the process of scaling the canvas, and then drawing to it is apparently throwing me off. What is happening now is that the canvas is scaling to the image size correctly, but what gets drawn to the canvas has its dimensions all whacked up.
Hopefully someone can give me some insight!
HTML
<div class="canvas">
<canvas id="myCanvas" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
</div>
<div class="options">
<input type='file' id="imgInp" />
</div>
<div id="status">
<h1>Image to Multidimensional Array</h1>
<p>What this does it pretty much self explanatory, it turns any uploaded image into a multidimensional array.
<br/>The array is structured so that for every array inside the main array is a Y coord. Every bit of data inside of that Y array is an X coord.</p>
<h2>Output</h2>
<br>
<p>
<span id="statusout" onClick="this.select();"></span>
</p>
</div>
JAVASCRIPT
function loadCanvas(dataURL, width, height) {
$('#myCanvas').css({
width: width,
height: height
});
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
// load image from data url
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(this, 0, 0);
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
var datalocation;
var datalocationhex;
var coord;
for (var y = 0; y < canvas.height; y++) {
$('#statusout').append("[");
for (var x = 0; x < canvas.width; x++) {
datalocation = context.getImageData(x, y, 1, 1).data;
datalocationhex = "#" + ("000000" + rgbToHex(datalocation[0], datalocation[1], datalocation[2])).slice(-6);
coord = "x=" + x + ", y=" + y;
if (x == 0) {
$('#statusout').append("'" + datalocationhex + "'");
} else {
$('#statusout').append(",'" + datalocationhex + "'");
}
}
if (y == canvas.height) {
$('#statusout').append("]<br>");
} else {
$('#statusout').append("],<br>");
}
}
};
function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}
imageObj.src = dataURL;
}
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function(e) {
var img = new Image();
img.src = e.target.result;
if (img.complete) { // was cached
var width = img.width;
var height = img.height;
loadCanvas(e.target.result, width, height);
} else { // wait for decoding
img.onload = function() {
var width = img.width;
var height = img.height;
loadCanvas(e.target.result, width, height);
}
}
}
reader.readAsDataURL(input.files[0]);
}
}
$("#imgInp").change(function() {
readURL(this);
});
So yeah, that's what I've got.
Thanks a lot for any help in advance!
EDIT
Here's a link to the project on CodePen to get a better view of things.
EDIT v2 It looks like the canvas is a little confused. The multidimensional array is getting spit out fine - well, kind of. It will go spits out the correct code for the image, and then a bunch of extra blank spots. So something is still going wrong with the canvas draw.
As it turns out, the Canvas element will only listen to width and height attributes, and not CSS rules when it comes to drawing the canvas.
This is what I changed:
$('#myCanvas').css({
width: width,
height: height
});
Turned into this:
$('#myCanvas').attr({
width: width,
height: height
});
I'm having an issue with drawImage() re-sizing a loaded image below a certain width and height. The image I'm trying to re-size is 1080x1920 and I want it to be re-sized to 540x960. If I apply drawImage() to reduce it to 764x1358, it works. But any resolution smaller than that (on either parameter) results in the image not being displayed on the canvas.
There is definitely some correlation between the lower bounds on the resolution, because both are approximately 70.7% of the original resolution. I'm wondering if there's an inherit limit on drawImage but I couldn't find any specification that said so.
Here's the relevant code:
var image = new Image();
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.canvas.width = this.width * 2;
ctx.canvas.height = this.height;
$(image).on("load", function() {
ctx.drawImage(image, 0, 0, 764, 1358);
});
image.src = "test2.jpg";
I edited the code to show where image came from.
Full Code:
<!DOCTYPE html>
<html>
<head>
<title>Canvas from scratch</title>
<meta charset="utf-8">
<script
src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="jcanvas.min.js"></script>
</head>
<body>
<canvas id="myCanvas" width="1920" height="1920">
</canvas>
<script>
$(document).ready(
function() {
$("<img>").attr("src", "test2.jpg").on('load', function() {
var imgWidth, imgHeight;
var imageData;
var image = new Image();
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.canvas.width = this.width * 2;
ctx.canvas.height = this.height;
$(image).on("load", function() {
/* This is where the image is loaded and
inserted into the canvas */
ctx.drawImage(image, 0, 0, 763, 1358);
});
image.src = "test2.jpg";
imageData = ctx.getImageData(0, 0,
this.width,
this.height);
/* This is just part of the image manipulation,
bland right now */
var newImgData = ctx.createImageData(imageData.width,
imageData.height);
for ( var i = 0; i < newImgData.data.length; i += 4) {
newImgData.data[i + 0] = 255;
newImgData.data[i + 1] = 0;
newImgData.data[i + 2] = 0;
newImgData.data[i + 3] = 255;
}
// ctx.putImageData(newImgData, imageData.width, 0);
});
});
</script>
</body>
</html>
Last Edit: I found out the solution.
As it turns out, my issue was the version of JQuery I was using. I was originally just version 1, but 1.8.3 fixed everything. So, the solution is to simply change
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
to
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
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(){....})
I have JS which resize the width and height of Image which is working fine if I used Alert before assigning the actual image src to the image object and if I omit the alertbox, it is not working. Please suggest me how to fix it.
`
<html>
<head>
<script type="text/javascript" language="javascript">
function resize_image_height_weight(id, hgt, wdth)
{
//alert(id);
Obj=document.getElementById(id);
myImage = new Image();
myImage.src = Obj.src;
var heights = myImage.height;
var widths = myImage.width;
// alert("Height=>" + heights + " Width=> " + widths);
if(heights > hgt || widths > wdth)
{
if(heights > widths)
{
var temp = heights/hgt;
var new_width = widths / temp;
new_width = parseInt(new_width);
heights = hgt;
widths = new_width;
}
else
{
var temp = widths/wdth;
var new_height = heights / temp;
new_height = parseInt(new_height);
heights = new_height;
widths = wdth;
}
}
Obj.height = heights;
Obj.width = widths;
}
</script>
</head>
<body>
<div>
<center>
<img src="http://www.google.co.in/intl/en_com/images/srpr/logo1w.png" id="i" alt="Google logo" height="150" width="150">
<script type="text/javascript">
document.images[document.images.length-1].onload = resize_image_height_weight("i",150,150);
</script>
</center>
</div>
</body>
</html>`
When you set the .src on an image, you have to wait until the image is successfully loaded until you can read it's height and width. There is an onload event handler that will tell you when the image is loaded.
Here's a place in your code where this issue could occur:
Obj=document.getElementById(id);
myImage = new Image();
myImage.src = Obj.src;
var heights = myImage.height;
var widths = myImage.width;
In this case, since the image was already elsewhere in the document, you should just read the height and width off the existing element rather than the new element.
Be very careful when testing because if the image is in your browser cache, it may load immediately, but when it's not in your cache, it takes some time to load and won't be available immediately.
Putting an alert at the right place in your script can allow the image to load before the script proceeds which could explain why it works with the alert.
As RobG mentions, your onload handler is also faulty (needs to be a function, not the returned result of a function).
Here's a simpler function to scale an image to fit the bounds. This uses a trick that you only need to set one size (height or width on the image) and the other will be scaled by the browser to preserve the aspect ratio.
function resizeImage(id, maxHeight, maxWidth) {
// assumes the image is already loaded
// assume no height and width is being forced on it yet
var img = document.getElementById(id);
var height = img.height || 0;
var width = img.width || 0;
if (height > maxHeight || width > maxWidth) {
var aspect = height / width;
var maxAspect = maxHeight / maxWidth;
if (aspect > maxAspect) {
img.style.height = maxHeight + "px";
} else {
img.style.width = maxWidth + "px";
}
}
}
You can see it in action here: http://jsfiddle.net/jfriend00/BeJg4/.
In the function assigned to onload:
> document.images[document.images.length-1].onload =
> resize_image_height_weight("i",150,150);
you are assigning the result of calling resize_image_height_weight which throws an error in IE. Set it to a function instead:
document.images[document.images.length-1].onload = function() {
resize_image_height_weight("i",150,150);
};