I want to generate images consisting of a label and an icon. The label part is going to vary a lot (50-100) while there are about 10 icons. I would like to make the final images in a modular way by splitting the final image in two parts, a label image and an icon image. I will build a service that returns dataURI for the labels while the icon dataURIs will be embedded in the page. Then I would like to combine these two different dataURIs to create a single dataURI representing a combined image.
How can I do this on the client side?
You can create images using your data uris and then draw a new image that includes them using canvas. Here's a simple example:
var nloaded = 0;
function checkload(event) {
nloaded++;
if (nloaded < 2) {
return;
}
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
context.drawImage(image1, 0, 0, 50, 50);
context.drawImage(image2, 50, 50, 100, 100);
var combined = new Image;
combined.src = canvas.toDataURL('data/gif');
document.body.appendChild(combined);
}
var image1 = new Image;
image1.onload = checkload;
image1.src = 'data:image/gif;base64,R0lGODdhAgACALMAAAAAAP///wAAAAAAAP8AAAAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAgACAAAEAxBJFAA7';
var image2 = new Image;
image2.onload = checkload;
image2.src = 'data:image/gif;base64,R0lGODdhAgACALMAAAAAAP///wAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAgACAAAEA5BIEgA7';
canvas {
display: none;
}
<canvas width=100 height=100></canvas>
.
Once you have the images loaded from the data URI and combined using the drawImage commands of the canvas context you can use the canvas to create a new image like:
var combined = new Image;
combined.src = canvas.toDataURL('data/gif');
Unfortunately this won't work in IE8.
Example using an array of image objects containing URI src and x, y offsets:
var images = [
{ src: 'data:image/gif;base64,R0lGODdhAgACALMAAAAAAP///wAAAAAAAP8AAAAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAgACAAAEAxBJFAA7', x: 0, y: 0 },
{ src: 'data:image/gif;base64,R0lGODdhAgACALMAAAAAAP///wAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAgACAAAEA5BIEgA7', x: 20, y: 20 },
];
var canvas = document.createElement('canvas');
var destination = document.getElementById('canvas');
Promise.all(images.map(imageObj => add2canvas(canvas, imageObj)))
.then(() => destination.append(canvas));
function add2canvas(canvas, imageObj) {
return new Promise( (resolve, reject) => {
if (!imageObj || typeof imageObj != 'object') return reject();
var image = new Image();
image.onload = function () {
canvas.getContext('2d')
.drawImage(this, imageObj.x || 0, imageObj.y || 0);
resolve();
};
image.src = imageObj.src;
});
}
Some more bells/whistles... function to generate a composite png:
function mergeImageURIs(images) {
return new Promise( (resolve, reject) => {
var canvas = document.createElement('canvas');
canvas.width = 1000; // desired width of merged images
canvas.height = 1000; // desired height of merged images
Promise.all(images.map(imageObj => add2canvas(canvas, imageObj))).then(() => resolve(canvas.toDataURL('image/png'), reject));
});
}
function add2canvas(canvas, imageObj) {
return new Promise( (resolve, reject) => {
if (!imageObj || typeof imageObj != 'object') return reject();
var x = imageObj.x && canvas.width ? (imageObj.x >=0 ? imageObj.x : canvas.width + imageObj.x) : 0;
var y = imageObj.y && canvas.height ? (imageObj.y >=0 ? imageObj.y : canvas.height + imageObj.y) : 0;
var image = new Image();
image.onload = function () {
canvas.getContext('2d').drawImage(this, x, y);
resolve();
};
image.src = imageObj.src;
});
}
Related
I'm currently trying to convert a base64Encoded string (representation of an image) into an ImageData object in Javascript. However, this comes back with an error:
Uncaught InvalidStateError: Failed to construct 'ImageData': The input data length is not a multiple of 4.
The encoded image is 940 x 740
What am I missing? Any help appreciated
JSFiddle link with full code
function _base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
const base64String = ""; //truncated because stackoverflow question too long.
const arrBuffer = _base64ToArrayBuffer (base64String );
var array = new Uint8ClampedArray(arrBuffer);
console.log(array);
var image = new ImageData(array, 900, 740);
console.log(image);
You can get the ImageData from a Canvas
function _base64ToImageData(buffer, width, height) {
return new Promise(resolve => {
var image = new Image();
image.addEventListener('load', function (e) {
var canvasElement = document.createElement('canvas');
canvasElement.width = width;
canvasElement.height = height;
var context = canvasElement.getContext('2d');
context.drawImage(e.target, 0, 0, width, height);
resolve(context.getImageData(0, 0, width, height));
});
image.src = 'data:image/png;base64,' + buffer;
document.body.appendChild(image);
setTimeout(() => {
document.body.removeChild(image);
}, 1);
});
}
window.addEventListener('load', () => {
_base64ToImageData(base64String, 2056, 1236).then(data => {
console.log(data);
});
});
Hello I created a promise and call it recursively from a loop to fill an array but the onload never triggers thus never resolving the promise.
Can anyone see anything I'm doing wrong?
function imageResizeToDataUriPromise(url, width, height) {
return new Promise(function (resolve, reject) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function () {
var imgWidth = img.naturalWidth;
var imgHeight = img.naturalHeight;
var result = _scaleImage(imgWidth, imgHeight, width, height, true);
//create an off-screen canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = result.width;
canvas.height = result.height;
//draw source image into the off-screen canvas:
ctx.drawImage(img, 0, 0, result.width, result.height);
resolve(canvas.toDataURL());
};
img.src = url;
});
}
this is my basic calling block but its in a loop
caller(logo, 150, 150).then
(function (response) {
console.log("Success!", response);
base64Urls.push(response);
});
It works after I use that promise to get an URL and set it to an image element... not sure how you would use the return Data URL. One possible issue is: can you check whether your image source is serving you the image. (double check the network tab in developer tool).
function imageResizeToDataUriPromise(url, width, height) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function() {
console.log("IMG ONLOAD handler invoked");
var imgWidth = img.naturalWidth;
var imgHeight = img.naturalHeight;
var result = img;
//create an off-screen canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = result.width;
canvas.height = result.height;
//draw source image into the off-screen canvas:
ctx.drawImage(img, 0, 0, result.width, result.height);
ctx.fillStyle = "white";
ctx.font = '21px sans-serif';
ctx.fillText('Hello world', 21, 51);
resolve(canvas.toDataURL());
};
img.src = url;
});
}
imageResizeToDataUriPromise("https://i.imgur.com/j7Ie7pg.jpg", 100, 100)
.then(url => document.querySelector("#an-image").src = url);
console.log("Promise obtained");
<img id="an-image">
The method below is being used as a parameter for another variable. But the problem here is that it is not returning the file back after it is being compressed.
async CompressImage(imageFile: File): Promise<File> {
return new Promise<File>((resolve, reject) => {
const cReader = new FileReader();
const image = new Image();
cReader.onload = () => {
image.src = cReader.result.toString();
image.onload = () => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
context.drawImage(image, 0, 0);
//width & height initialization
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, width, height);
const convertedFile = canvas.toBlob((blob) => {
const scaledDown = new File([blob], imageFile.name);
});
resolve(convertedFile);
}
};
cReader.readAsDataURL(mediaFile);
});
}
Here is the code:
var spriteFolder = "../../assets/Painter/sprites/";
var sprites = {};
sprites.background = new Image();
sprites.background.src = spriteFolder + "spr_background.jpg";
sprites.cannon_barrel = new Image();
sprites.cannon_barrel.src = spriteFolder + "spr_cannon_barrel.png";
sprites.cannon_red = new Image();
sprites.cannon_red.src = spriteFolder + "spr_cannon_red.png";
sprites.cannon_green = new Image();
sprites.cannon_green.src = spriteFolder + "spr_cannon_green.png";
sprites.cannon_blue = new Image();
sprites.cannon_blue.src = spriteFolder + "spr_cannon_blue.png";
var Canvas2D = {
canvas: undefined,
canvasContext: undefined
};
Canvas2D.initialize = function(canvasName) {
Canvas2D.canvas = document.getElementById(canvasName);
Canvas2D.canvasContext = Canvas2D.canvas.getContext("2d");
};
Canvas2D.clear = function() {
Canvas2D.canvas.clearRect(0, 0, Canvas2D.canvas.width, Canvas2D.canvas.height);
};
Canvas2D.drawImage = function(sprite, position, rotation, origin) {
Canvas2D.canvasContext.save();
Canvas2D.canvasContext.translate(position.x, position.y);
Canvas2D.canvasContext.rotate(rotation);
Canvas2D.canvasContext.drawImage(sprite,
0, 0, sprite.width, sprite.height,
-origin.x, -origin.y, sprite.width, sprite.height
);
Canvas2D.canvasContext.restore();
};
function init() {
Canvas2D.initialize("myCanvas");
setTimeout(function() {
Canvas2D.canvasContext.drawImage(sprites.background, 0, 0);
}, 1000);
}
document.addEventListener("DOMContentLoaded", init);
If I don't use setTimeout, then the image is not drawn. Why?
Because your images were not yet loaded.
Thanks to your 1sec timeout they have enough time to load.
Have a look at this example from MDN https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images
You need to use similar approach - see img.onload()
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.onload = function(){
ctx.drawImage(img,0,0);
ctx.beginPath();
ctx.moveTo(30,96);
ctx.lineTo(70,66);
ctx.lineTo(103,76);
ctx.lineTo(170,15);
ctx.stroke();
};
img.src = 'https://mdn.mozillademos.org/files/5395/backdrop.png';
}
I have researched issues with the base64 conversion and jspdf function quite a bit. ( PS this is my first question on stackoverflow, please bare with any rookie mistakes ).
All seems to work fine with the below code except that the pdf is generated and saved before the loop where the images are converted to base64 and placed to the document is finished. I added a couple alerts to check timing. Would the solution be to check when the loop is finished, the images placed before continuing with the pdf function? if so, how? please help.
$(document).ready(function(){
$("a#getdoc").click(function(){
var doc = new jsPDF('landscape, in, legal');
var myimages = 'img1.jpg|img2.jpg|img3.png';
var myimgarray = myimages.split('|');
function convertImgToBase64(url, callback, outputFormat){
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
return canvas.toDataURL("image/jpeg");
var dataURL = canvas.toDataURL("image/jpeg");
callback(dataURL);
canvas = null;
}
for(var i = 0; i < myimgarray.length; i++)
{
icount.count = i;
var img = new Image();
alert(checkoutimgarray);
img.src = '/Portals/0/repair-images/' + myimgarray[i];
img.onload = function(){
newData = convertImgToBase64(img);
alert(newData);
doc.addImage(newData, 'JPEG', (icount * 100), 10, 70, 15); // would doc be undefined here? out of scope?
};
}
doc.setFontSize(20);
doc.text(100, 20, "This is a test to see if images will show");
doc.save('My_file.pdf');
});
});
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
function convertImgToBase64(img, outputFormat){
// clear canvas
canvas.width = img.width;
canvas.height = img.height;
// draw image
ctx.drawImage(img, 0, 0);
// get data url of output format or defaults to jpeg if not set
return canvas.toDataURL("image/" + (outputFormat || "jpeg"));
}
var images = [];
for(var i = 0; i < myimgarray.length; i++) {
var img = new Image();
img.onload = function() {
images.push({
base64: convertImgToBase64(this),
width: this.width,
height: this.height
});
// all images loaded
if(images.length === myimgarray.length) {
for(var j = 0; j < images.length; j++) {
doc.addImage(images[j].base64, 'JPEG', (j * 100), 10, 70, 15);
}
doc.setFontSize(20);
doc.text(100, 20, "This is a test to see if images will show");
doc.save('My_file.pdf');
}
};
img.src = '/Portals/0/repair-images/' + myimgarray[i];
}