javascript - "Undo"用于编写文本的 Canvas 转换

标签 javascript canvas transformation

当使用 Canvas 应用转换时,生成的文本也(显然)被转换。有没有办法防止某些影响文本的转换,例如反射?

例如,我设置了一个全局变换矩阵,使 Y 轴指向上方,X 轴指向右侧,(0, 0) 点位于屏幕中心 (您对数学坐标系的期望)。

但是,这也会使文本颠倒。

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>

除了手动重置变换矩阵之外,是否有一种“智能”的方式让文本处于“正确”的方向?

最佳答案

要以 Tai 的回答为基础,这太棒了,您可能需要考虑以下几点:

    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));

如果对于您的实际应用程序,上面的内容很有用,您将在绘制非文本对象和绘制文本之间来回切换并且不想记住来回翻转 Canvas 。 (在当前的示例中这不是一个大问题,因为您绘制三 Angular 形然后绘制所有文本,所以您只需要翻转一次。但是如果您有一个更复杂的不同应用程序,那可能会很烦人。)在上面的示例中,我将 fillText 方法替换为自定义方法,该方法翻转 Canvas 、绘制文本,然后再次将其翻转回来,这样您就不必在每次绘制文本时都手动执行此操作。

结果:

enter image description here

如果您不喜欢覆盖默认的fillText,那么显然您可以创建一个具有新名称的方法;这样您也可以避免创建虚拟上下文,而只需在您的自定义方法中使用 this.fillText

编辑:上述方法也适用于任意缩放和平移。 scale(1, -1) 只是在 x 轴上反射(reflect)了 Canvas :在这个转换之后,位于 (x, y) 的点现在将位于 (x, -y)。无论平移和缩放如何,都是如此。如果您希望无论缩放如何,文本都保持恒定大小,那么您只需通过缩放来缩放字体大小。例如:

<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>

编辑:根据 Blindman67 的建议修改文本缩放方法。还通过使相机运动渐进来改进演示。

关于javascript - "Undo"用于编写文本的 Canvas 转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41551931/

相关文章:

javascript - 根据javascript中的日期字段删除重复元素

javascript - 鼠标悬停时灰度

html - 您将如何实现一些文本旋转 90° 的 Web UI

ASP.net Canvas 服务器控件

javascript - 计算通过 Canvas 高度的 float 对象

xml - Azure 集成帐户( map - XSLT)- 使用逻辑应用将 2 个不同的 XML 合并为单个 xml

visual-studio - 添加没有自定义工具的模板文件以编程方式进行项目

javascript - 带有 Javascript 回退的 CSS3 过渡

javascript - 使用窗口滚动变换比例图像

javascript - 页面高度仍然不一样