Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Canvas's putImageData not work when I specify target location?

Tags:

html

canvas

In trying to find documentation for Canvas context's putImageData() method, I've found things like this:

context.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight);

(from http://www.w3schools.com/tags/canvas_putimagedata.asp)

According to the documentation I've read, x and y are an index into the source image, whereas dirtyX and dirtyY specify coordinates in the target canvas where to draw the image. Yet, as you'll see from the example below (and JSFiddle) a call to putImageData(imgData,x,y) works while putImageData(imgData, 0, 0, locX, locY) doesn't. I'm not sure why.

EDIT:

I guess my real question is why the top row of the image is black, and there are only 7 rows, not 8. The images should start at the top-left of the Canvas. They DO start at the left (and have 8 columns). Why do they not start at the top?

Answer: that's due to divide by 0 on this line when yLoc is 0:

xoff = imgWidth / (yLoc/3);

The JSFiddle:

http://jsfiddle.net/WZynM/

Code:

<html>
    <head>
        <title>Canvas tutorial</title>

        <script type="text/javascript">
            var canvas;
            var context; // The canvas's 2d context

            function setupCanvas()
            {
                canvas = document.getElementById('myCanvas');
                if (canvas.getContext)
                {
                    context = canvas.getContext('2d');
                    context.fillStyle = "black";    // this is default anyway
                    context.fillRect(0, 0, canvas.width, canvas.height);
                }
            }

            function init()
            {
                loadImages();
                startGating();
            }

            var images = new Array();
            var gatingTimer;
            var curIndex, imgWidth=0, imgHeight;

                // Load images
            function loadImages()
            {
                for (n = 1; n <= 16; n++)
                {
                    images[n] = new Image();
                    images[n].src = "qxsImages/frame" + n + ".png";
                //  document.body.appendChild(images[n]);

                    console.log("width = " + images[n].width + ", height = " + images[n].height);
                }

                curIndex = 1;
                imgWidth = images[1].width;
                imgHeight = images[1].height;
            }

            function redrawImages()
            {
                if (imgWidth == 0)
                    return;

                curIndex++;
                if (curIndex > 16)
                    curIndex = 1;

                    // To do later: use images[1].width and .height to layout based on image size
                for (var x=0; x<8; x++)
                {
                    for (var y=0; y<8; y++)
                    {
                        //if (x != 1)
                    //      context.drawImage(images[curIndex], x*150, y*100);
                        //  context.drawImage(images[curIndex], x*150, y*100, imgWidth/2, imgHeight/2); // scale
                    //  else
                            self.drawCustomImage(x*150, y*100);
                    }
                }
            }

            function drawCustomImage(xLoc, yLoc)
            {
                    // create a new pixel array
                imageData = context.createImageData(imgWidth, imgHeight);

                pos = 0; // index position into imagedata array
                xoff = imgWidth / (yLoc/3); // offsets to "center"
                yoff = imgHeight / 3;

                for (y = 0; y < imgHeight; y++) 
                {
                    for (x = 0; x < imgWidth; x++) 
                    {
                        // calculate sine based on distance
                        x2 = x - xoff;
                        y2 = y - yoff;
                        d = Math.sqrt(x2*x2 + y2*y2);
                        t = Math.sin(d/6.0);

                        // calculate RGB values based on sine
                        r = t * 200;
                        g = 125 + t * 80;
                        b = 235 + t * 20;

                        // set red, green, blue, and alpha:
                        imageData.data[pos++] = Math.max(0,Math.min(255, r));
                        imageData.data[pos++] = Math.max(0,Math.min(255, g));
                        imageData.data[pos++] = Math.max(0,Math.min(255, b));
                        imageData.data[pos++] = 255; // opaque alpha
                    }
                }

                    // copy the image data back onto the canvas
                context.putImageData(imageData, xLoc, yLoc);        // Works... kinda
            //  context.putImageData(imageData, 0, 0, xLoc, yLoc, imgWidth, imgHeight);  // Doesn't work. Why?
            }


            function startGating()
            {
                gatingTimer = setInterval(redrawImages, 1000/25); // start gating
            }

            function stopGating()
            {
                clearInterval(gatingTimer);
            }
        </script>

        <style type="text/css">
            canvas { border: 1px solid black; }
        </style>
    </head>

    <body onload="setupCanvas(); init();">
        <canvas id="myCanvas" width="1200" height="800"></canvas>
    </body>
</html>

http://jsfiddle.net/WZynM/

like image 433
Vern Jensen Avatar asked Aug 03 '13 02:08

Vern Jensen


Video Answer


2 Answers

You just had your coordinates backwards.

context.putImageData(imageData, xLoc, yLoc, 0, 0, imgWidth, imgHeight); 

Live Demo

xLoc, and yLoc are where you are putting it, and 0,0,imgWidth,imgHeight is the data you are putting onto the canvas.

Another example showing this.

A lot of the online docs seem a bit contradictory but for the seven param version

putImageData(img, dx, dy, dirtyX, dirtyY, dirtyRectWidth, dirtyRectHeight)

the dx, and dy are your destination, the next four params are the dirty rect parameters, basically controlling what you are drawing from the source canvas. One of the most thorough descriptions I can find was in the book HTML5 Unleashed by Simon Sarris (pg. 165).

like image 148
Loktar Avatar answered Sep 30 '22 03:09

Loktar


Having been using this recently, I've discovered that Loktar above has hit upon a VERY important issue. Basically, some documentation of this method online is incorrect, a particularly dangerous example being W3Schools, to which a number of people will turn to for reference.

Their documentation states the following:

Synopsis:

  • context.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight);

Arguments:

  • imgData: Specifies the ImageData object to put back onto the canvas
  • x : The x-coordinate, in pixels, of the upper-left corner of the ImageData object [WRONG]
  • y : The y-coordinate, in pixels, of the upper-left corner of the ImageData object [WRONG]
  • dirtyX : Optional. The horizontal (x) value, in pixels, where to place the image on the canvas [WRONG]
  • dirtyY : Optional. The vertical (y) value, in pixels, where to place the image on the canvas [WRONG]
  • dirtyWidth : Optional. The width to use to draw the image on the canvas
  • dirtyHeight: Optional. The height to use to draw the image on the canvas

As Loktar states above, the CORRECT synopsis is as follows:

Correct Synopsis:

  • context.putImageData(imgData, canvasX, canvasY, srcX ,srcY, srcWidth, srcHeight);

Arguments:

  • imgData: Specifies the ImageData object to put back onto the canvas (as before);
  • canvasX : The x coordinate of the location on the CANVAS where you are plotting your imageData;
  • canvasY : The y coordinate of the location on the CANVAS where you are plotting your ImageData;
  • srcX : Optional. The x coordinate of the top left hand corner of your ImageData;
  • srcY : Optional. The y coordinate of the top left hand corner of your ImageData;
  • srcWidth : Optional. The width of your ImageData;
  • srcHeight : Optional. The height of your ImageData.

Use the correct synopsis above, and you won't have the problems that have been encountered above.

I'll give a big hat tip to Loktar for finding this out initially, but I thought it apposite to provide an expanded answer in case others run into the same problem.

like image 25
David Edwards Avatar answered Sep 30 '22 04:09

David Edwards