简体   繁体   中英

How to transfer rectangle coordinates from one canvas to another in Html5?

I'm using some codes to dynamically transfer co-ordinates of a rectangle on canvas to the another canvas to draw rectangle on the exact position on canvas2.

I've gone through different similar questions and solutions like: link1 and link2 on stackoverflow, but none of them is working in my case.

sendimg() function is transferring rectangle co-ordinates from canvas1 to the canvas2. As the canvas2 image is scaled to large size, rectangles are not drawing on the correct position.

Please check the attached snippet or codepen link for detail.

 var canvas = document.getElementById("canvas1"); var canvas2 = document.getElementById("canvas2"); var ctx = canvas.getContext("2d"); var ctx2 = canvas2.getContext("2d"); var img = new Image(); var rect = {}; var scale = 0; var scale2 = 0; var x = 0; var y = 0; var x2 = 0; var y2 = 0; var drag = false; img.onload = function () { //Setting dpi for canvas1 var dpi = window.devicePixelRatio || 1; canvas.setAttribute('width', canvas.clientWidth * dpi); canvas.setAttribute('height', canvas.clientHeight * dpi); //Setting dpi for canvas2 var dpi = window.devicePixelRatio || 1; canvas2.setAttribute('width', canvas2.clientWidth * dpi); canvas2.setAttribute('height', canvas2.clientHeight * dpi); ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); ctx.save(); ctx2.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); ctx2.save(); //fitting image to canvas fill scale = Math.max(canvas.clientWidth / img.width, canvas.clientHeight / img.height); //canvas1 scale scale2 = Math.max(canvas2.clientWidth / img.width, canvas2.clientHeight / img.height); //canvas2 scale x = (canvas.clientWidth / 2) - (img.width / 2) * scale; //canvas1 x y = (canvas.clientHeight / 2) - (img.height / 2) * scale; //canvas1 y x2 = (canvas2.clientWidth / 2) - (img.width / 2) * scale2; //canvas2 x y2 = (canvas2.clientHeight / 2) - (img.height / 2) * scale2; //canvas2 y ctx.drawImage(img, x, y, img.width * scale, img.height * scale); ctx2.drawImage(img, x2, y2, img.width * scale2, img.height * scale2); canvas.addEventListener('mousedown', mouseDown, false); canvas.addEventListener('mouseup', mouseUp, false); canvas.addEventListener('mousemove', mouseMove, false); } img.crossOrigin = "Anonymous"; img.src = 'https://i.imgur.com/1n8sbrF.jpg'; function mouseDown(e) { rect.startX = e.clientX - this.offsetLeft; rect.startY = e.clientY - this.offsetTop; drag = true; } function mouseUp() { drag = false; console.log(rect); } function mouseMove(e) { if (drag) { ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); ctx.save(); ctx.drawImage(img, x, y, img.width * scale, img.height * scale); rect.w = (e.clientX - this.offsetLeft) - rect.startX; rect.h = (e.clientY - this.offsetTop) - rect.startY; ctx.lineWidth = 2; ctx.strokeStyle = 'red'; ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h); } } function sendimg() { if(rect.startX === undefined) { alert("draw any rectangle first on canvas1"); return false; } ctx2.lineWidth = 2; ctx2.strokeStyle = "yellow"; //Following code is not drawing rectangle on correct position ctx2.strokeRect(rect.startX * scale2 + x2 , rect.startY* scale2 + y2, rect.w * scale2 , rect.h * scale2); }
 html, body{ width: 90%; height: 90%; } #div1 { margin: 10px; width: 800px; height: 600px; border: 2px solid red; } #div2 { position: absolute; top: 20px; left: 900px; margin: 10px; width: 1200px; height: 800px; border: 2px solid red; } canvas { width: 100%; height: 100%; }
 <button type="button" onclick="sendimg();">Send Rectangle on Canvas2</button> <div id="div1"> <canvas id="canvas1"></canvas> </div> <div id="div2"> <canvas id="canvas2"></canvas> </div>

Some major and minor issues

  • IMPORTANT When you call ctx.save you push the current 2D canvas state to a stack. If you don't call ctx.restore there is no point calling ctx.save . Worse is that every call to save is chewing up memory.
  • window is the global this. You dont need to use it. eg window.devicePixelRatio is the same as devicePixelRatio
  • You don't need to use setAttribute . Only use setAttribute when you want the Markup to reflect a properties state and the property is not defined by the DOM.
  • When you set the canvas width or height the canvas is cleared and the state is reset to default. There is no need to clear the canvas after setting its size nor to save its state.
  • Simplify the math. You have something like (a / 2) - (b / 2) * c when you calculate the origin. It has a common divisor 2 and thus becomes (a - b) * c / 2

Coordinate system

Use a common coordinate system and convert when rendering to either canvas. As both canvas render the same image (and it seams that its the image that is the relevant coord system) use the image coordinate system.

You have 4 coordinate systems, the image, the scaled image canvas1, the scaled image canvas2 and rect as canvas1 pixels. The problem is that you are converting from canvas1 pixel coordinates ( rect ) to canvas2 image coordinates

To fix your scaling and positioning convert canvas1 pixel coordinates to the image coordinates (see example function mouseEvents ). Then in your render function convert from image coords back to canvas pixels coords (see example function updateCanvas ).

Example

I removed a lot of the unneeded and repeated code to reduce complexity and make the code more readable and maintainable. Used modern JS and added some UI

  • A single function to setup the canvas
  • A single function to handle the mouse events
  • A single function to render rectangle
  • Give feedback using cursor "crosshair" and "none"

For the example I removed the button (not relevant to the question), reduced the two canvas size to fit the very small snippet window, and have the second rectangle update live as the first rectangle is being plotted.

Note that rect is in image coordinates.

 const COLOR1 = "#F00", COLOR2 = "#FF0", LINE_WIDTH = 2; const ctx1 = canvas1.getContext("2d"), ctx2 = canvas2.getContext("2d"); const img = new Image; const rect = {}; var drag = false; img.src = "https://i.imgur.com/1n8sbrF.jpg"; img.addEventListener("load",() => { setupCanvas(ctx1); setupCanvas(ctx2); canvas1.addEventListener("mousedown", mouseEvent); canvas1.addEventListener("mouseup", mouseEvent); canvas1.addEventListener("mousemove", mouseEvent); }, {once: true}); function setupCanvas(ctx, coords = {}) { const dPR = devicePixelRatio || 1; const w = ctx.canvas.width = ctx.canvas.clientWidth * dPR; const h = ctx.canvas.height = ctx.canvas.clientHeight * dPR; const scale = coords.scale = Math.max(w / img.width, h / img.height); const x = coords.x = (w - img.width * scale) / 2; const y = coords.y = (h - img.height * scale) / 2; ctx.drawImage(img, x, y, img.width * scale, img.height * scale); ctx.canvas.coords = coords; } function mouseEvent(e) { var cursor = "crosshair"; const co = canvas1.coords; const x = (e.clientX - this.offsetLeft - co.x) / co.scale; const y = (e.clientY - this.offsetTop - co.y) / co.scale; if (e.type === "mousedown") { rect.x = x; rect.y = y; drag = true; canvas1.title = ""; } else if (e.type === "mouseup") { drag = false } if (drag) { cursor = "none"; rect.w = x - rect.x; rect.h = y - rect.y; updateCanvas(ctx1, COLOR1, co); updateCanvas(ctx2, COLOR2, canvas2.coords); } canvas1.style.cursor = cursor; } function updateCanvas(ctx, color, {x, y, scale}) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.drawImage(img, x, y, img.width * scale, img.height * scale); ctx.lineWidth = LINE_WIDTH; ctx.strokeStyle = color; ctx.strokeRect(x + rect.x * scale, y + rect.y * scale, rect.w * scale, rect.h * scale); }
 html, body{ width: 90%; height: 90%; } #div1 { margin: 10px; width: 200px; height: 300px; border: 2px solid red; } #div2 { position: absolute; top: 10px; left: 240px; width: 300px; height: 200px; border: 2px solid #FF0; } canvas { width: 100%; height: 100%; }
 <div id="div1"> <canvas id="canvas1" title="Click and drag to select area."></canvas> </div> <div id="div2"> <canvas id="canvas2"></canvas> </div>

You need to reverse your scaling and translation of the first canvas before applying your transformation and scaling factors of the second canvas. This is like 90% of the way there, the Y axis is correct, but the X axis is translated wrong. See my call to ctx2.strokeRect and how it reverses the scaling and translation. My code is probably jacked because the canvas origin is in the top left and the formulas for translation and scaling assume it's the bottom left.

Edit: Fixed the code - it should be correct now.

 var canvas = document.getElementById("canvas1"); var canvas2 = document.getElementById("canvas2"); var ctx = canvas.getContext("2d"); var ctx2 = canvas2.getContext("2d"); var img = new Image(); var rect = {}; var scale = 0; var scale2 = 0; var x = 0; var y = 0; var x2 = 0; var y2 = 0; var drag = false; img.onload = function () { //Setting dpi for canvas1 var dpi = window.devicePixelRatio || 1; canvas.setAttribute('width', canvas.clientWidth * dpi); canvas.setAttribute('height', canvas.clientHeight * dpi); //Setting dpi for canvas2 var dpi = window.devicePixelRatio || 1; canvas2.setAttribute('width', canvas2.clientWidth * dpi); canvas2.setAttribute('height', canvas2.clientHeight * dpi); ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); ctx.save(); ctx2.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); ctx2.save(); //fitting image to canvas fill scale = Math.max(canvas.clientWidth / img.width, canvas.clientHeight / img.height); //canvas1 scale scale2 = Math.max(canvas2.clientWidth / img.width, canvas2.clientHeight / img.height); //canvas2 scale x = (canvas.clientWidth / 2) - (img.width / 2) * scale; //canvas1 x y = (canvas.clientHeight / 2) - (img.height / 2) * scale; //canvas1 y x2 = (canvas2.clientWidth / 2) - (img.width / 2) * scale2; //canvas2 x y2 = (canvas2.clientHeight / 2) - (img.height / 2) * scale2; //canvas2 y ctx.drawImage(img, x, y, img.width * scale, img.height * scale); ctx2.drawImage(img, x2, y2, img.width * scale2, img.height * scale2); canvas.addEventListener('mousedown', mouseDown, false); canvas.addEventListener('mouseup', mouseUp, false); canvas.addEventListener('mousemove', mouseMove, false); console.log(scale) console.log(scale2) } img.crossOrigin = "Anonymous"; img.src = 'https://i.imgur.com/1n8sbrF.jpg'; function mouseDown(e) { rect.startX = e.clientX - this.offsetLeft; rect.startY = e.clientY - this.offsetTop; drag = true; } function mouseUp() { drag = false; console.log(rect); } function mouseMove(e) { if (drag) { ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); ctx.save(); ctx.drawImage(img, x, y, img.width * scale, img.height * scale); rect.w = (e.clientX - this.offsetLeft) - rect.startX; rect.h = (e.clientY - this.offsetTop) - rect.startY; ctx.lineWidth = 2; ctx.strokeStyle = 'red'; ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h); } } function sendimg() { if(rect.startX === undefined) { alert("draw any rectangle first on canvas1"); return false; } ctx2.lineWidth = 2; ctx2.strokeStyle = "yellow"; //Following code is not drawing rectangle on correct position ctx2.strokeRect( ((rect.startX - x )/scale) * scale2 + x2, ((rect.startY - y )/scale) * scale2 + y2, (rect.w/scale) * (scale2), (rect.h/scale) * (scale2)); }
 html, body{ width: 90%; height: 90%; } #div1 { margin: 10px; width: 800px; height: 600px; border: 2px solid red; } #div2 { position: absolute; top: 20px; left: 900px; margin: 10px; width: 1200px; height: 800px; border: 2px solid red; } canvas { width: 100%; height: 100%; }
 <button type="button" onclick="sendimg();">Send Rectangle on Canvas2</button> <div id="div1"> <canvas id="canvas1"></canvas> </div> <div id="div2"> <canvas id="canvas2"></canvas> </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM