Sign in to follow this  
JDUK

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

Recommended Posts

JDUK    100
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 :)

Share this post


Link to post
Share on other sites
directrix    181
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.

Share this post


Link to post
Share on other sites
JDUK    100
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).

Share this post


Link to post
Share on other sites
Talonius    643
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
// USA

using 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.

Share this post


Link to post
Share on other sites
Nik02    4348
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.

Share this post


Link to post
Share on other sites
JDUK    100
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 :)

Share this post


Link to post
Share on other sites
Nik02    4348
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!

Share this post


Link to post
Share on other sites
JDUK    100
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.

Share this post


Link to post
Share on other sites
directrix    181
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.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this