JavaScript canvas image data manipulation - javascript

I want to resize image using very simple algorithm. I have something like this:
var offtx = document.createElement('canvas').getContext('2d');
offtx.drawImage(imageSource, offsetX, offsetY, width, height, 0, 0, width, height);
this.imageData = offtx.getImageData(0, 0, width, height).data;
offtx.clearRect(0, 0, width, height);
for(var x = 0; x < this.width; ++x)
{
for(var y = 0; y < this.height; ++y)
{
var i = (y * this.width + x) * 4;
var r = this.imageData[i ];
var g = this.imageData[i+1];
var b = this.imageData[i+2];
var a = this.imageData[i+3];
offtx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
offtx.fillRect(0.5 + (x * this.zoomLevel) | 0, 0.5 + (y*this.zoomLevel) | 0, this.zoomLevel, this.zoomLevel);
}
}
this.imageData = offtx.getImageData(0, 0, this.width * this.zoomLevel, this.height * this.zoomLevel);
However, the problem I have with this solution, is that the image looses any transparency information that way. I don't know if this happens somewere in this algorithm, or maybe putImageData that I am using later to display that image is doing this, but I can't seem to be able to preserve transparency.
Each time I do this I create a canvas, I put the image on that canvas and use getImageData to get image from that canvas as you can see in the first lines of the code. Maybe there is no other way, so I might not mind that...
But the problem is I use two for loops to draw resized image and then use getImageData to store that image information. This is a wierd way to do it. I would prefer to create empty image data and fill it with all the original image information only resized. I can't grasp that with my mind, I can't image the loop structure for this. To show what I mean:
for(var x = 0; x < this.width; ++x)
{
for(var y = 0; y < this.height; ++y)
{
var i = (y * this.width + x) * 4;
var r = this.imageData[i ];
var g = this.imageData[i+1];
var b = this.imageData[i+2];
var a = this.imageData[i+3];
//I WOULD LIKE MAGIC TO HAPPEN HERE THAT WILL
//RESIZE THAT CURRENT PIXEL AND MOVE IT TO THE NEW IMAGE DATA RESIZED
//SO EVERYTHING IS DONE NICE AND CLEAN IN THIS LOOP WITHOUT THE
//GETIMAGEDATA LATER AND MAYBE SET TRANSPARENT PIXELS WHILE I'M AT IT
}
}
I can't figure out the MAGIC part.
Thank you for reading!

Why not just use the built-in drawImage combined with image smoothing disabled? Doing this operation in a loop is not only relative slow but also prone to errors (as you already discovered).
Doing it the following way will give you the "pixel art" look and will also preserve the alpha channel:
var factor = 4; /// will resize 4x
offtx.imageSmoothingEnabled = false; /// prefixed in some browsers
offtx.drawImage(imageSource, offsetX, offsetY, width, height,
0, 0, width * factor, height * factor);
Here is an online demo.

Try using this library I recently made which can load an image, resize it fixed width & height or precentage.
It does exactly what you need, and much more like converting canvas to base64, blob, etc...
var CanvaWork = new CanvaWork();
CanvaWork.canvasResizeAll(obj.canvas, function(canvases){
// "canvases" will be an array containing 3 canvases with different sizes depending on initial options
});
https://github.com/vnbenny/canvawork.js
Hope this helps you!

Related

Draw font from Image on HTML5 Canvas

I have an image of a font I would like to draw on an HTML5 Canvas. At first I though about separating each letter into a different image but decided having a sprite sheet would be much cleaner. A problem with that though, is that not all the letters are the same size. Some are a few pixels wider than other characters.
While looking on Google, I came across one way that some people handled the problem. They added a line under each character to represent that characters length and then draw the bottom most line of the font image into an off screen canvas and analyze it pixel by pixel.
I tried to implement my own version of that idea, but was unable to get that far. Before I invest more time on that idea, I would like to know if it's a good solution or if there is any better ways of achieving the same thing.
So far I have a few small snippets i'm trying to put together, like this code:
getImagePixels: function( image, x, y, width, height )
{
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext('2d');
ctx.drawImage( image, 0, 0, image.width, image.height );
return ctx.getImageData( x, y, width, height );
}
and this
loadFontImage: function( image )
{
// Draw the bottommost line of this font image into an offscreen canvas
// and analyze it pixel by pixel.
// A run of non-transparent pixels represents a character and its width
this.height = image.height-1;
this.widthMap = [];
this.indices = [];
var px = getImagePixels( image, 0, image.height-1, image.width, 1 );
var currentChar = 0;
var currentWidth = 0;
for( var x = 0; x < image.width; x++ )
{
var index = x * 4 + 3; // alpha component of this pixel
if( px.data[index] > 127 )
{
currentWidth++;
}
else if( px.data[index] < 128 && currentWidth )
{
this.widthMap.push( currentWidth );
this.indices.push( x-currentWidth );
currentChar++;
currentWidth = 0;
}
}
}
As I can't comment I will just write this as an answer:
You could also simply create or generate a javascript object with all the widths included:
var fontWidths = {
a: 8,
b: 8
....
};
That way the overhead doesn't happen every time you're going to write something to the canvas.

Mirroring right half of webcam image

I saw that you have helped David with his mirroring canvas problem before. Canvas - flip half the image
I have a similar problem and hope that maybe you could help me.
I want to apply the same mirror effect on my webcam-canvas, but instead of the left side, I want to take the RIGHT half of the image, flip it and apply it to the LEFT.
This is the code you've posted for David. It also works for my webcam cancas. Now I tried to change it, so that it works for the other side, but unfortunately I'm not able to get it.
for(var y = 0; y < height; y++) {
for(var x = 0; x < width / 2; x++) { // divide by 2 to only loop through the left half of the image.
var offset = ((width* y) + x) * 4; // Pixel origin
// Get pixel
var r = data[offset];
var g = data[offset + 1];
var b = data[offset + 2];
var a = data[offset + 3];
// Calculate how far to the right the mirrored pixel is
var mirrorOffset = (width - (x * 2)) * 4;
// Get set mirrored pixel's colours
data[offset + mirrorOffset] = r;
data[offset + 1 + mirrorOffset] = g;
data[offset + 2 + mirrorOffset] = b;
data[offset + 3 + mirrorOffset] = a;
}
}
Even if the accepted answer you're relying on uses imageData, there's absolutely no use for that.
Canvas allows, with drawImage and its transform (scale, rotate, translate), to perform many operations, one of them being to safely copy the canvas on itself.
Advantages is that it will be way easier AND way way faster than handling the image by its rgb components.
I'll let you read the code below, hopefully it's commented and clear enough.
The fiddle is here :
http://jsbin.com/betufeha/2/edit?js,output
One output example - i took also a mountain, a Canadian one :-) - :
Original :
Output :
html
<canvas id='cv'></canvas>
javascript
var mountain = new Image() ;
mountain.onload = drawMe;
mountain.src = 'http://www.hdwallpapers.in/walls/brooks_mountain_range_alaska-normal.jpg';
function drawMe() {
var cv=document.getElementById('cv');
// set the width/height same as image.
cv.width=mountain.width;
cv.height = mountain.height;
var ctx=cv.getContext('2d');
// first copy the whole image.
ctx.drawImage(mountain, 0, 0);
// save to avoid messing up context.
ctx.save();
// translate to the middle of the left part of the canvas = 1/4th of the image.
ctx.translate(cv.width/4, 0);
// flip the x coordinates to have a mirror effect
ctx.scale(-1,1);
// copy the right part on the left part.
ctx.drawImage(cv,
/*source */ cv.width/2,0,cv.width/2, cv.height,
/*destination*/ -cv.width/4, 0, cv.width/2, cv.height);
// restore context
ctx.restore();
}

ColorPicker implementation using JavaScript and Canvas

I'm trying to implement ColorPicker using Canvas just for fun. But i seem lost. as my browser is freezing for a while when it loads due to all these for loops.
I'm adding the screenshot of the result of this script:
window.onload = function(){
colorPicker();
}
function colorPicker(){
var canvas = document.getElementById("colDisp"),
frame = canvas.getContext("2d");
var r=0,
g=0,
b= 0;
function drawColor(){
for(r=0;r<255;r++){
for(g=0;g<255;g++){
for(b=0;b<255;b++){
frame.fillStyle="rgb("+r+","+g+","+b+")";
frame.fillRect(r,g,1,1);
}
}
}
}
drawColor();
Currently , i only want a solution about the freezing problem with better algorithm and it's not displaying the BLACK and GREY colors.
Please someone help me.
Instead of calling fillRect for every single pixel, it might be a lot more efficient to work with a raw RGBA buffer. You can obtain one using context.getImageData, fill it with the color values, and then put it back in one go using context.putImageData.
Note that your current code overwrites each single pixel 255 times, once for each possible blue-value. The final pass on each pixel is 255 blue, so you see no grey and black in the output.
Finding a good way to map all possible RGB values to a two-dimensional image isn't trivial, because RGB is a three-dimensional color-space. There are a lot of strategies for doing so, but none is really optimal for any possible use-case. You can find some creative solutions for this problem on AllRGB.com. A few of them might be suitable for a color-picker for some use-cases.
If you want to fetch the rgba of the pixel under the mouse, you must use context.getImageData.
getImageData returns an array of pixels.
var pixeldata=context.getImageData(0,0,canvas.width,canvas.height);
Each pixel is defined by 4 sequential array elements.
So if you have gotten a pixel array with getImageData:
// first pixel defined by the first 4 pixel array elements
pixeldata[0] = red component of pixel#1
pixeldata[1] = green component of pixel#1
pixeldata[2] = blue component of pixel#1
pixeldata[4] = alpha (opacity) component of pixel#1
// second pixel defined by the next 4 pixel array elements
pixeldata[5] = red component of pixel#2
pixeldata[6] = green component of pixel#2
pixeldata[7] = blue component of pixel#2
pixeldata[8] = alpha (opacity) component of pixel#2
So if you have a mouseX and mouseY then you can get the r,g,b,a values under the mouse like this:
// get the offset in the array where mouseX,mouseY begin
var offset=(imageWidth*mouseY+mouseX)*4;
// read the red,blue,green and alpha values of that pixel
var red = pixeldata[offset];
var green = pixeldata[offset+1];
var blue = pixeldata[offset+2];
var alpha = pixeldata[offset+3];
Here's a demo that draws a colorwheel on the canvas and displays the RGBA under the mouse:
http://jsfiddle.net/m1erickson/94BAQ/
A way to go, using .createImageData():
window.onload = function() {
var canvas = document.getElementById("colDisp");
var frame = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
var imagedata = frame.createImageData(width, height);
var index, x, y;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
index = (x * width + y) * 4;
imagedata.data[index + 0] = x;
imagedata.data[index + 1] = y;
imagedata.data[index + 2] = x + y - 255;
imagedata.data[index + 3] = 255;
}
}
frame.putImageData(imagedata, 0, 0);
};
http://codepen.io/anon/pen/vGcaF

Raphael and seamless tiles (images)

I would like to paint several seamless images side by side with Raphael. This works in Chrome but not in firefox. For some reason firefox paint the images with small space between them.
You can see that in the fiddle: http://jsfiddle.net/bxK4b/4/
var paper = Raphael(0,0, 200, 200);
var x = 0;
var y = 0;
var size = 10;
var url = "http://us.123rf.com/400wm/400/400/alliedcomputergraphics/alliedcomputergraphics1206/alliedcomputergraphics120600904/14063568-ground-cover-seamless-texture-tile.jpg";
for (x = 0; x < 10; x++) {
for (y = 0; y < 10; y++) {
rect = paper.image(url, x * size, y * size , size, size);
}
}
Sometimes it is possible to paint the images with no space, but if you try to put them in a set and scale the complete set 2 or 3 times, the space between the images comes appears again.
What am I doing wrong? Any help would be appreciated!
Adding 0.5 to the x co-ordinate does it for me:
rect = paper.image(url, x * size + .5, y * size , size, size);
not sure why adding .5 to y is unnecessary but the concept is from here.

Find height of rendered/visible text

I know how to get this height of a font:
By placing the text in a div and getting offset height of the div.
But I would like to get this actual height (Which will depend on font family):
Is that in any way possible using web based programming?
Is there a simple solution? I think the answer is no.
If you're ok with a more involved (and processor-intensive) solution, you could try this:
Render the text to a canvas, then use canvasCtx.getImageData(..) to retrieve pixel information. Next you would do something similar to what this pseudo code describes:
first_y : null
last_y : null
for each y:
for each x:
if imageData[x][y] is black:
if first_y is null:
first_y = y
last_y = y
height = last_y - first_y
This basically looks for the top (lowest y-index) of the lettering (black pixels) and the bottom (highest y-index) then subtracts to retrieve the height.
I was writing the code while Jason answered, but I decided to post it anyway:
http://jsfiddle.net/adtn8/2/
If you follow the comments you should get the idea what's going on and why. It works pretty fast and it's not so complicated as it may sound. Checked with GIMP and it is accurate.
(code to be sure it wont be lost):
// setup variables
var c = document.createElement('canvas'),
div = document.getElementsByTagName('div')[0],
out = document.getElementsByTagName('output')[0];
// set canvas's size to be equal with div
c.width = div.offsetWidth;
c.height = div.offsetHeight;
var ctx = c.getContext('2d');
// get div's font from computed style and apply it to context
ctx.font = window.getComputedStyle(div).font;
// use color other than black because all pixels are 0 when black and transparent
ctx.fillStyle = '#bbb';
// draw the text near the bottom of the canvas
ctx.fillText(div.innerText, 0, div.offsetHeight);
// loop trough the canvas' data to find first colored pixel
var data = ctx.getImageData(0, 0, c.width, c.height).data,
minY = 0, len = data.length;
for (var i = 0; i < len; i += 4) {
// when you found it
if (data[i] != 0) {
// get pixel's y position
minY = Math.floor(i / 4 / c.width);
break;
}
}
// and print out the results
out.innerText = c.height - minY + 'px';
EDIT:
I even made jQuery plugin for this: https://github.com/maciek134/jquery-textHeight
Enjoy.

Categories

Resources