簡體   English   中英

用於書寫文本的“撤消”畫布轉換

[英]“Undo” canvas transformations for writing text

使用畫布應用轉換時,生成的文本也會(顯然)被轉換。 有沒有辦法防止影響文本的某些轉換,例如反射?

例如,我設置了一個全局變換矩陣,因此 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(vx, vy)); ctx.stroke(); ctx.closePath(); // draw labels ctx.textAlign = 'center'; ctx.font = '24px Arial'; triangle.forEach(v => ctx.fillText(v.label, vx, vy - 8));
 <canvas></canvas>

除了手動重置變換矩陣之外,是否有一種“智能”方法可以使文本處於“正確”方向?

我的解決方案是旋轉畫布,然后繪制文本。

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

希望有幫助:)

 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(vx, vy)); ctx.stroke(); ctx.closePath(); // draw labels ctx.textAlign = 'center'; ctx.font = '24px Arial'; ctx.scale(1,-1); triangle.forEach(v => { ctx.fillText(v.label, vx, -vy + 25); });
 <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(vx, vy)); 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*vx, 1.1*vy));

如果對於您的實際應用程序,您將在繪制非文本對象和繪制文本之間來回切換,並且不想記住來回翻轉畫布,則上述內容很有用。 (在當前示例中這不是一個大問題,因為您先繪制三角形然后繪制所有文本,因此您只需要翻轉一次。但如果您想使用更復雜的其他應用程序,那可能會很麻煩。)在上面的例子中,我用自定義方法替換了 fillText 方法,該方法翻轉畫布,繪制文本,然后再次翻轉回來,這樣您就不必每次要繪制文本時都手動執行此操作。

結果:

在此處輸入圖片說明

如果您不喜歡覆蓋默認的fillText ,那么顯然您可以創建一個具有新名稱的方法; 這樣你也可以避免創建虛擬上下文,只需在你的自定義方法中使用this.fillText

編輯:上述方法也適用於任意縮放和平移。 scale(1, -1)只是在 x 軸上反射畫布:在此轉換之后,位於 (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 的建議修改文本縮放方法。 還通過使相機運動漸進來改進演示。

我會采用一種方法,在沒有實際像素的情況下存儲繪圖的“狀態”,並定義一個可以在任何時候呈現此狀態的draw方法。

您必須實現自己的scale並為您的積分translate方法,但我認為這最終是值得的。

所以,在子彈中:

  • 存儲“要繪制的東西”列表(帶標簽的點)
  • 公開scaletranslate修改這些“事物”的方法
  • 公開一個draw這些“東西”的方法

例如,我創建了一個名為Figure的類,它顯示了這些功能的 1.0 實現。 我創建了一個引用畫布的新實例。 然后我通過傳遞一個xy和一個label向它添加點。 scaletransform更新這些點的xy屬性。 通過點draw循環以 a) 繪制“點”,和 b) 繪制標簽。

 const Figure = function(canvas) { const ctx = canvas.getContext('2d'); const origin = { x: canvas.width / 2, y: canvas.height / 2 }; const shift = p => Object.assign(p, { x: origin.x + px, y: origin.y - py }); let points = []; this.addPoint = (x, y, label) => { points = points.concat({ x, y, label }); } this.translate = (tx, ty) => { points = points.map( p => Object.assign(p, { x: px + tx, y: py + ty }) ); }; this.scale = (sx, sy) => { points = points.map( p => Object.assign(p, { x: px * sx, y: py * sy }) ); }; this.draw = function() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.beginPath(); const sPoints = points.map(shift); sPoints.forEach(p => drawDot(ctx, 5, px, py)); sPoints.forEach(p => drawLabel(ctx, p.label, px + 5, py)); ctx.fill(); } } const init = () => { const canvas = document.getElementById('canvas'); const fig = new Figure(canvas); // Generate some test data for (let i = 0, labels = "ABCD"; i < labels.length; i += 1) { fig.addPoint(i * 3, (i + 1) * 10, labels[i]); } const sX = parseFloat(document.querySelector(".js-scaleX").value); const sY = parseFloat(document.querySelector(".js-scaleY").value); const tX = parseFloat(document.querySelector(".js-transX").value); const tY = parseFloat(document.querySelector(".js-transY").value); fig.scale(sX, sY); fig.translate(tX, tY); fig.draw(); } Array .from(document.querySelectorAll("input")) .forEach(el => el.addEventListener("change", init)); init(); // Utilities for drawing function drawDot(ctx, d, x, y) { ctx.arc(x, y, d / 2, 0, 2 * Math.PI); } function drawLabel(ctx, label, x, y) { ctx.fillText(label, x, y); }
 canvas { background: #efefef; margin: 1rem; } input { width: 50px; }
 <div> <p> Scales first, translates second (hard coded, can be changed) </p> <label>Scale x <input type="number" class="js-scaleX" value="1"></label> <label>Scale y <input type="number" class="js-scaleY" value="1"></label> <br/> <label>Translate x <input type="number" class="js-transX" value="0"></label> <label>translate y <input type="number" class="js-transY" value="0"></label> </div> <canvas id="canvas" width="250" height="250"></canvas>

注意:使用輸入作為其工作原理的示例。 我選擇“提交”規模的變化並立即翻譯,所以訂單很重要! 您可能想要按全屏以同時查看畫布和輸入。

替代方案

  var x = 100;
  var y = 100;
  var pixelRatio = 2;
  var transform = {"x": 0, "y": 0, "k": 1}

  context.save();
  context.setTransform(pixelRatio, 0.0, 0.0, pixelRatio, 0.0, 0.0);
  context.translate(transform.x, 0);
  context.scale(transform.k, 1);

  context.save();
  // get Transformed Point
  var context_transform = context.getTransform();
  var pt = context_transform.transformPoint({
        x: x,
        y: y
      });

  // Reset previous transforms
  context.setTransform(pixelRatio, 0.0, 0.0, pixelRatio, -pt.x, -pt.y);
  

  // draw with the values as usual
  context.textAlign = "left";
  context.font = "14px Arial";
  context.fillText("Hello", pt.x, pt.y);
  context.restore();

  context.restore();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM