List unique colors in an HTML Canvas - html

Is there an efficient way to get a list of colors of what is currently drawn in an HTML Canvas?
I guess the correct term would be a Color Palette of the current composite

Assume you have an html document like the following:
<canvas id="example" width="500" height="300"></canvas>
<ul id="list">
<h3>Click the canvas to extract colors</h3>
</ul>
​I have created a fiddle in which you have a ready made canvas with some shapes and colors that we will be processing when we click on it. I'm not creating a color picker for you, just listing as that was the original answer and should serve as the means to achieve the second part of it.
Here's the fiddle
The function that extracts the colors an lists them is the following, I'm avoiding copying all the canvas drawing stuff since it just serves as the example and isn't generic enough.
var colorList = []; //This will hold all our colors
function getColors(){
var canvas = document.getElementById('example');
var context = canvas.getContext('2d');
var imageWidth = canvas.width;
var imageHeight = canvas.height;
var imageData = context.getImageData(0, 0, imageWidth, imageHeight);
var data = imageData.data;
// quickly iterate over all pixels
for(var i = 0, n = data.length; i < n; i += 4) {
var r = data[i];
var g = data[i + 1];
var b = data[i + 2];
//If you need the alpha value it's data[i + 3]
var hex = rgb2hex("rgb("+r+","+g+","+b+")");
if ($.inArray(hex, colorList) == -1){
$('#list').append("<li>"+hex+"</li>");
colorList.push(hex);
}
}
}
This will produce a result containing a seemingly unordered list of hex values that represent the order in which they appear in your image (hence the 'seemingly'), if you want a color picker however you may want to consider the use of colorList.sort(); which will order the array into a black-to-white fashion.
Note that I'm using the rgb2hex function to convert the values returned to the hex format I'm assuming your wanting, if you're ok with rgb feel free to not use it, the function is the following:
function rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
And the source is this, as I stated as a comment in the code, you could easily extract the alpha channel too if you needed to but I went for the most generic scenario (and simplest) that I could think of.

Related

AS3 - RGB coloring?

I am making this game in AS3. It's about a ball and a pad. The user can move the pad to prevent the ball from hitting the floor. Anyways, I want the ball to change color to a randomly generated color. So I have got a mathrandom for every color value, R G B.
var r;
var g;
var b;
function getColor(){
r = Math.round(Math.random()*256);
g = Math.round(Math.random()*256);
b = Math.round(Math.random()*256);
}
But I don't know how to color the ball with RGB colors. I don't even know if it's possible.
var r;
var g;
var b;
function getColor():uint{
var resultColor:uint;
r = Math.round(Math.random()*0xFF); //0xFF equivalent to 255
g = Math.round(Math.random()*0xFF);
b = Math.round(Math.random()*0xFF);
resultColor = r<<16 | g<<8 | b;
return resultColor;
}
The Color is in 0xRRGGBB format in AS3. So we combine the 3 values using bitwise operations.
Hope this might help. Check this for further info : Bit Operations in AS3
It depends on your implementation. If you're programmatically creating your ball (such as)...
var ball:Shape = new Shape();
ball.graphics.beginFill(0xFFFFFF);
ball.graphics.drawCircle(0, 0, 10);
ball.graphics.endFill();
... then you could easily draw the updated circle by first calling ball.clear() and redrawing it with your new color value. Be aware that beginFill() uses uint values, and you can easily generate these using the same Math.random(). Here's a modified version of your function that allows you to define the low & high values.
function randomRange(low:Number=0, high:Number=1):Number {
/* Returns a random number between the low and high values given. */
return Math.floor(Math.random() * (1+high-low)) + low;
}
... at which point your beginFill() would look like this:
ball.graphics.beginFill(randomRange(0x000000, 0xFFFFFF);
Which effectively retrieves a random color between black and white.
However, if you're modifying an existing image (such as a loaded Bitmap), you want a colorTransform(). Here's one for RGB I wrote:
tintRGB(target:Object, r:Number, g:Number, b:Number, alpha:Number = 1):void {
if (target.hasOwnProperty("transform")) {
target.transform.colorTransform = new ColorTransform(0,0,0,alpha,r,g,b,0);
}
}

AS3 - How to remove all black and grey from bitmap, but retain color?

I'd like to be able to alter an image (specifically a bitmap) in order to replace all dark grey and black pixels with white in ActionScript 3, but maintain all other colors in the image. I am familiar with ColorMatrixFilter and bitmapdata.threshold, but I don't know how to use them to either target the colors I want to remove or check within a specific range of colors. Is there any (efficient) way to go about doing this?
Thanks for any help you can offer.
The AS3 API gives pretty good documentation on how to use the treshhold value. You can find it here. Their example actually checks for a certain range of colors. I've modified their example to work for your problem. I haven't tested it, so it might need some tweaking.
var bmd2:BitmapData = new BitmapData(200, 200, true, 0xFFCCCCCC);
var pt:Point = new Point(0, 0);
var rect:Rectangle = new Rectangle(0, 0, 200, 200);
var threshold:uint = 0x00A9A9A9; //Dark Grey
var color:uint = 0x00000000; //Replacement color (white)
var maskColor:uint = 0xFFFFFFFF; //What channels to affect (this is the default).
bmd2.threshold(bmd1, rect, pt, ">", threshold, color, maskColor, true);
Another option would be to use a double for loop that iterates over all pixels and takes a certain action based on the value of the pixel.
for(var y:int = 0; y < height; y++){
for(var x:int = 0; x < width; x++){
var currentPixel:uint = image.getPixel( x, y );
if(currentPixel != color){
image.setPixel( destPoint.x + j, destPoint.y + i, currentPixel );
}
}
}

Ye olde Pipe Flow Animation

i try to script a flow-animation ( basic color, nothing fancy ) through a grid of "pipes".
( think of a 5*5 tiled screen)
since the pipes are created completely dynamic at runtime, the animation has to be scripted also.
At the moment it escapes my mind on how to do this in actionscript, without pre-generated masks.
thanks for all hints!
You wish to visually 'simulate' the flow of some liquid (for instance water) through pipes in the same style that it is done in pipe games?
http://www.mclelun.com/img/blog/120411_pipe_02.jpg
Alright...
Are you willing to use bitmapData (pixel) to create this effect?
Here is how I would go about it..
create a short script to fill a rectangle (block) of pixels gradually..
ie
var animateOn : Boolean = true;
var startPoint : Point = new Point(beginX , beginY);
var endPoint : Point = new Point(finishX, finishY);
var step : Number = 1 / Point.Distance(startPoint, endPoint);
var currentPos : Number = 0;
onEnterFrame(e : Event):void
{
var p : point = Point.interpolate(startPoint, endPoint, currentPos);
bitmap.drawRect(p.x - 2, p.y - 2, 4, 4, someColor);
currentPos += step;
}
This is just an example of the top of my head (will not compile)
The idea is to keep feeding in the right startPoint and endPoint for each tile..
You could easily animate it like with without using any mask.
You can define each type of tile as a vector of points and then iterate from one point to another by adding the position of the tile to each point.
To fill a curve dynamically you may want to use this closed formula for a Bezier curve:
//start point
var s = new Point(x0, y0);
//cont point
var c = new Point(x1, y1);
//end point
var e = new Point(x2, y2);
var step : Number = 1 / (Point.distance(startPoint, controlPoint) + Point.distance(controlPoint, endPoint));
var t : Number = 0.0;
private function onEnterFrame(e : Event):void
{
var p : Point = new Point();
p.x = (s.x * (1-t) + c.x * t) * (1 - t) + (c.x * (1-t) + e.x * t) * t;
//do the same for y axis
drawSomething(p.x, p.y);
t+= step;
}
this will animate a curve style flow

Is it possible to draw on multiple canvases at once?

All of my canvas drawing functions start something like this:
function drawMe(){
var canvas = document.getElementById('canvas-id');
var ctx = null;
ctx = canvas.getContext('2d');
...
}
However I now want to draw the same image on a (variable) number of canvases, is there some alternative to getElementById() (perhaps in jQuery?) which can be used to easily grab more than one at once?
Thanks!
Josh
drawing to multiple canvases will be extremely inefficient, because rendering is one of most expensive operations you can do.
what you want to do is to use buffer. you can simply draw from one canvas to another.
var canvas1 = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
var ctx1 = canvas1.getContext("2d");
var ctx2 = canvas2.getContext("2d");
ctx1.fillStyle = "black";
ctx1.fillRect(10, 10, 10, 10);
ctx2.drawImage(canvas1, 0, 0);
here's a fiddle.
remember, you only need to call ctx.drawImage once - after you're done with all drawing on first canvas.
With jQuery, if you do $('.blah') you will get all elements of the class 'blah'. So if you give all your canvases that class you could merely iterate through them all and draw on each one at that point.
It is ideal to get all of the contexts only once though, so unless drawMe is only called once, you should collect all the contexts just once and then pass that array into drawMe each time it is called.
Interesting... I'm sure it's not the best solution (I'm not entirely sure it'll work!), and it assumes a class by which to identify your canvas, but try this:
var canvases, contexts, imgdata = 0;
function init() {
canvases = document.getElementsByClassName('cvs-class');
contexts = [];
for(var i = 0; i < canvases.length; i++) {
contexts[i] = canvases[i].getContext('2d'); //initialize each canvas with context.
}
}
function drawToCanvas() {
// Do your drawing on canvases[0];
imgdata = contexts[0].getImageData(0,0,canvases[0].width,canvases[0].height);
paintToAllContexts();
}
function paintToAllContexts() {
for(var i=0; i<canvases.length; i++) {
contexts[i].putImageData(imgdata,0,0);
}
}
function document.getElementsByClassName(class) {
var nodes = [];
var cl = new RegExp('\\b'+cl+'\\b');
var el = this.getElementsByTagName('*');
for(var i = 0; i < el.length; i++) {
var cls = el[i].className;
if(cl.test(cls)) nodes.push(el[i]);
}
return nodes;
}
If you are drawing a complex image on to several canvases, it might be better to:
Draw a complex image to the first canvas.
Paste that canvas to the other canvases via drawImage() (which can take a canvas parameter).
This way you just copy the image pixels rather than drawing something complicated repeatedly. If it's just a single image, though, it's probably better just to draw that directly like the other answers propose.
Give each canvas a class and loop through each canvas with the class.
var allCanvases = document.getElementsByTagName('canvas');

As3 and Image ByteArray Data

How can I get the bytearray data from an image that is under certain shape, for example a circule or square?
Let's say I want to modify ONLY the pixel inside this circule, how can I get this Bytearray data?
Any ideas?
Define a rectangle containing the circle, relative to the top left of the image.
var radius:Number = 100;
var centerX:Number = 50;
var centerY:NUmber = 400;
var rect:Rectangle = new Rectangle(centerX-radius, centerY-radius, radius*2, radius*2);
Then use the getPixels() to return a ByteArray of the pixels inside the rectangle.
Now you can loop through each pixel and check if it's contained inside the circle.
var image:BitmapData;
var pixels:ByteArray = image.getPixels(rect);
for(var x:int; x<rect.width; x++){
for(var y:int=0; y<rect.height; y++){
// Read the pixels data ->
var pixel:uint = pixels.readUnsignedInt();
// Check this pixels distance from the center to make sure it is inside the circle.
var dx:Number = x - radius;
var dy:Number = y - radius;
if(dx*dx+dy*dy <= radius*radius){
// This pixel is inside the circle.
...
}
}
}
Then once you've modified it if you want you can write it back to the image using setPixels()
image.setPixels(rect, pixels);
I haven't actually used or tested any of this so it might not work.
It might also be easier to work with the data if you use getVector() and setVector()
instead.