Click here to Skip to main content
15,211,653 members

A fast way to work on Windows bitmap pixel data without using the slow GetPixel and SetPixel methods

Rate this:
3.50 (6 votes)
Please Sign up or sign in to vote.
3.50 (6 votes)
4 Jun 2010CPOL
When GetPixel() and SetPixel() are very slow, try this solution.

Introduction

This article shows a fast way to view and change pixel color data (Windows bitmaps) without using the GetPixel and SetPixel methods.

Background

I spent a couple of months in trouble not knowing a fast way to edit images with C++ (I was using the Windows API functions GetPixel and SetPixel). I lost a couple of clients. I then spent a couple of weeks browsing through forums trying to find answers. But eventually, I got through this by myself and learned this easy pointer arithmetic method.

Using the Code

We use a pointer to bitmap bits: bitmapzor.GetBits();. Then, we use a pointer offset to get to another line in the bitmap pixel colors array: bitmapzor.GetPitch();.

The complete code with comments is listed here:

// FastImageManipulations.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    //Okay Now you need a bitmap to work with. Find any bmp file on your PC and
    //Put it to C:\\1.bmp location. You can enter any name, just alter the file
    //path below:

    CImage bitmapzor;
    bitmapzor.Load(_T("C:\\1.bmp"));

    //I use ATL CImage class because it is easy to use. Low number of code lines
    //You can use HBITMAP, HGDIOBJ of GDIplus and other methods,
    //They work the same way and at the same speed, but take tons of code lines 
    //For instance you can use HBITMAP bitmapzor = (HBITMAP)ImageLoad(...)
    //And then you are to use GDI and API functions
    //Let us see the difference between standard API and pointer way:
    printf ("Now we use ATL (Api) GetPixel and SetPixel functions to " 
            "enforce (for example) all green pixels by 30%\n please " 
            "press any key to start and start counting minutes :)");

    getchar();
    COLORREF PixColor=0; //This is a color data
    int R=0,G=0,B=0; //These are the channel values

    //First we shall for instance allter all pixel colors to green using standard way
    for (int i=0; i<bitmapzor.GetWidth(); i++) //along line
        for (int j=0; j<bitmapzor.GetHeight(); j++) //new line
        {
            //This evil slow function extracts 24bit pixel color as BGR value
            PixColor = bitmapzor.GetPixel(i,j);
            R=GetRValue(PixColor);//This macro extracts red channel value
            G=GetGValue(PixColor);//This macro extracts green channel value
            B=GetBValue(PixColor);//This extracts blue pixel color
            G=(int)((float)G*1.3); //There we enforce green color by 30%.
            if (G>255) G=255; //There we ensure G is within 0-255 range
            //We can assemble the BGR 24 bit number that way
            //PixColor = B*0x10000+G*0x100+R;
            PixColor = RGB(R,G,B);//Or we can use this function
            //Now we save a new "green" pixel value back to bitmap
            bitmapzor.SetPixel(i,j,PixColor);
        }

    //Now we need to save this result to a HD:
    LPCTSTR filename=_T("C:\\GetPixel.bmp");
    //you can change the location of this file

    bitmapzor.Save(filename);
    printf ("Done, file is saved to: %s\n Please press " 
            "any key for another method demo", filename);
    getchar();

    //Okay now another way with the pointer arithmetics:
    printf ("Pointer arithmetics demo (without GetPixel and SetPixel functions): \n");
    bitmapzor.Destroy(); //Unload the changed bitmap from memory
    bitmapzor.Load("C:\\1.bmp");
    //this is a pointer to the exact bitmap pixel color array location
    BYTE* byteptr = (BYTE*)bitmapzor.GetBits();

    //You can use other functions for HBITMAP and HGDIOBJ methods to find out the 
    //container for bitmap color table.
    //For instance when you use GDI and HBITMAPS, you should use
    //BYTE* byteptr = (BYTE*)(HBITMAPINFOHEADER + HBITMAPINFOHEADER->biSize);
    //simple pointer arithmetics

    int pitch = bitmapzor.GetPitch(); //This is a pointer offset to get new line of the bitmap

    for (int i=0; i<bitmapzor.GetWidth();i++)
        for (int j=0; j<bitmapzor.GetHeight();j++)
        {
            //pointer arithmetics to find (i,j) pixel colors:
            R= *(byteptr+pitch*j+3*i);
            G= *(byteptr+pitch*j+3*i+1);
            B= *(byteptr+pitch*j+3*i+2); 

            //allter pixel G color:
            G=(int)((float)G*1.3);
            if (G>255) G=255;
            //write down the new G-Color
            *(byteptr+pitch*j+3*i+1)=G;
        }

    //Save the bitmap:
    LPCTSTR filename2 = _T("C:\\ptrarithm.bmp"); //you can use any other file name
    bitmapzor.Save(filename2);
    printf ("Done, file is saved to: %s\n Please press any key to exit program", filename2);
    getchar();
    bitmapzor.Destroy(); //destroy the bitmap
    return 0;
}

Points of Interest

A normal bitmap is a WxH pixel table with a header. Each value in the pixel table is a 24 bit integer or 3xCHARs. To access these values, we need to know the starting point of the pointer and the offset. Normal bitmaps have a negative offset; e.g., if the pointer to some line of the color table is a byteptr, then the pointer to the next line is byteptr-3*W. But sometimes, bitmaps are upside-down oriented and the pointer to the next line is byteptr+3*W. Anyway, the offset negative or positive is always given by the GetPitch() method.

History

This is version 1.0.2 of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jan Kurbanaliev
Software Developer Stellar Intelligence
Kazakstan Kazakstan
I saw the atom spectrum splitting in labs. I touched irradiated rocks. I measured the mass and charge of electrons. I made Galileo's telescope, I've modeled the big bang, I saw sunspots, ships and aircrafts on satellite pictures. I touched stingers, patriots and S-300 missiles.
I have eventually understood computers, electronics, cryptography, programming and the Internet. Quite tough for a 25 yrs old guy, huh? That all is to be the first to discover something new within the next 50 years.

you can contact me through atommic@mail.ru mailbox
153428517 ICQ
jkurbanaliev skype

Comments and Discussions

 
GeneralGetBits usage Pin
Darryl Bryk5-Oct-10 12:22
MemberDarryl Bryk5-Oct-10 12:22 
GeneralMy vote of 2 Pin
Member 1657988-Jun-10 22:03
MemberMember 1657988-Jun-10 22:03 
GeneralRe: My vote of 2 Pin
Jan Kurbanaliev10-Jun-10 5:27
MemberJan Kurbanaliev10-Jun-10 5:27 
GeneralPixels Pin
geoyar8-Jun-10 14:21
Membergeoyar8-Jun-10 14:21 
GeneralRe: Pixels Pin
Jan Kurbanaliev10-Jun-10 5:27
MemberJan Kurbanaliev10-Jun-10 5:27 
GeneralRe: Pixels Pin
geoyar10-Jun-10 14:14
Membergeoyar10-Jun-10 14:14 
NewsI think you've probably missed the point. You've ignored the cpu caching Pin
gavery8-Jun-10 12:01
Membergavery8-Jun-10 12:01 
GeneralRe: I think you've probably missed the point. You've ignored the cpu caching Pin
Johann Gerell10-Jun-10 1:39
MemberJohann Gerell10-Jun-10 1:39 
GeneralRe: I think you've probably missed the point. You've ignored the cpu caching Pin
Jan Kurbanaliev10-Jun-10 5:24
MemberJan Kurbanaliev10-Jun-10 5:24 
GeneralRe: I think you've probably missed the point. You've ignored the cpu caching Pin
Jan Kurbanaliev10-Jun-10 5:31
MemberJan Kurbanaliev10-Jun-10 5:31 
GeneralHey guys in a week or so I shall modify teh article and get rid of bugs and make it looking more like an article so pls do not delete Pin
Jan Kurbanaliev8-Jun-10 3:30
MemberJan Kurbanaliev8-Jun-10 3:30 
QuestionHow fast? And what about compressed images? Pin
jsc428-Jun-10 1:15
professionaljsc428-Jun-10 1:15 
AnswerRe: How fast? And what about compressed images? Pin
Jan Kurbanaliev8-Jun-10 2:51
MemberJan Kurbanaliev8-Jun-10 2:51 
GeneralCompressed images? Pin
jsc428-Jun-10 3:37
professionaljsc428-Jun-10 3:37 
I think you would struggle with JPEG, but PNG and compressed BMP / DIB is doable. I don't use C++ so am unsure of what the CImage class gives you.

I started writing a JavaScript module for dynamic creation / editing of BMPs, but other tasks have pushed it down my stack of things to do so it is incomplete and not fully debugged. FWIW, here is the extract from it for handling BMPs, including compressed ones (you can get the BMP file structure and then convert that into C++ syntax):

//	-------------------------------------------------------------------------
//
//	Load a Windows Bitmap format image
function	Picture.prototype.fromBMP(text)
{
	// See Picture.prototype.toBMP() for layout details
	if	(text.length == 0)
		return	this.error.raise(Picture.E_FROMBMP_NOTEXT);
	else if	(	text.substring(0, 2) != 'BM' 
		||	text.toNumber4R(2) <= 54
		||	text.length != text.toNumber4R(2)
		)
		return	this.error.raise(Picture.E_FROMBMP_BADSIGNATURE);

	// Break the text into its components
	var	width	= text.toNumber4R(18);
	var	height	= text.toNumber4R(22);
	var	depth	= text.toNumber2R(28);	// Depth (1, 4, 8, or 24)
	var	compression	= text.charCodeAt(30);	// 0 = off, 1 = 8 bits RLE, 2 = 4 bits RLE
	var	imageMapLength	= text.toNumber4R(34);
	var	plen	= text.toNumber4R(46);	// No of colours in the palette (0 for depth 24)
	if	(plen == 0 && depth < 24)	// 0 implies full palette at this depth
		plen	= 1 << depth;

	var	plen4	= plen * 4;
	var	imageMap	= text.substring(54 + plen4);	// Compressed image

	// Palette
	var	palette	= new Array();
	for	(var i = 0; i < plen4; i += 4)
		palette[i >> 2]	= text.toNumber3R(54 + i);	// 4 byte field but only first 3 bytes are significant

	// Create the internal image elements
	this.canvas	= new Array();
	this.crop(1, width, 1, height);
	this.moveTo(1, 1);
	this.foregroundColour	= plen < 1 ? this.foregroundColour : palette[0]; 
	this.backgroundColour	= plen < 2 ? this.backgroundColour : palette[1];

	// Parse the image map
	switch	(depth)
	{
	case	1:	// Monochrome
		var	paddedWidth	= ((width + 31) >> 5) << 2;	// No of allocated bytes in each row
		for	(var row = 0; row < height; row++)
		{
			var	rowStartOffset	= row * paddedWidth;
			for	(var col = 0; col < width; col += 8)
			{
				var	pByte	= imageMap.charCodeAt(rowStartOffset + col / 8);
				for	(var i = 0; i < 8; i++)	// Bits within the byte
					if	((pByte & (1 << i)) == 0)	// Foreground pixel
						this.pixel(col + 8 - i, row + 1, this.foregroundColour);
			}	// for
		}	// for
		break;

	case	4:	// 16 colours, two palette indices per byte
		if	(compression)
		{
			var	row	= 1;
			var	col	= 1;
			var	ix	= 0;	// Index in the image map
			while	(ix < imageMap.length)
			{
				var	i	= imageMap.charCodeAt(ix++);
				if	(i == 00)	// Special value
				{
					i	= imageMap.charCodeAt(ix++);
					switch	(i)
					{
					case	00:	// 00 00 = end of line
						col	= 0;
						row++;
						break;

					case	01:	// 00 01 = end of bitmap
						ix	= imageMap.length;
						break;

					case	02:	// 00 02 xx yy = go to X+xx, Y+yy
						row	+= imageMap.charCodeAt(ix++);
						col	+= imageMap.charCodeAt(ix++);
						break;

					default:	// 00 nn <(nn+1)/2 bytes>
							// nn 1/2 word palette indices
							// (pad to word boundary with 00 if necessary)
						for	(var j = 0; j < i; j += 2)
						{
							var	pByte	= imageMap.charCodeAt(ix + j / 2);
							this.pixel(++col, row + 1, palette[pByte >> 4]);	// Left nibble
							if	(j + 1 < i)
								this.pixel(++col, row + 1, palette[pByte & 0xF]);	// Right nibble
						}	// for
						ix	+= (i + 1) > 1;
					}	// switch
				}	// if
				else	// nn ab = Repeat pixels with palette indices a and b for nn pixels
				{
					var	pByte	= imageMap.charCodeAt(ix++);
					var	a	= palette[pByte >> 4];	// Left nibble
					var	b	= palette[pByte & 0xF];	// Right nibble
					for	(var j = 0; j < i; j += 2)
					{
						this.pixel(++col, row + 1, a);
						if	(j + 1 < i)
							this.pixel(++col, row + 1, b);
					}	// for
				}	// else
			}	// while
		}	// if
		else	// Uncompressed
		{
			var	paddedWidth	= ((width + 7) >> 3) << 2;	// No of allocated bytes in each row
			for	(var row = 0; row < height; row++)
			{
				var	rowStartOffset	= row * paddedWidth;
				for	(var col = 0; col < width; col += 2)
				{
					var	pByte	= imageMap.charCodeAt(rowStartOffset + col / 2);
					this.pixel(col + 1, row + 1, palette[pByte >> 4]);	// Left nibble
					this.pixel(col + 2, row + 1, palette[pByte & 0xF]);	// Right nibble
				}	// for
			}	// for
		}	// else
		break;

	case	8:	// 256 colours, one palette index per byte
		if	(compression)
		{
			var	row	= 1;
			var	col	= 1;
			var	ix	= 0;	// Index in the image map
			while	(ix < imageMap.length)
			{
				var	i	= imageMap.charCodeAt(ix++);
				if	(i == 00)	// Special value
				{
					i	= imageMap.charCodeAt(ix++);
					switch	(i)
					{
					case	00:	// 00 00 = end of line
						col	= 0;
						row++;
						break;

					case	01:	// 00 01 = end of bitmap
						ix	= imageMap.length;
						break;

					case	02:	// 00 02 xx yy = go to X+xx, Y+yy
						row	+= imageMap.charCodeAt(ix++);
						col	+= imageMap.charCodeAt(ix++);
						break;

					default:	// 00 nn <nn bytes> = sequence (pad with 00 to word boundary)
						for	(var j = 0; j < i; j++)
							this.pixel(col++, row, palette[imageMap.charCodeAt(ix++)]);

						if	(i & 0x1)	// Padding needed?
							ix++;
					}	// switch
				}	// if
				else	// nn xx = repeat palette index xx for nn pixels
				{
					var	xx	= imageMap.charCodeAt(ix++);
					if	(xx)	// Background colour?
						for	(var j = 0; j < i; j++)
							this.pixel(col++, row, palette[xx]); 
					else	// It is the background colour
						col	+= i;
				}	// else
			}	// while
		}	// if
		else	// Uncompressed
		{
			var	paddedWidth	= (width + 3) & 0xFFFFFF8;	// Pad to 4-byte boundary
			for	(var row = 0; row < height; row++)
			{
				var	rowStartOffset	= row * paddedWidth;
				for	(var col = 0; col < width; col++)
					this.pixel(col + 1, row + 1, palette[imageMap.charCodeAt(rowStartOffset + col)]);
			}	// for
		}	// else

		break;

	case	24:	// 2**24 colours, one byte in 3 pixels (Green, Blue, Red)
		for	(var row = 0; row < height; row++)
		{
			var	rowStartOffset	= row * height * 3;
			for	(var col = 0; col <= width; col++)
				this.pixel(col + 1, row + 1, imageMap.toNumber3R(rowStartOffset + col * 3));
		}	// for
		break;

	default:
		this.error.raise(Picture.E_FROMBMP_UNSUPPORTED, width, height, depth, compression, plen, imageMap.length);
	}	// switch
}	// Picture.prototype.fromBMP
//
Picture.error(
	'E_FROMBMP_NOTEXT',
		'Cannot read BMP image: No data found', 
	'E_FROMBMP_BADSIGNATURE',
		'Cannot read BMP image: Image is not in a Windows Bitmap (BMP) format', 
	'E_FROMBMP_UNSUPPORTED',
		'Cannot read BMP image: BMP image format not supported: ' +
			'Width = %1, Height = %2, Depth = %3, Compression = %4, ' +
			'No of colours = %5, Image map size = %6 chars'
	); 
//
//	-------------------------------------------------------------------------
//
//	Convert the canvas to BMP (Windows Bitmap) format
function	Picture.prototype.toBMP()
{
	// Bitmaps can be
	//	Depth = 1	Max colours = 2
	//	Depth = 4	Max colours = 16	*** Not included here to speed up conversions ***
	//	Depth = 8	Max colours = 256
	//	Depth = 24	Max colours = 16777216

	// Compressions	(RLE-8 (for 256 colours) / RLE-4 (for 16 colours))
	//	00 00			Both	End of line
	//	00 01			Both	End of bitmap
	//	00 02 xx yy		Both	Go to X+xx, Y+yy (where X,Y is current location)
	//
	//	00 nn <nn bytes> 00	RLE-8	nn palette indices (pad to word boundary with 00 if necessary) 03 <= nn <= FF
	//	nn xx			RLE-8	Repeat palette index xx for nn pixels (nn > 0)
	//
	//	00 nn <(nn+1)/2 bytes>	RLE-4	nn 1/2 word palette indices (pad to word boundary with 00 if necessary). 03 <= nn <= FF
	//	nn ab			RLE-4	Repeat pixels with palette indices a and b for nn pixels (nn > 0)

	// Expand text to double word boundary
	var	pad	=
		new Function(
			"text",
			"return	text.length % 4 == 0 " +
				"? text " +
				": text + '\\0\\0\\0\\0'.substring(text.length % 4); "
			);

	// Create the palette
	this.palette	= new Array(this.foregroundColour, this.backgroundColour); 
	this.palette.maxLen	= 256;	// Extension property: Max no of entries permitted in the palette
	this.palette.findNearest	= false;	// Extension property: Select nearest matching colour if the palette is full?
	var	bgColour24	= this.backgroundColour.toBinary3R();	// Background colour as used in 24 bit depth bitmaps

	// Basic values
	var	c	= this.crop();	// Get dimensions
	var	imageMap24	= '';	// 2**24 colours
	var	imageMap8	= '';	// 256 colours
	var	imageMap1	= '';	// 2 colours
	var	emptyRowMap24	= pad(bgColour24.repeat(this.width));
	var	emptyRowMap8	= Picture.toBMP_RLE8_compressRepeated('\x01', this.width) + '\0\0';	// Not padded
	var	emptyRowMap1	= pad('\xFF'.repeat((this.width + 7) >> 3));	// Pad to 32 bits (long word)

	// Scan the canvas (Note: Scan is from bottom to top as bitmaps are 'upside down' relative to canvas coordinates)
	for	(var row = c.minY; row <= c.maxY; row++)
	{
		// Get row details
		var	crow	= this.canvas[row];
		var	plen	= this.palette.length;	// No of colours in the palette
		if	(crow && crow.minX <= c.maxX && crow.maxX >= c.minX)	// Row defined and has significant content
		{
			// Row is defined. Get length before first significant pixel / after last significant pixel
			var	firstPixel	= crow.minX < c.minX ? c.minX : crow.minX;
			var	lastPixel	= crow.maxX > c.maxX ? c.maxX : crow.maxX;
			var	gapLeft	= firstPixel - c.minX;
			var	gapRight	= c.maxX - lastPixel;

			if	(plen > 256)	// Only 24 bit bitmap wanted
			{
				var	rowMap24	= '';
				for	(var col = firstPixel; col <= lastPixel; col++)
					rowMap24	+= 
						typeof crow[col] == 'undefined' ? bgColour24 : crow[col].toBinary3R();

				imageMap24	+= 
					pad(
						(gapLeft ? bgColour24.repeat(gapLeft) : '') + 
						rowMap24 + 
						(gapRight ? bgColour24.repeat(gapRight) : '')
						);
			}	// if
			else	// 24, 8 and (possibly) 1 bit bitmap(s) wanted
			{
				var	rowMap24	= '';
				var	rowMap8	= '';
				var	rowMap1	= '';
				var	rowMap1BitNo	= 7 - (gapLeft & 0x7);	// Next bit to overwrite in rowMap1Byte (76543210)
				var	rowMap1Byte	= (0xFF << (rowMap1BitNo + 1)) & 0xFF;	// Set start bits to background pixel palette index (always 1)

				for	(var col = firstPixel; col <= lastPixel; col++)
				{
					var	pixel	= typeof crow[col] == 'undefined' ? this.backgroundColour : crow[col];
					rowMap24	+= pixel.toBinary3R();
					var	index	= this.paletteIndex(pixel);
					rowMap8	+= String.fromCharCode(index);
					if	(plen <= 2)	// Add to monochrome bitmap
						if	(rowMap1BitNo)
							rowMap1Byte	|= index << rowMap1BitNo--;
						else	// At end of byte
						{
							rowMap1	+= String.fromCharCode(rowMap1Byte + index);
							rowMap1Byte	= 0;
							rowMap1BitNo	= 7;
						}	// else
				}	// for

				// Build row from components and add to image maps
				imageMap24	+= 
					pad(
						(gapLeft ? bgColour24.repeat(gapLeft) : '') + 
						rowMap24 + 
						(gapRight ? bgColour24.repeat(gapRight) : '')
						);

				imageMap8	+= // No padding required. Background colour palette index is always 1
					(gapLeft ? Picture.toBMP_RLE8_compressRepeated('\x01', gapLeft) : '') + 
					Picture.toBMP_RLE8_compress(rowMap8) +
					(gapRight ? Picture.toBMP_RLE8_compressRepeated('\x01', gapRight) : '') +
					'\0\0';

				if	(plen <= 2)	// Monochrome bitmap
				{
					while	(gapRight)	// Add trailing background pixels
						if	(rowMap1BitNo)	// Background pixel palette index is always 1
						{
							rowMap1Byte	|= 0x01 << rowMap1BitNo--;
							gapRight--;
						}	// if
						else	// At end of byte
						{
							rowMap1	+= String.fromCharCode(rowMap1Byte + 0x01);
							gapRight--;
							rowMap1Byte	= 0;
							rowMap1BitNo	= 7;
							if	(gapRight >= 8)	// Add whole bytes of background pixel palette indices
							{
								rowMap1	+= '\xFF'.repeat(gapRight >> 3);
								gapRight	&= 0x7;
							}	// if
						}	// else

					imageMap1	+= // Background colour palette index is always 1
						pad(
							(gapLeft > 7 ? '\xFF'.repeat(gapLeft >> 3) : '') + 
							rowMap1 + 
							(rowMap1BitNo == 7 ? '' : String.fromCharCode(rowMap1Byte))
							);
				}	// if
			}	// else
		}	// if
		else	// Undefined row
		{
			imageMap24	+= emptyRowMap24;
			if	(plen <= 256)
			{
				imageMap8	+= emptyRowMap8;
				if	(plen <= 2)
					imageMap1	+= emptyRowMap1;
			}	// if
		}	// else
	}	// for

	// Get RGBQUADs for the paletted colours
	var	paletteMap	= ''; 
	var	plen	= this.palette.length; 
	var	depth	= plen > 256 ? 24 : (plen > 2 ? 8 : 1);
	if	(plen <= 256)
	{
		imageMap24	= depth == 8 ? imageMap8 : imageMap1;
		for	(var i = 0; i < plen; i++)
			paletteMap	+= (this.palette[i] & 0x00FFFFFF).toBinary4R(); 
	}	// if

	// Build components into the BMP file layout
	// Win3DIBFile = BitmapFileHeader + BitmapInfoHeader + Win3ColorTable + Image
	return	(
		// BitmapFileHeader (14 bytes)
		'BM' +	// Type
		(14 + 40 + paletteMap.length + imageMap24.length).toBinary4R() +	// Size
		'\0\0\0\0' +	// Reserved1, Reserved2
		(14 + 40 + paletteMap.length).toBinary4R() +	// OffsetBits
		// BitmapInfoHeader (40 bytes)
		'\50\0\0\0' +	// Size (40)
		this.width.toBinary4R() + 
		this.height.toBinary4R() + 
		"\1\0" +	// Planes (1) +
		depth.toBinary2R() +	// BitCount
		(depth == 8 ? '\1\0\0\0' : '\0\0\0\0') +	// Compression (0 = off, 1 = 8 bits RLE, 2 = 4 bits RLE)
		imageMap24.length.toBinary4R() +	// SizeImage
		'\0\0\0\0' +	// XPelsPerMeter
		'\0\0\0\0' +	// YPelsPerMeter
		(paletteMap.length / 4).toBinary4R() +	// ColorsUsed (0 for 24 bit)
		'\0\0\0\0' +	// (paletteMap.length / 4).toBinary4R() +	// ColorsImportant (0 = all)
		// Win3ColorTable
		paletteMap +
		// Image
		imageMap24
		);
}	// Picture.prototype.toBMP
//
//	-------------------------------------------------------------------------
//
//	Support functions for Picture.prototype.toBMP
//
//	- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
//	Compress an 8-bit BMP pixel stream
/* private */ function	Picture.toBMP_RLE8_compress(text)
{
	var	result	= '';
	if	(text)	// Ignore empty stream
	{
		// Split into segments and repetitions
		var	prevch	= text.charAt(0);
		var	repeatStart	= 0; 
		var	segStart	= 0;
		var	tLen	= text.length;
		for	(var i = 1; i < tLen; i++)
		{
			var	ch	= text.charAt(i);
			if	(ch != prevch)
			{
				if	(i > repeatStart + 2)	// End of a repeated section?
				{
					result	+= 
						(	segStart == repeatStart 
						?	''
						:	Picture.toBMP_RLE8_compressSegment(text.substring(segStart, repeatStart))
						) + 
						Picture.toBMP_RLE8_compressRepeated(prevch, i - repeatStart); 
					segStart	= i;
				}	//if

				// Start a new repetition block
				prevch	= ch;
				repeatStart	= i;
			}	// if
		}	// for

		// Output tail end
		if	(tLen > repeatStart + 2)	// End of a repeated section?
			result	+= 
				(	segStart == repeatStart 
				?	''
				:	Picture.toBMP_RLE8_compressSegment(text.substring(segStart, repeatStart))
				) + 
				Picture.toBMP_RLE8_compressRepeated(prevch, tLen - repeatStart);
			else	// Not in a repetition
				result	+= Picture.toBMP_RLE8_compressSegment(text.substring(segStart)); 
	}	// if

	return	result;
}	// Picture.toBMP_RLE8_compress
//
//	- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
//	Compress a repeated pixel in an 8-bit BMP pixel stream
/* private */ function	Picture.toBMP_RLE8_compressRepeated(pixel, count)
{
	// Note: Pixel is palette index as a character
	// Max of 255 for repetition count
	var result = ''; 
	while	(count >= 255)
	{
		result	+= '\xFF' + pixel;
		count	-= 255;
	}	// while

	// Add any tail
	return	result + (count ? String.fromCharCode(count) + pixel : '');
}	// Picture.toBMP_RLE8_compressRepeated
//
//	- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
//	Output a segment of palette indices in an 8-bit BMP pixel stream
/* private */ function	Picture.toBMP_RLE8_compressSegment(text)
{
	// Max of 255 bytes needs extra padding byte, so only use 254 bytes (no padding needed)
	var	result	= '';
	while	(text.length > 254)
	{
		result	+= '\x00\0xFE' + text.substring(0, 254);
		text	= text.substring(254);
	}	// while

	// Add tail
	switch	(text.length)
	{
	case	0:	// Nothing left to do
		break;

	case	1:	// One pixel left
		result	+= '\x01' + text;	// One pixel, repeated once
		break;

	case	2:	// Two pixels left
		result	+= '\x01' + text.charAt(0) + '\x01' + text.charAt(1);	// 2 * (one pixel, repeated once)
		break;

	default:	// 3 to 254 pixels (Pad if odd no of bytes)
		result	+= '\x00' + String.fromCharCode(text.length) + text + (text.length & 0x1 ? '\x00' : '');
	}	// switch

	return	result;
}	// Picture.toBMP_RLE8_compressSegment
//
//	-------------------------------------------------------------------------


//	- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
//
//	Convert a binary string to a number (LSB, ..., MSB)
String.prototype.toNumber2R	= 
	new Function(
		"optStartOffset",	// Optional start location. Default: 0. Saves having to get substrings
		"var	i = optStartOffset ? optStartOffset : 0; " +
		"return	(this.charCodeAt(i + 1) << 8) | this.charCodeAt(i); "
		);
//
String.prototype.toNumber3R	= 
	new Function(
		"optStartOffset",	// Optional start location. Default: 0. Saves having to get substrings
		"var	i = optStartOffset ? optStartOffset : 0; " +
		"return	(this.charCodeAt(i + 2) << 16) | (this.charCodeAt(i + 1) << 8) | this.charCodeAt(i); "
		);
//
String.prototype.toNumber4R	= 
	new Function(
		"optStartOffset",	// Optional start location. Default: 0. Saves having to get substrings
		"var	i = optStartOffset ? optStartOffset : 0; " +
		"return	(this.charCodeAt(i + 3) << 24) | (this.charCodeAt(i + 2) << 16) | (this.charCodeAt(i + 1) << 8) | this.charCodeAt(i); "
		);
//
//	- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
//
//	Convert a number to a binary string (LSB, ..., MSB)
Number.prototype.toBinary2R	= new Function("return	String.fromCharCode(this & 0xFF, (this & 0xFF00) >> 8); ");
Number.prototype.toBinary3R	= new Function("return	String.fromCharCode(this & 0xFF, (this & 0xFF00) >> 8, (this & 0xFF0000) >> 16); ");
Number.prototype.toBinary4R	= new Function("return	String.fromCharCode(this & 0xFF, (this & 0xFF00) >> 8, (this & 0xFF0000) >> 16, (this & 0xFF000000) >>> 24); ");
//

GeneralRe: Compressed images? Pin
Jan Kurbanaliev8-Jun-10 6:26
MemberJan Kurbanaliev8-Jun-10 6:26 
QuestionWhat's with all the negativity? Pin
S.H.Bouwhuis7-Jun-10 22:04
MemberS.H.Bouwhuis7-Jun-10 22:04 
AnswerRe: What's with all the negativity? Pin
Jan Kurbanaliev7-Jun-10 22:33
MemberJan Kurbanaliev7-Jun-10 22:33 
GeneralYou are doing it wrong Pin
mluri7-Jun-10 1:28
Membermluri7-Jun-10 1:28 
GeneralRe: You are doing it wrong [modified] Pin
Jan Kurbanaliev7-Jun-10 22:31
MemberJan Kurbanaliev7-Jun-10 22:31 
GeneralBout that stuff [modified] Pin
Jan Kurbanaliev5-Jun-10 3:07
MemberJan Kurbanaliev5-Jun-10 3:07 
GeneralNot quite an article Pin
Dave Kreskowiak5-Jun-10 2:25
mveDave Kreskowiak5-Jun-10 2:25 
GeneralRe: Not quite an article Pin
Jan Kurbanaliev8-Jun-10 6:34
MemberJan Kurbanaliev8-Jun-10 6:34 
GeneralProblems Pin
#realJSOP5-Jun-10 1:14
mva#realJSOP5-Jun-10 1:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 4 Jun 2010

Tagged as

Stats

64.8K views
24 bookmarked