putImageData not working checked existing questions - javascript

ctx.drawImage(preview,0,0, preview.width, preview.height)
var imgdata = ctx.getImageData(0,0, preview.width, preview.height).data;
ctx.putImageData(opaque(ctx.createImageData(10,10)), 0,0)
The opaque function sets the alpha data to 1. so as to pain a black square of 10*10.
function opaque(imgData){
for(var i=0, data = imgData.data; i<data.length; i=i+4){
data[i+3] = 1;
}
return imgData;
}
But my canvas looks white.

What are you trying to do?
If you want to draw a black square, use context.fillRect(...). It's more efficient than manipulating imageData.
If you want to make parts of your imageData wholly transparent, you need to set the alpha channel to 0. The alpha channel is in range [0..255], with zero meaning wholly transparent, and 255 meaning wholly opaque.
Though I haven't actually run it, from reading your opaque function, all it seems to do is make its imageData argument's pixels almost wholly transparent.

Related

Drawing tiles on canvas has blurry gaps

Example: https://jsfiddle.net/jm1y9c0L/1/
Code:
const context = document.getElementById("canvas").getContext("2d");
const scale = document.getElementById("scale");
const translate = document.getElementById("translate");
scale.value = 17.78598548284369;
translate.value = 10.02842190048295;
function draw() {
context.fillStyle="#f00";
context.fillRect(0,0,1,1);
context.fillRect(1,0,1,1);
}
function update() {
const s = Number(scale.value);
const t = Number(translate.value);
context.clearRect(0,0,100,100);
context.save();
context.translate(t,t);
context.scale(s,s);
draw();
context.restore();
}
update();
Question: How do I draw tiles on canvas in a scaled context on canvas without blurry gaps?
A few things to note:
If I don't change the background to black, things look ok:
https://jsfiddle.net/jm1y9c0L/2/
I think if I draw all tiles on a buffer and draw the buffer on the canvas scaled, it would work. But that has many issues, one being it potentially uses a lot of memory if I'm scaling a very large buffer smaller.
Edit:
To explain my goal better, I want to draw two rectangles next to each other with its boundaries being a fraction. So if I draw two rectangles, one in green and one in red with a black background. I want to see the boundary being half green, half red, but not black at all.
It is because you are using floats and not integers, canvas cant draw 0.78598548284369 of a pixel, I would recommend putting Math.floor() around your scale in the scale() function:
context.scale(Math.floor(s),Math.floor(s));
Hope this helps :D
You can't do subpixel rendering in a browser (at least you can't expect consistent results). In your case, the end of one rectangle isn't necesarily the start of the other, since there may or may not be a pixel-wide gap which comes from rounding.
You might even notice some weird behaviour, such as getting different results in different screen positions, different browsers, when resizing, etc.
The solution is to calculate the value of each pixel yourself, not let the browser do it for you.

HTML Canvas putImageData with transparency causes incorrect RGB to be saved

I am trying to set individual pixels in the HTML canvas by using putImageData(). When I do this and then immediately read those pixels back out using getImageData(), the RBG values I just set have been changed! See example:
var ct = canvas.getContext('2d');
var image = ct.getImageData(0,0,1,1);
var data = image.data;
data[0] = 200; //r
data[1] = 100; //g
data[2] = 50; //b
data[3] = 25; //a
console.log(data); //[200, 100, 50, 25] Yeah :)
ct.putImageData(image,0,0);
var debug = ct.getImageData(0,0,1,1);
console.log(debug.data); //[204, 102, 51, 25] Boo :(
If I set the alpha channel to 255 (no transparency) then the RGB values aren't altered. If I set the alpha channel to 0 (transparent) then the RGB comes back as 0,0,0. Obviously it has something to do with transparency. It probably has something to do with the RGB color space and math.
I am trying to figure out why this happens or at least be able to predict the results in some way. Can someone please clue me in about what is going on here?
It is due to the process of compositing and in particular with premultiplying the alpha channel. The standard states:
Due to the lossy nature of converting to and from premultiplied alpha
colour values, pixels that have just been set using putImageData()
might be returned to an equivalent getImageData() as different values.
It's a relative deep and wide topic, but if you want to dive into the particulars for the math behind it, I would recommend looking at the Porter-Duff algorithms found at this link. See in particular blending and alpha compositing. Considering also that the browser uses 8-bit integer values in these formulas.

Check whether canvas is black

I have a script which takes a picture and creates black and white R, G and B channels. And I would like to count how many channels have something on them (black = no colour, white = colour data).
It works by going through every single pixel and saving data in 3 different canvas elements. Each one showing different channel.
It works great but when I give a Red and Blue picture only, the green channel (obviously) is completely black.
So my question is, how do I check whether the canvas is completely black? I would like to avoid looping through every pixel in every channel to see if it's all black.
cheers
Ok I've tested a few things and the easiest solution I could come up with was creating a 1x1 offscreen canvas, then drawing either your image or your original canvas you want to check for data on it, scaled down to 1 pixel and check it (I also added basic caching).
It's not a precise check, and the result depends on how the pixel data is distributed in the original image, so as GameAlchemist noted, you may loop through all the pixels anyway if the result of the check is 0 if you want accurate results.
function quickCheck(img){
var i = arguments.callee; //caching the canvas&context in the function
if(!i.canvas){i.canvas = document.createElement("canvas")}
if(!i.ctx) {i.ctx = i.canvas.getContext("2d")}
i.canvas.width=i.canvas.height = 1;
i.ctx.drawImage(img,0,0,1,1);
var result = i.ctx.getImageData(0,0,1,1);
return result.data[0]+result.data[1]+result.data[2]+result.data[3];
}
You must iterate the canvas's pixel data--no magic method to avoid that.
The performance cost of iterating through 2 images is trivial (2 images = your front image and back image).
[ Removed un-necessary part of answer after visiting questioner's site ]
Another quick check would be to calculate the sum of the image data. If the sum is equal to the amount of pixels in the canvas times 255, it is probably all black. There is a small chance that some other combination of pixels could cause the sum to be equal to the same number, but the chance is very low (unless you are looping through all the canvas combinations).

How to draw on an HTML5 Canvas, pixel-by-pixel

Suppose that I have a 900x900 HTML5 Canvas element.
I have a function called computeRow that accepts, as a parameter, the number of a row on the grid and returns an array of 900 numbers. Each number represents a number between 0 and 200. There is an array called colors that contains an array of strings like rgb(0,20,20), for example.
Basically, what I'm saying is that I have a function that tells pixel-by-pixel, what color each pixel in a given row on the canvas is supposed to be. Running this function many times, I can compute a color for every pixel on the canvas.
The process of running computeRow 900 times takes about 0.5 seconds.
However, the drawing of the image takes much longer than that.
What I've done is I've written a function called drawRow that takes an array of 900 numbers as the input and draws them on the canvas. drawRow takes lots longer to run than computeRow! How can I fix this?
drawRow is dead simple. It looks like this:
function drawRow(rowNumber, result /* array */) {
var plot, context, columnNumber, color;
plot = document.getElementById('plot');
context = plot.getContext('2d');
// Iterate over the results for each column in the row, coloring a single pixel on
// the canvas the correct color for each one.
for(columnNumber = 0; columnNumber < width; columnNumber++) {
color = colors[result[columnNumber]];
context.fillStyle = color;
context.fillRect(columnNumber, rowNumber, 1, 1);
}
}
I'm not sure exactly what you are trying to do, so I apologize if I am wrong.
If you are trying to write a color to each pixel on the canvas, this is how you would do it:
var ctx = document.getElementById('plot').getContext('2d');
var imgdata = ctx.getImageData(0,0, 640, 480);
var imgdatalen = imgdata.data.length;
for(var i=0;i<imgdatalen/4;i++){ //iterate over every pixel in the canvas
imgdata.data[4*i] = 255; // RED (0-255)
imgdata.data[4*i+1] = 0; // GREEN (0-255)
imgdata.data[4*i+2] = 0; // BLUE (0-255)
imgdata.data[4*i+3] = 255; // APLHA (0-255)
}
ctx.putImageData(imgdata,0,0);
This is a lot faster than drawing a rectangle for every pixel. The only thing you would need to do is separate you color into rgba() values.
If you read the color values as strings from an array for each pixel it does not really matter what technique you use as the bottleneck would be that part right there.
For each pixel the cost is split on (roughly) these steps:
Look up array (really a node/linked list in JavaScript)
Get string
Pass string to fillStyle
Parse string (internally) into color value
Ready to draw a single pixel
These are very costly operations performance-wise. To get it more efficient you need to convert that color array into something else than an array with strings ahead of the drawing operations.
You can do this several ways:
If the array comes from a server try to format the array as a blob / typed array instead before sending it. This way you can copy the content of the returned array almost as-is to the canvas' pixel buffer.
Use a web workers to parse the array and pass it back as a transferable object which you them copy into the canvas' buffer. This can be copied directly to the canvas - or do it the other way around, transfer the pixel buffer to worker, fill there and return.
Sort the array by color values and update the colors by color groups. This way you can use fillStyle or calculate the color into an Uint32 value which you copy to the canvas using a Uint32 buffer view. This does not work well if the colors are very spread but works ok if the colors represent a small palette.
If you're stuck with the format of the colors then the second option is what I would recommend primarily depending on the size. It makes your code asynchronous so this is an aspect you need to deal with as well (ie. callbacks when operations are done).
You can of course just parse the array on the same thread and find a way to camouflage it a bit for the user in case it creates a noticeable delay (900x900 shouldn't be that big of a deal even for a slower computer).
If you convert the array convert it into unsigned 32 bit values and store the result in a Typed Array. This way you can iterate your canvas pixel buffer using Uint32's instead which is much faster than using byte-per-byte approach.
fillRect is meant to be used for just that - filling an area with a single color, not pixel by pixel. If you do pixel by pixel, it is bound to be slower as you are CPU bound. You can check it by observing the CPU load in these cases. The code will become more performant if
A separate image is created with the required image data filled in. You can use a worker thread to fill this image in the background. An example of using worker threads is available in the blog post at http://gpupowered.org/node/11
Then, blit the image into the 2d context you want using context.drawImage(image, dx, dy).

Comparing two fonts in HTML5 Canvas

I'm trying to put together a tool that checks whether a given character is displayed in the specified style font or a system default font. My ultimate goal is to be able to check, at least in modern (read: IE8+) browsers, whether a ligature is supported in a given font.
I've got two canvases displaying the same ligature (in this case, st). I turn those canvases into data and compare them to see if the characters match.
Arial (like most fonts) does not have an st ligature, so it falls back to the default serif font. Here's where it gets weird: although they're displaying the same font, the two canvases don't have the same data.
Why? Because their positions on the canvas aren't exactly the same. I'm guessing it has something to do with the different relative heights of the fonts (one is slightly taller than the other, though which varies font to font). The difference appears to be one of a pixel or two, and it varies font by font.
How might one go about solving this? My only current idea is finding a way to measure the height of the font and adjusting its position accordingly, but I have no idea how to do that, unfortunately. Are there other approaches I might take to make the two images identical?
You can see the code below. Both canvases are successfully initialized and appended to the body of the element so I can see what's going on visually (though that's not necessary in the actual script I'm working on). I've dropped the initialization and context, as those are all working just fine.
function checkLig() {
lig = 'fb06' // this is the unicode for the st ligature
canvas0.width = 250;
canvas0.height = 50;
context0.fillStyle = 'rgb(0, 0, 0)';
context0.textBaseline = 'top';
context0.font = 'normal normal normal 40px Arial';
context0.fillText(String.fromCharCode(parseInt(lig, 16)), 0, 0);
var print0 = context0.getImageData(0, 0, 720, 50).data;
canvas1.width = 250;
canvas1.height = 50;
context1.fillStyle = 'rgb(0, 0, 0)';
context1.textBaseline = 'top';
context1.font = 'normal normal normal 40px serif';
context1.fillText(String.fromCharCode(parseInt(lig, 16)), 0, 0);
var print1 = context1.getImageData(0, 0, 720, 50).data;
var i = 0, same = true, len = 720 * 50 * 4;
while (i < len && same === true) {
if (print0[i] !== print1[i]) {
same = false;
}
else {
i++;
}
}
return same;
}
So i understand the question correctly, the problem is one canvas is specifying Arial (but falls back to Serif) and the other is Serif and when you do the pixel matching, and it's not a match because one of them has a slight offset?
One suggestion is to grab a reference pixel from each canvas and compare the positions of the two reference pixels to get an offset. And then factor that offset into your comparison loop.
For example, in this case you could get your reference pixel by starting to check pixels from the upper left corner of one canvas and scan downwards in that column and when you reach the bottom, go back to the top of the next column and scan down.
As soon as you hit a pixel that is not the color of your background, record the position and use that as your reference pixel. That should be the edge of your font.
Do the same with your next canvas and then compare the positions of the two reference pixels to get an offset. Take that offset into consideration in your comparison loop.
Hope I have the right idea of the problem and i hope that helps!

Categories