Image dimension resize and rotate by 90 degree using canvas - html5-canvas

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var width = 0;
var height = 0;
var diagonal = 0;
var image = new Image();
image.crossOrigin = 'Anonymous';
image.src = `source url`;
image.onload = () => {
width = image.naturalHeight;
height = image.naturalWidth;
diagonal = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
ctx.canvas.height = diagonal;
ctx.canvas.width = diagonal;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(diagonal / 2, diagonal / 2);
ctx.rotate((0 * Math.PI) / 180);
ctx.drawImage(image, -width / 2, -height / 2);
ctx.restore();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(diagonal / 2, diagonal / 2);
ctx.rotate((90 * Math.PI) / 180);
ctx.save();
canvas.height = image.height;
canvas.width = image.width;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
ctx.restore();
console.log(canvas.toDataURL("image/jpeg"))
};
<canvas id="canvas"></canvas>
I have an image of dimensions 1200x900. I want to rotate it by 90 degrees in clockwise and after rotation change its dimension again to 1200*900. How can I do this using canvas of HTML5.
I am attaching image here.
I want the destination image to be of size 1200x900. Please let me know, how to do this.

I don't see anywhere where you calculate the ratio between height & width...
We calculate that and use it in our drawImage to reduce the size.
...see sample below:
I keep the canvas size to the original image to show it in the background
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var image = new Image();
image.src = "http://i.stack.imgur.com/UFBxY.png";
image.onload = () => {
canvas.height = image.naturalHeight;
canvas.width = image.naturalWidth;
// original image
ctx.globalAlpha = 0.2;
ctx.drawImage(image, 0, 0);
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill()
// rotated image
ctx.save();
ctx.globalAlpha = 1;
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI / 2);
let ratio = canvas.height/canvas.width
ctx.drawImage(image, -canvas.height / 2, -canvas.width / 2, canvas.width/ratio, canvas.width);
ctx.restore();
};
<canvas id="canvas"></canvas>
I was not sure what your diagonal calculation was needed for, so I just removed that and anything else not relevant... in future questions if you can explain your logic we can better respond to what/where your approach is going wrong
You can use same logic for other similar transformation...
here is another code sample:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var image = new Image();
image.src = "http://i.stack.imgur.com/UFBxY.png";
image.onload = () => {
canvas.height = canvas.width = Math.max(image.naturalWidth, image.naturalHeight);
// original image
ctx.globalAlpha = 0.2;
ctx.drawImage(image, 0, 0);
ctx.rect(0, 0, image.naturalWidth, image.naturalHeight);
ctx.fill()
// rotated image
ctx.save();
ctx.globalAlpha = 1;
ctx.translate(image.naturalHeight / 2, image.naturalWidth / 2);
ctx.rotate(Math.PI / 2);
ctx.drawImage(image, -image.naturalWidth / 2, -image.naturalHeight / 2);
ctx.restore();
};
<canvas id="canvas"></canvas>

Related

How to Draw circle path like this in d3?

I want to draw circle path like image below in d3.
I try this but not working.
const patternCanvas = document.createElement('canvas');
const patternContext = patternCanvas.getContext('2d');
patternCanvas.width = 50;
patternCanvas.height = 50;
patternContext.fillStyle = '#fff';
patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
patternContext.stroke();
const pattern = this.context.createPattern(patternCanvas, 'repeat');
this.context.fillStyle = pattern;
this.context.beginPath();
this.context.closePath();
this.context.fill();
this.context.stroke();
Thank for your help!
See the documentation & examples here: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc
The basic circle outline is done like so:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
ctx.stroke();
The basic filled circle is done like so:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#888'
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
ctx.fill();

Piecing together meshes in ThreeJS causes visible seam

I'm trying to piece together a sphere with individual slices. Basically, I have multiple SphereGeoemtery slices that form a sphere and used to project a panorama. Slices are used for lazy loading very large panoramas.
With the default texture wrapping mode (THREE.ClampToEdgeWrapping) on these slices, from far away the panorama looks fine but if you zoom in it's very clear the edges of the meshes are stretching, causing visible seams. It make sense since it's stretching the last pixel at the edge..
I also tried changing wrapping mode to THREE.RepeatWrapping, however, the seam becomes completely visible:
So my question is, what's the best method here for piecing together meshes? Or is this just unavoidable?
Off the top of my head you'd have to make each texture contain one border row and border column in each direction that's a repeat of the its neighbor, then adjust the UV coordinates appropriately
For example if the big image is 8 pixels wide and 6 pixels tall
ABCDEFGH
IJKLMNOP
QRSTUVWX
YZ123456
789abcde
fghijklm
And you want to divide it into into 4 parts (each 4, 3)
then you'd need these 4 parts
ABCDE DEFGH
IJKLM LMNOP
QRSTU TUVWX
YZ123 23456
QRSTU TUVWX
YZ123 23456
789ab abcde
fghij ijklm
Also to make it easy repeat the edges so
AABCDE DEFGHH
AABCDE DEFGHH
IIJKLM LMNOPP
QQRSTU TUVWXX
YYZ123 234566
QQRSTU TUVWXX
YYZ123 234566
7789ab abcdee
ffghij ijklmm
ffghij ijklmm
Repeating the edges is because I'm assuming you're splitting into more than 2x2 so technically if you were going to split something 50 pixels wide into 5 parts you could do parts that are 11, 12, 12, 12, 11 in width. The edges being only 11 pixels instead of 12 would need a different UV adjustment. But, by repeating the edges we can make them all 12, 12, 12, 12, 12 so everything is consistant.
testing, left is normal split showing the seam. Right is the fixed one, no seam.
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 1;
const scene = new THREE.Scene();
// make our texture using a canvas to test
const bigImage = document.createElement('canvas');
{
const ctx = bigImage.getContext('2d');
const width = 32;
const height = 16;
ctx.canvas.width = width;
ctx.canvas.height = height;
const gradient = ctx.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.5, 'yellow');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
const forceTextureInitialization = function() {
const material = new THREE.MeshBasicMaterial();
const geometry = new THREE.PlaneBufferGeometry();
const scene = new THREE.Scene();
scene.add(new THREE.Mesh(geometry, material));
const camera = new THREE.Camera();
return function forceTextureInitialization(texture) {
material.map = texture;
renderer.render(scene, camera);
};
}();
// bad
{
const ctx = document.createElement('canvas').getContext('2d');
// split the texture into 4 parts across 4 planes
const across = 2;
const down = 2;
const pixelsAcross = bigImage.width / across;
const pixelsDown = bigImage.height / down;
ctx.canvas.width = pixelsAcross;
ctx.canvas.height = pixelsDown;
for (let y = 0; y < down; ++y) {
for (let x = 0; x < across; ++x) {
ctx.clearRect(0, 0, pixelsAcross, pixelsDown);
ctx.drawImage(bigImage,
x * pixelsAcross, (down - 1 - y) * pixelsDown, pixelsAcross, pixelsDown,
0, 0, pixelsAcross, pixelsDown);
const texture = new THREE.CanvasTexture(ctx.canvas);
// see https://threejsfundamentals.org/threejs/lessons/threejs-canvas-textures.html
forceTextureInitialization(texture);
const geometry = new THREE.PlaneBufferGeometry(1 / across, 1 / down);
const material = new THREE.MeshBasicMaterial({map: texture});
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
plane.position.set(-1 + x / across, y / down - 0.25, 0);
}
}
}
// good
{
const ctx = document.createElement('canvas').getContext('2d');
// split the texture into 4 parts across 4 planes
const across = 2;
const down = 2;
const pixelsAcross = bigImage.width / across;
const pixelsDown = bigImage.height / down;
ctx.canvas.width = pixelsAcross + 2;
ctx.canvas.height = pixelsDown + 2;
// just draw the image at all these offsets.
// it would be more efficient to draw the edges
// 1 pixel wide but I'm lazy
const offsets = [
[ 0, 0],
[ 1, 0],
[ 2, 0],
[ 0, 1],
[ 2, 1],
[ 0, 2],
[ 1, 2],
[ 2, 2],
[ 1, 1],
];
for (let y = 0; y < down; ++y) {
for (let x = 0; x < across; ++x) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
let srcX = x * pixelsAcross - 1;
let srcY = (down - 1 - y) * pixelsDown - 1;
let dstX = 0;
let dstY = 0;
let width = pixelsAcross + 2;
let height = pixelsDown + 2;
ctx.drawImage(bigImage,
srcX, srcY, width, height,
dstX, dstY, width, height);
// handle edges
if (srcX < 0) {
// repeat left edge
ctx.drawImage(bigImage,
0, srcY, 1, height,
0, dstY, 1, height);
}
if (srcY < 0) {
// repeat top edge
ctx.drawImage(bigImage,
srcX, 0, width, 1,
dstX, 0, width, 1);
}
if (srcX + width > bigImage.width) {
// repeat right edge
ctx.drawImage(bigImage,
bigImage.width - 1, srcY, 1, height,
ctx.canvas.width - 1, dstY, 1, height);
}
if (srcY + height > bigImage.height) {
// repeat bottom edge
ctx.drawImage(bigImage,
srcX, bigImage.height - 1, width, 1,
dstX, ctx.canvas.height - 1, width, 1);
}
// TODO: handle corners
const texture = new THREE.CanvasTexture(ctx.canvas);
texture.minFilter = THREE.LinearFilter;
// offset UV coords 1 pixel to skip the edge pixel
texture.offset.set(1 / ctx.canvas.width, 1 / ctx.canvas.height);
// only textureSize - 2 of the pixels in the texture
texture.repeat.set(pixelsAcross / ctx.canvas.width, pixelsDown / ctx.canvas.height);
// see https://threejsfundamentals.org/threejs/lessons/threejs-canvas-textures.html
forceTextureInitialization(texture);
const geometry = new THREE.PlaneBufferGeometry(1 / across, 1 / down);
const material = new THREE.MeshBasicMaterial({map: texture});
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
plane.position.set(1 + x / across - 0.5, y / down - 0.25, 0);
}
}
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>

How to have foreground text with repeat pattern on canvas?

I am trying to have a repeated background image with the text foreground but when I add pattern on canvas the text is not visible, here is me code:
HTML
<canvas id="canvas"></canvas>
JavaScript
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = "https://cdn.pixabay.com/photo/2019/03/03/20/23/flowers-4032775__340.png";
img.height = 10;
img.onload = function(){
ctx.font = "30px Calibri";
ctx.fillText("DEXIE", 10, 50);
ctx.drawImage(img, 0, 0);
var ptrn = ctx.createPattern(img, 'repeat');
ctx.fillStyle = ptrn;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
Draw the text after drawing the pattern. Changing the fillStyle to what color you want the text to be.
canvas.width = innerWidth;
canvas.height = innerHeight;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = "https://cdn.pixabay.com/photo/2019/03/03/20/23/flowers-4032775__340.png";
img.onload = function(){
img.height = 10;
ctx.drawImage(img, 0, 0); // Do you really want to draw the image on the canvas????
var ptrn = ctx.createPattern(img, 'repeat');
// Draw pattern first
ctx.fillStyle = ptrn;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw text on top
ctx.font = "30px Calibri";
ctx.fillStyle = "Black"; // <<== Set text color
ctx.fillText("DEXIE", 10, 50);
}

Image loaded onto CanvasTexture appears pixelated

There's been a-lot of questions around this but none of those have fixed my problem. Any image that I upload onto the object becomes pixelated regardless of the minFilter or magFilter that I use - and I've used all of them:
THREE.NearestFilter
THREE.NearestMipMapNearestFilter
THREE.NearestMipMapLinearFilter
THREE.LinearFilter
THREE.LinearMipMapNearestFilter
THREE.LinearMipMapLinearFilter
Here's the object with a pixelated image:
And here's a snapshot of how I'm loading the image on:
// Build a canvas object and add the image to it
var imageCanvas = this.getCanvas(imageLayer.guid, 'image');
var imageLoader = new THREE.ImageLoader();
imageLoader.load(imageUrl, img => {
// this.drawImage(img, gr, imageCanvas.canvas, imageCanvas.ctx);
var canvas = imageCanvas.canvas;
var ctx = imageCanvas.ctx;
canvas.width = 1024;
canvas.height = 1024;
var imgAspectRatioAdjustedWidth, imgAspectRatioAdjustedHeight;
var pushDownValueOnDy = 0;
var grWidth = canvas.width / 1.618;
if(img.width > img.height) {
grWidth = canvas.width - grWidth;
}
var subtractFromDx = (canvas.width - grWidth) / 2;
var grHeight = canvas.height / 1.618;
if(img.height > img.height) {
grHeight = canvas.height - grHeight;
}
var subtractFromDy = (canvas.height - grHeight) / 2;
var dx = (canvas.width / 2);
dx -= subtractFromDx;
var dy = (canvas.height / 2);
dy -= (subtractFromDy + pushDownValueOnDy);
imgAspectRatioAdjustedWidth = (canvas.width - grWidth) + 50;
imgAspectRatioAdjustedHeight = (canvas.height - grHeight) + 50;
ctx.globalAlpha = 0.5;
ctx.fillStyle = 'blue;'
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1.0;
ctx.drawImage(img, dx, dy, imgAspectRatioAdjustedWidth, imgAspectRatioAdjustedHeight);
});
After this the canvas data is added to an array to be painted onto the object - it is at this point that the CanvasTexture gets the mapped canvas:
var canvasTexture = new THREE.CanvasTexture(mainCanvas.canvas);
canvasTexture.magFilter = THREE.LinearFilter;
canvasTexture.minFilter = THREE.LinearMipMapLinearFilter;
// Flip the canvas
if(this.currentSide === 'front' || this.currentSide === 'back'){
canvasTexture.wrapS = THREE.RepeatWrapping;
canvasTexture.repeat.x = -1;
}
canvasTexture.needsUpdate = true;
// { ...overdraw: true... } seems to allow the other sides to be transparent so we can see inside
var material = new THREE.MeshBasicMaterial({map: canvasTexture, side: THREE.FrontSide, transparent: false});
for(var i = 0; i < this.layers[this.currentSide].length; i++) {
mainCanvas.ctx.drawImage( this.layers[this.currentSide][i].canvas, 0, 0, this.canvasWidth, this.canvasHeight);
}
Thanks to #2pha for the help as his suggestions lead me to the correct answer and, it turns out, that the pixelated effect was caused by different dimensions of the canvases.
For example the main canvas itself was 1024x1024 whereas the text & image canvases were only 512x512 pixels meaning that it would have to be stretched to cover the size of the main canvas.

img object width and height ignored when drawing on canvas

I'm changing width and height of an image object before drawing on canvas:
earth.onload = function () {
this.width = 50;
this.height = 50;
}
earth.src = 'images/earth-transparent.png';
// and later on
function drawPlanet(xCenter, yCenter, radius, speed, img) {
var ms = time.getSeconds() * 1000 + time.getMilliseconds();
var angle = ((2 * Math.PI) / (speed * 1000) * ms);
var xDelta = radius * Math.sin(angle);
var yDelta = radius * Math.cos(angle);
var x = xCenter + xDelta;
var y = yCenter + yDelta;
ctx.drawImage(img, x - img.width / 2, y - img.height / 2, img.width, img.height);
}
var time = new Date();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'rgba(0,153,255,0.4)';
ctx.save();
drawPlanet(300, 300, 200, 6, earth);
(drawPlanet is called in the callback of a setInterval, thus the load event of the image has fired) Unfortunately the size of the image being drawn is the original one. When I output width and height in the debugger their value is 50. Why is this?

Resources