Fabric Canvas - Image resolution is very poor - javascript

I'm implementing Fabricjs in my application for an editing tool. I need to set a high resolution image into the canvas. If I use setBackgroundImage method, it is only working for small sized (Size very less than canvas) images. So I need to reduce the size of the image (but need to keep the ratio) to have something good looking.
My first idea is down sampling which works fine for some pictures but not for all. Here is the code :
Method 1)
var steps = 2;
var imgAspect = img.width / img.height;
var oc = document.createElement('canvas');
octx = oc.getContext('2d');
for(var i = 0; i < steps; i++){
if(i == 0){
oc.width = img.width * 0.5;
oc.height = img.height * 0.5;
octx.drawImage(img, 0, 0, oc.width, oc.height);
}
else{
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
}
}
_w = canvas.width;
_h = canvas.height;
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5, 0, 0, _w, _h);
var data = ctx.getImageData(0, 0, canvas.width, canvas.height);
var c = document.createElement('canvas');
c.setAttribute('id', '_temp_canvas');
c.width = canvas.width;
c.height = canvas.height;
c.getContext('2d').putImageData(data, 0, 0);
var img = fabric.Image.fromURL(c.toDataURL(), function(img) {
img.left = 00;
img.top = 00;
img.isFixed = true;
img.selectable = false;
canvas.add(img);
c = null;
$('#_temp_canvas').remove();
canvas.renderAll();
});
My second idea, I'm following from here which looks very simple
var canvasCopy = document.createElement("canvas")
var copyContext = canvasCopy.getContext("2d")
var ratio = 1;
if(img.width > canvas.width){
ratio = canvas.width / img.width;
}
else if(img.height > canvas.height){
ratio = canvas.height / img.height;
}
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
The issue is none of them producing good resolution. Method 1 works well for some pictures but it is failed for few pictures. If I try the method 2 it is working for the failed pictures from method 1. Can somebody help what is missing regarding the resolution ?
See the fiddle1 and fiddle2 with 2 different images. You can see the difference by calling the method1 and method2 in the _img.onload function to see the difference.

I don't know if this is your case, but for me worked imageSmoothingEnabled=false like
var canvasCopy = document.createElement("canvas")
var copyContext = canvasCopy.getContext("2d")
copyContext.imageSmoothingEnabled = false;
reference http://fabricjs.com/lanczos-webgl

Related

Canvas Rotate, toDataUrl, and then Crop is ruining image quality

I have an image that I'm allowing users to rotate 90 degrees in any direction. Every time they rotate, I use canvas to perform the image manipulations and then save the data returned by the canvas.toDataURL("image/png", 1).
The problem is that the image quality decreases every time I rotate the image.
My end goal is to rotate a rectangular image without losing image quality and also saving the new data url.
function rotateAndSave(image: HTMLImageElement, degrees: number): string {
const imageWidth = image.naturalWidth;
const imageHeight = image.naturalHeight;
const startedHorizontalEndedVertical = imageWidth > imageHeight;
const canvasSize = startedHorizontalEndedVertical ? imageWidth : imageHeight;
const canvas = document.createElement("canvas");
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext("2d");
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// center and rotate canvas
const translateCanvas = canvasSize / 2;
ctx.translate(translateCanvas, translateCanvas);
ctx.rotate(degrees * Math.PI / 180);
// draw from center
const translateImageX = startedHorizontalEndedVertical ? -translateCanvas : (-imageWidth / 2);
const translateImageY = startedHorizontalEndedVertical ? (-imageHeight / 2) : -translateCanvas;
ctx.drawImage(image, translateImageX, translateImageY);
// I got 'cropPlusExport' from another stackoverflow question.
function cropPlusExport(img, cropX, cropY, cropWidth, cropHeight) {
// create a temporary canvas sized to the cropped size
const canvas1 = document.createElement('canvas');
canvas1.width = cropWidth;
canvas1.height = cropHeight;
const ctx1 = canvas1.getContext('2d');
ctx1.setTransform(1, 0, 0, 1, 0, 0);
ctx1.clearRect(0, 0, canvas1.width, canvas1.height);
// use the extended from of drawImage to draw the
// cropped area to the temp canvas
ctx1.drawImage(img, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
return canvas1.toDataURL("image/png", 1);
}
// Start Cropping
let squareImage = new Image();
squareImage.src = canvas.toDataURL("image/png", 1);
squareImage.onload = () => {
const sx = startedHorizontalEndedVertical ? ((canvasSize - imageHeight) / 2) : 0;
const sy = startedHorizontalEndedVertical ? 0 : ((canvasSize - imageWidth) / 2);
const sw = imageHeight;
const sh = imageWidth;
const data = cropPlusExport(squareImage, sx, sy, sw, sh);
// Update DOM via angular binding...
const dataUrl = data.split(",")[1];
this.imageSource = dataUrl;
squareImage = null;
}
example html
<div class="view">
<img [src]="imageSource" />
</div>
Keep in mind that I am cropping to the natural width and height of the image. So, what's weird is that if I don't crop, then the image quality doesn't change but when I do crop, the image quality changes.
Canvas drawing is lossy, and rotating an image induce hard modifications of the pixels. So indeed, if you start always from the last state, you'll end up adding more and more artifacts to your image.
Simply store the original image somewhere and always start from there instead of using the modified version.
// will fire in a loop
img.onload = e => elem.rotateAndSave(1);
const elem = {
// store a copy of the original image
originalimage: img.cloneNode(),
angle: 0,
rotateAndSave(degrees) {
// always use the stored original image
const image = this.originalimage;
// keep track of current transform
this.angle += degrees;
const imageWidth = image.naturalWidth;
const imageHeight = image.naturalHeight;
const startedHorizontalEndedVertical = imageWidth > imageHeight;
const canvasSize = startedHorizontalEndedVertical ? imageWidth : imageHeight;
const canvas = document.createElement("canvas");
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext("2d");
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// center and rotate canvas
const translateCanvas = canvasSize / 2;
ctx.translate(translateCanvas, translateCanvas);
ctx.rotate(this.angle * Math.PI / 180);
// draw from center
const translateImageX = startedHorizontalEndedVertical ? -translateCanvas : (-imageWidth / 2);
const translateImageY = startedHorizontalEndedVertical ? (-imageHeight / 2) : -translateCanvas;
ctx.drawImage(image, translateImageX, translateImageY);
// I got 'cropPlusExport' from another stackoverflow question.
function cropPlusExport(img, cropX, cropY, cropWidth, cropHeight) {
// create a temporary canvas sized to the cropped size
const canvas1 = document.createElement('canvas');
canvas1.width = cropWidth;
canvas1.height = cropHeight;
const ctx1 = canvas1.getContext('2d');
ctx1.setTransform(1, 0, 0, 1, 0, 0);
ctx1.clearRect(0, 0, canvas1.width, canvas1.height);
// use the extended from of drawImage to draw the
// cropped area to the temp canvas
ctx1.drawImage(img, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
return canvas1.toDataURL("image/png", 1);
}
// Start Cropping
let squareImage = new Image();
squareImage.src = canvas.toDataURL("image/png", 1);
squareImage.onload = () => {
const sx = startedHorizontalEndedVertical ? ((canvasSize - imageHeight) / 2) : 0;
const sy = startedHorizontalEndedVertical ? 0 : ((canvasSize - imageWidth) / 2);
const sw = imageHeight;
const sh = imageWidth;
const data = cropPlusExport(squareImage, sx, sy, sw, sh);
// Update DOM via angular binding...
img.src = data;
}
}
};
<img crossorigin src="https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg" id="img">

Pixelated resize using canvas with transparent PNG

I want to accomplish a pixelated effect using the canvas option imageSmoothingEnabled=false; so the image "unblurs" on scroll.
Everything works fine until using transparent images namely PNGs. The scaled image is projected, which stays in the background.
Also the image does not get loaded until the user has scrolled a few pixels.
I've found out that the canvas.drawImage() function owns parameters to set the offset. However I haven't found a solution to this.
Demo https://jsfiddle.net/aLjfemru/
var ctx = canvas.getContext('2d'),
img = new Image(),
play = false;
/// turn off image smoothing - this will give the pixelated effect
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
/// wait until image is actually available
img.onload = function(){
image1.src="nf.png";
context.drawImage(image1, 50, 50, 10, 10);
};
img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png';
/// MAIN function
function pixelate(v) {
document.getElementById("v").innerHTML = "(v): " + v;
/// if in play mode use that value, else use slider value
var size = v * 0.01;
var w = canvas.width * size;
var h = canvas.height * size;
/// draw original image to the scaled size
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, w, h);
ctx.drawImage(canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height);
}
function onScroll() {
$(window).on('scroll', function() {
var y = window.pageYOffset;
if (y > 10) {
y = Math.pow(y, 0.8);
if (y >= 60) {
y = 100;
}
pixelate(y);
}
});
}
onScroll();
Some quick changes to get it happening
Use a second canvas to do the pixelation
Wait for the images to load before doing the rendering.
The onscroll will not fire until you scroll, so when image has loaded call the rendering function to display the image.
canvas.width = innerWidth-20;
ctx = canvas.getContext("2d");
var ctxImage;
const img = new Image;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png';
/// wait until image is actually available
img.onload = function(){
// I dont knwo what this is for so removed the following two lines
//image1.src="nf.png";
//context.drawImage(image1, 50, 50, 10, 10);
// Create a canvas to match the image
var c = document.createElement("canvas");
canvas.width = Math.min(canvas.width,(c.width = this.naturalWidth));
canvas.height = c.height = this.naturalHeight;
ctxImage = c.getContext("2d");
// changing canvas size resets the state so need to set this again.
ctx.imageSmoothingEnabled = false;
onScroll();
pixelate(100); // call first time
};
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.fillText("Loading please wait.",ctx.canvas.width /2, ctx.canvas.height / 4);
/// MAIN function
function pixelate(v) {
document.getElementById("v").innerHTML = "(v): " + v;
/// if in play mode use that value, else use slider value
var size = Number(v) * 0.01;
var w = img.width * size;
var h = img.height * size;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctxImage.clearRect(0, 0, ctxImage.canvas.width, ctxImage.canvas.height);
ctxImage.drawImage(img, 0, 0, w, h);
ctx.drawImage(ctxImage.canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height);
}
function onScroll() {
addEventListener("scroll", function() {
var y = window.pageYOffset;
if (y > 10) {
y = Math.pow(y, 0.65);
if (y >= 100) {
y = 100;
}
pixelate(y);
}
});
}
#fix {
position: fixed;
}
html {
height: 2000px;
}
<div id="fix">
<p id="v" value="Animate">1</p><br />
<canvas id="canvas"></canvas>
</div>
This has since been made into an extremely minimalist library, and my PR for PNG support can be found here.
Once it has been merged I will come back and update this answer.
The full code, generalized and simplified from #Blindman67's answer:
/**
* 8bit
*
* A module that converts an image into a pixelated version (just like
* 8bit artwork).
*
* #author rogeriopvl <https://github.com/rogeriopvl>
* #license MIT
*/
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.eightBit = factory();
}
} (this, function () {
// Necessary to hide the original image with PNG transparency
const invisibleCanvas = document.createElement("canvas");
const invisibleCtx = invisibleCanvas.getContext("2d");
/**
* Draws a pixelated version of an image in a given canvas.
* #param {object} canvas - a canvas object
* #param {object} image - an image HTMLElement object
* #param {number} quality - the new quality: between 0 and 100
*/
const eightBit = function (canvas, image, quality) {
quality /= 100;
canvas.width = invisibleCanvas.width = image.width;
canvas.height = invisibleCanvas.height = image.height;
const scaledW = canvas.width * quality;
const scaledH = canvas.height * quality;
const ctx = canvas.getContext("2d");
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
// Draws image scaled to desired quality on the invisible canvas, then
// draws that scaled image on the visible canvas.
ctx.clearRect(0, 0, canvas.width, canvas.height);
invisibleCtx.clearRect(0, 0, invisibleCtx.canvas.width, invisibleCtx.canvas.height);
invisibleCtx.drawImage(image, 0, 0, scaledW, scaledH);
ctx.drawImage(invisibleCtx.canvas, 0, 0, scaledW, scaledH, 0, 0, canvas.width, canvas.height);
};
return eightBit;
}));

Resize image, need a good library [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 7 years ago.
Improve this question
First of all - The problem
I am developing a web application that enables the user to take a picture with their smartphone, tablet or just browse for a picture on their pc. I need to upload this file to my database, which is working perfectly IF the picture is small enough.
After experimenting a bit with certain photos, I found out that a smartphone takes a picture with a size of at least 1MB and this is way to big too upload to my database.
The uploading goes as following:
1. Convert the image to a Base64 encoded string.
2. Send the string in an array (contains string in pieces) to WebAPI
3. Join the strings into one and convert this into byte array.
I noticed that if a file is around 70-90kb, this would be the maximum size of a file that can be inserted in the database. The moment I have a file that is bigger then 100kb, the inserting fails.
So I am searching for a good resizing library to resize the picture that was chosen. I don't know if this is even possible, but everything has to be done client-side.
Technology
I am using Javascript/jQuery and AngularJS for the client side and VB.NET for my WebAPI. This is because I'm doing an internship and don't have another choice to use VB.NET.
But this has nothing to do with the issue, I just need to find a way to compress/resize/minimize the chosen file, so this can be uploaded to the database.
The upload is happening via the $http.post() from AngularJS.
If someone could advice a library, care to help a fellow programmer out with some basic example code? I mostly have trouble figuring out how to use a plugin or library, because I'm quite new to all this. And I would appreciate it if you guys could provide me with at least some information to get me on track.
Thanks in advance!
Sorry that I couldn't provide any code or something other, it's more an informative question than a coding problem. This might popup when I have a library that I can use. Also, if there are any remarks, I'll consider them, because I have a deadline and don't have much time left to start fixing small problems. Most of it works by now, except for that image problem.
You can use the HTML5 Canvas element to blit your image on it and then resize it appropriately
You create a canvas on-the-fly and perform pixel operations on it - so don't worry it's not visible anywhere on the DOM - think of it like a virtual canvas
Here is a little script I've written a while ago(with some help from another S.O question which I don't really remember) - that allows you to do this - whilst keeping the aspect ratios of the image:
Arguments
You provide an image directly into it
You provide a maxWidth/maxHeight and it will preserve at least one of the 2
It allows you to also rotate the image by defining the degrees parameter(which would be really useful on mobile devices since different devices provide arbitrarily rotated images - you will find out about this the hard way soon if you are dealing with mobile devices)
Returns
It returns a Base64 string of the resized image
function resizeImg(img, maxWidth, maxHeight, degrees) {
var imgWidth = img.width,
imgHeight = img.height;
var ratio = 1,
ratio1 = 1,
ratio2 = 1;
ratio1 = maxWidth / imgWidth;
ratio2 = maxHeight / imgHeight;
// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
if (ratio1 < ratio2) {
ratio = ratio1;
} else {
ratio = ratio2;
}
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
var canvasCopy2 = document.createElement("canvas");
var copyContext2 = canvasCopy2.getContext("2d");
canvasCopy.width = imgWidth;
canvasCopy.height = imgHeight;
copyContext.drawImage(img, 0, 0);
// init
canvasCopy2.width = imgWidth;
canvasCopy2.height = imgHeight;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
var rounds = 1;
var roundRatio = ratio * rounds;
for (var i = 1; i <= rounds; i++) {
// tmp
canvasCopy.width = imgWidth * roundRatio / i;
canvasCopy.height = imgHeight * roundRatio / i;
copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);
// copy back
canvasCopy2.width = imgWidth * roundRatio / i;
canvasCopy2.height = imgHeight * roundRatio / i;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
} // end for
canvas.width = imgWidth * roundRatio / rounds;
canvas.height = imgHeight * roundRatio / rounds;
canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);
if (degrees == 90 || degrees == 270) {
canvas.width = canvasCopy2.height;
canvas.height = canvasCopy2.width;
} else {
canvas.width = canvasCopy2.width;
canvas.height = canvasCopy2.height;
}
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
if (degrees == 90 || degrees == 270) {
canvasContext.translate(canvasCopy2.height / 2, canvasCopy2.width / 2);
} else {
canvasContext.translate(canvasCopy2.width / 2, canvasCopy2.height / 2);
}
canvasContext.rotate(degrees * Math.PI / 180);
canvasContext.drawImage(canvasCopy2, -canvasCopy2.width / 2, -canvasCopy2.height / 2);
var dataURL = canvas.toDataURL();
return dataURL;
}
And here's a working code snippet that allows you to upload from your filesystem and resize on the fly:
/*
-------------------------------
-------HANDLE FILE UPLOAD------
-------------------------------
*/
var input = document.getElementById('input');
input.addEventListener('change', handleFiles);
function handleFiles(e) {
var img = new Image;
img.src = URL.createObjectURL(e.target.files[0]);
img.onload = function() {
var base64String = resizeImg(img, 300, 300, 0); //HERE IS WHERE THE FUNCTION RESIZE IS CALLED!!!!
alert(base64String);
document.getElementById('previewImg').src = base64String;
}
}
/*
-------------------------------
-------RESIZING FUNCTION-------
-------------------------------
*/
function resizeImg(img, maxWidth, maxHeight, degrees) {
var imgWidth = img.width,
imgHeight = img.height;
var ratio = 1,
ratio1 = 1,
ratio2 = 1;
ratio1 = maxWidth / imgWidth;
ratio2 = maxHeight / imgHeight;
// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
if (ratio1 < ratio2) {
ratio = ratio1;
} else {
ratio = ratio2;
}
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
var canvasCopy2 = document.createElement("canvas");
var copyContext2 = canvasCopy2.getContext("2d");
canvasCopy.width = imgWidth;
canvasCopy.height = imgHeight;
copyContext.drawImage(img, 0, 0);
// init
canvasCopy2.width = imgWidth;
canvasCopy2.height = imgHeight;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
var rounds = 1;
var roundRatio = ratio * rounds;
for (var i = 1; i <= rounds; i++) {
// tmp
canvasCopy.width = imgWidth * roundRatio / i;
canvasCopy.height = imgHeight * roundRatio / i;
copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);
// copy back
canvasCopy2.width = imgWidth * roundRatio / i;
canvasCopy2.height = imgHeight * roundRatio / i;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
} // end for
canvas.width = imgWidth * roundRatio / rounds;
canvas.height = imgHeight * roundRatio / rounds;
canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);
if (degrees == 90 || degrees == 270) {
canvas.width = canvasCopy2.height;
canvas.height = canvasCopy2.width;
} else {
canvas.width = canvasCopy2.width;
canvas.height = canvasCopy2.height;
}
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
if (degrees == 90 || degrees == 270) {
canvasContext.translate(canvasCopy2.height / 2, canvasCopy2.width / 2);
} else {
canvasContext.translate(canvasCopy2.width / 2, canvasCopy2.height / 2);
}
canvasContext.rotate(degrees * Math.PI / 180);
canvasContext.drawImage(canvasCopy2, -canvasCopy2.width / 2, -canvasCopy2.height / 2);
var dataURL = canvas.toDataURL();
return dataURL;
}
/*
-------------------------------
-------UNNECESSARY CSS---------
-------------------------------
*/
#import url(http://fonts.googleapis.com/css?family=Lato);
.container {
margin: 0 auto;
width: 400px;
height: 400px;
box-shadow: 1px 1px 1px 1px gray;
}
h3,
h4,
h5,
h6 {
margin: 4px !important;
}
.container,
.container * {
display: block;
margin: 12px auto;
font-family: 'Lato';
}
.header {
background-color: #2196F3;
padding: 12px;
color: #fff;
}
.container input {
width: 128px;
height: 32px;
cursor: pointer;
}
.container img {
display: block;
margin: 12px auto;
}
<div class="container">
<div class="header">
<h3>Choose a file</h3>
<h6>and I will alert back to you the base64 string of it's resized version</h6>
</div>
<input type="file" id="input" />
<hr>
<h5>Image Preview:</h5>
<img id="previewImg" src="" />
</div>

Javascript canvas memory issue in Firefox

I'm trying to resize an image in multiple steps to increase quality of the final image. This works fine in Opera browser, but i think there is a memory issue in Firefox. After resizing a few images, the following error occours: Error: Image corrupt or truncated: blob:[XY]. So my question: do you see a memory issue in the following code?
loadImage.halfSize = function (i) {
var c = document.createElement("canvas");
c.width = i.width / 2;
c.height = i.height / 2;
var ctx = c.getContext("2d");
ctx.drawImage(i, 0, 0, c.width, c.height);
return c;
};
loadImage.renderImageToCanvas = function (
canvas,
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
destX,
destY,
destWidth,
destHeight
) {
canvas.width = sourceWidth;
canvas.height = sourceHeight;
canvas.getContext('2d').drawImage(
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight);
if(sourceWidth <= destWidth && sourceHeight <= destHeight) {
return canvas;
}
while (canvas.width > destWidth * 2) {
canvas = loadImage.halfSize (canvas);
}
var canvasRes = document.createElement("canvas");
canvasRes.width = destWidth;
canvasRes.height = destHeight;
var canvasResCtx = canvasRes.getContext("2d");
canvasResCtx.drawImage(canvas, 0, 0, canvasRes.width, canvasRes.height);
return canvasRes;
};
I can't see a leak, because i'm using local variables everywhere, so memory of the created canvas-elements should be freed after each call to halfSize()?
Thanks for your help!
Ben

Resize an image with javascript for use inside a canvas createPattern

I've seen a few tricks on how you resize an image you want to
use inside an IMG-tag but I want to have an image variable inside
a Javascript, resize it and then use the image inside a
context.createPattern(image, "repeat"). I have not found any hint
on how to do that.
You can find a functional demo at http://karllarsson.batcave.net/moon.html
with images on what I want to do.
The solution from Loktar looks good. I haven't had the time yet to fix the
correct aspect but now I know how to do it. Thank you once again. Here is an
working demo http://karllarsson.batcave.net/moon2.html
This is the two lines I don't get to work as I want them too.
image.width = side * 2;
image.height = side * 2;
function drawShape() {
try {
var canvas = document.getElementById('tutorial');
var image = new Image();
image.src = "http://xxx.yyy.zzz/jjj.jpg";
image.width = side * 2;
image.height = side * 2;
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = ctx.createPattern(image, "repeat");
ctx.beginPath();
var centerX = canvas.width / 2 - side / 2;
var centerY = canvas.height / 2 - side / 2;
ctx.rect(centerX, centerY, side, side);
ctx.fill();
} else {
alert('You need Safari or Firefox 1.5+ to see this demo.');
}
} catch (err) {
console.log(err);
}
}
What you can do is make a temporary canvas, and copy the image to it with the dimensions you require, and then use that temporary canvas as the pattern rather than the image itself.
Live Demo
First create the canvas
var tempCanvas = document.createElement("canvas"),
tCtx = tempCanvas.getContext("2d");
tempCanvas.width = side*2;
tempCanvas.height = side*2;
Now draw the image to it, making the the scaled size you require
tCtx.drawImage(image,0,0,image.width,image.height,0,0,side*2,side*2);
And now use the canvas you just created as the pattern
ctx.fillStyle = ctx.createPattern(tempCanvas, "repeat");
Full code
edit created a more generic reusable example
function drawPattern(img, size) {
var canvas = document.getElementById('canvas');
canvas.height = 500;
canvas.width = 500;
var tempCanvas = document.createElement("canvas"),
tCtx = tempCanvas.getContext("2d");
tempCanvas.width = size;
tempCanvas.height = size;
tCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, size, size);
// use getContext to use the canvas for drawing
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = ctx.createPattern(tempCanvas, 'repeat');
ctx.beginPath();
ctx.rect(0,0,canvas.width,canvas.height);
ctx.fill();
}
var img = new Image();
img.src = "http://static6.depositphotos.com/1070439/567/v/450/dep_5679549-Moon-Surface-seamless.jpg";
img.onload = function(){
drawPattern(this, 100);
}
There is another way, which worked great for me:
const ctx: CanvasRenderingContext2D;
const patternAsBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRjIyRTA3RDgyNkUxMUU5ODQ1QUM2QjE5RUMzRTMwRCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRjIyRTA3RTgyNkUxMUU5ODQ1QUM2QjE5RUMzRTMwRCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkZGMjJFMDdCODI2RTExRTk4NDVBQzZCMTlFQzNFMzBEIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZGMjJFMDdDODI2RTExRTk4NDVBQzZCMTlFQzNFMzBEIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+y2y13AAAAV1JREFUeNqc0kFLAkEUB/B9b1ZR1m4FQZeocx2MYI3CxC8QmVBfJAjqVLQQfY/MFI+dREpQIeqQxwgiCrq72661M9Nb0ZKo3dU5zOU//Hj/3Qep04YUn04Ludxq7mdflYCzfFKbprdnLKatc9u8Z1zZbBxkHwc58q79gpGYLgBKqaPqTBB4s5t5Ewx2XNu8YvHEImeysnJYnf8GoyCz/MO+ZXFN56iUw6JSxW333SJ0YoEzpbJq1Oe8DLxLN2qzQoqSGkss8a41Un1wZUGNa2mq346A3IBB6KGSUOahdqeFEsZAO20YDnXjktDomKgoUMM0/A6HJ3UJZSNOCn+FPVSIMv3FpOuYTSaUfGj0v/AH1ZLcsZoYEgW/sF+/TPUJNUOh6Be29jJPAJijlbgjNEV7ehG0p6CEOIP62K/v900xXyyyILA3KWJO2FbgpPj8MHm+dnw9FRp1/Ot/CTAAd/XmF3ta0fUAAAAASUVORK5CYII=';
const imagePattern = new Image();
imagePattern.onload = () => {
const pattern: CanvasPattern = ctx.createPattern(imagePattern, 'repeat-y');
const svgMatrix: SVGMatrix = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix();
svgMatrix.scale(0.5);
pattern.setTransform(svgMatrix);
}
imagePattern.src = patternAsBase64;
Basically the same as setting the transform matrix to this (the following down under) (But attention! patterns setTransform method awaits a SVGMatrix.
This did still work for me, too in Chrome (But TypeScript complained of course):
pattern.setTransform({
a: 0.5, // Horizontal scaling. A value of 1 results in no scaling.
b: 0, // Vertical skewing.
c: 0, // Horizontal skewing.
d: 0.5, // Vertical scaling. A value of 1 results in no scaling.
e: 0, // Horizontal translation (moving).
f: 0 // Vertical translation (moving).
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform
});
Keep in mind, that you also maybe need to translate your canvas before drawing the pattern, otherwise it could not start where you want it to start to.
const offset_x: number = positionXOfElement;
const offset_y: number = positionYOfElement;
// offset
ctx.translate(offset_x, offset_y);
// draw
ctx.fillStyle = pattern;
roundRect.fill();
//ctx.fillRect(0, 0, 300, 300);
// undo offset
ctx.translate(-offset_x, -offset_y);
Here is another way to resize pattern on image with range button.
var img1 = new Image, img2 = new Image, cnt = 2,
canvas = document.getElementById("canvas"),
canvas_original = document.getElementById("canvas_original"),
ctx_original = canvas_original.getContext("2d"),
ctx = canvas.getContext("2d");
img1.onload = img2.onload = function() {if (!--cnt) go()};
img1.src = "https://www.itailor.co.uk/images/product/shirt/it1823-1.png"; // shirt
img2.src = "https://i.stack.imgur.com/tkBVh.png"; // pattern
function go(source = null) {
canvas.width = document.getElementById("first").clientWidth;
canvas.height = window.innerHeight / 2;
canvas_original.width = document.getElementById("second").clientWidth;
canvas_original.height = window.innerHeight / 2;
var oc = document.createElement('canvas'), octx = oc.getContext('2d');
var cur = {
width: Math.floor(img1.width * 0.5),
height: Math.floor(img1.height * 0.5)
}
oc.width = cur.width;
oc.height = cur.height;
octx.drawImage(img1, 0, 0, cur.width, cur.height);
while (cur.width * 0.5 > document.getElementById("first").clientWidth) {
cur = {
width: Math.floor(cur.width * 0.5),
height: Math.floor(cur.height * 0.5)
};
octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
}
ctx_original.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
//****//
var rate = $('#rate').val();
var tempCanvas = document.createElement("canvas"),
tCtx = tempCanvas.getContext("2d");
tempCanvas.width = canvas.width / 4 * rate;
tempCanvas.height = canvas.height / 4 * rate;
tCtx.drawImage(img2, 0, 0, img2.width, img2.height, 0, 0, tempCanvas.width, tempCanvas.height);
//****//
if (source !== null) {
ctx.fillStyle = ctx.createPattern(tempCanvas, "repeat");
}
else {
ctx.fillStyle = ctx.createPattern(tempCanvas, "repeat");
}
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "multiply";
ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}
HTML Markup
<div class="container" style="width: 100%; padding-right: 0 !important; padding-left: 0 !important;">
<div id="clothesDiv" class="col-lg-2" style="max-height: 100vh; overflow-y: auto;">
</div>
<div class="col-lg-10">
<div class="row" style="margin-right: 0 !important; margin-left: 0 !important; border: 0.5px solid black;">
<div id="first" class="col-lg-6" style="padding-right: 0 !important; padding-left: 0 !important;">
<canvas id="canvas_original"></canvas>
</div>
<div id="second" class="col-lg-6" style="padding-right: 0 !important; padding-left: 0 !important;">
<canvas id="canvas"></canvas>
</div>
</div>
<div class="row" style="margin-right: 0 !important; margin-left: 0 !important;">
<div class="col-lg-offset-3 col-lg-6">
<p class="text-center">Desen Ölçeklendirme</p>
<input id="rate" type="range" min="1" max="4" value="4" onchange="resizePattern()"/>
</div>
</div>
And function
function resizePattern() {
go();
}
You can tweak it to see different results.

Categories

Resources