I am trying to size an image with canvas. My goal is to have an image of dimensions A x B sized to M x N without changing proportions - like CSS contain. For example, if the source image is 1000x1000 and the destination is 400x300, it should cut off a piece 100 pixels toll at the bottom, and that should correspond to 250 pixels in the source image.
My code is below:
const canvas = document.createElement('canvas');
const img = new Image();
img.src = promotedImage;
const FINAL_WIDTH = 400;
const FINAL_HEIGHT = 250;
const width = img.width;
const height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height, 0, 0, FINAL_WIDTH, FINAL_HEIGHT);
const finalImage = b64toFile(canvas.toDataURL("image/jpg"));
This is not working, like I want to, though. I am obviously using drawImage incorrectly. For me, if copies source to destination without sizing.
Is this because I need to size (change dimensions) for canvas prior to drawing? Please advise.
I have also tried something like Mozilla image upload. It does even scale the image, but it does not crop. Plus, it sizes a source square to the smaller target side, instead of clipping it.
Setting an image source is asynchronous, can be very fast, but often not fast enough to keep up with still-running code. Generally, to make them work reliably, you set an onload handler first and then set src. The canvas element defaults to 300x150 so would also need to be sized. (Canvas obeys CORS. .crossOrigin = '' sets us as anonymous and imgur has a permissive CORS policy. We wouldn't be able to convert the canvas to an image while using a third-party image in this snippet otherwise.)
const MAX_WIDTH = 400;
const MAX_HEIGHT = 300;
const img = new Image();
img.crossOrigin = '';
img.onload = () => {
const wRatio = MAX_WIDTH / img.width;
const hRatio = MAX_HEIGHT / img.height;
var width, height;
if(wRatio > hRatio) {
width = MAX_WIDTH;
height = wRatio * img.height;
}
else {
width = hRatio * img.width;
height = MAX_HEIGHT;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
//const finalImage = b64toFile(canvas.toDataURL("image/jpg"));
const imgElement = document.createElement('img');
imgElement.src = canvas.toDataURL('image/jpg');
document.body.appendChild(imgElement);
};
img.src = 'https://i.imgur.com/TMeawxt.jpeg';
img { border: 1px solid red; }
Related
thank you for taking a look.
What I am trying to do here is... load image from computer (by using FileReader), put img tag's src to the file. Then draw canvas with that image.
function previewFile(){
var preview = document.querySelector('img'); //selects the query named img
var file = document.querySelector('input[type=file]').files[0]; //sames as here
var reader = new FileReader();
reader.onload = function () {
preview.src = reader.result;
drawCanvas();
}
if (file) {
reader.readAsDataURL(file); //reads the data as a URL
} else {
preview.src = "sample.jpg";
}
}
previewFile(); //calls the function named previewFile
window.onload = function () {drawCanvas(); };
function drawCanvas() {
var img = document.querySelector("img");
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
ctx.filter = window.getComputedStyle(document.querySelector("img")).filter;
ctx.drawImage(img, 0, 0);
}
Problem is when I load another image file. It loads in the canvas, but not fit in the canvas size. It keeps the original image size and "part" of the image is shown in the canvas.
In order to solve it, I tried following:
ctx.drawImage(img, 0, 0, document.getElementById('canvas').width,
document.getElementById('canvas').height);
It works, however the image quality becomes really bad... since it is not original image size. It is adjusted to the certain height and forced to be resized.
All I want to do is... keep the original canvas size (width: some px; height: auto), so when I load the image, it fits to the canvas width and adjusted height.
Would you please help me? Thank you.
Added
I researched myself and found following:
I came up with an idea - change the image size first based on the current width of the canvas.
var img = document.querySelector("img");
var canvas = document.getElementById("image");
var ratio = img.height / img.width;
img.width = canvas.offsetWidth;
img.height = img.width * ratio;
Then draw with the edited image.
var ctx = canvas.getContext('2d');
ctx.filter = window.getComputedStyle(img).filter;
ctx.drawImage(img, 0, 0, img.width, img.height);
Then I found the problem. I checked "img.height" variable is 199px. But canvas's height becomes 150px somehow. I checked and there is no css applied to the canvas at all.
So this time, I set canvas width, before drawImage().
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.filter = window.getComputedStyle(img).filter;
ctx.drawImage(img, 0, 0);
And again, the original image coming back... and "part" of the image is shown in the canvas. The canvas's size is what I wanted though...
Thank you so much for taking a look the issue. Especially #Kaiido who tried to help. Thank you. I am really new to this stackoverflow... so I didn't know how to create demo.
I found the solution. Well... it was really basic knowledge of the Canvas,,, but I think many people will forget / miss following:
var ctx = canvas.getContext('2d');
ctx.canvas.width = 1000;
ctx.canvas.height = 1000;
ctx.drawImage(img, 0, 0, 800, 800);
ctx.canvas.width is canvas's width. ctx.canvas.height is canvas's height.
In drawImage, those 800s are width and height of the image will be drawn, not the canvas's size!! So if you set... let's say 1000, 1000 in drawImage, the image will be resized to the size and it will be drawn into the canvas. If it is bigger than the canvas size, it will show only "part" of your image.
So what I did was... get the div width size where I want to put my canvas. Then calculate the ratio of the image first ( ratio = image's height / image's width ). Then set canvas size to following ( ctx.canvas.width = div width size, ctx.canvas.height = div width size * ratio ). Then draw canvas with canva's width and height ( ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height) ).
Hope this helps someone new like me :) Thank you.
This is pretty simple!
try this.
getting the ratio, setting the height, drawing the image
var ratio = img.naturalWidth / img.naturalHeight;
var width = canvas.width;
var height = width / ratio;
ctx.drawImage(img, 0, 0, width, height);
this worked for me, try this.
see the line
var width = canvas.width;
you can replace the canvas.width by any other value.
I am currently trying to draw an image to canvas, I have this so far:
"use strict";
var debugging = true;
var canvas = document.getElementById('astoniaCanvas');
var ctx = canvas.getContext('2d');
function loadUI() {
var topOverlay = new Image();
topOverlay.src = "/images/00000999.png";
topOverlay.onload = function() {
ctx.drawImage(topOverlay, 0, 0, canvas.width, 10);
}
var bottomOverlay = new Image();
bottomOverlay.src = "/images/00000998.png";
if (debugging) {
console.log('Drawing');
}
}
loadUI();
That works fine, but the image loads and looks like this:
When it should look like this:
The dimensions of the good looking picture are 800x40.
If I remove the
canvas {
width: 100%;
height: 100%;
}
the image goes back to looking normal, how can I scale my canvas?
Any information would be great thanks.
You arent accounting for height. Canvas can be confusing when it comes to height/width vs clientHeight/clientWidth
When you create a canvas the css width and height has no bearing on the number of pixels the internal canvas contains. Unless specifically set a canvas comes with a width height of 300x150.
A trick I have used in the past is to use the clientWidth and a scale to set everything
"use strict";
var debugging = true;
var canvas = document.getElementById('astoniaCanvas');
var ctx = canvas.getContext('2d');
function loadUI() {
var topOverlay = new Image();
topOverlay.onload = function() {
// use a scale between the image width and the canvas clientWidth
var scale = topOverlay.width / canvas.clientWidth;
var newWidth = canvas.clientWidth;
var newHeight = topOverlay.height * scale;
// resize canvas based on clientWidth
canvas.width = newWidth;
canvas.height = newHeight;
ctx.drawImage(topOverlay, 0, 0, newWidth, newHeight);
}
topOverlay.src = "http://i.stack.imgur.com/AJnjh.png";
// var bottomOverlay = new Image();
// bottomOverlay.src = "/images/00000998.png";
if (debugging) {
console.log('Drawing');
}
}
loadUI()
<canvas id="astoniaCanvas" style="width: 100%"></canvas>
On load of my image I:
var img = new Image();
img.src = e.target.result;
var canvas = $('#test-canvas')[0];
$('#test-canvas').width(img.width);
$('#test-canvas').height(img.height);
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
But the image is drawn larger than it's original size? I've tried a few images, same problem.
What's the fix?
jQuery will resize the element through CSS, which won't actually change the canvas internal height and width. This will resize the actual canvas element.
var canvas = document.GetElementById('test-canvas');
canvas.width = img.width;
canvas.height = img.height;
jsFiddle (http://jsfiddle.net/L0drfwgL/) just show it drawing it to scale, and resizing the canvas item itself.
It looks like you are pulling the image from an image element on load. You can use the src from the image element rather than recreating an image object and then get the image width/height from the element to draw the image to canvas.
<script>
$(document).ready(function(){
$('#testImg').on('load',function(){
var image = document.getElementById('testImg');
var canvas = document.getElementById('test-canvas');
canvas.width = image.width;
canvas.height = image.height;
var ctx=canvas.getContext("2d");
ctx.drawImage(image,0,0);
})
});
</script>
/** with vue3 -(to fix drawing canvas image is larger than actual image size **/
const captureImage = () => {
let width = video.value.getBoundingClientRect().width;
let height = video.value.getBoundingClientRect().height;
let context = canvas.value.getContext('2d')
canvas.value.width = width
canvas.value.height = height
context.drawImage(video.value, 0, 0, width, height)
}
I'm trying to load an image from a URL into a HTML canvas at a 1:1 scale. I load the image, and set the canvas DOM element to the appropriate dimensions, but for some reason the image in the canvas is significantly upscaled and therefore only the top left hand corner is drawn.
This is demonstrated by the following JSFiddle: http://jsfiddle.net/KdrYr/1/
var img = new Image();
var cv = document.getElementById('thecanvas');
img.src = 'http://www.photographyblogger.net/wp-content/uploads/2009/12/Picture-in-a-picture4.jpg';
img.onload = function() {
var ctx = cv.getContext('2d');
cv.style.width = img.width + 'px';
cv.style.height = img.height + 'px';
ctx.drawImage(img, 0, 0);
};
For example, I'm trying to draw this (sorry about the big images :/)
But end up with this
What could be causing this?
You need to assign the canvas actual width and height, not via its style:
cv.width = img.width;
cv.height = img.height;
Live test case.
As for the why, well, it's explained already here.
Is there a way to set a custom DPI/PPI when creating an image using the HTML5 canvas? I know how can I draw on the canvas and export it as an image, but how can I make sure the output image is of certain DPI/PPI. I guess using SVG elemnts to draw on the canvas is a way, but wouldn't that be flattened out when I export the whole canvas as an image? Or calculating the device DPI and then scaling the image to meet my DPI requirement, but that doesn't seem like the correct solution.
Canvases have two different 'sizes': their DOM width/height and their CSS width/height. You can increase a canvas' resolution by increasing the DOM size while keeping the CSS size fixed, and then using the .scale() method to scale all of your future draws to the new bigger size. Here's an example:
function changeResolution(canvas, scaleFactor) {
// Set up CSS size.
canvas.style.width = canvas.style.width || canvas.width + 'px';
canvas.style.height = canvas.style.height || canvas.height + 'px';
// Resize canvas and scale future draws.
canvas.width = Math.ceil(canvas.width * scaleFactor);
canvas.height = Math.ceil(canvas.height * scaleFactor);
var ctx = canvas.getContext('2d');
ctx.scale(scaleFactor, scaleFactor);
}
The canvas default resolution is 96dpi (CSS inches, not based on the actual screen). So a scaleFactor of 2 gives 192dpi, 3 is 288dpi, etc. In fact, here's a version that should give your desired DPI:
function setDPI(canvas, dpi) {
// Set up CSS size.
canvas.style.width = canvas.style.width || canvas.width + 'px';
canvas.style.height = canvas.style.height || canvas.height + 'px';
// Resize canvas and scale future draws.
var scaleFactor = dpi / 96;
canvas.width = Math.ceil(canvas.width * scaleFactor);
canvas.height = Math.ceil(canvas.height * scaleFactor);
var ctx = canvas.getContext('2d');
ctx.scale(scaleFactor, scaleFactor);
}
Have fun! Note that both these code samples can only be used once per canvas, they assume the current DOM size is the original (they could be tweaked to change that). Also the rescaling needs to happen before you do any drawing on the canvas. Thanks to this post for the method and information!
Edit: Here is a more robust function that will scale future draws and maintain existing canvas contents. This can be called to rescale multiple times.
function setDPI(canvas, dpi) {
// Set up CSS size.
canvas.style.width = canvas.style.width || canvas.width + 'px';
canvas.style.height = canvas.style.height || canvas.height + 'px';
// Get size information.
var scaleFactor = dpi / 96;
var width = parseFloat(canvas.style.width);
var height = parseFloat(canvas.style.height);
// Backup the canvas contents.
var oldScale = canvas.width / width;
var backupScale = scaleFactor / oldScale;
var backup = canvas.cloneNode(false);
backup.getContext('2d').drawImage(canvas, 0, 0);
// Resize the canvas.
var ctx = canvas.getContext('2d');
canvas.width = Math.ceil(width * scaleFactor);
canvas.height = Math.ceil(height * scaleFactor);
// Redraw the canvas image and scale future draws.
ctx.setTransform(backupScale, 0, 0, backupScale, 0, 0);
ctx.drawImage(backup, 0, 0);
ctx.setTransform(scaleFactor, 0, 0, scaleFactor, 0, 0);
}
You cannot (ugh) access the DPI of a display of the current web page in any browser:
Detecting the system DPI/PPI from JS/CSS?
For printing: You most likely cannot set the DPI of exported <canvas> image (PNG, JPEG) using browser standard functions. However, if you use a pure Javascript encoder image encoder you are free to create any sort of binary file you wish and manually adjust the DPI value embedded int he binary.
https://gist.github.com/1245476
If you just want to set the dpi of the PNG (ie not increase the number of pixels) then this library lets you set the pHYs chunk (amongst other things):
https://github.com/imaya/CanvasTool.PngEncoder
Minimal example to export an HTML5 canvas to base64-encoded PNG:
// convert dots per inch into dots per metre
var pixelsPerM = dpi * 100 / 2.54;
var param = {
bitDepth : 8,
colourType : 2,
filterType : 0,
height : canvas.height,
interlaceMethod : 0,
phys : {
unit : 1,
x : pixelsPerM,
y : pixelsPerM
},
width : canvas.width
};
var array = canvas.getContext('2d').getImageData(0, 0, canvas.width,
canvas.height).data;
var png = new window.CanvasTool.PngEncoder(array, param).convert();
var base64 = 'data:image/png;base64,' + btoa(png);
Use the library changedpi:
npm install changedpi --save
Also see
https://github.com/shutterstock/changeDPI
https://github.com/hongru/canvas2image
Example code that also allows to adapt the px size and resolution for png or jpg export:
Canvas2Image.saveAsImage('fileName.png', canvas, 2000, 3000, 300, 'png');
-
import Url from './url';
import * as ChangeDpi from 'changeDPI';
export default class Canvas2Image {
static saveAsImage(fileName, canvas, width, height, dpi, type) {
type = this._fixType(type);
canvas = this._scaleCanvas(canvas, width, height);
let dataUrl = canvas.toDataURL(type);
let dataUrlWithDpi = ChangeDpi.changeDpiDataUrl(dataUrl, dpi)
dataUrlWithDpi = dataUrlWithDpi.replace(type, 'image/octet-stream');
Url.download(fileName, dataUrlWithDpi);
}
static _fixType(type) {
type = type.toLowerCase().replace(/jpg/i, 'jpeg');
const r = type.match(/png|jpeg|bmp|gif/)[0];
return `image/${r}`;
}
static _scaleCanvas(canvas, width, height) {
const w = canvas.width;
const h = canvas.height;
if (width === undefined) {
width = w;
}
if (height === undefined) {
height = h;
}
const retCanvas = document.createElement('canvas');
const retCtx = retCanvas.getContext('2d');
retCanvas.width = width;
retCanvas.height = height;
retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);
return retCanvas;
}
}
-
export default class Url {
static download(fileName, url) {
const element = document.createElement('a');
element.setAttribute('href', url);
element.setAttribute('download', fileName);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
static createUrlForBlob(blob) {
return this._URL.createObjectURL(blob);
}
static clearBlobUrl(blobUrl) {
this._URL.revokeObjectURL(blobUrl);
}
static get _URL() {
return window.URL || window.webkitURL || window;
}
}