BitmapData Average Colours

If you want a very simple way of extracting a colour palette from an image, one technique would be to average the colour values within specific areas. Averaging colour values is almost identical to averaging numbers, except with the added initial step of finding the red, green and blue components of the colour. To do this we can use bitwise operators, in this case bitwise shift, to perform fast operations on each bit inside the unsigned integer returned by getPixel or getPixel32. If you want to know more about bitwise operators, Moock has written a detailed and, as ever, very clear article on where, when and why to use bitewise operations. You can read it here.

So once you’re familiar with how to shift the bits of an integer, you can easily get the RGB values from a 24 bit hexadecimal by moving the bits to the right by a certain amount using the bitwise right shift operator (>>).

For example, getting the red, green and blue values from a 24 bit integer would look like:

var colour:uint = 0x33CC99;

var R:Number = colour >> 16 & 0xFF;
var G:Number = colour >> 8  & 0xFF;
var B:Number = colour & 0xFF;

And finding the alpha, red, green and blue values of a 32 bit integer:

var colour:uint = 0xFF33CC99;

var A:Number = colour >> 24 & 0xFF;
var R:Number = colour >> 16 & 0xFF;
var G:Number = colour >> 8 & 0xFF;
var B:Number = colour & 0xFF;

So we know that by shifting the bits to the right, we can effectively break an unsigned integer representing a colour into its specific components, but what about changing these components back into an RGB or ARGB value? Well, it’s simply a matter of shifting the bits in the other direction, using the bitwise left shift operator, which looks like this << (two less than operators next to each other).

So using this bitwise shift left operator, you can take your RGB or ARGB values and cram the little buggers back into a usable format, with a little help from bitwise OR:

// RGB
var colourRBG:uint = (R << 16 | G << 8 | B);

// ARGB
var colourARBG:uint = (A << 24 | R << 16 | G << 8 | B);

Right, so we know how to take an unsigned integer, for example one that’s been returned from getPixel or getPixel32 and break it down into its RGB or ARGB components – and then for the sake of universal harmony, put it back together again and release it back into its 24 or 32bit world with all its binary friends.

So now, finding the average colour within a set of colours is simply a question of averaging the red, green and blue values of all the colours, and then turning these averages into a new colour.

The method bellow does just this, by looping through the pixels in a BitmapData object, adding up all of the red, green and blue values, dividing them by the total number of pixels and then creating a new colour from the results.

public static function averageColour( source:BitmapData ):uint
{
	var red:Number = 0;
	var green:Number = 0;
	var blue:Number = 0;

	var count:Number = 0;
	var pixel:Number;

	for (var x:int = 0; x < source.width; x++)
	{
		for (var y:int = 0; y < source.height; y++)
		{
			pixel = source.getPixel(x, y);

			red += pixel >> 16 & 0xFF;
			green += pixel >> 8 & 0xFF;
			blue += pixel & 0xFF;

			count++
		}
	}

	red /= count;
	green /= count;
	blue /= count;

	return red << 16 | green << 8 | blue;
}

So taking this a step further; say we want an array of 64 colours from an image, we can break the input image down into 64 chunks and find the averages of each. The easiest way of doing this is to build a grid, copy the pixels in each cell and use the averageColour function to return the average colour value of that cell.

public static function averageColours( source:BitmapData, colours:int ):Array
{
	var averages:Array = new Array();
	var columns:int = Math.round( Math.sqrt( colours ) );

	var row:int = 0;
	var col:int = 0;

	var x:int = 0;
	var y:int = 0;

	var w:int = Math.round( source.width / columns );
	var h:int = Math.round( source.height / columns );

	for (var i:int = 0; i < colours; i++)
	{
		var rect:Rectangle = new Rectangle( x, y, w, h );

		var box:BitmapData = new BitmapData( w, h, false );
		box.copyPixels( source, rect, new Point() );

		averages.push( averageColour( box ) );
		box.dispose();

		col = i % columns;

		x = w * col;
		y = h * row;

		if ( col == columns - 1 ) row++;
	}

	return averages;
}

In terms of building an accurate colour palette from an image, this isn’t a great method, mainly because averaging the colours doesn’t give you a fair representation of the range of colours in the image. The palette returned can often be somewhat muddy; however it is still a technique that I’ve found a use for on a surprisingly regular basis and so thought it worth sharing. Becoming familiar with bitwise operators is also a useful exercise and so, if they aren’t already part of your life, I suggest you begin to woo them with your nerdy charm.

Posted on 10 Oct 2008
25 Comments
12 Trackbacks

Meta

BitmapData Average Colours was posted on October 10th 2008 in the category Code / Actionscript 3.0, Flash, Open Source and tagged; , , , , , , .

You can Leave a comment.


Warning: file_get_contents(http://search.twitter.com/search.atom?q=from:soulwire&rpp=1) [function.file-get-contents]: failed to open stream: HTTP request failed! HTTP/1.0 410 Gone in /home/soulwire/webapps/soulwire_blog/wp-content/themes/soulwire/functions.php on line 203

Discussion

28 Responses to BitmapData Average Colours

Leave a Reply

Pingbacks / Trackbacks

  1. 8 years ago AS3 Colour class for extracting the color palette from a BitmapData image or photo, ColourUtils.as

    [...] Comments BitmapData Average Colours 17% similar Finding the average colours in an image [...]

  2. 7 years ago Color Palettes of Game Advertisements in German Video Game Entertainment Magazins | MINDsPAGE

    [...] Soulwire – Finding the average colours in an image [...]

  3. 7 years ago Who wins the award for most popular pixel? | BLOG.RUEDAMINUTE>COM

    [...] Soulwire, a snippet that gets the average color in an [...]

  4. 7 years ago Case Study : ActionScript 3 Performance Optimization at Mike Chambers

    [...] an image, created by averaging the colors within that image. Upon searching on google, I found a very good solution over at soulwire.co.uk, which I will use as the base for creating the palette. I want to point out that the original code [...]

  5. 7 years ago Median Cut « GrgrDvrt

    [...] couleurs d’une belle image, tu peux tout de suite aller chez soulwire voir ces articles: couleurs moyennes et extraire une palette de couleurs, c’est plus simple et ça marche mieux. Par contre tu vas [...]

  6. 7 years ago Collage Creator | Flash Monkey

    [...] I actually did a quick Google search for this one before I started writing it myself and found Justin Windle’s post which goes into detail about how this works. Once I had the color values I just compare them to [...]

  7. 6 years ago Making the most of your toy robot (Part 3 of 4) – Object Detection | Electric Pineapple

    [...] spot is a colored ring that can be averaged out and determined to be in a specific range. Using the technique outlined here by Justin Windle we have averaged out the ring color and can ask if the RGB values are in the range [...]

  8. 5 years ago Tip: Use AS3′s Histogram() method for color averaging

    [...] I’ve been reusing a simple method of finding the average color of a region, based on an often-used technique of looping over each pixel, grabbing the pixel’s color using [...]

  9. 5 years ago Anonymous

    [...] [...]

  1. sekati 7 years ago

    You might be interested in the ColorUtil I wrote as part of my framework (used in a large colorful cosmetics application as well); it offers averaging for any display object (or portion of a display object) in R, G, B, saturation, contrast, lightness and value which can really come in handy.

    src: http://code.google.com/p/sekati/source/browse/trunk/src/sekati/utils/ColorUtil.as

    doc: http://docs.sekati.com/sekati/sekati/utils/ColorUtil.html

    Reply to this comment

  2. Luis 7 years ago

    Do you have a full working version? The demo only copies 16 colors to clipboard and I need at least 64 colors… Thanks

    Reply to this comment

  3. Soulwire 7 years ago

    Hi Luis,

    Oops, my bad – this data copied to the clipboard wasn’t being updated when you select a new amount for the colours.

    The demo is updated now and will give you your desired amount of colours. Clear your cache and you’re good to go!

    Reply to this comment

  4. Elliot Geno 7 years ago

    There’s a much easier way to find the average color of an area of pixels. It can streamline you code immensely, be more accurate, and be easy on your processor. First take your bitmapData for the bitmap you are sampling. Scale it down using a matrix… then blow it back up using a different matrix! The following essentially pixelates the image and blows it back up to fill the bitmap. Simply supply the function with a bitmap, the desired pixel width, and the desired pixel height. You can then sample each of those sections much easier.

     [ function pixelate(bitmap:Bitmap,pixelWidth:Number,pixelHeight:Number):BitmapData {
    	var pixelScaleX:Number=1/pixelWidth;//find scale
    	var pixelScaleY:Number=1/pixelHeight;
    	// shrink bitmap
    	var smallBitmapData:BitmapData=new BitmapData(pixelScaleX*bitmap.width,pixelScaleY*bitmap.height,true,0x00000000);//create small bitmap
    	var smallMatrix:Matrix=new Matrix();
    	smallMatrix.scale(pixelScaleX,pixelScaleY);//physically scales the bitmap
    	smallBitmapData.draw(bitmap,smallMatrix,new ColorTransform(),"normal",new Rectangle(0,0,bitmap.width,bitmap.height),false);// get original bitmap at smaller size using the smallMatrix
    	// enlarge bitmap
    	var newBitmapData:BitmapData=new BitmapData(bitmap.width,bitmap.height,true,0x00000000);//create normal sized bitmap
    	var newMatrix:Matrix=new Matrix();
    	newMatrix.scale(bitmap.width/smallBitmapData.width,bitmap.height/smallBitmapData.height);
    	newBitmapData.draw(smallBitmapData,newMatrix,new ColorTransform(),"normal",new Rectangle(0,0,bitmap.width,bitmap.height),false);
    	// return the new bitmap
    	return newBitmapData;
    } ] 

    Reply to this comment

  5. Anonymous 7 years ago

    THANK YOU!!! Found your site via Google. Saved me a ton of headaches with my code on a project at work. Was only getting the “R” or “B” of an image (not the whole image with all three colors combined). Thank you thank you thank you!!!

    Reply to this comment

  6. Robert Jan Bijlsma 5 years ago

    isn’t there an error in your drawing method… all is shifted one row to the right and down?
    should your draw function not contain the loop (can’t find your original source code anymore )

    for (var i:int = 0; i < arr.length; i++)
    {

    col = i % columns;

    _x = _w * col;
    _y =_h * row;

    _sprt.graphics.beginFill(arr[i]);
    _sprt.graphics.drawRect(_x, _y, _w, _h);
    _sprt.graphics.endFill();

    if ( col == columns – 1 ) row++;

    }

    Reply to this comment