Dominic Bou-Samra
Dominic Bou-Samra

Reputation: 15416

Formula for drawing a pixel at an X,Y coordinate on Canvas

So I am aware the HTML5 canvas pixel methods are a little different then normal. I am porting a ray-tracer from a Scala Swing application where the old draw method looked like this:

for {
  x <- pixels.s.indices
  y <- pixels.s(x).indices
} {
  g.setColor(pixels.s(x)(y))
  g.fillRect(x, y, x, y)
}

Where pixels.s was a 2D array like so:

[[Color, Color, Color], 
 [Color, Color, Color], 
 [Color, Color, Color]]

I.e. it mapped exactly to a 2D cartesian plane.

So now I have changed tack slightly, and I have a FLATTENED array of objects with x, y and color:

[{ x: 0, y: 0, color: { red: 33, green: 220, blue: 181 },
 { x: 0, y: 1, color: { red: 33, green: 220, blue: 231 },
 { x: 0, y: 2, color: { red: 33, green: 220, blue: 221 },
 ...
 { x: 319, y: 238, color: { red: 23, green: 10, blue: 31 },
 { x: 319, y: 239, color: { red: 13, green: 120, blue: 11 }]

I'm trying to draw my flattened array of colors to the screen using this code:

var imageData = context.createImageData(width, height);
var numPixels = width*height;
for (var i = 0; i < numPixels; i++) {
  imageData.data[(i*4)]     = tracedScene[i].color.red;
  imageData.data[(i*4) + 1] = tracedScene[i].color.green;
  imageData.data[(i*4) + 2] = tracedScene[i].color.blue;
  imageData.data[(i*4) + 3] = 255; // alpha  color
};
context.putImageData(imageData, 0, 0);

But it's coming out a bit wrong. I've tripled checked my tests and the actual array is the correct screen data. I'm just rendering it incorrectly. I'm currently getting this:

enter image description here

Whereas I should see:

enter image description here

Now I've had the exact same graphical oddities in the Swing version before (years ago), and it was because of the way I was drawing the image. I can't remember how I fixed it however.

What I am looking to be able to do, is have some method:

drawPixel(x, y, color, canvas) { ... } 

that is able to draw a particular pixel at x, y with a color. OR... a way to do it traditionally.

Upvotes: 1

Views: 1128

Answers (2)

Simon Sarris
Simon Sarris

Reputation: 63830

Image data goes from (0, 0) to (1, 0) and your code goes from (0, 0) to (0, 1). In other words, there's a conflict of row/column major order.

Anyway, depending on your performance needs you might be overthinking this.

What I am looking to be able to do, is have some method:

drawPixel(x, y, color, canvas) { ... }

that is able to draw a particular pixel at x, y with a color.

Why not just fill single pixels?

ctx.fillStyle = 'rgba(red, green, blue, alpha)';
ctx.fillRect(x, y, 1, 1);

This will be inefficient at around 100 pixels filled, and you'll want to switch to image data.

If you want to keep your data structure the same you could use something like this, which should work:

var imageData = context.createImageData(width, height);
var numPixels = width*height;
var pixelCount = 0;
for (var i = 0; i < width; i++) {
    for (var j = 0; j < height; j++) {
        var wpixel = j * width * 4;
        wpixel += i * 4;
        console.log(wpixel);
        imageData.data[(wpixel)]     = tracedScene[pixelCount].red;
        imageData.data[(wpixel) + 1] = tracedScene[pixelCount].green;
        imageData.data[(wpixel) + 2] = tracedScene[pixelCount].blue;
        imageData.data[(wpixel) + 3] = 255; // alpha  color
        pixelCount++; // independent of i and j the way I wrote it
    }
}
context.putImageData(imageData, 0, 0);

Demo: http://jsfiddle.net/t9edv/

Upvotes: 1

Alexei Levenkov
Alexei Levenkov

Reputation: 100545

Your input "flattened" array in wrong order: it should be by rows, but yours is by columns.

[{ x: 0, y: 0, color: { red: 33, green: 220, blue: 181 },
 { x: 1, y: 0, color: { red: 33, green: 220, blue: 231 },
 { x: 2, y: 0, color: { red: 33, green: 220, blue: 221 },
 ...

(Assuming y is top to bottom, x - left to right)

Upvotes: 1

Related Questions