Higher DPI graphics with HTML5 canvas - javascript

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;
}
}

Related

Resize Canvas html to fit the inner content

I am trying to record audio and draw the bars on canvas. But I am stuck on resizing the canvas according to the length of bars that are there. Following is the JS for all the work that is being done. I tried adding function to resize canvas, and override the canvas width but none is working.
css width:auto; is also not working. Following is the function That I have used to resize canvas according the to BARS length that are drawn.
I tried to override the canvas width inside stop function [Current Visual of Bars inside canvas][1]
Following code can be tested in this codepen https://codepen.io/smashingmag/pen/qBVzRaj
function resizeCanvas() {
CANVAS.width = BARS.length;
console.log(CANVAS.width);
}
resizeCanvas();
//Getting and initializing canvas from html to create bars
const CANVAS = document.querySelector('canvas')
const DRAWING_CONTEXT = CANVAS.getContext('2d')
//setting canvas height and width and bars configuration
CANVAS.width = CANVAS.offsetWidth
CANVAS.height = CANVAS.offsetHeight
const CONFIG = {
fft: 2048,
show: true,
duration: 0.8,
fps: 100,
barWidth: 0.1,
barMinHeight: 0.01,
barMaxHeight: 0.3,
barGap: 0.01,
}
//here I am creating bars
const addBar = (volume = 100) => {
const BAR = {
x: CANVAS.width + CONFIG.barWidth / 2,
// Note the volume is 0
size: gsap.utils.mapRange(
50,
150,
CANVAS.height * CONFIG.barMinHeight/3,
CANVAS.height * CONFIG.barMaxHeight*3
)(volume),
}
const drawBars = () => {
DRAWING_CONTEXT.clearRect(0, 0, CANVAS.width, CANVAS.height)
for (const BAR of BARS) {
drawBar(BAR)
}
}
STOP.addEventListener('click', () => {
if (recorder) recorder.stop()
AUDIO.setAttribute('controls', true)
AUDIO_CONTEXT.close()
timeline.pause()
SCRUB(START_POINT)
var delayInMilliseconds = 1000; //1 second
setTimeout(function() {
//Here i am getting the canvas image on console
const img = CANVAS.toDataURL("image/png");
console.log(img);
}, delayInMilliseconds);
})
drawBars()
[1]: https://i.stack.imgur.com/JBBjT.png
I looked at your codepen and I think yours has a simple solution
function resizeCanvas() {
CANVAS.style.width = BARS.length + "px";
CANVAS.width = BARS.length;
}
This is just setting the CSS width property using javascript.
Doing CANVAS.width = 500 does not overwrite the CSS which tells it to be 300px. So it was still a CSS problem.

Resizing image with HTML5 canvas

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; }

How to get original image from webcam in js?

How can I get the webcam video on the left to be in the same format as the right where the image is drawn?
Had this problem for a while and can't figure it out!
here's the code:
document.getElementById('capture').addEventListener('click', function() {
var w = video.videoWidth;
var h = video.videoHeight;
canvas.width = w;
canvas.height = h;
context.drawImage(video,0,0,w,h);
canvas.style.display='block';
});
You are using the size of your video html element instead of the size of the stream. That's why the photo is stretched. You should use the size of the video track.
If you specified it while calling MediaDevices.getUserMedia use those values.
Otherwise retrieve them from the video track (MediaStreamTrack.getSettings())
video.videoWidth is not the real width of video so you get different ratio on new canvas. Try this:
var videoRatio = video.videoWidth / video.videoHeight;
var width = video.offsetWidth, height = video.offsetHeight;
var elementRatio = width/height;
if(elementRatio > videoRatio) width = height * videoRatio;
else height = width / videoRatio;
canvas.width = width;
canvas.height = height;
context.drawImage(video,0,0,width,height);
canvas.style.display='block';

If I resize my canvas, my createJS text becames blurry

I'm using createJS to drawn inside the canvas. I have my canvas set to occupy the browser window maintaining aspect ratio using the resize() function.
This is the code:
mytext = new createjs.Text("Lorem ipsum dolor sit amet 2","19px Calibri","#073949");
mytext.x = 450
mytext.y = 300;
stage.addChild(mytext);
resize = function() {
var canvas = document.getElementById('canvas');
var canvasRatio = canvas.height / canvas.width;
var windowRatio = window.innerHeight / window.innerWidth;
var width;
var height;
if (windowRatio < canvasRatio) {
height = window.innerHeight - 35;
width = height / canvasRatio;
} else {
width = window.innerWidth;
height = width * canvasRatio;
}
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
}()
What happens is that the text gets blurry (decrease of quality) when the canvas resizes.
http://i.imgur.com/RQOSajs.png
vs
http://i.imgur.com/Xwhf5c5.png
How can I solve this issue?
Since you are using CreateJS, you can simply resize the canvas, and scale the entire stage to redraw everything at the new size:
// just showing width to simplify the example:
var newWidth = 800;
var scale = newWidth/myCanvas.width;
myCanvas.width = newWidth;
myStage.scaleX = myStage.scaleY = scale;
myStage.update(); // draw at the new size.
#Adam's answer is correct as far as scaling the canvas goes. You do NOT want to scale with CSS, as it will stretch your canvas instead of changing its pixel dimensions. Set the width and height of the canvas using JavaScript instead.
stage.canvas.width = window.innerWidth;
stage.canvas.height = window.innerHeight;
As you stated in your comment, this will only change the canvas size, and not reposition or scale your content. You will have to do this manually. This is fairly simple. Generally, I recommend putting your "resize" listener in the JavaScript in your HTML file, rather than on a frame script.
First, determine the scale, based on the size of the window and the size of your content. You can use the exportRoot.nominalBounds.width and exportRoot.nominalBounds.height which is the bounds of the first frame. If you want to scale something else, use its nominalBounds instead.
Note that nominalBounds is appended to all MovieClips exported from Flash/Animate. If you enable multi-frame bounds, and want to use those, you will have to modify your approach.
The main idea is to use the original, unscaled size of your contents.
var bounds = exportRoot.nominalBounds;
// Uses the larger of the width or height. This will "fill" the viewport.
// Change to Math.min to "fit" instead.
var scale = Math.max(window.innerWidth / bounds.width, window.innerHeight / bounds.height);
exportRoot.scaleX = exportRoot.scaleY = scale;
You can then center it if you want.
exportRoot.x = *window.innerWidth - bounds.width*scale)/2;
exportRoot.y = *window.innerHeight - bounds.height*scale)/2;
Here is a quick sample of a responsive canvas using a simple shape as the scaling contents:
http://jsfiddle.net/lannymcnie/4yy08pax/
Doing this with Flash/Animate CC export has come up a few times, so it is on my list of future EaselJS demos to include on createjs.com, and in the EaselJS GitHub.
I hope this helps.
Take a look at my jsfiddle : https://jsfiddle.net/CanvasCode/ecr7o551/1/
Basically you just store the original canvas size and then use that to work out new positions and sizes
html
<canvas id="canvas" width="400" height="400">
Canvas was unable to start up.
</canvas>
<button onclick="resize()">Click me</button>
javascript
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var originalWidth = canvas.width;
var originalHeight = canvas.height;
render = function()
{
context.fillStyle = "#DDD";
context.fillRect(0,0, originalWidth * (canvas.width / originalWidth), originalHeight * (canvas.height / originalHeight));
context.fillStyle = "#000";
var fontSize = 48 * (canvas.width / originalWidth);
context.font = fontSize+"px serif";
context.fillText("Hello world", 100 * (canvas.width / originalWidth), 200 * (canvas.height / originalHeight));
}
resize = function()
{
canvas.width = 800;
canvas.height = 600;
render();
}
render();
The HTML5 canvas element works with two different sizes
Visual size on screen, controlled via CSS, like you're setting with canvas.style.width/height
Size of pixel buffer for the drawing, controlled via numeric width and height pixel attributes on the canvas element.
The browser will stretch the buffer to fit the size on screen, so if the two values are not 1:1 ratio text will look blurry.
Try adding the following lines to your code
canvas.width = width;
canvas.height = height;
I created a function to resize all the elements on the screen after resizing the canvas. It saves the initial coordinates and scales for the elements with the original width of 900 px and then it changes them according to the current width ratio relative to the original width ratio. The text isn't blurry/bad quality anymore.
resize = function() {
var canvas = document.getElementById('canvas');
var canvasRatio = canvas.height / canvas.width;
var windowRatio = window.innerHeight / window.innerWidth;
var width;
var height;
if (windowRatio < canvasRatio) {
height = window.innerHeight;
width = height / canvasRatio;
} else {
width = window.innerWidth;
height = width * canvasRatio;
}
canvas.width = width;
canvas.height = height;
reziseElements();
};
reziseElements = function()
{
var canvrat = canvas.width / 900;
//Simplified
stage.scaleX = stage.scaleY = canvrat;
//Old Version
/*for (i=0; i<stage.numChildren ;i++)
{
currentChild = stage.getChildAt(i);
if (typeof currentChild.oscaleX == 'undefined')
{
currentChild.oscaleX = currentChild.scaleX;
currentChild.ox = currentChild.x;
currentChild.oy = currentChild.y;
}
}
for (i=0; i<stage.numChildren ;i++)
{
currentChild = stage.getChildAt(i);
currentChild.scaleX = currentChild.scaleY = currentChild.oscaleX * canvrat
currentChild.x = currentChild.ox * canvrat
currentChild.y = currentChild.oy * canvrat
} */
}

Windows 8 Javascript set Canvas to fill screen

I'm trying to create a HTML5 canvas using Windows 8 / WinJS. Here's what I have:
var body = document.getElementById("body");
var canvas = document.createElement("canvas");
canvas.id = "myCanvas";
body.appendChild(canvas);
var canvasContext = canvas.getContext("2d");
canvas.width = document.width;
canvas.height = document.height;
canvasContext.fillStyle = "#f00";
canvasContext.fillRect(canvas.width - 100, canvas.height - 50, 100, 50);
console.log("canvas width " + canvas.width + ", height " + canvas.height);
Most of this is directly from a tutorial, but for all the tutorials I've seen, the width and height are set to hard coded numbers, for example:
canvas.width = 800;
canvas.height = 600;
Admittedly that does work, but I want the canvas to be as big as the screen, whatever the resolution. How do I achieve this?
You can just use the viewport with and viewport height CSS values to do this (see some information on these here)
In this specific case you could just do:
canvas.style.width = "100vw";
canvas.style.height = "100vh";
The advantage of this solution is that you don't have to load 3rd party libraries, e.g. JQuery to size to the full width / height.
By "screen", do you mean the browser window size?
If so, you can use window.outerWidth and window.outerHeight instead of document.width and document.height. Or if you use jQuery: $(window).width() and $(window).height().
Also, you want to listen to the resize event on the window so that you can reinitialize the canvas when the browser is resized.

Categories

Resources