SDL Colour formats ahhhh!

Started by
4 comments, last by rip-off 14 years, 9 months ago
I've been creating a destructible 2D object test today using SDL and I've finally got it all working but I'm still confused about certain aspects of SDL surfaces. I have a 32 bit 800x600 display surface and three 24 bit surfaces. One of these is loaded with pixel data from a .BMP file and the other two I created using SDL_CreateRGBSurface(). I've used SDL_DisplayFormat() on the 24 bit surfaces in order to get them compatible with the display. When I manipulate pixels in the 24 bit surfaces I have to provide a 4 byte colour value otherwise I get strange artefacts when I blit them to the display. However when I used, for example, SDL_FillRect on the display surface directly I have to use only a 3 byte colour, without an alpha component, otherwise the colours go strange. Why is it that a 24 bit surface requires 4 byte colour values when a 32 bit surface does not? Here is the code I'm referring to here. It's messy, hacky and slow but I'm not interested in any of that right now (I just wanted to prove to myself that I could do it more than anything)

#include <SDL.h>
#include <SDL_draw.h>
#include <SDL_gfxPrimitives.h>
#include <vector>

int main(int argc, char *argv[])
{
	SDL_Init(SDL_INIT_VIDEO);

	SDL_Surface* display = SDL_SetVideoMode(800, 600, 32, 0);

	SDL_Surface* temp = SDL_LoadBMP("C:\\Documents and Settings\\Dominic\\My Documents\\Games Development\\Projects\\Destructable Test\\Test\\Debug\\Fort.bmp");
	SDL_Surface* fortImage = SDL_DisplayFormat(temp);
	SDL_FreeSurface(temp);
	temp = SDL_CreateRGBSurface(0, fortImage->w, fortImage->h, 24, 0xFF0000, 0x00FF00, 0x0000FF, 0);
	SDL_Surface* composite = SDL_DisplayFormat(temp);
	SDL_FreeSurface(temp);
	temp = SDL_CreateRGBSurface(0, fortImage->w, fortImage->h, 24, 0xFF0000, 0x00FF00, 0x0000FF, 0);
	SDL_Surface* damageImage = SDL_DisplayFormat(temp);	
	SDL_FreeSurface(temp);

	struct bullet
	{
		unsigned int x, y, speed;
	};

	std::vector<bullet> bullets;
		
	// Fill damage image with white
	SDL_Rect fillRect;
	fillRect.w = damageImage->w;
	fillRect.h = damageImage->h;
	fillRect.x = 0;
	fillRect.y = 0;

	SDL_FillRect(damageImage, &fillRect, 0x000000);

	bool quit = false;
	while(!quit)
	{
		SDL_Event anEvent;

		//Get events
		if (SDL_PollEvent(&anEvent))
		{
			switch (anEvent.type)
			{
			case SDL_QUIT:
				{
					quit = true;
					break;
				}
			case SDL_KEYDOWN:
				{
					switch (anEvent.key.keysym.sym)
					{
					case SDLK_ESCAPE:
						{
							quit = true;
							break;
						}
					}
					break;
				}
			case SDL_MOUSEBUTTONDOWN:
				{
					//if(anEvent.button.x <= 256 && anEvent.button.y <= 512)
					//{
						// Create a circle in the damage image where mouse is					
						/*SDL_Rect damageRect;
						damageRect.w = 50;
						damageRect.h = 50;
						damageRect.x = anEvent.button.x - 25;
						damageRect.y = anEvent.button.y - 25;

						SDL_FillRect(damageImage, &damageRect, 0xFFFFFF);
						Sint16 x = (Sint16)anEvent.button.x;
						Sint16 y = (Sint16)anEvent.button.y;
						Draw_FillCircle(damageImage, x, y, 25, 0xFFFFFFFF);*/
						//filledCircleColor(damageImage, anEvent.button.x, anEvent.button.y, 25, 0xFFFFFFFF);
					//}

					// Launch a projectile
					bullet newBullet;
					newBullet.x = anEvent.button.x;
					newBullet.y = anEvent.button.y;
					newBullet.speed = 5;
					bullets.push_back(newBullet);

					break;
				}
			}
		}	

		// Fill screen with white
		SDL_Rect fillRect;
		fillRect.w = 800;
		fillRect.h = 600;
		fillRect.x = 0;
		fillRect.y = 0;

		SDL_FillRect(display, &fillRect, 0xFFFFFF);

		// Use the pixel values in the damage map to remove pixels in the fort image		

		SDL_LockSurface(fortImage);
		SDL_LockSurface(damageImage);
		SDL_LockSurface(composite);

		for(int x = 0; x < composite->w; x++)
		{
			for(int y = 0; y < composite->h; y++)
			{
				unsigned int* pixelIndex = 0;
				unsigned int damagePixel = 0;
				unsigned char r, g, b;
				SDL_PixelFormat* fortFormat = fortImage->format;
				SDL_PixelFormat* damageFormat = damageImage->format;
				SDL_PixelFormat* compositeFormat = composite->format;

				pixelIndex = (unsigned int*)damageImage->pixels;
				damagePixel = pixelIndex[(y * (damageImage->pitch / 4)) + x];

				r = damagePixel & fortFormat->Rmask;
				r = damagePixel >> fortFormat->Rshift;
				r = damagePixel << fortFormat->Rloss;

				g = damagePixel & fortFormat->Gmask;
				g = damagePixel >> fortFormat->Gshift;
				g = damagePixel << fortFormat->Gloss;

				b = damagePixel & fortFormat->Bmask;
				b = damagePixel >> fortFormat->Bshift;
				b = damagePixel << fortFormat->Bloss;

				if(r == 0xFF && g == 0xFF && b == 0xFF)
				{
					pixelIndex = (unsigned int*)composite->pixels;	
					unsigned int* pixelIndex2 = (unsigned int*)damageImage->pixels;
					pixelIndex[(y * (composite->pitch / 4)) + x] = pixelIndex2[(y * (damageImage->pitch / 4)) + x];
					composite->pixels = (void *)pixelIndex;
				}
				else
				{					
					pixelIndex = (unsigned int*)composite->pixels;	
					unsigned int* pixelIndex2 = (unsigned int*)fortImage->pixels;
					pixelIndex[(y * (composite->pitch / 4)) + x] = pixelIndex2[(y * (fortImage->pitch / 4)) + x];
					composite->pixels = (void *)pixelIndex;
				}
			}
		}

		
		SDL_UnlockSurface(fortImage);
		SDL_UnlockSurface(damageImage);
		SDL_UnlockSurface(composite);

		// Draw the final fort image
		SDL_BlitSurface(composite, NULL, display, NULL);

		//DEBUG draw damage image
		SDL_Rect testRect;
		testRect.w = 256;
		testRect.h = 512;
		testRect.x = 300;
		testRect.y = 0;
		SDL_BlitSurface(damageImage, NULL, display, &testRect);

		// Update and draw all bullets		
		for(int i = 0; i < bullets.size(); i++)
		{
			bullets.x -= bullets.speed;

			SDL_Rect bulletRect;
			bulletRect.w = 40;
			bulletRect.h = 20;
			bulletRect.x = bullets.x;
			bulletRect.y = bullets.y;

			SDL_FillRect(display, &bulletRect, 0xFF0000);

			if(bullets.x <= 256)
			{
				SDL_LockSurface(damageImage);

				unsigned int* pixelIndex = 0;
				unsigned int damagePixel = 0;
				unsigned char r, g, b;		
				SDL_PixelFormat* damageFormat = damageImage->format;	
				SDL_PixelFormat* fortFormat = fortImage->format;

				pixelIndex = (unsigned int*)damageImage->pixels;
				damagePixel = pixelIndex[((bullets.y - 1) * (damageImage->pitch / 4)) + (bullets.x - 1)];

				r = damagePixel & fortFormat->Rmask;
				r = damagePixel >> fortFormat->Rshift;
				r = damagePixel << fortFormat->Rloss;

				g = damagePixel & fortFormat->Gmask;
				g = damagePixel >> fortFormat->Gshift;
				g = damagePixel << fortFormat->Gloss;

				b = damagePixel & fortFormat->Bmask;
				b = damagePixel >> fortFormat->Bshift;
				b = damagePixel << fortFormat->Bloss;

				if(r == 0x00 && g == 0x00 && b == 0x00)
				{
					filledCircleColor(damageImage, (bullets.x - 1), (bullets.y - 1), 25, 0xFFFFFFFF);
					bullets.erase(bullets.begin() + i);
				}

				SDL_UnlockSurface(damageImage);
			}		
		}
		
		SDL_UpdateRect(display, 0, 0, 800, 600);		
	}

	SDL_FreeSurface(damageImage);
	SDL_FreeSurface(fortImage);
	SDL_FreeSurface(composite);
	SDL_Quit();

	return 0;
}

Oh and also I don't understand the shift: b = damagePixel << fortFormat->Bloss; When retrieving colour values from a number. Cheers.
It's not a bug... it's a feature!
Advertisement
I'll start with the "loss" pixel format parameters. The pixel format is divided into mask, shift and loss. The mask is used to isolate that channel. The loss is when that pixel fragment has a degree of loss. Consider a 16 bit RGB surface. Each channel can have 5 bits of data, and one gets a extra bit.

For the sake of argument, lets say that green gets the extra bit. We manage to isolate a 16 bit value containing this pixel value.

Now, we usually think of channels as having 8 bits of precision. So when SDL encodes such a value into less than 8 bits, you are going to have some loss. This loss is effectively dropping a few of the least significant bits, or a right shift.

You "undo" this effect by left shifting (which fills the "lost" data with zeros).

Other that, I am not 100% sure what you are referring to here:
Quote:
When I manipulate pixels in the 24 bit surfaces I have to provide a 4 byte colour value otherwise I get strange artefacts when I blit them to the display. However when I used, for example, SDL_FillRect on the display surface directly I have to use only a 3 byte colour, without an alpha component, otherwise the colours go strange.

Why is it that a 24 bit surface requires 4 byte colour values when a 32 bit surface does not?

Firstly, when you called SDL_DisplayFormat() on the surface, this changes its format. So you probably have four 32 bit surfaces. You can check this by printing the value of surface->format->BitsPerPixel after the conversion.

You are supposed to use SDL_MapRGB() when passing pixels to SDL_FillRect().

I would recommend using these functions for accessing individual pixels in a surface
Quote:Original post by rip-off
Firstly, when you called SDL_DisplayFormat() on the surface, this changes its format. So you probably have four 32 bit surfaces. You can check this by printing the value of surface->format->BitsPerPixel after the conversion.

You are supposed to use SDL_MapRGB() when passing pixels to SDL_FillRect().

I would recommend using these functions for accessing individual pixels in a surface


I didn't realise I had to use SDL_MapRGB() so that clears that up. Thanks!

I still don't really understand what you mean "loss". Am I right in thinking that not all channels may experience loss? For example in the 16 bit surface you talked about would the green channel be the only one that experiences loss?

"Now, we usually think of channels as having 8 bits of precision. So when SDL encodes such a value into less than 8 bits, you are going to have some loss. This loss is effectively dropping a few of the least significant bits, or a right shift."

I don't really understand what you mean here. Sorry. Why would SDL be encoding an 8 bit value into something smaller than 8 bits and why would that effect my retrieval of said values?

It's not a bug... it's a feature!
Quote:
I still don't really understand what you mean "loss". Am I right in thinking that not all channels may experience loss?

I will try an explain. Let us consider 8 bits per channel perfect colour (it isn't, but whatever). For a RGB surface, you need 24 bits for perfect colour. For RGBA, you need 32 bits. 16 or 8 bit surface aren't perfect, there is some loss at these bit depths.

Quote: For example in the 16 bit surface you talked about would the green channel be the only one that experiences loss?

No, they all experience loss. Green loses 2 bits, having 6 out of 8 bits of precision. Red and Blue lose 3 bits, being only 5/8 precision.

Quote:
Why would SDL be encoding an 8 bit value into something smaller than 8 bits and why would that effect my retrieval of said values?

The 8 bit value is the nominal value of the colour channel in a pixel. When we write a pixel to a surface, we are either writing three or four 8 bit channels.

You probably won't have to deal with loss. You are talking about 24 or 32 bit surfaces, so your RGB channels will be complete and your alpha channel likely the only one to take a hit (fun fact, my "screen" surfaces are 32 bits, but with 8 alpha loss).

Its mainly to be cross platform, because there are lots of systems out there with odd graphics formats.
OK So loss only occurs when, say, an 8 bit channel has been packed into 5 bits?
The 3 least significant bits are lost. So when we try to read the new 5 bit number back into 8 bits we need to adjust for this loss of precision by shifting everything back up.

If a colour channel was originally stored as a 5 bit number and was moved to an 8 bit storage then no shifting would be required?
It's not a bug... it's a feature!
Quote:
If a colour channel was originally stored as a 5 bit number and was moved to an 8 bit storage then no shifting would be required?

You need to shift to view it in the "standard" format. It depends on what you mean.

This topic is closed to new replies.

Advertisement