(C#) Whats the best way to scan an image pixel by pixel?

Started by
8 comments, last by directrix 19 years, 4 months ago
Im planning a 2D game in C# using Managaed DirectX, Direct Draw. for my collision detection i plan to check if any of the onscreen entities clash with the bounding box of the player sprite then Working out collisions by checking if any of the non-transparent pixels on screen position from the player clash with any of the pixels in the entities that breach the players bounding box. SO whats the best picture format to use and the best way to check it? from a little previous experiance ive see bitmaps need to use pointers and unsafe mode and .RAW files can be read like binary files. Any C# programmers able to clue me into the best image format and process to use to read it pixel by pixel. Thanks :)
Advertisement
I assume you're going to be working in 32bpp and storing the transparency data for your sprites in the alpha channel. So the best image format would be one which supports an alpha channel, such as TGA, where you can store the transparency data for the sprite.

You should create an additional buffer for every sprite that stores a copy of the alpha channel (do this while loading the sprite). You don't want to lock the directdraw surface to read the alpha channel, this is very slow.

Then, to do the collision checks,

(1) do the bounding box test
(2) if the bounding box test passes, loop through the alpha channel data (from the buffer you created, not the directdraw surface data) for both sprites involved in the possible collision.
(3) if there is an alpha value from both sprites, at the same location, that is not transparent (i.e. alpha!=0 for the same pixel for both sprites) then there's a collision.

edit: btw, I've read in from a TGA to a direct3d textures in c# and, if I remember correcly, didn't have to use pointers or unsafe mode. So I don't think it'll be too different with directdraw surfaces or any other type of image format. Read in the pixels, lock the surface, copy the pixels onto the surface, unlock the surface.
From what i have read Direct Draw (as opposed to Direct3D) doesnt support alpha channels and you perform transparency by just keying one colour in the pallete to be transparent.

So that way i just chech for the presence(or lack) of that colour to seperate transparency from a collidable pixel.

Though imay be wrong about the alpha channel suuport in DD (it seems odd that it doesnt support it).

You can use managed (safe) code to load graphics. The largest problem is that most of the routines are slow - without a pointer to the memory you end up doing a Read/Write for each pixel.

You could probably create a MemoryStream and read it into that, then use the pre-existing routines to create a graphic from that. I wonder why I hadn't thought of that before...

Here's the code I have for loading a TGA into .NET. The file type isn't supported natively by GDI -- I don't know whether Managed DirectX supports it.

// TargaSupport assembly (C) 2003 Brian M. Schkerke// http://www.schkerke.com//// This work is licensed under the Creative Commons Attribution License. // To view a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ // or send a letter to //		Creative Commons//		559 Nathan Abbott Way//		Stanford, California 94305//		USAusing System;using System.IO;using System.Drawing;using System.Drawing.Imaging;using System.Windows.Forms;using System.Collections;using System.Diagnostics;using System.Collections.Specialized;namespace TargaSupport{	public class Targa	{		private	uint	targaImageSize;							// Computed size of file.		private	uint	identificationFieldLength;				// Byte 0 in file.		private	uint	colorMapType;							// Byte 1 in file.		private	uint	imageTypeCode;							// Byte 2 in file.  Where the file format gets its name.		private	uint	colorMapOrigin;							// Byte 3 and 4 in file.  Ignored if colorMapType == 0.		private uint	colorMapLength;							// Byte 5 and 6 in file.  Ignored if colorMapType == 0.		private	uint	colorMapEntrySize;						// Byte 7 in file.  Ignored if colorMapType == 0.		private	uint	imageXOrigin;							// Byte 8 and 9 in file.		private	uint	imageYOrigin;							// Byte 10 and 11 in file.		private	uint	targaWidth;								// Byte 12 and 13 in file.		private uint	targaHeight;							// Byte 14 and 15 in file.		private	uint	targaBpp;								// Byte 16 in file.		private	uint	imageDescriptorByte;					// Byte 17 in file.		private	uint	imageAttributeBits;						// Bits 0 to 3 of imageDescriptorByte.  16 = 0 or 1.  24 = 0.  32 = 8.		private	uint	imageReservedBit;						// Bit 4 of imageDescriptionByte.  Must be set to 0.		private	uint	imageScreenOrigin;						// Bit 5 of imageDescriptionByte.  0 = lower left hand corner, 1 = upper left hand corner.		private	uint	imageInterleaving;						// Bit 6 and 7 of imageDescriptionByte.  00 = non interleaved.  01 = two way interleaving.																// 10 = four way interleaving.  11 = reserved.		private	byte[]	imageIdentificationField;				// Length is determined by identificationFieldLength.  Usually 0, and omitted.		private	byte[]	imageColorMapData;						// Length is determined by colorMapLength.  If colorMapType == 0 this is omitted.				private TgaByte[] tgaBytes;								// Storage of bytes.		private	string	filename;		public Targa()		{		}		public Targa( string passedFilename )		{			filename = passedFilename;			Load();		}		public void Load()		{			FileStream fileStream = new FileStream( filename, FileMode.Open );			BinaryReader binaryReader = new BinaryReader( fileStream );			// Read the header of the file.  This gives us the information we need to consume the rest of the file properly.			ReadHeader( binaryReader );						// Load the image into memory.			ReadData( binaryReader );			binaryReader.Close();		}		public Bitmap ToBitmap()		{			Bitmap bitmap = new Bitmap( (int) targaWidth, (int) targaHeight );			int curByteIndex = 0;			for( int counterHeight = 0; counterHeight < targaHeight; counterHeight++ )			{				for( int counterWidth = 0; counterWidth < targaWidth; counterWidth++ )				{					bitmap.SetPixel					( 						counterWidth, 						counterHeight, 						Color.FromArgb						( 							(int) tgaBytes[ curByteIndex ].Alpha,							(int) tgaBytes[ curByteIndex ].Red, 							(int) tgaBytes[ curByteIndex ].Green, 							(int) tgaBytes[ curByteIndex++ ].Blue						) 					);				}			}			bitmap.RotateFlip( RotateFlipType.Rotate180FlipNone );			return bitmap;		}		private void ReadData( BinaryReader binaryReader )		{			if( imageTypeCode == 2 )				ReadType2Data( binaryReader );			if( imageTypeCode != 2 )				throw new NotImplementedException( "Only type 2 TGA files are currently supported." );			// TODO:  Implement type 1, 9, and 10.		}		private void ReadType2Data( BinaryReader binaryReader )		{			tgaBytes = new TgaByte[ targaWidth * targaHeight ];						for( int imageLength = 0; imageLength < targaImageSize; imageLength++ )			{				tgaBytes[imageLength] = new TgaByte();				tgaBytes[imageLength].Blue = binaryReader.ReadByte();				tgaBytes[imageLength].Green = binaryReader.ReadByte();				tgaBytes[imageLength].Red = binaryReader.ReadByte();				if( targaBpp == 32 )					tgaBytes[imageLength].Alpha = binaryReader.ReadByte();			}		}		private void ReadHeader( BinaryReader binaryReader )		{			identificationFieldLength = binaryReader.ReadByte();			colorMapType		= binaryReader.ReadByte();			imageTypeCode		= binaryReader.ReadByte();			colorMapOrigin		= binaryReader.ReadUInt16();			colorMapLength		= binaryReader.ReadUInt16();			colorMapEntrySize	= binaryReader.ReadByte();			imageXOrigin		= binaryReader.ReadUInt16();			imageYOrigin		= binaryReader.ReadUInt16();			targaWidth			= binaryReader.ReadUInt16();			targaHeight			= binaryReader.ReadUInt16();			targaBpp			= binaryReader.ReadByte();			imageDescriptorByte = binaryReader.ReadByte();			BitVector32 bitVector = new BitVector32( (int) imageDescriptorByte );			BitVector32.Section imageAttributeBitsSection	= BitVector32.CreateSection( 8 );			BitVector32.Section imageReservedBitSection		= BitVector32.CreateSection( 1, imageAttributeBitsSection );			BitVector32.Section imageScreenOriginSection	= BitVector32.CreateSection( 1, imageReservedBitSection );			BitVector32.Section imageInterleavingSection	= BitVector32.CreateSection( 2, imageScreenOriginSection );			imageAttributeBits	= (uint) bitVector[imageAttributeBitsSection];			imageReservedBit	= (uint) bitVector[imageReservedBitSection];			imageScreenOrigin	= (uint) bitVector[imageScreenOriginSection];			imageInterleaving	= (uint) bitVector[imageInterleavingSection];			if( identificationFieldLength > 0 )				imageIdentificationField = binaryReader.ReadBytes( (int) identificationFieldLength );			if( colorMapType != 0 )				ReadColorMap( binaryReader );			targaImageSize		= targaWidth * targaHeight;		}		private void ReadColorMap( BinaryReader binaryReader )		{			// TODO:  Examine color map specification and read in here.			/*			 * | varies | varies |  Color map data.                                           |			 * |        |        |                                                            |			 * |        |        |  If the Color Map Type is 0, this field doesn't exist.     |			 * |        |        |  Otherwise, just read past it to get to the image.         |			 * |        |        |  The Color Map Specification, describes the size of each   |			 * |        |        |  entry, and the number of entries you'll have to skip.     |			 * |        |        |  Each color map entry is 2, 3, or 4 bytes.                 |			 */			// How, where, why do I store?  No references to this in the rest of the file.			// This is ignored in "unmapped" targas, i.e type 2 tga format.		}	}	class TgaByte	{		private	uint	red		=	0;		private	uint	blue	=	0;		private	uint	green	=	0;		private	uint	alpha	=	0;		public TgaByte()		{		}		public uint Red		{				get	{	return red;		}			set	{	red = value;	}		}		public uint Blue		{			get	{	return blue;	}			set	{	blue = value;	}		}		public uint Green		{			get	{	return green;	}			set	{	green = value;	}		}		public uint Alpha		{	get	{	return alpha;	}			set	{	alpha = value;	}		}	}}


There are *many* improvements possible. I used the above for a utility where speed wasn't an issue.
..what we do will echo throughout eternity..
You could store a partition tree of the collidable pixels - that is, divide the collidable area into tiles, say for example 32x32 - and test whether the collider is in one of these tiles first. When you've found the tile that the collider is in, only then perform the per-pixel collision test, inner loop of which is now reduced to 32x32 (again, for example) pixels.

A vast number of 2d games use this particular approach; furthermore, even most 3d games use the fundamentally same concept of binary/quad/octa-trees for space partitioning to save computation time.

Niko Suni

Thanks Talonius thats the sort of stuff im after :)

Nik02 thats pretty much what im doing the player sprites bounding area (its tile x tile area) is checked each frame for anything on the sprite list (except its self) that has an on screen position that breaches that area (and checks what direction is comes from)and adds it to the collision list.
Then it loops through each enemy sprite in that list and lists it's (Non-transparent) pixel cords. then checks every pixel in that list against each (Non-transparent) pixel in the player.
Its a low res (800x600) game with small sprites so i didnt really find the need to carve player and enemy locations within thier bounding area into trees.
Though in a 2D beat-em up (a futre project im looking at) i can see this being useful so thx for the idea :)
Consider the "tree" in this context as just a 2d array of 2d arrays - after all, it's not actually much else [smile]
The tree structure is most efficient when representing backgrounds; the characters are just fine as separate entities, unless they are very complex.

Good luck with your project!

Niko Suni

Nik2 do you mean a "tile engine" ?
if so im way ahead of ya ;) got that part sorted for my level
I use a grid of level tile images and a 2d array(levelWidth/tileSize x levelHeight/tileSize) to reprisent the level each element holing the info on the tile to be displayed at that point rendering only those tiles that fall within the screen space.
I do the level collision with the tile engine... its was just the sprite on sprite stuff i needed pixel perfect collision ignoring trasnparent squares.
Image manipulation reads loads of pixels and changes them.

This guy at code project has done decent tutorials on the subject:



http://www.codeproject.com/cs/media/displacementfilters.asp
Anything posted is personal opinion which does not in anyway reflect or represent my employer. Any code and opinion is expressed “as is” and used at your own risk – it does not constitute a legal relationship of any kind.
Quote:Original post by JDUK
From what i have read Direct Draw (as opposed to Direct3D) doesnt support alpha channels and you perform transparency by just keying one colour in the pallete to be transparent.

So that way i just chech for the presence(or lack) of that colour to seperate transparency from a collidable pixel.

Though imay be wrong about the alpha channel suuport in DD (it seems odd that it doesnt support it).


Ah, yes, your right, there is no alpha blending support in directdraw only color keying. I forgot about that, I haven't used directdraw in a long time. So, basically, any image format that supports 24 bpp will work fine for what your trying to do. TGA is probably one of the better ones since it supports RLE compression.

This topic is closed to new replies.

Advertisement