Related
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>
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>
I have a canvas element on which I am drawing a number of images and overlaying them with text. Unfortunately the problem requires that some of these images and corresponding text be rotated. Added to this the problem that there must be a corresponding background color on some of the images (images are simple outlines of desks for a floorplan)
Here is the function I have built to handle adding a single desk to the plan. The problem I am having is that when I use the rotate neither the text nor the background colors show up, while the appear correctly if I do not rotate the image, except that they are not rotated and the background fillRect() is oriented 90 degrees off.
function redrawDesk(desk, ctx, color) {
var rotate = desk.rotation == 90 || desk.rotation == 270;
if (rotate) {
ctx.save();
ctx.rotate(Math.PI / 2);
ctx.clearRect(desk.left, desk.top, desk.width, desk.height);
ctx.restore()
}
var img = $("#desk_" + desk.rowID)[0];
ctx.drawImage(img, desk.left, desk.top, desk.height, desk.width);
var x = desk.left;
var y = desk.top;
var h = desk.height;
var w = desk.width;
if (rotate) {
//ctx.save()
ctx.rotate(Math.PI / 2);
var tmp=x;
x=y;
y=tmp;
tmp=h;
h=w;
w=tmp;
}
ctx.textAlign = "center";
ctx.fillText(desk.deskID, x + w / 2,y + h/ 2);
if (color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, w, h);
}
//ctx.restore();
if (rotate) {
ctx.rotate(Math.PI / -2);
}
}
Thank you
The main problem is that you are defining the desk and text as absolute coordinates.
Define objects in there local coordinate system. Eg the desk has a height and width but not a position. Its draw relative to its self (around 0,0)
const desk = {
w : 10, h : 10,
color : "blue",
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(-this.w / 2, -this.h / 2, this.w, this.h);
}
};
You can then position the desk into the world coordinate system (the canvas) by defining where its center will be.
function drawObj(obj, x, y) { // what to draw and where
ctx.setTransform(1,0,0,1,x,y); // Same as ctx.translate if 2D API is in default context
// The means you do not have to use ctx.save and
// ctx.restore in this function
obj.draw(); // draw desk
}
For a full transform its much the same
function drawObj(obj, x, y, scale, rotate) { // rotate is in radians
ctx.setTransform(scale, 0, 0, scale, x, y);
ctx.rotate(rotate);
obj.draw();
}
To add text you can add it as an object to the desk and draw it to its own local coordinate system
desk.name = {
text : "Desk",
color : "black",
font : "bold " + 20 + "px Calibri",
draw() {
ctx.font = this.font;
ctx.textAlign = "center";
ctx.fillStyle = this.color;
ctx.fillText(this.text, 0,0);
}
};
You can now draw the desk and name using the draw object function
drawObj(desk,200, 200, 1, Math.PI / 2); // Draw at 200,200 rotated 90deg CW
drawObj(desk.name, 200, 200, 1, Math.PI / 2); // draw the text rotated same and centered over desk
// Or if the text should be above and not rotated
drawObj(desk.name, 200, 200 - 30, 1, 0);
As the above functions use setTransform you may need to restore the transform. There are two ways to do this.
ctx.resetTransform(); // Check browser support for this call
ctx.setTransform(1,0,0,1,0,0); // same as above just does it manaly
In my code I tested to see if there is a rotation needed. If so I set a translate on the canvas to give me a new start point: ctx.translate(x, y); This allowed me to simplify my location settings for placing text and the background colors, which means they are showing up correctly. Here is the changed code to compare with the original:
if (rotate) {
ctx.save();
tmp = h;
h = w;
w = tmp;
ctx.translate(x, y);
}
if (color) {
ctx.fillStyle = color;
ctx.fillRect(0, 0, w, h);
}
ctx.font = "bold " + w / 2 + "px Calibri";
ctx.textAlign = "center";
ctx.fillStyle = "#000";
var c=ctx.canvas;
ctx.rotate(Math.PI / -2);
ctx.fillText(desk.deskID, 0-h/2, w/2); //x + w / 2, y + h / 2);
ctx.restore();
Once a transformation of coordinates has taken place during a the rendering of a computer graphics scene, how do you map inputs on the rendered scene back to the original actor(s) coordinate systems?
Using this JSFiddle https://jsfiddle.net/bbz5s183/3/ as a starting point, implement the canvas click event handler so that.
It can identify if a star was clicked.
It will work consistently no matter how the canvas is resized.
JSFIDDLE SCRIPT CONTENT BELOW
var draggable = document.getElementById('draggable')
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// Draw a star in a 1 x 1 coordinate plane.
function star(color) {
context.beginPath();
context.moveTo(0.5, 0);
context.lineTo(0.15, 1.0);
context.lineTo(1.0, 0.4);
context.lineTo(0, 0.4);
context.lineTo(0.85, 1.0);
context.closePath();
context.fillStyle = color;
context.fill();
}
// Draw a scene of stars in a coordinate plane defined by the canvas.
// This is initially 300 x 300, but can be resized to anything by dragging the gray border.
function render() {
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(canvas.width / 2, canvas.height / 2);
context.scale(canvas.width / 5, canvas.height / 5);
star('red');
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(canvas.width / 4, canvas.height / 4);
context.scale(canvas.width / 5, canvas.height / 5);
star('yellow');
}
// Pop an alert indicating which star (if any) was clicked on.
// NOTE: The logic MUST work consistently no matter how the canvas is resized.
canvas.addEventListener('click', function (event) {
event.stopImmediatePropagation();
// HELP ME !!!
// HELP ME !!!
// HELP ME !!!
// HELP ME !!!
});
// IGNORE: It allows the canvas to resized by dragging on it.
draggable.addEventListener('mousedown', function handleMouseDown(mousedown) {
document.addEventListener('mouseup', function handleMouseUp(mouseup) {
var currWidth = Number(canvas.getAttribute('width'));
var deltaWidth = mouseup.clientX - mousedown.clientX;
var currHeight = Number(canvas.getAttribute('height'));
var deltaHeight = mouseup.clientY - mousedown.clientY;
canvas.setAttribute('width', currWidth + deltaWidth);
canvas.setAttribute('height', currHeight + deltaHeight);
document.removeEventListener('mouseup', handleMouseUp);
render();
});
});
render();
Answered my own question: https://jsfiddle.net/bbz5s183/4/
JAVASCRIPT FOLLOWS
// Draw a scene of stars in a coordinate plane defined by the canvas.
// This is initially 300 x 300, but can be resized to anything by dragging the gray border.
function render() {
bounds = [];
/* RENDER RED ACTOR - BOUNDING BOX */
var red = {
name: 'red',
// Translate to 25% right, 25% down on canvas.
x: 0.25 * canvas.width,
y: 0.25 * canvas.height,
// Scale to fill 20% of canvas.
width: 0.2 * canvas.width,
height: 0.2 * canvas.height
};
context.setTransform(1, 0, 0, 1, 0, 0);
box('red', red);
bounds.push(red);
/* RENDER RED ACTOR - MODEL */
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(red.x, red.y);
context.scale(red.width, red.height);
star('red');
/* RENDER YELLOW ACTOR - BOUNDING BOX */
var yellow = {
name: 'yellow',
// Translate to 50% right, 50% down on canvas.
x: 0.50 * canvas.width,
y: 0.50 * canvas.height,
// Scale to fill 20% of canvas.
width: 0.2 * canvas.width,
height: 0.2 * canvas.height
};
context.setTransform(1, 0, 0, 1, 0, 0);
box('yellow', yellow);
bounds.push(yellow);
/* RENDER YELLOW ACTOR - MODEL */
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(yellow.x, yellow.y);
context.scale(yellow.width, yellow.height);
star('yellow');
}
// Pop an alert indicating which star (if any) was clicked on.
// NOTE: The logic MUST work consistently no matter how the canvas is resized.
canvas.addEventListener('click', function (event) {
var x = event.pageX - event.target.offsetLeft;
var y = event.pageY - event.target.offsetTop;
for (var i = 0; i < bounds.length; i++) {
if (boxIntersection(bounds[i], x, y)) {
alert(bounds[i].name);
};
}
event.stopImmediatePropagation();
return false;
});
I have a problem of flickering twin images in https://github.com/dzenanr/educ_memory_game.
In the Board class of view/board.dart, the following code in the _imageBox method creates and loads images:
var imagePath = 'images/${cell.image}';
ImageElement image = new Element.tag('img');
image.src = imagePath;
image.onLoad.listen((event) {
context.drawImageToRect(image, new Rect(x, y, boxSize, boxSize));
});
A click on a board cell shows an image for a second. When the same 2 images are discovered, those twin images stay displayed , but they flicker every second (a timer refresh interval).
In order to solve the flickering problem, I create those images in a constructor of the Board class:
for (var cell in memory.cells) {
ImageElement image = new Element.tag('img');
image.src = 'images/${cell.image}';
imageMap[cell.image] = image;
}
Then, I get an image from the map. However neither of the following two works:
ImageElement image = imageMap[cell.image];
image.onLoad.listen((event) {
context.drawImageToRect(image, new Rect(x, y, boxSize, boxSize));
});
or
ImageElement image = imageMap[cell.image];
context.drawImageToRect(image, new Rect(x, y, boxSize, boxSize));
Changing the src attribute of the image imply a network access and is not good, you have to used the cached images.
To display the image correctly, just change the _imageBox function a little bit.
void _imageBox(Cell cell) {
var x = cell.column * boxSize;
var y = cell.row * boxSize;
context.beginPath();
// Moved at the beginning otherwise it is drawn above the image.
context.rect(x, y, boxSize, boxSize);
context.fill();
context.stroke();
context.closePath();
if (cell.hidden ) {
context.fillStyle = HIDDEN_CELL_COLOR_CODE;
var centerX = cell.column * boxSize + boxSize / 2;
var centerY = cell.row * boxSize + boxSize / 2;
var radius = 4;
context.arc(centerX, centerY, radius, 0, 2 * PI, false);
} else {
ImageElement image = imageMap[cell.image]; // if decomment, comment the above 3 lines
context.drawImageToRect(image, new Rect(x, y, boxSize, boxSize));
}
}