Why is HTML5 Canvas blurry? - javascript

I have a canvas that is supposed to fill 100% of its parent div and scale height based on a ratio. The image that is placed in it is supposed to fill the canvas's height while scaling its width so it doesn't get distorted. That seems to be working but everything on the canvas is really blurry. Why is this happening and how might I display crisp images, shapes and text?
const photo = 'https://docplayer.net/docs-images/54/10835314/images/page_1.jpg';
const canvasRef = document.getElementById('canvas');
const mugRatioWidth = 2681;
const mugRatioHeight = 1110;
const canvasRatio = mugRatioHeight / mugRatioWidth;
const img = new Image();
const ctx = canvasRef.getContext("2d");
canvasRef.style.width = '100%';
img.src = photo;
img.onload = () => {
const hRatio = canvasRef.width / img.width ;
const vRatio = canvasRef.height / img.height ;
const ratio = Math.min ( hRatio, vRatio );
const centerShift_x = ( canvasRef.width - img.width*ratio ) / 2;
const centerShift_y = ( canvasRef.height - img.height*ratio ) / 2;
ctx.clearRect(0,0,canvasRef.width, canvasRef.height);
ctx.drawImage(img, 0,0, img.width, img.height, centerShift_x,centerShift_y,img.width*ratio, img.height*ratio);
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
ctx.stroke();
ctx.fillText("Text example!", 10, 90);
}
<canvas id='canvas'/>

Related

Createjs: How to pixelate bitmaps?

I have a very specific project I'm working on and after 2 weeks the best option seems to be using a bitmap within an empty movie clip. It's perfect apart from one issue - I can't figure out how to pixelate the image.
Here is my code so far:
init_image = () => {
props.image = null
_.holder_grid.bitmap = null
props.image = new Image()
props.image.src = "images/myImage.jpg"
props.image.onload = function() {
stage.holder_grid.bitmap = new createjs.Bitmap(props.image)
stage.holder_grid.bitmap.scale = props.image_scale
stage.holder_grid.addChild(_.holder_grid.bitmap);
const coords = redraw.get_centre()
stage.holder_grid.bitmap.x = coords.x
stage.holder_grid.bitmap.y = coords.y
settings.update()
}
}
init_image()
Its all working as I want but I can't figure out how to pixelate the image.
I found one solution where I draw the image using canvas.context.drawImage() but due to the parameters of the project it's not adequate. That worked like so:
const canvas = document.getElementById("canvas2")
const base = document.getElementById("canvas")
const ctx = canvas.getContext("2d")
const image = new Image()
props.image = image
image.src = "images/myImage.jpg"
image.onload = function() {
const width = base.clientWidth
const height = base.clientHeight
canvas.width= width
canvas.height= height
const scale= props.image_scale
const x = (ctx.canvas.width - image.width * scale) / 2
const y = (ctx.canvas.height - image.height * scale) / 2
//draws tiny
const size = props.pixelate/1
const w = canvas.width * size
const h = canvas.height * size
ctx.drawImage(image, 0, 0, w, h);
// turn off image aliasing
ctx.msImageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
// enlarge the minimized image to full size
ctx.drawImage(canvas, 0, 0, w, h, x, y, image.width * scale, image.height * scale);
}
So basically you draw it small then use that small instance as the image source to draw it bigger and viola, it's pixelated.... but due to other issues I've encountered doing this I cannot use this method.
Can anyone help me with this issue please?
The pixelation in your example is accomplished by drawing the original image at a lower resolution to an html <canvas> element. With createJS however you don't have built-in support for manipulating the sources of it's own Bitmap object.
There's hope though. Besides URL's to images, the constructor to a Bitmap also accepts a <canvas> element. So the trick here is loading the image, preparing - thus pixelating it - using a temporary <canvas> and once it's done, pass this element to the Bitmap constructor.
For example:
let stage = new createjs.Stage("canvas");
function pixelate(src) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let image = new Image();
image.crossOrigin = "anonymous";
image.onload = (e) => {
canvas.width = e.target.width;
canvas.height = e.target.height;
const width = e.target.width;
const height = e.target.height;
canvas.width = width;
canvas.height = height;
const scale = 1;
const x = (ctx.canvas.width - image.width * scale) / 2;
const y = (ctx.canvas.height - image.height * scale) / 2;
const size = 0.125 / 1;
const w = canvas.width * size;
const h = canvas.height * size;
ctx.drawImage(e.target, 0, 0, w, h);
ctx.msImageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(canvas, 0, 0, w, h, x, y, e.target.width * scale, e.target.height * scale);
let bitmap = new createjs.Bitmap(canvas);
stage.addChild(bitmap);
stage.update();
}
image.src = src;
}
pixelate("https://api.codetabs.com/v1/proxy?quest=https://picsum.photos/id/237/400/300");
<script src="https://code.createjs.com/1.0.0/createjs.min.js"></script>
<canvas id="canvas" width="400" height="300"></canvas>

image doesn't cover the full area of the canvas

I want to stretch the image to the size of the canvas. But the image takes its original size instead of taking the canvas size.Any help is appreciated.
var canvas = document.createElement("canvas");
canvas.setAttribute("width", "100px");
canvas.setAttribute("height", "180px");
//alert(canvas.height);
myImg.width = canvas.width;
myImg.height = canvas.height;
var ctx44 = canvas.getContext("2d");
ctx44.drawImage(myImg, 0, 0, canvas.width, canvas.height,0,0,canvas.width,canvas.height);
// image gets displayed in its original size instead of taking the size of the canvas
The 2nd ~ 4th parameters of drawImage represent the source rectangle. That is, a rectangle from where your image will be cropped, before being applied to the destination, resized by the rectangle defined in the following rectangle.
Here is a simple demo:
var img = new Image();
img.onload = e => {
const source = document.getElementById('source');
const destination = document.getElementById('destination');
const imgWIDTH = source.width = destination.width = 300;
const imgHEIGHT = source.height = destination.height = 300;
const src_x = source.getContext('2d');
const dest_x = destination.getContext('2d');
src_x.drawImage(img, 0, 0, imgWIDTH, imgHEIGHT); // simple copy to source canvas
const source_rect = {
x: 20,
y: 20,
w: 150,
h: 150
};
const dest_rect = {
x: 0,
y: 0,
w: 300,
h: 300
};
destination.getContext('2d')
.drawImage(source,
source_rect.x,
source_rect.y,
source_rect.w,
source_rect.h,
dest_rect.x,
dest_rect.y,
dest_rect.w,
dest_rect.h
);
// show the source rectangle on the 'source' canvas
src_x.strokeRect(
source_rect.x,
source_rect.y,
source_rect.w,
source_rect.h,
);
};
img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"
<canvas id="source"></canvas>
<canvas id="destination"></canvas>
So in your case, if your original image is smaller than the 100x180px of your canvas, you are defining the source rectangle to be outside of the source image, this will then result in no apparent changes from the original image.
What you probably wanted was
ctx44.drawImage(myImg, 0, 0, myImg.naturalWidth, myImg.naturalHeight,0,0,canvas.width,canvas.height);
which is the same as the 5 param version
ctx44.drawImage(myImg,0,0,canvas.width,canvas.height);
var myImg = new Image();
myImg.onload = function(e) {
var canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 180;
var ctx44 = canvas.getContext("2d");
ctx44.drawImage(myImg, 0,0,canvas.width,canvas.height);
document.body.appendChild(myImg);
document.body.appendChild(canvas);
};
myImg.src = "https://i.stack.imgur.com/LbM0l.jpg?s=32&g=1"

Rotating dynamic canvas image with javascript

I have the following code that allows the user to upload an image which gets put into a canvas, but once it has been drawn I want users to be able to rotate the image with the click of a button, but I don't know how to re-access the image object to be able to rotate the canvas. The code below is what works:
onFilePicked (e) {
const files = e.target.files;
for (let file of files) {
if(file !== undefined) {
let image = {
thumbnail: '/img/spinner.gif'
};
this.images.push(image);
this.loadImage(file, image);
}
}
},
loadImage(file, image) {
const fr = new FileReader();
fr.readAsDataURL(file);
fr.addEventListener('load', () => {
var img = new Image();
img.src = fr.result;
img.onload = () => {
image.thumbnail = this.resizeImage(img, 400, 300);
image.large = this.resizeImage(img, 1280, 960);
}
})
},
resizeImage(origImg, maxWidth, maxHeight) {
let scale = 1;
if (origImg.width > maxWidth) {
scale = maxWidth / origImg.width;
}
if (origImg.height > maxHeight) {
let scale2 = maxHeight / origImg.height;
if (scale2 < scale) scale = scale2;
}
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = origImg.width * scale;
canvas.height= origImg.height * scale;
ctx.drawImage(origImg, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL("image/jpeg");
},
And seen below is the function I built out to rotate the image- it works in that if I replace the code inside of the resizeImage function with the code below that the image is drawn in a way that is rotated correctly, but I don't know how to access the origImg object to be able to redraw the canvas in a separate function.
rotateImage(origImg, maxWidth, maxHeight){
let scale = 1;
if (origImg.width > maxWidth) {
scale = maxWidth / origImg.width;
}
if (origImg.height > maxHeight) {
let scale2 = maxHeight / origImg.height;
if (scale2 < scale) scale = scale2;
}
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = origImg.height * scale;
canvas.height= origImg.width * scale;
ctx.translate(canvas.width, 0);
ctx.rotate(90 * Math.PI / 180);
ctx.drawImage(origImg, 0, 0, canvas.height, canvas.width);
return canvas.toDataURL("image/jpeg");
},
Running this function as-is triggers the following console error:
Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)'
How do I get/reuse the origImg object from the resizeImage function so I can use it in the rotateImage function?
you can try with this code:
var myCanvas = document.getElementById('my_canvas_id');
var ctx = myCanvas.getContext('2d');
var img = new Image;
img.onload = function(){
ctx.drawImage(origImg,0,0); // Or at whatever offset you like
};
And apply your code insede onload function of img and finally transform img source to date URL
Try this code, based on one file picker, two buttons. The first one resize image and the second one rotete the image
function resizeImg()
{
var oPicker = document.getElementById('avatar');
var oImage = document.getElementById('imgOut');
var file = oPicker.files[0];
const fr = new FileReader();
fr.readAsDataURL(file);
fr.addEventListener('load', () => {
var img = new Image();
img.src = fr.result;
img.onload = () => {
oImage.thumbnail = this.resizeImage(img, 400, 300);
oImage.src = this.resizeImage(img, 1280, 960);
}
})
}
function rotateImg()
{
var imgOut = document.getElementById('imgOut');
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let scale = 1;
canvas.width = imgOut.height * scale;
canvas.height= imgOut.width * scale;
ctx.translate(canvas.width, 0);
ctx.rotate(90 * Math.PI / 180);
ctx.drawImage(imgOut, 0, 0, canvas.height, canvas.width);
imgOut.src = canvas.toDataURL("image/jpeg");
}
function resizeImage(origImg, maxWidth, maxHeight) {
let scale = 1;
if (origImg.width > maxWidth) {
scale = maxWidth / origImg.width;
}
if (origImg.height > maxHeight) {
let scale2 = maxHeight / origImg.height;
if (scale2 < scale) scale = scale2;
}
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = origImg.width * scale;
canvas.height= origImg.height * scale;
ctx.drawImage(origImg, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL("image/jpeg");
}
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Image test</h1>
<img src="" id="imgOut" />
<label for="avatar">Choose a profile picture:</label>
<input type="file" id="avatar" name="avatar" accept="image/png, image/jpeg">
<input type="button" id="resImg" onclick="resizeImg()" value="Resize" />
<input type="button" id="rotImg" onclick="rotateImg()" value="Rotate" />
</body>
</html>
As you have a part in onFilePicked() where you store something about the images:
let image = {
thumbnail: '/img/spinner.gif'
};
this.images.push(image);
and later update the same objects in loadImage() (well, an event handler in it) as
image.thumbnail = this.resizeImage(img, 400, 300);
image.large = this.resizeImage(img, 1280, 960);
It could be simply extended to
image.original = img;
image.thumbnail = this.resizeImage(img, 400, 300);
image.large = this.resizeImage(img, 1280, 960);
Starting from this point, the objects in your images array would have an original field, storing the original, non-resized variant of the image.

How can I implement nearest neighbor interpolation in a JavaScript matrix operation?

The following code uses GPU.js, a wrapper for WebGL that makes it easy to run matrix operations with WebGL by simply writing JS functions, I render an image on the canvas, but I want to resize it. I've read about nearest neighbor interpolation but I'm confused on how to implement it. I've already set up the resize kernel, all that's left to be done is interpolation logic.
Notes:
the current indexes are available within the kernel function as this.thread.x, this.thread.y, and this.thread.z, depending on the dimensions of the matrix your kernel is computing.
You'll notice the canvas is sized weird. This is a "feature" of GPU.js related to WebGL texture handling (I think they're planning on ironing that out later).
Edit: Made progress but not quite perfected: http://jsfiddle.net/0dusaytk/59/
const canvas1 = document.createElement("canvas");
const context1 = canvas1.getContext("webgl2");
document.body.appendChild(canvas1);
const canvas2 = document.createElement("canvas");
const context2 = canvas2.getContext("2d");
const gpu = new GPU({
canvas: canvas1,
webGl: context1
});
const image = new Image();
image.crossOrigin = "Anonymous";
image.src = "https://i.imgur.com/sl2J6jm.jpg";
image.onload = function() {
const length = 4 * image.height * image.width;
const gpuTexturize = gpu
.createKernel(function(sprite) {
return sprite[this.thread.x];
})
.setOutput([length])
.setOutputToTexture(true);
const gpuResize = gpu
.createKernel(function(sprite, w, h) {
return sprite[this.thread.x];
})
.setOutput([length])
.setOutputToTexture(true);
const gpuRender = gpu
.createKernel(function(sprite, w, h) {
var index = this.thread.x * 4 + (h - this.thread.y) * w * 4;
var r = sprite[index];
var g = sprite[index + 1];
var b = sprite[index + 2];
this.color(r / 255, g / 255, b / 255);
})
.setOutput([image.width, image.height])
.setGraphical(true);
canvas2.width = image.width;
canvas2.height = image.height;
context2.drawImage(image, 0, 0);
const imgData = context2.getImageData(
0,
0,
canvas2.width,
canvas2.height
);
const texture = gpuTexturize(imgData.data);
const resized = gpuResize(texture, 100, 100);
gpuRender(resized, image.width, image.height);
};
body {
background-color: #3a4659;
}
canvas {
background-color: #bcc8db;
}
<script src="https://cdn.jsdelivr.net/npm/gpu.js#latest/dist/gpu-browser.min.js"></script>
I added the second canvas with pixelated render in order to compare this implementation with the browser css default method.
Demo: https://codepen.io/rafaelcastrocouto/pen/pOaaEd
const scale = 4;
// canvas1 will be handled by the gpu
const canvas1 = document.createElement("canvas");
canvas1.className = 'c1';
const context1 = canvas1.getContext("webgl2");
const gpu = new GPU({
canvas: canvas1,
webGl: context1
});
document.body.appendChild(canvas1);
// canvas2 will render the image
const canvas2 = document.createElement("canvas");
canvas2.className = 'c2';
const context2 = canvas2.getContext("2d");
document.body.appendChild(canvas2);
canvas2.style.transform = 'scale('+scale+')';
// load the image
const image = new Image();
image.crossOrigin = "Anonymous";
image.src = "https://placeholdit.imgix.net/~text?txtsize=20&txtpad=1&bg=000&txtclr=fff&txt=64x32&w=64&h=32";
image.onload = function() {
// render image to canvas2
canvas2.width = image.width;
canvas2.height = image.height;
context2.drawImage(image, 0, 0);
// scale imageData
const imgData = context2.getImageData(0,0,image.width,image.height);
const gpuRender = gpu
.createKernel(function(sprite) {
var x = floor(this.thread.x/this.constants.s) * 4;
var y = floor(this.constants.h - this.thread.y/this.constants.s) * 4 * this.constants.w;
var index = x + y;
var r = sprite[ index ]/255;
var g = sprite[index+1]/255;
var b = sprite[index+2]/255;
var a = sprite[index+3]/255;
this.color(r, g, b, a);
},{
constants: {
w: image.width,
h: image.height,
s: scale
}
})
.setOutput([image.width*scale, image.height*scale])
.setGraphical(true);
gpuRender(imgData.data);
};
body {
background-color: #3a4659;
}
canvas {
background-color: #bcc8db;
display: block;
margin: 4em;
}
.c2 {
image-rendering: pixelated;
transform-origin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/gpu.js#latest/dist/gpu-browser.min.js"></script>

crop an image using canvas - result is small image

I'm trying to crop an image using canvas.
Original image is 2217 x 790.
Loading on page it is scaled to 1515 x 540
Canvas is 960 x 540.
Both image and canvas are on center of screen, so aligned horizontally.
I need to crop central area - 960 x 540.
var img = document.getElementById("imgt");
var canvas = document.getElementById("canvasa");
var ctx = canvas.getContext("2d");
var a = $('#imgt').width() - 960;
var a = a/2; // this is 277.7...
ctx.drawImage(img, a, 0, 960, 540, 0, 0, 960, 540);
//also tried:
ctx.drawImage(img, 0, 0, 960, 540, 0, 0, 960, 540);
var newimg = new Image();
newimg.src = canvas.toDataURL('image/jpeg');
var dl = document.createElement("a");
dl.href = canvas.toDataURL("image/jpeg");
dl.download = true;
document.body.appendChild(dl);
dl.click();
Downloading newimg what I see - it is 300 x 150 !
See my comment for differences between width attribute and width as style. They are not exactly the same. Besides, I just made a fiddle since you didn't and I don't get the same behavior! The downloaded image is 960 * 540
HERE - fires with delay
ONLOAD
setTimeout(function(){
var canvas = document.createElement("canvas");
canvas.width = "960";
canvas.height="540";
var ctx = canvas.getContext("2d");
ctx.drawImage(document.images[0], 0, 0, 960, 540, 0, 0, 960, 540);
var a = document.createElement("a");
a.download = "image.jpeg";
a.href = canvas.toDataURL("image/jpeg");
a.click();
},5000);
In general, if you want to crop an area from a source image, and draw it into a canvas without breaking the aspect ratio and not hardcoding the dimensions into the routine, you can do this:
const canvasAspectRatio = canvas.width / canvas.height;
const cropWidth = canvas.width;
const cropHeight = cropWidth / canvasAspectRatio;
const sx = img.width / 2 - cropWidth / 2;
const sy = img.height / 2 - cropHeight / 2;
ctx.drawImage(img, sx, sy, cropWidth, cropHeight, 0, 0, canvas.width, canvas.height);
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = document.getElementById('img');
const canvasAspectRatio = canvas.width / canvas.height;
// Crop central canvas sized rectangle area into canvas
const cropWidth = canvas.width;
const cropHeight = cropWidth / canvasAspectRatio; // Here you should calculate the height based on aspect ratio instead of assuming it matches that of the canvas
const sx = img.width / 2 - cropWidth / 2;
const sy = img.height / 2 - cropHeight / 2;
ctx.drawImage(img, sx, sy, cropWidth, cropHeight, 0, 0, canvas.width, canvas.height);
body {
background-color: black;
}
#img {
visibility: hidden;
}
#canvas {
border: 1px solid #f00;
}
<canvas id="canvas" width="960" height="540"></canvas>
<img id="img" src="http://via.placeholder.com/2217x790">

Categories