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>
Related
I'm working on a box styling project with Canvas and Javascript, and I can't rotate the text the way I want (written from bottom to the top).
(source: hostpic.xyz)
I followed a tutorial (https://newspaint.wordpress.com/2014/05/22/writing-rotated-text-on-a-javascript-canvas/) and tried to adapt it in my code, but I couldn't make it.
You can find the code on the JSFiddle link below, there's an input where you can type your text and it should be written as the "brand" word in the JSFiddle example.
https://jsfiddle.net/ParkerIndustries/mgne9x5u/5/
Everything is in the init() function:
function init() {
var elem = document.getElementById('elem');
var circle_canvas = document.createElement("canvas");
var context = circle_canvas.getContext("2d");
var img = new Image();
img.src = "https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg";
context.drawImage(img, 0, 0, 500, 650);
circle_canvas.width = 500;
circle_canvas.height = 650;
context.fillStyle = "#000000";
//context.textAlign = 'center';
var UserInput = document.getElementById("UserInput");
context.save();
context.translate( circle_canvas.width - 1, 0 );
UserInput.oninput = function() {
context.clearRect(0, 0, 500, 650);
context.drawImage(img, 0, 0, 500, 650);
var text = UserInput.value;
console.log(text);
if (text.length < 12) {
context.rotate(3*Math.PI/2);
console.log("-12");
context.font = "50px Righteous";
context.fillText(text, -350, -170);
context.restore();
} else {
context.rotate(3*Math.PI/2);
context.font = "25px Righteous";
context.fillText(text, -350, -170);
context.restore();
}
}
elem.appendChild(circle_canvas);
}
init();
I tried a lot a values in the context.rotate() function but in any way my text is upside down.
(source: hostpic.xyz)
You're pretty close here. I suggest performing the canvas translation to the middle of the screen (width / 2, height / 2) and then rotating by 270 degrees:
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(270 * Math.PI / 180);
Beyond that, I recommend a round of code cleanup to avoid inconsistent variable names and hardcoded numbers (try to make everything proportional to img.width and img.height, then avoid literal values. This makes it easier to adjust your code dynamically without having to re-type all of the values. You can access img.width and img.height after the img.onload function fires.
Another useful function is context.measureText(text), which makes it simpler to proportionally scale text size.
Full example:
function init() {
var userInput = document.getElementById("user-input");
var elem = document.getElementById("elem");
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
var img = new Image();
img.onload = function () {
canvas.width = img.width / 3;
canvas.height = img.height / 3;
context.drawImage(img, 0, 0, canvas.width, canvas.height);
context.fillStyle = "#000000";
context.textAlign = "center";
elem.appendChild(canvas);
userInput.oninput = function () {
var text = userInput.value;
var textSizePx = 50 - context.measureText(text).width / 10;
context.font = textSizePx + "px Righteous";
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(img, 0, 0, canvas.width, canvas.height);
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(270 * Math.PI / 180);
context.fillText(text, 0, -canvas.width / 20);
context.restore();
}
};
img.src = "https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg";
}
init();
<div id="elem">
<input type="text" id="user-input" maxlength="15">
</div>
With that proof-of-concept working, one problem is that scaling the image on canvas causes visual artifacts. This problem can be overcome with JS or by scaling down the source image itself, but at this point, it's best to make the image as an element and circumvent the entire problem.
Similarly, there's no obvious reason to render text on canvas either; we can make an element for this as well. Moving text to HTML/CSS gives more power over how it looks (I didn't do much with style here, so it's an exercise to make it blend more naturally into the image).
Here's a rewrite without canvas that looks much cleaner and should be more maintainable:
function init() {
var userInput = document.querySelector("#user-input");
var img = document.querySelector("#product-img");
var imgRect = img.getBoundingClientRect();
var overlayText = document.querySelector("#overlay-text");
var overlay = document.querySelector("#overlay");
overlay.style.width = (imgRect.width * 0.86) + "px";
overlay.style.height = imgRect.height + "px";
userInput.addEventListener("input", function () {
overlayText.innerText = userInput.value;
overlayText.style.fontSize = (50 - userInput.value.length * 2) + "px";
});
}
init();
#product-img {
position: absolute;
}
#overlay {
position: absolute;
display: flex;
}
#overlay-text {
font-family: Verdana, sans-serif;
color: #333;
transform: rotate(270deg);
margin: auto;
cursor: default;
}
<div>
<input type="text" id="user-input" maxlength="15">
</div>
<div id="product-container">
<img id="product-img" src="https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg" width=666 height=500 />
<div id="overlay">
<div id="overlay-text"></div>
</div>
</div>
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
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;
}));
> What I want here are 10 canvases, 5 on the top and 5 on the bottom, each with width 17% and height 25.5%, and appropriate spacing between (see bottom image). Each image drawn on a canvas corresponds to the same area of the full image (see top image). This is kind of like a destination-in, but this is really just a crop of the full image onto smaller canvases using context.translate and context.drawImage(). Please see the two lines inside the innermost for loop with comments that deal wtih context.translate and context.drawImage for clue as to what might be going on.
Please see the attached image of what I am trying to achieve with html5 context.translate() and context.drawImage().
Any help greatly appreciated. Thank you.
//get parent's width and height
var parent = document.getElementById("parent");
var parentWidth = parent.offsetWidth;
var parentHeight = parent.offsetHeight;
//get below canvas
var belowCanvas = document.getElementById('belowCanvas');
var belowCtx = belowCanvas.getContext('2d');
//create temporary canvas
var tmpCanvas = document.createElement('canvas');
var tmpCtx = tmpCanvas.getContext('2d');
//initialize width and height of temporary canvas and below canvas to equal parent
tmpCanvas.width = belowCanvas.width = parentWidth;
tmpCanvas.height = belowCanvas.height = parentHeight;
//draw below canvas in black for visual aid of how things are cropped in above canvases
belowCtx.rect(0,0,parentWidth,parentHeight);
belowCtx.fillStyle = 'black';
belowCtx.fill();
//draw temporary canvas
var centerX = parentWidth/4;
var centerY = parentHeight/4;
var radius = parentHeight/4;
tmpCtx.rect(0,0,parentWidth,parentHeight);
tmpCtx.fillStyle = 'blue';
tmpCtx.fill();
tmpCtx.beginPath();
tmpCtx.arc(centerX, centerY, radius*1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'green';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth - centerX, centerY, radius*1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'purple';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth/2, parentHeight/2, radius*2, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'white';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(centerX, parentHeight - centerY, radius*1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'red';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth - centerX, parentHeight - centerY, radius*1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'yellow';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
//set spacing between canvases
var horizontalSpacing = parentWidth*0.025
var verticalSpacing = parentWidth*0.03
//initialize canvases width and height
var widthCanvas = parentWidth*0.17;
var heightCanvas = parentWidth*0.255;
var xStart;
var yStart = verticalSpacing;
for(var i=0; i< 2; i++)
{
xStart = horizontalSpacing;
for(var j=0; j < 5; j++)
{
//get specific destinationInCanvas by id and its respective context
var canvas = document.getElementById("canvas" + ((i*5)+j));
var ctx = canvas.getContext('2d');
canvas.width = widthCanvas;
canvas.height = heightCanvas;
/***!!!
Problem next two lines
if the next line is commented out, each canvas is drawn, but of course not translated; so you only see last canvas, canvas9
if the next line is NOT commented out, only canvas0 is drawn and translated, the rest of the canvases 1-9 are not drawn*/
ctx.translate(xStart, yStart);//comment out this line to see effect
ctx.drawImage(tmpCanvas, xStart, yStart, widthCanvas, heightCanvas, 0, 0, widthCanvas, heightCanvas);
xStart += (horizontalSpacing + widthCanvas);
}
yStart += (verticalSpacing + heightCanvas);
}
#parent {
width: 1000px;
height: 600px;
}
#belowCanvas{
position: absolute;
z-index:-1;
}
#canvas0 {
position: absolute;
z-index:0;
}
#canvas1 {
position: absolute;
z-index:1;
}
#canvas2 {
position: absolute;
z-index:2;
}
#canvas3 {
position: absolute;
z-index:3;
}
#canvas4 {
position: absolute;
z-index:4;
}
#canvas5 {
position: absolute;
z-index:5;
}
#canvas6 {
position: absolute;
z-index:6;
}
#canvas7 {
position: absolute;
z-index:7;
}
#canvas8 {
position: absolute;
z-index:8;
}
#canvas9 {
position: absolute;
z-index:9;
}
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style-test.css">
</head>
<body>
<div id="parent">
<canvas id="belowCanvas"></canvas>
<canvas id="canvas0"></canvas>
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
<canvas id="canvas3"></canvas>
<canvas id="canvas4"></canvas>
<canvas id="canvas5"></canvas>
<canvas id="canvas6"></canvas>
<canvas id="canvas7"></canvas>
<canvas id="canvas8"></canvas>
<canvas id="canvas9"></canvas>
</div>
<script type="text/javascript" src="script-test.js"></script>
</body>
</html>
You've got some problem while incrementing yStart and xStart:
You should do so by multiplying heightCanvas/widthCanvas by i/j state of your loop.
Also, in your CSS, if you do add position:absolute without setting marginsor left/top values, your canvases will get in stack and you'll only be able to see last one. Only add position:absolute to your #belowCanvas. Actually, if you only need a black background, you might consider wrapping all your canvases into a div with background:black;. It would be easier to align your elements then.
Finally, you won't need ctx.translate since ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); already does the translation on source image (sx, sy).
Edit after I talked to OP on chat:
Actually, he wanted the canvases' holes to fit to a layout, created with a context.globalCompositeOperation='destination-out';.
I did remove the belowCanvas and replaced it with a divwhich background is set to black via CSS.
To prevent ghost margins with your small canvases, you'll need to set vertical-align:bottom; float: left since browsers display canvas element as a character.
I refactored the code to create the small canvases programmatically, this will allow you to change the grid format.
For the example, I added click listener to show/hide layers:
left click triggers destinationOutLayer and right click triggers small canvases layer.
//get parent's width and height
var parent = document.getElementById("parent");
var parentWidth = parent.offsetWidth;
var parentHeight = parent.offsetHeight;
var destinationOutCanvas = document.getElementById('destinationOutCanvas');
var destinationOutCtx = destinationOutCanvas.getContext('2d');
//create temporary canvas
var tmpCanvas = document.createElement('canvas');
var tmpCtx = tmpCanvas.getContext('2d');
//initialize width and height of temporary canvas and destOut canvas to equal parent
destinationOutCanvas.width = tmpCanvas.width = parentWidth;
destinationOutCanvas.height = tmpCanvas.height = parentHeight;
//draw temporary canvas
var centerX = parentWidth / 4;
var centerY = parentHeight / 4;
var radius = parentHeight / 4;
tmpCtx.rect(0, 0, parentWidth, parentHeight);
tmpCtx.fillStyle = 'blue';
tmpCtx.fill();
tmpCtx.beginPath();
tmpCtx.arc(centerX, centerY, radius * 1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'green';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth - centerX, centerY, radius * 1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'purple';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth / 2, parentHeight / 2, radius * 2, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'white';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(centerX, parentHeight - centerY, radius * 1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'red';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth - centerX, parentHeight - centerY, radius * 1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'yellow';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
//set the grid
//Strange bugs can occur with some fractions (e.g 18 rows);
var rows = 5;
var lines = 2;
//set spacing between canvases
//rounded to the nearest even number, since we divide this by 2 below
var horizontalSpacing = 2*Math.round((parentWidth*0.025)/2); // or whatever you want
var verticalSpacing = 2*Math.round((parentHeight*0.03)/2); // or whatever you want
//initialize canvases width and height
var widthCanvas = 2*(Math.round(parentWidth / rows)/2);
var heightCanvas = 2*(Math.round(parentHeight / lines)/2);
destinationOutCtx.drawImage(tmpCanvas, 0, 0);
destinationOutCtx.globalCompositeOperation = 'destination-out';
destinationOutCtx.fillStyle = 'orange';
for (var i = 0; i < lines; i++) {
for (var j = 0; j < rows; j++) {
//get specific destinationInCanvas by id and its respective context
//var canvas = document.getElementById("canvas" + ((i * rows) + j));
//create the canvases on the go
var canvas= document.createElement('canvas');
canvas.width = widthCanvas;
canvas.height = heightCanvas;
var ctx = canvas.getContext('2d');
//only needed for the demonstration toggler
canvas.className = "small";
//set the transform variables
var hS = horizontalSpacing/2,
vS = verticalSpacing/2,
xStart = (widthCanvas*j)+hS,
yStart = (heightCanvas*i)+vS,
cropedWidth = widthCanvas-hS*2,
cropedHeight = heightCanvas-hS*2;
ctx.drawImage(tmpCanvas, xStart, yStart, cropedWidth, cropedHeight, hS, vS, cropedWidth, cropedHeight);
destinationOutCtx.fillRect(xStart, yStart, cropedWidth, cropedHeight);
parent.appendChild(canvas);
}
}
//Toggle opacity with right and left click
destinationOutCanvas.addEventListener('click', function () {
if (this.style.opacity == 0) {
this.style.opacity = 1
} else {
this.style.opacity = 0
}
});
destinationOutCanvas.style.opacity = 1;
destinationOutCanvas.addEventListener('contextmenu', function (e) {
console.log('triggered');
e.preventDefault();
var smalls = document.querySelectorAll('.small');
console.log(smalls[0].style.opacity);
if (smalls[0].style.opacity == 0) {
for (i = 0; i < smalls.length; i++) {
smalls[i].style.opacity = 1
}
} else {
for (i = 0; i < smalls.length; i++) {
smalls[i].style.opacity = 0;
};
}
});
#parent {
width: 1000px !important;
height: 600px;
background:#000;
}
html, body {
margin:0
}
canvas {
vertical-align:bottom;
float: left
}
.destinationOutLayer, #tmp {
position: absolute;
z-index: 2;
top:0;
}
<div id="parent">
</div>
<canvas id="destinationOutCanvas" class="destinationOutLayer"></canvas>
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.