Let me explain what I want to achieve..
I'm building something just for fun and as a learning experiment similar to
www.milliondollarhomepage.com
Except, I want to use canvas and/or Fabric.js to achieve the same thing. How would I go about manipulating each pixel in a canvas?
Scenario:
I want to offer a 1000 pixels up for grabs.
User can choose an image and where it should go on the canvas
Image can be resized according to how many pixels the user wants and deducted from the overall remaining pixels.
Any help on this would be appreciated!
The method in the HTML5 canvas api for manipulating individual pixels is
context.getImageData(x,y,width,height);
for example
var map = context.getImageData(0,0,canvas.width,canvas.height);
This returns a massive array that contains in repeating order:
[red,green,blue,alpha,red,green,blue,alpha...]
Every 4 numbers represent the red, green, blue, and alpha channels for every single pixel on the chosen area, left-to-right, top-to-bottom.
The values for each of these numbers is an integer ranging from 0 - 255.
To loop through every pixel and drop their red and blue channels, thus turning the image green, for example
//assume map variable from earlier
for(var i = 0; i < map.data.length; i+=4){
map.data[i] = 0; // drop red to 0
map.data[i+2] = 0; // drop blue to 0
}
context.putImageData(map,0,0);
See Example
Note that this procedure can only be done on a server, and without images from other domains "contaminating" the canvas. If these requirements are not met, a security error DOM exception will be thrown.
Related
I have a super simple need of OCR.
My application allows creating image from text. Its very simple. People choose a font face, bold or not, and size.
So they get outputs like this, ignoring the border:
I wanted to create a very simple OCR to read these. I thought of this approach:
In the same way I generate an image for the message. I should generate an image for each character. Then I go through and try to match each character image to the black occourances in the canvas. Is this right approach?
The method I use to draw element to image is this copy paste example here: MDN :: Drawing DOM objects into a canvas
Ok, another couple of tries...
Another method that's simpler than OCR: use Steganography to embed the text message as part of the image itself. Here's a script that uses the alpha channel of an image to store text: http://www.peter-eigenschink.at/projects/steganographyjs/index.html
You can try this "home brewed" OCR solution...but I have doubts about its effectiveness.
Use the clipping form of context.drawImage to draw just the message-text area of your image on the canvas.
Use context.getImageData to grab the pixel information.
Examine each vertical column starting from the left until you find an opaque pixel (this is the left side of the first letter).
Continue examining each vertical column until you find a column with all transparent pixels (this is the right side of the first letter).
Resize a second canvas to exactly contain the discovered letter and drawImage just the first letter to a second canvas.
Set globalCompositeOperation='destination-out' so that any new drawing will erase any existing drawings where the new & old overlap.
fillText the letter "A" on the second canvas.
Use context.getImageData to grab the pixel information on the second canvas.
Count the opaque pixels on the second canvas.
If the opaque pixel count is high, they you probably haven't matched the letter A, so repeat steps 5-9 with the letter B.
If the opaque pixel count is low, then you may have found the letter A.
If the opaque pixel count is medium-low, you may have found the letter A but the 2 A's are not quite aligned. Repeat steps 5-9 but offset the A in step#7 by 1 pixel horizontally or vertically. Continue offsetting the A in 1 pixel offsets and see if the opaque pixel count becomes low.
If step#12 doesn't produce a low pixel count, continue with the letter B,C,etc and repeat steps 5-9.
When you're done discovering the first letter, go back to step#1 and only draw the message-text with an offset that excludes the first letter.
OCR is always complex and often inaccurate.
I hate to wave you off of a solution, but don't use OCR for your purpose
Simple and effective solution...
Put your message in the image's file name.
Solution found - GOCR.js - https://github.com/antimatter15/gocr.js/tree/d820e0651cf819e9649a837d83125724a2c1cc37
download gocr.js
decide if you want to go from WebWorker, or mainthread
worker
In the worker put this code:
importScripts(gocr.js)
GOCR(aImgData)
where aImgData is, take an image, load it, draw it to canvas, then send the data to the webworker. (see mainthread method)
mainthread
<script src="gocr.js">
<script>
var img = new Image()
img.onerror = function() {
console.error('failed')
}
img.onload = function() {
var can = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
can.width = img.width;
can.height = img.height;
var ctx = can.getContext('2d')
ctx.drawImage(img, 0, 0)
// to use this in a worker, do ctx.getImageData(0, 0, img.width, img.height), then transfer the image data to the WebWorker
var text = GOCR(can);
}
</script>
I'm working on a project that requires me to make a text box in which the user will enter a number between 1 and 10. The program will then take this input and create a series of shapes (if the user enters 4, then 4 shapes should be drawn on the canvas). These shapes are also supposed to be different sizes, each shape smaller than the last. The shapes are also supposed to each be a different color.
The problem is I have no idea how to do this, I know how to create a canvas and draw shapes, but the whole, different sizes, colors, and drawing as many shapes as is entered in the text box is throwing me off. Could anyone point me in the right direction?
Firstly to draw the number of boxes that the user inputs we can just have the input be used as a for loop condition:
// "input" is the users entered number, make sure to parseInt() it
for(var i = input; i > 0; i--) {
// Draw shape of size "i" and color "colors[i-1]"
}
Since 10 is the largest number we can have we can make an array of ten colors: colors = ["red","blue","yellow", ... ,"green"]. Now the reason why I went from input to 0 in the for loop is because we have an number that's getting smaller, we can use i as the size of the box in the loop. And you can use i for positioning for the boxes (for example times it by 10 and use it as a varying x position). Or simply make the x and y random.
Now with the concept down a full example might look like so:
var ctx = document.getElementById("canvas").getContext("2d");
var canvasWidth = document.getElementById("canvas").width;
var yPos = 100;
var input = 8;
var colors = ["red","blue","yellow","pink","grey","black","aqua","brown","cyan","green"]
for(var i = input; i > 0; i--) {
// Draw shape of size "i" and color "colors[i]"
ctx.fillStyle = colors[i-1];
ctx.fillRect(canvasWidth - i*25, yPos, i*3, i*3);
}
canvas{ border: 1px solid black; }
<canvas id="canvas" width=300 height=300></canvas>
Edit: Changed sizes to show up left-to-right, per OP's request.
When the user click submit, you should call a javascript function which does the following:
Get the number of shapes to be drawn
Get canvas element by ID
Based on the number of shapes, using conditional statements, draw the shapes (the starting point would be previous object x (or y depending on your preference) + current x (or y))
To get different colors use on border use:
ctx.strokeStyle="green"; & for different fill color use: ctx.fillStyle="red";
For tutorial on how to go about different shapes refer: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes
Fiddle:
http://jsfiddle.net/7v85bzam/1/
You may need to experiment a bit with the positioning (x, y co-ordinates)
I want to write some PS Javascript code to get a pixel's ARGB color value. A function which would look like:
function getPixelARGB(doc, x, y);
I searched for a while and found a method by Mike Hale in this site: a link
The answer proposed by Mike Hale works fine, the only problem is that it can't get the alpha value(opacity) of the selected pixel, which is what I want to know.
Anybody have any idea on how to get the ARGB value of a selected pixel by PS script? Thanks in advance.
Ok, if you want to support Photoshop CS2, I have something that will work, but is quite hacky - to say the least.
The basic idea is to get ImageMagick to do it on behalf of Photoshop - and that is probably quicker than anything accessing individual pixels in Photoshop's ExtendScript anyway. So, the ImageMagick command to see pixels in text/human-readable form is this:
convert out.png txt:
# ImageMagick pixel enumeration: 256,256,255,srgba
0,1: (255,0,0,0.996078) #FF0000FE srgba(255,0,0,0.996078)
1,1: (255,0,0,0.996078) #FF0000FE srgba(255,0,0,0.996078)
2,1: (255,0,0,0.996078) #FF0000FE srgba(255,0,0,0.996078)
3,1: (255,0,0,0.996078) #FF0000FE srgba(255,0,0,0.996078)
You can see the transparency is FE or 0.996078 for this row.
So, if you want 1 pixel, say the one at 128,128, you would do this:
convert out.png -crop 1x1+128+128 -depth 8 txt:
# ImageMagick pixel enumeration: 1,1,255,srgba
0,0: (255,0,0,0.498039) #FF00007F srgba(255,0,0,0.498039)
and it has an opacity of 7F or 0.498039.
So, to realise what you want to do, your putative function getPixelARGB(doc, x, y) will have to do the following steps:
1. duplicate document `doc`
2. save duplicate as `PNG` (to preserve transparency) on somewhere like `/tmp`
3. invoke ImageMagick - see below
4. read result - see below
So, how do you invoke ImageMagick and read its output? You can use this:
app.system("convert /tmp/tmp.png -crop 1x1+128+128 -depth 8 txt: > /tmp/result.txt")
var w = new File("/tmp/result.txt");
w.open('r');
var str = "";
while(!w.eof)
str += w.readln();
w.close();
alert(str);
I can tell you briefly how to do it, but I don't have time to write the code this very minute - maybe over the next couple of days if you are lucky. So, hopefully your Extendscript skills are reasonable.... :-)
The issue is that the Color Sampler doesn't pass back transparency, just RGB values. So, the technique will be to force the transparency into the RGB values so you can use them. Here are the steps:
Duplicate the document - because we are going to be a bit destructive
Select all & copy merged (Cmd-A,Shift+Cmd+C)
Flatten (Layer->Flatten Image)
Fill with black (Edit->Fill->Black)
Paste (the layer we copy merged)
Lock transparent pixels on pasted layer (top of layers palette - first checkerboard icon on left)
Fill with white (Edit->Fill->White)
Add a color sampler at the pixel position you want
Read sampler's red value (or green, or blue - they are all the same) and that is the transparency
Most of the Extendscript for the above is fairly simple, the only two harder bits are setting the transparent pixels to locked, which you do like this:
docLay[l].transparentPixelsLocked = true;
and the samplers - where you might be well advised to clear all existing samplers (as only max 4 are permitted)
var sampler = doc.colorSamplers.add([64, 64]);
I have made a little animated GIF here that shows you the process. I start with a red->transparent gradient image. Note at the end when I have the color dropper tool running, you will see the greyscale values in the Color Info window change as I move up and down the image with the mouse, these reflect the original transparency.
I might as well go for a third method while I am thinking about this... :-)
If you create a gradient image which is solid red to transparent, like this:
then load it into Photoshop and select the Color Sampler tol and move it around, you will see in the Info window, that Photoshop reports it as pure red wherever you move the sampler - basically ignoring the transparency.
Now, create a new layer and fill it with black, and then in the Layers window, drag the new black layer below the transparent one - leave the blending mode as normal and opacity and fill at 100%. It will look like this:
Now move the Color Sampler around and you will see the Red RGB value is directly inversely proportional to the opacity - and now the GOOD NEWS - you can get the red channel with Extendscript! So, the technique would be to get the red pixel's RGB value, then put the layer behind and get the red pixel's value again and the difference between the two is proportional to the opacity.
I guess if you filled with white instead of black, the red channel would be directly proportional to the opacity.
After searching the web for just over an hour, I have not found any luck.
I am wondering if it is possible, if so how do I create a transparent image on a js canvas not to be considered as a rectangle, rather only the visible area of it.
For example if you click in a transparent spot on the png the script does not considered that part of the object.
Thank you :)
Yes, you can get info about every pixel on the canvas using context.getImageData
A Demo: http://jsfiddle.net/m1erickson/tMmzc/
This code will get an array containing info about every pixel on the canvas:
var data=ctx.getImageData(0,0,canvas.width,canvas.height).data;
The data array is organized with 4 sequential element representing the red,green,blue & alpha(opacity) information about one pixel.
The data array's elements #0-3 have the top-left pixel's r,g,b,a info.
The data array's elements #4-7 have the next rightward pixel's r,g,b,a info.
...and so on...
Therefore, given the mouse position on the canvas you can fetch that pixel's alpha info. If the alpha value is zero then that pixel is transparent.
This code will read the alpha value under the mouse and determine if it's transparent:
var isTransparent = data[(mouseY*canvas.width+mouseX)*4+3]>0;
I've built an analytical data visualization engine for Canvas and have been requested to add tooltip-like hover over data elements to display detailed metrics for the data point under the cursor.
For simple bar & Gaant charts, tree graphs and node maps with simple square areas or specific points of interest, I was able to implement this by overlaying absolutely-positioned DIVs with :hover attributes, but there are some more complicated visualizations such as pie charts and a traffic flow rendering which has hundreds of separate areas defined by bezeir curves.
Is is possible to somehow attach an overlay, or trigger an event when the user mouses over a specific closed path?
Each area for which hover needs to be specified is defined as follows:
context.beginPath();
context.moveTo(segmentRight, prevTop);
context.bezierCurveTo(segmentRight, prevTop, segmentLeft, thisTop, segmentLeft, thisTop);
context.lineTo(segmentLeft, thisBottom);
context.bezierCurveTo(segmentLeft, thisBottom, segmentRight, prevBottom, segmentRight, prevBottom);
/*
* ...define additional segments...
*/
// <dream> Ideally I would like to attach to events on each path:
context.setMouseover(function(){/*Show hover content*/});
// </dream>
context.closePath();
Binding to an object like this is almost trivial to implement in Flash or Silverlight, since but the current Canvas implementation has the advantage of directly using our existing Javascript API and integrating with other Ajax elements, we are hoping to avoid putting Flash into the mix.
Any ideas?
You could handle the mousemove event and get the x,y coordinates from the event. Then you'll probably have to iterate over all your paths to test if the point is over the path. I had a similar problem that might have some code you could use.
Looping over things in this way can be slow, especially on IE. One way you could potentially speed it up - and this is a hack, but it would be quite effective - would be to change the color that each path is drawn with so that it is not noticeable by humans but so that each path is drawn in a different color. Have a table to look up colors to paths and just look up the color of the pixel under the mouse.
Shadow Canvas
The best method I have seen elsewhere for mouseover detection is to repeat the part of your drawing that you want to detect onto a hidden, cleared canvas. Then store the ImageData object. You can then check the ImageData array for the pixel of interest and return true if the alpha value is greater than 0.
// slow part
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillRect(100,100,canvas.width-100,canvas.height-100);
var pixels = ctx.getImageData(0,0,canvas.width,canvas.height).data;
// fast part
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (pixels[idx]) { // alpha > 0
...
}
Advantages
You can detect anything you want since you're just repeating the context methods. This works with PNG alpha, crazy compound shapes, text, etc.
If your image is fairly static, then you only need to do this one time per area of interest.
The "mask" is slow, but looking up the pixel is dirt cheap. So the "fast part" is great for mouseover detection.
Disadvantages
This is a memory hog. Each mask is W*H*4 values. If you have a small canvas area or few areas to mask, it's not that bad. Use chrome's task manager to monitor memory usage.
There is currently a known issue with getImageData in Chrome and Firefox. The results are not garbage collected right away if you nullify the variable, so if you do this too frequently, you will see memory rise rapidly. It does eventually get garbage collected and it shouldn't crash the browser, but it can be taxing on machines with small amounts of RAM.
A Hack to Save Memory
Rather than storing the whole ImageData array, we can just remember which pixels have alpha values. It saves a great deal of memory, but adds a loop to the mask process.
var mask = {};
var len = pixels.length;
for (var i=3;i<len;i+=4) if ( pixels[i] ) mask[i] = 1;
// this works the same way as the other method
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (mask[idx]) {
...
}
This could be done using the method ctx.isPointInPath, but it is not implemented in ExCanvas for IE.
But another solution would be to use HTML maps, like I did for this little library : http://phenxdesign.net/projects/phenx-web/graphics/example.htm you can get inspiration from it, but it is still a little buggy.
I needed to do detect mouse clicks for a grid of squares (like cells of an excel spreadsheet). To speed it up, I divided the grid into regions recursively halving until a small number of cells remained, for example for a 100x100 grid, the first 4 regions could be the 50x50 grids comprising the four quadrants.
Then these could be divided into another 4 each (hence giving 16 regions of 25x25 each).
This requires a small number of comparisons and finally the 25x25 grid could be tested for each cell (625 comparisons in this example).
There is a book by Eric Rowell named "HTML5 CANVAS COOKBOOK". In that book there is a chapter named "Interacting with the Canvas: Attaching Event Listeners to Shapes and Regions". mousedown, mouseup, mouseover, mouseout, mousemove, touchstart, touchend and touchmove events can be implemented. I highly suggest you read that.
This can't be done (well, at least not that easily), because objects you draw on the canvas (paths) are not represented as the same objects in the canvas. What I mean is that it is just a simple 2D context and once you drawn something on it, it completely forgets how it was drawn. It is just a set of pixels for it.
In order to watch mouseover and the likes for it, you need some kind of vector graphics canvas, that is SVG or implement your own on top of existing (which is what Sam Hasler suggested)
I would suggest overlaying an image map with proper coordinates set on the areas to match your canvas-drawn items. This way, you get tooltips AND a whole lot of other DOM/Browser functionality for free.