-2

I am trying to sketch a rectangle, like how you do in Paint. But currently when I try to sketch a rectangle, this is what my canvas looks like--double drawings, messy rectangles.

This is what my whole code looks like:

(deleted code)

How do I sketch a rectangle that looks like this: like one rectangle?

  • 1
    Questions need to stand on their own and be specific. – Dave Newton Apr 03 '20 at 21:59
  • You may need to clear the dirty area with [clearRect](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clearRect) (or whatever) before each redraw. – ray Apr 03 '20 at 22:00
  • I recommend posting your code in your question. You can use markdown if you want to make it show nicely. This will help you get better answers to your question – Brian Pursley Apr 03 '20 at 22:10
  • @rayhatfield: I don't want to clear all of the canvas, just to post over the shape that it being dragged to make the final rectangle, if that makes any sense? – intuitiveprogramming Apr 03 '20 at 22:17
  • One issue I see in your code/screenshot is that the you use 2 as index instead of x2. also you need to clear the screen every time the user drags the mouse and re-draw it again. – sney2002 Apr 03 '20 at 22:22
  • @sney20002 "you need to clear the screen every time the user drags the mouse and re-draw it again" Yes!!! That's exactly what I want to do! How do I do that? – intuitiveprogramming Apr 03 '20 at 22:31
  • 1
    @intuitiveprogramming please post the complete code to see what you need to change – sney2002 Apr 03 '20 at 22:33
  • **DO NOT post images of code, data, error messages, etc.** - copy or type the text into the question. [ask] – Rob Apr 03 '20 at 22:36
  • @sney20002 Just uploaded the entire code. – intuitiveprogramming Apr 03 '20 at 22:47

3 Answers3

0

The code you post has arrays:
ctx.rect(x[x1],y[y1]
but my guess is you are drawing more than just rectangles ...

Here is a sample:

ctx = document.getElementById('c').getContext('2d');
ctx.lineWidth = 2;
x = [5, 30, 90]
y = [5, 50, 30]

for (i = 0; i < x.length; i++) {
  ctx.rect(x[i], y[i], 50, 20)
}
ctx.stroke();
<canvas height="100" width="300" id="c">

Just rectangles nothing else, no double drawings or messiness, the code you provided should not cause any issues like what you show in the picture.


Here is an update based on your new code...

var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
ctx.lineWidth = 2;

var x = 0;
var y = 0;
draw = false;

function down() {
  x = event.clientX;
  y = event.clientY;
  draw = true;
}

function move() {
  if (draw) {
    var a = event.clientX;
    var b = event.clientY;

    ctx.clearRect(0, 0, canvas.width, canvas.height)
    addCircles()
    ctx.beginPath()
    ctx.rect(x, y, a - x, b - y);
    ctx.stroke();
  }
}

function up() {
  draw = false
}


function addCircles() {
  ctx.beginPath()
  ctx.fillStyle = '#F00';
  ctx.arc(50, 50, 30, 0, Math.PI * 2)
  ctx.fill();
  ctx.beginPath()
  ctx.fillStyle = '#0F0';
  ctx.arc(100, 100, 30, 0, Math.PI * 2)
  ctx.fill();
  ctx.beginPath()
  ctx.fillStyle = '#00F';
  ctx.arc(200, 80, 30, 0, Math.PI * 2)
  ctx.fill();
}


addCircles()
<canvas id="myCanvas" width="300" height="160" style="border:1px solid #000000;" onmousedown="down()" onmousemove="move()" onmouseup="up()"> </canvas>

you had a mess of variables, I removed everything not absolutely required to draw a rectangle, you can complicate that later anyway you like to draw multiple objects, but I would recommend you to test your code often, small incremental changes making sure it does what you want.

Helder Sepulveda
  • 15,500
  • 4
  • 29
  • 56
  • No, the code I have has the mousedown coordinates (x[x1],y[y1]) and the mousemove coordinates (x[x2],y[y2]). I do not want to program rectangles to show up on the canvas. want to create a rectangle, like you do in Paint. – intuitiveprogramming Apr 03 '20 at 22:42
  • @intuitiveprogramming See my update based on your full code – Helder Sepulveda Apr 03 '20 at 23:13
  • Is there an alternative to clearrect? Like canvas.todataURL? Because I want to keep the other stuff that may be on the canvas. So I don't want to clear the canvas everytime I want to draw a rectangle. – intuitiveprogramming Apr 03 '20 at 23:22
  • unfortunately, you will have to clear the canvas everytime, but you can redraw everything you need, let me do a small sample to show you – Helder Sepulveda Apr 03 '20 at 23:30
  • @intuitiveprogramming I added a sample drawing multiple objects that clear the canvas every time, the rest is up to you – Helder Sepulveda Apr 03 '20 at 23:38
0

First of all I think it's more clear if you store the coordinates in separated variables instead of an array (startX, startY, lastX, lastY).

Also you should subtract the canvas position from the mouse position to get the coordinate inside the canvas, right now it kind of work because the canvas is at the top of the page.

I see you want to keep the old content, I used a canvas to store the previous drawn content, then before you draw the current rectangle you must redraw the old content (using ctx.drawImage).

You should add 0.5 to the coordinates so the lines do not get blurry, this only happens if you draw a line with an odd width (1, 3, 5). this is because if you try to draw 1px line on say coordinate x=1 you must draw half a pixel to both sides (0.5 to 1.5), so it looks blurry.

but if you draw it at say x=0.5 you draw the line from 0 to 1 which is exactly one pixel.

var isDown = false;
var startX, startY;
var lastX, lastY;

var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var canvasRect = canvas.getBoundingClientRect();

var backBuffer = canvas.cloneNode(true);
var backBufferCtx = backBuffer.getContext('2d');

canvas.addEventListener('mousedown', function down() {
    startX = event.clientX - canvasRect.left;
    startY = event.clientY - canvasRect.top;
    isDown = true;
});

canvas.addEventListener('mouseup', function up() {
    isDown = false;

    // Draw Current Canvas Content
    updateCanvas();

    // Save current content
    backBufferCtx.clearRect(0, 0, backBuffer.width, backBuffer.height);
    backBufferCtx.drawImage(canvas, 0, 0);
});

canvas.addEventListener('mousemove', function move() {
    if (! isDown) return;

    lastX = event.clientX - canvasRect.left;
    lastY = event.clientY - canvasRect.top;
    updateCanvas();
});

function updateCanvas() {
    // Clear the canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Draw Current Canvas Content
    ctx.drawImage(backBuffer, 0, 0);

    // Draw New Rectangle
    ctx.beginPath();
    // add 0.5 so the line do not get blurry
    ctx.rect(startX + 0.5, startY + 0.5, lastX - startX, lastY - startY);
    ctx.stroke();
}
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000; margin-top: 100px"></canvas>
sney2002
  • 856
  • 3
  • 6
  • Cool, this works! Is there an easier way, like by using .todataURL and placing the old version before the stroke? Because I did something like that in the original version before losing that code. – intuitiveprogramming Apr 04 '20 at 01:11
  • @intuitiveprogramming I think this is the easier way because you can use the other canvas (backbuffer) as an image, but using toDataURL you need to create an image and set the image.src to the dataurl before you can draw it on the canvas. – sney2002 Apr 04 '20 at 02:46
  • Got it! Thanks for your help! – intuitiveprogramming Apr 04 '20 at 04:47
0

The context of a canvas is a very powerful thing, I suggest you to check out all of its properties, methods on https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D

In particular for your use-case: the context provides methods for reading-writing canvas data directly, getImageData()-putImageData() is the method pair for that. Using them you can store whatever the canvas contains when user starts drawing the new rectangle, and restore it when the current rectange is resized. The stored data can even provide a one-step "Undo" feature as a by-product:

var cnv=document.getElementById("cnv"),
    ctx=cnv.getContext("2d"),
    col=document.getElementById("color"),
    ubtn=document.getElementById("undo"),
    copy,
    pick=false;

function mdown(event){
  copy=ctx.getImageData(0,0,cnv.width,cnv.height);
  pick={
    x:event.offsetX,
    y:event.offsetY
  };
}

function mup(event){
  pick=false;
  ubtn.disabled=false;
}

function mmove(event){
  if(pick){
    ctx.putImageData(copy,0,0);
    ctx.strokeStyle=col.value;
    ctx.lineWidth=2;
    ctx.strokeRect(pick.x,pick.y,event.offsetX-pick.x,event.offsetY-pick.y);
  }
}

function undo(){
  ctx.putImageData(copy,0,0);
  ubtn.disabled=true;
}
<input type="color" id="color"><button id="undo" disabled onclick="undo()">Undo</button><br>
<canvas id="cnv" width="300" height="140" style="border:1px solid black;cursor:crosshair" onmousedown="mdown(event)" onmousemove="mmove(event)" onmouseup="mup(event)"></canvas>
tevemadar
  • 12,389
  • 3
  • 21
  • 49