Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canvas lineTo() drawing y coordinate in wrong place

I'm trying to draw some rectangles on a canvas using ctx.lineTo(). They get drawn but the y coordinate is never right. The rectangles become too tall and on the wrong place on the y axis. When I step through with the debugger it shows the y coordinates within the lineTo() methods as being correct, but I made a canvas.click event to alert the coordinates (which are correct as I click in the top left and it alerts (0,0)). The click event shows that the y coordinate is not actually where it states it will be drawn in the lineTo() method. The x coordinate is however always correct. One thing to be considered is I create my canvas by appending html to an element with javascript, and I add an image to it that I draw on. I rescale the coordinates of the rectangles so they are appropriately place on the canvas which is scaled to the image size that works for the size of the device. Here is all my code from canvas creation to using the lineTo() method.

Canvas gets created in early stages of this method (in appendPicture()):

function appendSection(theSection, list) {
    list.append('<label class="heading">' + theSection.description + '</label><br/><hr><br/>');
    if (theSection.picture) {
        appendPicture(list, theSection);
        var canvas = document.getElementById('assessmentImage');
        var ctx=canvas.getContext("2d");
         canvas.addEventListener("mousedown", relMouseCoords, false);

        var img=new Image();
            img.onload = function() {
                ctx.drawImage(img, 0, 0,canvas.width,canvas.height);
            }
            img.src = "data:image/jpeg;base64,"+ theSection.picture;

        img.addEventListener('load', function() { 
            if(theSection.allHotSpots.length > 0) {
                for( var x = 0; x < theSection.allHotSpots.length; x++) {
                    appendHotSpot(theSection.allHotSpots[x], theSection.thePicture, ctx);
                }
            }

        }, false);

    }
    appendSectionQuestions(theSection, list);
    if (theSection.allSubSections) {
        for (var x = 0; x < theSection.allSubSections.length; x++) {
            var theSectionA = theSection.allSubSections[x];
            appendSection(theSectionA, list);
        }
    }
}

Here is appendPicture which creates the canvas html and appends it to an element.

function appendPicture(list, theSection) {

        list.append('<div id="wrapper' + platform + '" style="width:100%; text-align:center">\
            <canvas class="assessmentImageSmall" style="width:100%;height:' + Math.round(theSection.thePicture.ySize * (document.getElementById('assessmentSectionForm' + platform).clientWidth / theSection.thePicture.xSize)) + 'px" id="assessmentImage' + platform + '" align="middle" ></canvas>\
            <!--<p style="color:#666;" id="imageInstruction">Tap image to enlarge.</p>-->\
            </div>');
        $("#wrapper").kendoTouch({
                                     tap: function (e) {
                                         switchImage();
                                     }
                                 });
}

Here is where I draw the rectangle (I call rectangles hotspots in this function)

function appendHotSpot(HotSpot, picture, ctx) {
    var imageWidth = document.getElementById('assessmentImage' + platform).clientWidth;

    var scale = imageWidth / picture.xSize;

    HotSpot.topLeft = [Math.round(HotSpot.topLeft[0] * scale), Math.round(HotSpot.topLeft[1] * scale)];
    HotSpot.bottomRight = [Math.round(HotSpot.bottomRight[0] * scale), Math.round(HotSpot.bottomRight[1] * scale)];

    var rect = {x1: HotSpot.topLeft[0], y1: HotSpot.topLeft[1], x2: HotSpot.bottomRight[0], y2: HotSpot.bottomRight[1]};

    ctx.strokeStyle="red";
    ctx.beginPath();
    ctx.moveTo(rect.x1, rect.y1);
    ctx.lineTo(rect.x2, rect.y1);
    ctx.lineTo(rect.x2, rect.y2);
    ctx.lineTo(rect.x1, rect.y2);
    ctx.lineTo(rect.x1, rect.y1);
    ctx.stroke();
}
like image 282
btf Avatar asked Jan 25 '15 22:01

btf


2 Answers

Canvas' width and height are different from its CSS width and height!

This means that the canvas gets stretched to align to CSS sizes. This is good for scaling, but can result in issues like the ones you're having with width: 100%.


Two solutions (depending on what you need):

  • read the canvas' DOM element size and apply it to the canvas itself (see the 4th canvas in the example below)
  • modify its CSS styles based on the canvas' actual size (see the 5th canvas below)

Simple example:

function buildCanvas(w, h, sizeFromDOM, changeCSS, looksFine) {
  var canvas = document.createElement('canvas');
  var ctx = canvas.getContext('2d');
  document.body.appendChild(canvas);
  canvas.style.borderColor = looksFine ? "green" : "red";

  if (sizeFromDOM) {
    // read its size from the DOM
    canvas.width = canvas.offsetWidth;
    canvas.height = canvas.offsetHeight;
  } else {
    // or simply apply what was given
    canvas.width = w;
    canvas.height = h;
  }
  
  // change CSS styles if needed
  if (changeCSS) {
    canvas.style.width = canvas.width + 'px';
    canvas.style.height = canvas.height + 'px';
  }
  
  // draw the same 80x80 square on each
  ctx.strokeStyle = looksFine ? "green" : "red";
  ctx.beginPath();
  ctx.moveTo(10, 10);
  ctx.lineTo(90, 10);
  ctx.lineTo(90, 90);
  ctx.lineTo(10, 90);
  ctx.lineTo(10, 10);
  ctx.stroke();
}

buildCanvas(200, 200, false, false, true); // this canvas is fine as it matches the CSS sizes
buildCanvas(300, 200); // this canvas is stretched horizontally
buildCanvas(200, 300); // this canvas is stretched vertically
buildCanvas(200, 300, true, false, true); // let's fix this one
buildCanvas(300, 200, false, true, true); // this one too, but by changing its CSS
canvas {
  width: 200px;
  height: 200px;
  border: 1px solid #aaa;
  margin: 4px;
}
like image 153
Shomz Avatar answered Nov 18 '22 04:11

Shomz


Here's a nice little kludge that will set all canvas elements' heights and widths to equal their CSS settings.

var canvases = document.getElementsByTagName("canvas");
for(var i=0; i<canvases.length; i++)
{
  canvas = canvases[i];
  canvas.width = canvas.offsetWidth;
  canvas.height = canvas.offsetHeight;
}

Now you can continue to set canvas dimensions by CSS!

(I wouldn't have figured this out without @Shomz' answer.)

like image 29
Grant Birchmeier Avatar answered Nov 18 '22 06:11

Grant Birchmeier