When applying a transformation with canvas, the resulting text is also (obviously) transformed. Is there a way to prevent certain transformations, such as reflection, of affecting text?
For example, I set a global transformation matrix so the Y-axis points upwards, X-axis to the right, and the (0, 0)
point is in the center of the screen (what you'd expect of a mathematical coordinate system).
However, this also makes the text upside-down.
const size = 200;
const canvas = document.getElementsByTagName('canvas')[0]
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);
const triangle = [
{x: -70, y: -70, label: 'A'},
{x: 70, y: -70, label: 'B'},
{x: 0, y: 70, label: 'C'},
];
// draw lines
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(triangle[2].x, triangle[2].y);
triangle.forEach(v => ctx.lineTo(v.x, v.y));
ctx.stroke();
ctx.closePath();
// draw labels
ctx.textAlign = 'center';
ctx.font = '24px Arial';
triangle.forEach(v => ctx.fillText(v.label, v.x, v.y - 8));
<canvas></canvas>
Is there a "smart" way to get the text in "correct" orientation, apart from manually resetting transformation matrices?
In the canvas toolbar, click the Select/Transform tool, then double-click text in the canvas. In the Layer's list, select a text layer, then in the Text editor (at the bottom of the Text Inspector's Format pane), drag within or double-click text.
You use context. clearRect(), but first you have to figure out the rectangle to clear. This is based off a number of factors, such as the size of the text and the textAlign property of the canvas context when the text was originally drawn.
To build off of Tai's answer, which is fantastic, you might want to consider the following:
const size = 200;
const canvas = document.getElementsByTagName('canvas')[0]
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
// Create a custom fillText funciton that flips the canvas, draws the text, and then flips it back
ctx.fillText = function(text, x, y) {
this.save(); // Save the current canvas state
this.scale(1, -1); // Flip to draw the text
this.fillText.dummyCtx.fillText.call(this, text, x, -y); // Draw the text, invert y to get coordinate right
this.restore(); // Restore the initial canvas state
}
// Create a dummy canvas context to use as a source for the original fillText function
ctx.fillText.dummyCtx = document.createElement('canvas').getContext('2d');
ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);
const triangle = [
{x: -70, y: -70, label: 'A'},
{x: 70, y: -70, label: 'B'},
{x: 0, y: 70, label: 'C'},
];
// draw lines
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(triangle[2].x, triangle[2].y);
triangle.forEach(v => ctx.lineTo(v.x, v.y));
ctx.stroke();
ctx.closePath();
// draw labels
ctx.textAlign = 'center';
ctx.font = '24px Arial';
// For this particular example, multiplying x and y by small factors >1 offsets the labels from the triangle vertices
triangle.forEach(v => ctx.fillText(v.label, 1.2*v.x, 1.1*v.y));
The above is useful if for your real application, you'll be going back and forth between drawing non-text objects and drawing text and don't want to have to remember to flip the canvas back and forth. (It's not a huge problem in the current example, because you draw the triangle and then draw all the text, so you only need one flip. But if you have in mind a different application that's more complex, that could be an annoyance.) In the above example, I've replaced the fillText method with a custom method that flips the canvas, draws the text, and then flips it back again so that you don't have to do it manually every time you want to draw text.
The result:
If you don't like overriding the default fillText
, then obviously you can just create a method with a new name; that way you could also avoid creating the dummy context and just use this.fillText
within your custom method.
EDIT: The above approach also works with arbitrary zoom and translation. scale(1, -1)
simply reflects the canvas over the x-axis: after this transformation, a point that was at (x, y) will now be at (x, -y). This is true regardless of translation and zoom. If you want the text to remain a constant size regardless of zoom, then you just have to scale the font size by zoom. For example:
<html>
<body>
<canvas id='canvas'></canvas>
</body>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
var framesPerSec = 100;
var msBetweenFrames = 1000/framesPerSec;
ctx.font = '12px Arial';
function getRandomCamera() {
return {x: ((Math.random() > 0.5) ? -1 : 1) * Math.random()*5,
y: ((Math.random() > 0.5) ? -1 : 1) * Math.random()*5+5,
zoom: Math.random()*20+0.1,
};
}
var camera = getRandomCamera();
moveCamera();
function moveCamera() {
var newCamera = getRandomCamera();
var transitionFrames = Math.random()*500+100;
var animationTime = transitionFrames*msBetweenFrames;
var cameraSteps = { x: (newCamera.x-camera.x)/transitionFrames,
y: (newCamera.y-camera.y)/transitionFrames,
zoom: (newCamera.zoom-camera.zoom)/transitionFrames };
for (var t=0; t<animationTime; t+=msBetweenFrames) {
window.setTimeout(updateCanvas, t);
}
window.setTimeout(moveCamera, animationTime);
function updateCanvas() {
camera.x += cameraSteps.x;
camera.y += cameraSteps.y;
camera.zoom += cameraSteps.zoom;
redrawCanvas();
}
}
ctx.drawText = function(text, x, y) {
this.save();
this.transform(1 / camera.zoom, 0, 0, -1 / camera.zoom, x, y);
this.fillText(text, 0, 0);
this.restore();
}
function redrawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(canvas.width / 2 - (camera.x * camera.zoom),
canvas.height / 2 + (camera.y * camera.zoom));
ctx.scale(camera.zoom, -camera.zoom);
for (var i = 0; i < 10; i++) {
ctx.beginPath();
ctx.arc(5, i * 2, .5, 0, 2 * Math.PI);
ctx.drawText(i, 7, i*2-0.5);
ctx.fill();
}
ctx.restore();
}
</script>
</html>
EDIT: Modified text scaling method based on suggestion by Blindman67. Also improved demo by making camera motion gradual.
My solution is rotate the canvas and then draw the text.
ctx.scale(1,-1); // rotate the canvas
triangle.forEach(v => {
ctx.fillText(v.label, v.x, -v.y + 25); // draw with a bit adapt position
});
Hope that helps :)
const size = 200;
const canvas = document.getElementsByTagName('canvas')[0]
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);
const triangle = [
{x: -70, y: -70, label: 'A'},
{x: 70, y: -70, label: 'B'},
{x: 0, y: 70, label: 'C'},
];
// draw lines
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(triangle[2].x, triangle[2].y);
triangle.forEach(v => ctx.lineTo(v.x, v.y));
ctx.stroke();
ctx.closePath();
// draw labels
ctx.textAlign = 'center';
ctx.font = '24px Arial';
ctx.scale(1,-1);
triangle.forEach(v => {
ctx.fillText(v.label, v.x, -v.y + 25);
});
<canvas></canvas>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With