Canvas getImageData() bizarre issue - html

I have faced quite bizarre issue lately, please take a look at the code snippet below
<canvas id="cancan" width="320", height="480">One color image</canvas>
<script type="text/javascript">
function imageLoaded(ev) {
element = document.getElementById("cancan");
c = element.getContext("2d");
im = ev.target; // the image, assumed to be 200x200
// read the width and height of the canvas
width = element.width;
height = element.height;
// stamp the image on the left of the canvas:
c.drawImage(im, 0, 0);
// get all canvas pixel data
imageData = c.getImageData(0, 0, width, height);
console.log(imageData.data[0] + " " + imageData.data[1] + " " + imageData.data[2]);
// output is "243 52 47"
// matlab and c# output is: "237 36 27"
}
im = new Image();
im.onload = imageLoaded;
im.src = "imgtest1.jpg"; // image is 320x480
</script>
imgtest1.jpg used in this example is constant - each pixel is (237,36,27). Pixel color returned by getImageData() differs - it is brighter then what is returned from - for example - matlab - any ideas what could be the reason ?

Lightness or Brightness or Intensity can be computed as (R+G+B)/3 (see HSI color code). After your sample code result, it is obvious that your output-image is a little bit bright than the original one because your R-G-B values are higher than the original ones (from Matlab or C++).
The question must be "Why your code computes higher values?". I don't know, but you could re-scale the values in order to have the same brightness.

Related

Adding Letter Spacing in HTML Canvas

I've read a lot of StackOverflow answers and other pages talking about how to do letter spacing in Canvas. One of the more useful ones was Letter spacing in canvas element
As that other question said, 'I've got this canvas element that I'm drawing text to. I want to set the letter spacing similar to the CSS letter-spacing attribute. By that I mean increasing the amount of pixels between letters when a string is drawn.' Note that letter spacing is sometimes, and incorrectly, referred to as kerning.
I notice that the general approach seems to be to output the string on a letter by letter basis, using measureText(letter) to get the letter's width and then adding additional spacing. The problem with this is it doesn't take into account letter kerning pairs and the like. See the above link for an example of this and related comments.
Seems to me that the way to do it, for a line spacing of 'spacing', would be to do something like:
Start at position (X, Y).
Measure wAll, the width of the entire string using measureText()
Remove the first character from the string
Print the first character at position (X, Y) using fillText()
Measure wShorter, the width of the resulting shorter string using measureText().
Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter
Increment X by wChar + spacing
wAll = wShorter
Repeat from step 3
Would this not take into account kerning? Am I missing something? Does measureText() add a load of padding that varies depending on the outermost character, or something, and if it does, would not fillText() use the same system to output the character, negating that issue? Someone in the link above mentioned 'pixel-aligned font hinting' but I don't see how that applies here. Can anyone advise either generally or specifically if this will work or if there are problems with it?
EDIT: This is not a duplicate of the other question - which it links to and refers to. The question is NOT about how to do 'letter spacing in canvas', per the proposed duplicate; this is proposing a possible solution (which as far as I know was not suggested by anyone else) to that and other questions, and asking if anyone can see or knows of any issues with that proposed solution - i.e. it's asking about the proposed solution and its points, including details of measureText(), fillText() and 'pixel-aligned font hinting'.
Well, I've written the code, based on the pseudocode above, and done a few comparisons by screenshotting and eyeballing it for differences (zoomed, using straight lines from eg clip boxes to compare X position and width for each character). Looks exactly the same for me, with spacing set at 0.
Here's the HTML:
<canvas id="Test1" width="800px" height="200px"><p>Your browser does not support canvas.</p></canvas>
Here's the code:
this.fillTextWithSpacing = function(context, text, x, y, spacing)
{
//Start at position (X, Y).
//Measure wAll, the width of the entire string using measureText()
wAll = context.measureText(text).width;
do
{
//Remove the first character from the string
char = text.substr(0, 1);
text = text.substr(1);
//Print the first character at position (X, Y) using fillText()
context.fillText(char, x, y);
//Measure wShorter, the width of the resulting shorter string using measureText().
if (text == "")
wShorter = 0;
else
wShorter = context.measureText(text).width;
//Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter
wChar = wAll - wShorter;
//Increment X by wChar + spacing
x += wChar + spacing;
//wAll = wShorter
wAll = wShorter;
//Repeat from step 3
} while (text != "");
}
Code for demo/eyeball test:
element1 = document.getElementById("Test1");
textContext1 = element1.getContext('2d');
textContext1.font = "72px Verdana, sans-serif";
textContext1.textAlign = "left";
textContext1.textBaseline = "top";
textContext1.fillStyle = "#000000";
text = "Welcome to go WAVE";
this.fillTextWithSpacing(textContext1, text, 0, 0, 0);
textContext1.fillText(text, 0, 100);
Ideally I'd throw multiple random strings at it and do a pixel by pixel comparison. I'm also not sure how good Verdana's default kerning is, though I understand it's better than Arial - suggestions on other fonts to try gratefully accepted.
So... so far it looks good. In fact it looks perfect.
Still hoping that someone will point out any flaws in the process.
In the meantime I will put this here for others to see if they are looking for a solution on this.
My answer got deleted.
So, I'm using chrome and here is my complete code.
second_image = $('#block_id').first();
canvas = document.getElementById('canvas');
canvas.style.letterSpacing = '2px';
ctx = canvas.getContext('2d');
canvas.crossOrigin = "Anonymous";
canvasDraw = function(text, font_size, font_style, fill_or_stroke){
canvas.width = second_image.width();
canvas.height = second_image.height();
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(second_image.get(0), 0, 0, canvas.width, canvas.height);
//refill text
ctx.font = font_size +'px '+ font_style + ',Symbola';
$test = ctx.font;
ctx.textAlign = "center";
if(fill_or_stroke){
ctx.fillStyle = "#d2b76d";
ctx.strokeStyle = "#9d8a5e";
ctx.strokeText(text,canvas.width*$left,canvas.height*$top);
ctx.fillText(text,canvas.width*$left,canvas.height*$top);
}
else{
ctx.strokeStyle = "#888888";
ctx.strokeText(text,canvas.width*$left,canvas.height*$top);
}
};
And you don't need to use this function this.fillTextWithSpacing. I didn't use and it worked like a charm)

HTML5: Inverse text-color on canvas

I want to draw text on a canvas in the inverse color of the background (to make sure the text is readible no matter the background color). I believe in oldskool bitblt-ing, this was an XOR operation.
How to do this?
Update: most of the newer browsers now support the blending mode "difference" which can achieve the same result.
context.globalCompositeOperation = "difference";
Updated demo.
Old answer:
One should think that the XOR mode for composition would do this, but unfortunately canvas' XOR only XORs the alpha bits.
By applying the following code we can however receive a result such as this:
You can make an extension to the canvas like this:
CanvasRenderingContext2D.prototype.fillInversedText =
function(txt, x, y) {
//code - see below
}
Now you can call it on the context as the normal fillText, but with a slight change:
ctx.fillInversedText(txt, x, y);
For this to work we do the following first - measure text. Currently we can only calculate width of text and then assume the height. This may or may not work well as fonts can be very tall and so forth. Luckily this will change in the future, but for now:
var tw = this.measureText(txt).width;
var th = parseInt(ctx.font, '10');
th = (th === 0) ? 10 : th; //assume default if no font and size is set
Next thing we need to do is to setup an off-screen canvas to draw the text we want ot invert:
var co = document.createElement('canvas');
co.width = tw;
co.height = th;
Then draw the actual text. Color does not matter as we are only interested in the alpha channel for this canvas:
var octx = co.getContext('2d');
octx.font = this.font;
octx.textBaseline = 'top';
octx.fillText(txt, 0, 0);
Then we extract the pixel buffers for the area we want to draw the inverted text as well as all the pixels for the off-screen canvas which now contains our text:
var ddata = this.getImageData(x, y, tw, th);
var sdata = octx.getImageData(0, 0, tw, th);
var dd = ddata.data; //cache for increased speed
var ds = sdata.data;
var len = ds.length;
And then we invert each pixel where alpha channel for pixel is greater than 0.
for (var i = 0; i < len; i += 4) {
if (ds[i + 3] > 0) {
dd[i] = 255 - dd[i];
dd[i + 1] = 255 - dd[i + 1];
dd[i + 2] = 255 - dd[i + 2];
}
}
Finally put back the inverted image:
this.putImageData(ddata, x, y);
This may seem as a lot of operations, but it goes pretty fast.
Demo (warning if you are sensitive to flicker)
(the psychedelic background is just to have some variations as fiddle needs external images and most are prevented by CORS when we use pixel manipulation).
I've removed my old answer, as it did not solve the question. As of recently, there are new globalCompositeOperations that do all kinds of great things. I've created an example that shows how to obtain inverted text. In case that link breaks, the method is essentially this:
ctx.globalCompositeOperation = "difference";
ctx.fillStyle = "white";
//draw inverted things here
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation

Insert a single pixel into HTML5 canvas

I want to insert a pixel with a color, and I use this code:
context.fillStyle='RGB('+s[i]+')';
context.fillRect(i,y,1,1)
Is there a shorter way to do it? e.g. in a single line of code?
My main goal is to reduce the amount of code.
There really isn't a shorter way to do it besides the method you used above. You don't have to include a fillStyle every time so it essentially is only one line of code to fill a pixel.
Like Petteri pointed out there is another way to fill pixels, it involves manipulating the pixel data directly.
Live Demo
var canvasData = ctx.getImageData(0,0,canvasWidth,canvasHeight);
//color 100,100 red
canvasData.data[((100*(canvasData.width*4)) + (100*4)) + 0] = 255;
ctx.putImageData(canvasData,0,0);
also note with the above method you would need to repeat that line 3 times once for each component of the color. For example to set red, green, blue, and the alpha you would use
canvasData.data[((100*(canvasData.width*4)) + (100*4)) + 0] //red
canvasData.data[((100*(canvasData.width*4)) + (100*4)) + 1] //green
canvasData.data[((100*(canvasData.width*4)) + (100*4)) + 2] //blue
canvasData.data[((100*(canvasData.width*4)) + (100*4)) + 3] //alpha
granted you could have your data in an array, and just loop through that and color as needed.
You can edit the image data of the canvas directly. Here is a good example how to do it: http://beej.us/blog/2010/02/html5s-canvas-part-ii-pixel-manipulation/
No, there is no single line of code way to change a single pixel to one color. Well, there sort-of is.
As Petteri noted, there is a way to change each pixel directly, which will probably accomplish what you want. I assume what you want is to change one pixel to one color, and the next pixel to another color, etc.
For instance here is a function for desaturating a canvas. What it does is takes every pixel and averages the RGB values to be color-neutral (have no saturation). The result is a grayscale image.
function grayscale() {
var imageData = ctx.getImageData(0,0,can.width, can.height);
var pixels = imageData.data;
var numPixels = pixels.length;
ctx.clearRect(0, 0, can.width, can.height);
for (var i = 0; i < numPixels; i++) {
var average = (pixels[i*4] + pixels[i*4+1] + pixels[i*4+2]) /3;
// set red green and blue pixels to the average value
pixels[i*4] = average;
pixels[i*4+1] = average;
pixels[i*4+2] = average;
}
ctx.putImageData(imageData, 0, 0);
}
As you can see it is iterating over each pixel. It could be easily mofied to have each pixel changed as a one-liner.
Instead of:
pixels[i*4] = average;
pixels[i*4+1] = average;
pixels[i*4+2] = average;
You'd write:
// Take out 3 values starting at i*4 and add the new RGB for that pixel
pixels.splice(i*4,3,REDVALUE,GREENVALUE,BLUEVALUE);
Which would accomplish what you'd want. It's not the most efficient way under the sun, but it would accomplish your goal :)
fillStyle="rgb("+a[m];
fillRect(m,o,1,1);
Someone did with that :P

Bitmap conversion - Creating a transparent + black image from a B&W source

I have a whole bunch of jpg files that I need to use in a project, that for one reason or another cannot be altered. Each file is similar (handwriting), black pen on white BG. However I need to use these assets against a non-white background in my flash project, so I'm trying to do some client-side processing to get rid of the backgrounds using getPixel and setPixel32.
The code I am currently using currently uses a linear comparison, and while it works, the results are less than expected, as the shades of grey are getting lost in the mix. Moreso than just tweaking my parameters to get things looking proper, I get the feeling that my method for computing the RGBa value is weak.
Can anyone recommend a better solution than what I'm using below? Much appreciated!
private function transparify(data:BitmapData) : Bitmap {
// Create a new BitmapData with transparency to return
var newData:BitmapData = new BitmapData(data.width, data.height, true);
var orig_color:uint;
var alpha:Number;
var percent:Number;
// Iterate through each pixel using nested for loop
for(var x:int = 0; x < data.width; x++){
for (var y:int = 0; y < data.height; y++){
orig_color = data.getPixel(x,y);
// percent is the opacity percentage, white should be 0,
// black would be 1, greys somewhere in the middle
percent = (0xFFFFFF - orig_color)/0xFFFFFF;
// To get the alpha value, I multiply 256 possible values by
// my percentage, which gets multiplied by 0xFFFFFF to fit in the right
// value for the alpha channel
alpha = Math.round(( percent )*256)*0xFFFFFF;
// Adding the alpha value to the original color should give me the same
// color with an alpha channel added
var newCol = orig_color+alpha;
newData.setPixel32(x,y,newCol);
}
}
var newImg:Bitmap = new Bitmap(newData);
return newImg;
}
Since it's a white background, blendMode may give you a better result.

Multiple Canvas Zooms behaving erratically

I'm running into a problem with the HTML5 Canvas tag and adjusting the scale several times. After zooming twice, the canvas only uses a fraction of the available canvas height & width, even though I'm adjusting for the zoom level.
<html>
<head>
<script>
var ctx;
var nScale = 1.00;
function pageLoad() {
ctx=document.getElementById('cnvUni').getContext('2d');
// canvas on page load is 500x500
drawGrid(); // 5 boxes across & 5 down
zoom(0.5); // canvas should be now zoomed out to 1000x1000
drawGrid(); // 10 boxes across & 10 down
zoom(0.5); // effective zoom is now 0.25 = 2000x2000
drawGrid(); // should be 20 boxes across & 20 down
// NOTE: At this point, the grid is drawing boxes # 20x20 but only using 1/4 of the
// canvas size.
}
function zoom(nZoomLevel) {
nScale = nZoomLevel * nScale
ctx.scale(nScale,nScale);
}
function drawGrid() {
var nWidth, nHeight;
nWidth = Math.floor(ctx.canvas.width / nScale);
nHeight = Math.floor(ctx.canvas.height / nScale);
var nGridSize = 100;
var nGridY = 0;
var nGridX = 0;
// sets a random colour each time grid is drawn.
ctx.strokeStyle = 'rgb(' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ')';
for (nGridY=0;nGridY < nHeight;nGridY+=nGridSize) {
for (nGridX=0;nGridX < nWidth;nGridX+=nGridSize) {
// draw the box;
ctx.strokeRect(nGridX, nGridY, nGridSize, nGridSize);
}
}
}
</script>
</head>
<body onload="pageLoad();">
<canvas id="cnvUni" width="500" height="500">
Canvas doesn't work.
</canvas>
</body>
</html>
If I were to multiply the height & width by 2 when drawing the grid for the last time it'll draw out the entire canvas size, but I can't figure out why that would be required.
What I'm wondering is:
Is there a way to query a canvas context to find out what the scale value (or the calculated height/width) is? Or am I approaching this correctly and keeping track of values myself?
If so, then I assume it must be something with my math that's messing this up; I just can't pinpoint it. I'm sure I'm just too close to this problem and not seeing the issue. Another set of eyes would help.
Any suggestions would be appreciated.
Thanks!
I've got a version working on http://jsfiddle.net/sBXTn/5/ without using save/restore. This is the code change:
nScale = nZoomLevel * nScale
ctx.scale(nZoomLevel, nZoomLevel);
Previously using ctx.scale(nScale, nScale) meant that when you zoomed by 0.25 (0.5 twice) you were zooming by 0.25 on a context that was 1000x1000. This meant it increased the size to 4000x4000. Using nZoomLevel means you are zooming in relation to the dimensions of the current context.
I was way on the wrong track with this one and I feel silly for it. My math was correct in calculating the effective scale value (1 * 0.5 = 0.5, then scaling it by 0.5 again does = 0.25), but what I was doing was calculating the effective width & height by the original width & height, not the updated width & height.
So if I've scaled the original down to 0.5, the dimensions of the original 500x500 would be 1000x1000. Scaling it further by 0.5, the effective scale is 0.25, but the new width & height should be 4000x4000 (found by scaling 1000 by 0.25 and NOT 500 by 0.25).
Here's the updated code:
var ctx;
var nScale = 1.00;
var nEffWidth, nEffHeight;
function pageLoad() {
ctx=document.getElementById('cnvUni').getContext('2d');
nEffWidth = ctx.canvas.width;
nEffHeight = ctx.canvas.height;
// canvas on page load is 500x500
drawGrid(); // 5 boxes across & 5 down
zoom(0.5); // canvas should be now zoomed out to 1000x1000
drawGrid(); // 10 boxes across & 10 down
zoom(0.5); // effective zoom is now 0.25 = 4000x4000 based on new scaled width/height
drawGrid(); // should be 40 boxes across & 40 down
}
function zoom(nZoomLevel) {
nScale = nZoomLevel * nScale
nEffHeight = nEffHeight / nScale;
nEffWidth = nEffWidth / nScale;
ctx.scale(nScale,nScale);
}
function drawGrid() {
var nGridSize = 100;
var nGridY = 0;
var nGridX = 0;
// sets a random colour each time grid is drawn.
ctx.strokeStyle = 'rgb(' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ')';
for (nGridY=0;nGridY < nEffHeight;nGridY+=nGridSize) {
for (nGridX=0;nGridX < nEffWidth;nGridX+=nGridSize) {
// draw the box;
ctx.strokeRect(nGridX, nGridY, nGridSize, nGridSize);
}
}
}
</script>
</head>
<body onload="pageLoad();">
<canvas id="cnvUni" width="500" height="500">
Canvas doesn't work.
</canvas>
</body>
</html>
Thanks for everyone who took a peek!