This question already has answers here:
Canvas is stretched when using CSS but normal with "width" / "height" properties
(10 answers)
Closed 4 years ago.
I have a fairly simple test, a 400 x 400 white image with text on it saying "1" over and over again.
I draw it on a fairly simple 1000 x 1000 canvas, trying to resize it to 100 x 100.
var image = new Image();
document.body.appendChild(image);
image.addEventListener("load",function (event) {
var image1 = event.target;
var tempCanvas = window.document.createElement("canvas");
tempCanvas.style.width = "1000px";
tempCanvas.style.height = "1000px";
tempCanvas.style.background = "rgba(0, 0, 0, 0.1)";
document.body.appendChild(tempCanvas);
tempCanvas.getContext("2d").drawImage(image1, 0, 0, 100, 100);
});
image.src = "1.png";
But despite all that being squares, I end up with an odd-looking, deformed, weirdly scaled result that's rectangular, low quality, and without having any of its dimensions being 100px.
On the left, you can see the original image, on the right, that's the top left corner of my canvas.
If you want the original image, here it is: https://i.stack.imgur.com/yDkLx.png
What am I missing?
Try setting the width and height values on the canvas element prior to the drawImage(), rather than relying on the styles as you are.
Setting the width and height attributes corresponds to setting the dimensions of that canvas element.
Once you've defined the dimensions of a canvas element, the rendering behavior of the canvas becomes much more predicatable:
var image = new Image();
document.body.appendChild(image);
image.addEventListener("load",function (event) {
var image1 = event.target;
var tempCanvas = window.document.createElement("canvas");
//tempCanvas.style.width = "400px";
//tempCanvas.style.height = "400px";
tempCanvas.style.background = "rgba(0, 0, 0, 0.1)";
tempCanvas.width = 100;
tempCanvas.height = 100;
document.body.appendChild(tempCanvas);
tempCanvas.getContext("2d").drawImage(image1, 0, 0, 100, 100);
});
image.src = "https://puu.sh/C4HE2/d96b531d08.png";
canvas {
border:1px solid blue;
}
img {
border:1px solid red;
}
The code snippet above shows the original source image with red border, and the down-scaled canvas rendered image with blue border - hope this helps!
I have a canvas which is loaded with a png image. I get its jpg base64 string by .toDataURL() method like this:
$('#base64str').val(canvas.toDataURL("image/jpeg"));
But the transparent parts of the png image are shown black in the new jpg image.
Any solutions to change this color to white? Thanks in advance.
This blackening occurs because the 'image/jpeg' conversion involves setting the alpha of all canvas pixels to fully opaque (alpha=255). The problem is that transparent canvas pixels are colored fully-black-but-transparent. So when you turn these black pixels opaque, the result is a blackened jpeg.
The workaround is to manually change all non-opaque canvas pixels to your desired white color instead of black.
That way when they are made opaque they will appear as white instead of black pixels.
Here's how:
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i]=255;
data[i+1]=255;
data[i+2]=255;
data[i+3]=255;
}
}
ctx.putImageData(imgData,0,0);
After spending a lot of time on this and this post specifically, and these solutions kinda worked expect I just couldn't get the canvas to look right. Anyway I found this solution elsewhere and wanted to post it here incase it helps someone else from spending hours trying to get the black background to white and look like the original.
public getURI(): string {
let canvas = <HTMLCanvasElement>document.getElementById('chartcanvas');
var newCanvas = <HTMLCanvasElement>canvas.cloneNode(true);
var ctx = newCanvas.getContext('2d');
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);
ctx.drawImage(canvas, 0, 0);
return newCanvas.toDataURL("image/jpeg");
}
This answer is a bit longer, but I find it to be more 'correct' in that it handles these things without directly modifying raw canvas data. I find that to be a pretty messy and theoretically unsatisfying solution. There are built in functions to achieve that, and they ought to be used. Here is the solution I found/pilfered:
function canvasToImage(backgroundColor){
var context = document.getElementById('canvas').getContext('2d');
canvas = context.canvas;
//cache height and width
var w = canvas.width;
var h = canvas.height;
var data;
//get the current ImageData for the canvas.
data = context.getImageData(0, 0, w, h);
//store the current globalCompositeOperation
var compositeOperation = context.globalCompositeOperation;
//set to draw behind current content
context.globalCompositeOperation = "destination-over";
//set background color
context.fillStyle = backgroundColor;
//draw background / rect on entire canvas
context.fillRect(0,0,w,h);
//get the image data from the canvas
var imageData = this.canvas.toDataURL("image/jpeg");
//clear the canvas
context.clearRect (0,0,w,h);
//restore it with original / cached ImageData
context.putImageData(data, 0,0);
//reset the globalCompositeOperation to what it was
context.globalCompositeOperation = compositeOperation;
//return the Base64 encoded data url string
return imageData;
}
Basically, you create a white background image and underlay it under the canvas and then print that. This function is mostly plagiarized from someone's blog, but it required a bit of modification -- such as actually getting the context -- and copied directly from my (working) code, so as long as your canvas element has the id 'canvas', you should be able to copy/paste it and have it work.
This is the blog post I modified it from:
http://www.mikechambers.com/blog/2011/01/31/setting-the-background-color-when-generating-images-from-canvas-todataurl/
The big advantage of my function over this is that it outputs to jpeg instead of png, which is more likely to work well in chrome, which has a dataurl limit of 2MB, and it actually grabs the context, which was a glaring omission in the original function.
Marks answer is correct, but when a picture has some antialiasing applied, the exported image won't be as good as it should be (mainly text). I would like to enhance his solution:
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i] = 255 - data[i];
data[i+1] = 255 - data[i+1];
data[i+2] = 255 - data[i+2];
data[i+3] = 255 - data[i+3];
}
}
ctx.putImageData(imgData,0,0);
If you want to move to white only full transparent pixels just check for (data[i+3]==0) instead of (data[i+3]<255).
Why not to save it as PNG?
canvas.toDataURL("image/png");
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i] = 255 - data[i];
data[i+1] = 255 - data[i+1];
data[i+2] = 255 - data[i+2];
data[i+3] = 255 - data[i+3];
}
Here is my function that resizes a photo and handles the black transparent background problem:
resizeImage({ file, maxSize, backgroundColor }) {
const fr = new FileReader();
const img = new Image();
const dataURItoBlob = (dataURI) => {
const bytes = (dataURI.split(',')[0].indexOf('base64') >= 0)
? window.atob(dataURI.split(',')[1])
: window.unescape(dataURI.split(',')[1]);
const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
const max = bytes.length;
const ia = new Uint8Array(max);
for (let i = 0; i < max; i += 1) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ia], { type: mime });
};
const resize = () => {
// create a canvas element to manipulate
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'canvas');
const context = canvas.getContext('2d');
// setup some resizing definitions
let { width, height } = img;
const isTooWide = ((width > height) && (width > maxSize));
const isTooTall = (height > maxSize);
// resize according to `maxSize`
if (isTooWide) {
height *= maxSize / width;
width = maxSize;
} else if (isTooTall) {
width *= maxSize / height;
height = maxSize;
}
// resize the canvas
canvas.width = width;
canvas.height = height;
// place the image on the canvas
context.drawImage(img, 0, 0, width, height);
// get the current ImageData for the canvas
const data = context.getImageData(0, 0, width, height);
// store the current globalCompositeOperation
const compositeOperation = context.globalCompositeOperation;
// set to draw behind current content
context.globalCompositeOperation = 'destination-over';
// set background color
context.fillStyle = backgroundColor;
// draw background / rect on entire canvas
context.fillRect(0, 0, width, height);
// get the image data from the canvas
const imageData = canvas.toDataURL('image/jpeg');
// clear the canvas
context.clearRect(0, 0, width, height);
// restore it with original / cached ImageData
context.putImageData(data, 0, 0);
// reset the globalCompositeOperation to what it was
context.globalCompositeOperation = compositeOperation;
// return the base64-encoded data url string
return dataURItoBlob(imageData);
};
return new Promise((resolve, reject) => {
if (!file.type.match(/image.*/)) {
reject(new Error('VImageInput# Problem resizing image: file must be an image.'));
}
fr.onload = (readerEvent) => {
img.onload = () => resolve(resize());
img.src = readerEvent.target.result;
};
fr.readAsDataURL(file);
});
},
That is a Vue JS instance method that can be used like this:
// this would be the user-uploaded file from the input element
const image = file;
const settings = {
file: image,
maxSize: 192, // to make 192x192 image
backgroundColor: '#FFF',
};
// this will output a base64 string you can dump into your database
const resizedImage = await this.resizeImage(settings);
My solution here is a combination of about 74 different StackOverflow answers related to resizing images client-side, and the final boss was to handle transparent PNG files.
My answer would not be possible without Laereom's answer here.
I'm currently working on a game to teach myself HTML5 and Javascript, etc.
Originally I just had static canvases in the HTML body but it became a pain to pass around to my objects, etc. Basically it's easier for me to let each object just create its own as needed.
However, I don't know how to layer them properly like I had before. Now I have have my different canvases stacked atop of each other (my background sits above the player icon and below the objects to avoid).
How do I do this? I know it's related to the z-index (maybe innerHTML) but I'm very n00bish with HTML Javascript.
Thanks!
<canvas id="pointsCanvas"
style="z-index: 40;
position:absolute;
left:0px;
top:0px;
">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<canvas id="menuCanvas"
style="z-index: 50;
position:absolute;
left:0px;
top:0px;
">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
I had this.
Now I'm trying this:
this.canvasElement = document.createElement('canvas')
Add them to a container element with its position set to relative (preferably create a rule, not an inline style like in this example):
<div id="container" style="position:relative"></div>
Then add canvases with their position set to absolute and left/top to 0:
var canvas = document.createElement("canvas");
canvas.width = 500;
canvas.height = 400;
canvas.style.cssText = "position:absolute;left:0;top:0";
// add to container
document.getElementById("container").appendChild(canvas);
The first canvas added will be at the bottom, the last on top.
You can use z-indexes but it's better just to create and add canvases in the order you want, knowing first is bottom etc. (imo).
Demo:
var canvas = document.createElement("canvas");
canvas.width = 500;
canvas.height = 400;
canvas.style.cssText = "position:absolute;left:0;top:0";
var ctx = canvas.getContext("2d");
ctx.font = "80px sans-serif";
ctx.fillText("BOTTOM", 10, 120);
// add to element
document.getElementById("container").appendChild(canvas);
// SECOOND CANVAS
var canvas2 = document.createElement("canvas");
canvas2.width = 500;
canvas2.height = 400;
canvas2.style.cssText = "position:absolute;left:0;top:0";
var ctx2 = canvas2.getContext("2d");
ctx2.font = "80px sans-serif";
ctx2.fillStyle = "blue";
ctx2.fillText("TOP", 16, 120);
// add to element
document.getElementById("container").appendChild(canvas2);
#container {position:relative}
<div id="container"></div>
A more traditional method is to have one canvas display multiple (layered) objects.
To do this you can create an array containing the definitions of each of your objects:
var gameObjects=[];
var gameObjects.push({
type: 'rect',
x:50, y:50,
width:100, height:75,
fill: 'red'
});
... and so on ...
Then for each game frame, you clear the canvas and use the definitions-array to draw the game objects in their new position.
// move the rect 5 pixels rightward
gameObjects[0].x += 5;
context.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<gameObjects.length;i++){
var obj=gameObjects[i];
if(i.type='rect'){ drawRect(obj); }
}
function drawRect(r){
context.fillStyle=r.fill;
context.fillRect(r.x,r.y,r.width,r.height);
}
Layering
To do layering, you can add a z-index property to each of your gameObjects. It's not a z-index like a traditional html/css z-index. Instead it just lets you sort your gameObjects into any order (layers) you need.
var gameObjects.push({
type: 'rect',
x:50, y:50,
width:100, height:75,
fill: 'red',
zIndex=3
});
// sort the array by zIndex to apply layering
gameObjects.sort(function(a,b){return a.zIndex - b.zIndex});
// and now draw the "relayered" objects in the array
for(var i=0;i<gameObjects.length;i++){
var obj=gameObjects[i];
if(i.type='rect'){ drawRect(obj); }
}
I am using canvas drawImage() twice. One is a background image, and the other is some kind of 'mask' used to get the wanted image from the background.
The resulting image is the part of the background that's behind the overlay. I want to save that part of the image. Here's an example where I get the Hulk's face -- I want to create an image out of that face and save it on my server. Saving this image as is (right-click save image as) takes the full size of the canvas, and not just the wanted face.
http://jsfiddle.net/s248a76w/
HTML looks like:
<canvas id="canvas" width="900" height="500"></canvas>
JS:
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
//Load the images to be blended
var userPic = new Image;
userPic.src = "http://img2.wikia.nocookie.net/__cb20090320033607/marvelmovies/images/5/5e/The-hulk-2003.jpg";
var maskTemplate = new Image;
maskTemplate.src = "http://www.botaiusti.com/img/1.svg";
//Set the globalCompositeOperation to lighter
// ctx.globalCompositeOperation = "lighter";
ctx.globalCompositeOperation = "destination-atop";
//Draw the source image
userPic.onload = function() {
ctx.drawImage(userPic, 100, 00);
}
//Draw the destination image
maskTemplate.onload = function() {
ctx.drawImage(maskTemplate, 340, 70);
}
}
I'm looking for a way to overlay the visible areas of a transparent black silhouette PNG file with a certain pattern.
Is there a CSS, JavaScript, mixed solution for this?
Example: I have one image of a weapon silhoutte and a set of camo patterns which I want to lay over the weapon.
Demo: In Photoshop the same result is givin when selecting layer mask > overlay.
Ps: my question is similar to this thread: Silhouette a PNG image using CSS except I am looking for the exact opposite.
You can do this using canvas with globalCompositeOperation set to destination-in. For example
var canvas = document.createElement('canvas');
canvas.width = 250;
canvas.height = 250;
var canvas_context = canvas.getContext("2d");
var img = new Image();
img.onload = function(){
var msk = new Image();
msk.onload = function(){
canvas_context.drawImage(img, 0, 0);
canvas_context.globalCompositeOperation = "destination-in";
canvas_context.drawImage(msk, 0, 0);
canvas_context.globalCompositeOperation = "source-over";
};
msk.src = 'silhouette.png';
}
img.src = 'pattern.jpg';
document.body.appendChild(canvas);