← index
Old.Learning.Projects/mgex/MasterGraphicEx.cpp
Source: Old.Learning.Projects/mgex/MasterGraphicEx.cpp
#include "MasterGraphicEx.h"

#include <fstream>


///////////////////////////////////////////////////////////////

//Name: MasterGraphicEx ()

//Info: Constructor just clears ddblyfx variable

//Params: none

//Returns: 0L - failure

//		   else - a pointer to the pixels of this graphic

///////////////////////////////////////////////////////////////

MasterGraphicEx::MasterGraphicEx ()
{
	ZeroMemory (&m_ddbltfx, sizeof (m_ddbltfx));
	m_ddbltfx.dwSize = sizeof (m_ddbltfx);
	color_key = RGB (0, 0, 0);
}

///////////////////////////////////////////////////////////////

//Name: generic_draw ()

//Info: Basically uses Blt () to display the surface

//Params: x, y - position

//			dest_w, dest_h - new size of graphic (for scaling)

//			blt_flags - flags for Blt function

//Returns: none

///////////////////////////////////////////////////////////////


void MasterGraphicEx::generic_draw (const int x, const int y, 
									const int dest_w, const int dest_h, 
									DWORD blt_flags)
{
	DWORD flags = DDBLT_WAIT | blt_flags;
	
	//add color key flag if necessary

	if (ckey)
	{
		flags|=DDBLT_KEYSRC; 
	}

	RECT dest, source;
	//rect of image

	source.left=0;
	source.top=0;
	source.right=w;
	source.bottom=h;

	//destination rect

	dest.left = x;
	dest.top = y;
	dest.right = x + dest_w;
	dest.bottom = y + dest_h;

	if (surf)
	{
	//blit the surface to the screen (it will scale automatically)

	HRESULT ddrval= scr->lpBack->Blt(&dest, surf, &source, flags, &m_ddbltfx);
	
	//if there is a problem, reload the graphic

	if(ddrval != DD_OK)
	{
		ReLoadGraphic();
	}
	}
}

///////////////////////////////////////////////////////////////

//Name: generic_draw_clip ()

//Info: Basically uses Blt () to display the surface but

//		also adds support for clipping

//Params: x, y - position

//			dest_w, dest_h - new size of graphic (for scaling)

//			clip_rect - clips graphic to this rect

//			blt_flags - flags for Blt function

//Returns: none

///////////////////////////////////////////////////////////////


void MasterGraphicEx::generic_draw_clip (const int x, const int y, 
										 const int dest_w, const int dest_h, 
										 const RECT* clip_rect, 
										 DWORD blt_flags)
{

	DWORD flags = DDBLT_WAIT | blt_flags;

	//add flages for colorkey

	if (ckey)
	{
		flags|=DDBLT_KEYSRC; 
	}

	RECT dest, source;

	//image rect

	source.left=0;
	source.top=0;
	source.right=w;
	source.bottom=h;
	
	//destination rect

	dest.left = x;
	dest.top = y;
	dest.right = x + dest_w;
	dest.bottom = y + dest_h;

	//this is basic clipping math...

	if (dest.left < clip_rect->left)
	{
		source.left = clip_rect->left-dest.left;
		dest.left = clip_rect->left;
	}

	if (dest.top < clip_rect->top)
	{
		source.top = clip_rect->top-dest.top;
		dest.top = clip_rect->top;
	}

	if (dest.right > clip_rect->right)
	{
		source.right -= dest.right-clip_rect->right;
		
		dest.right = clip_rect->right;
	}

	if (dest.bottom > clip_rect->bottom)
	{
		source.bottom -= dest.bottom-clip_rect->bottom;
		dest.bottom = clip_rect->bottom;
	}

	// Out of clipping borders

	if (dest.right<dest.left || dest.bottom<dest.top) return; 

	if (surf)
	{
	//now we are ready to Blt...

	HRESULT ddrval = scr->lpBack->Blt(&dest, surf, &source, flags, &m_ddbltfx);
	
	if(ddrval != DD_OK)
	{
		ReLoadGraphic();
	}
	}
}

///////////////////////////////////////////////////////////////

//Name: filter ()

//Info: Applies various filters to an RGB value.

//Params: type - type of filter

//			r - assigns new red value to this param

//			g - "		"	green	"	"	"   "

//			b - "		'	blue	"	"	"	"

//			amount - some filters have different intensities (range 0-1)

//Returns: applies a filter of type to r, g, b. On failure, r, g, b stay 

//			the same

///////////////////////////////////////////////////////////////

void MasterGraphicEx::filter (const int type, int& r, int& g, int& b, const float r_amount, const float g_amount, const float b_amount)
{
	switch (type)
	{
	//grayscale

	case GRAYSCALE:
		{
			//just average the 3 rgb values

			int s = (r + g + b) / 3;
			r = g = b = s;
		}
		break;

	//darken

	case DARKEN:
		{
			//darken each rgb value

			r = (int)((1.0f - r_amount)*r);
			g = (int)((1.0f - g_amount)*g);
			b = (int)((1.0f - b_amount)*b);
		}
		break;

	//brighten

	case BRIGHTEN:
		{
			//brighten each rgb value

			r = (int)(r + r_amount*(float)(255-r));
			g = (int)(g + g_amount*(float)(255-g));
			b = (int)(b + b_amount*(float)(255-b));
		}
		break;
	
	//complement (inverting colors)

	case COMPLEMENT:
		{
			//invert each rgb value

			r = 255 - r;
			g = 255 - g;
			b = 255 - b;
		}
		break;

	//color balance (basically, controls the brighten/darken for each

	// rgb component

	case BALANCE:
		{
			if (r_amount < 0)
			{
				r = (int)((1.0f - r_amount)*r);
			}
			else
			{
				r = (int)(r + r_amount*(float)(255-r));
			}

			if (g_amount < 0)
			{
				g = (int)((1.0f - g_amount)*g);
			}
			else
			{
				g = (int)(g + g_amount*(float)(255-g));
			}

			if (b_amount < 0)
			{
				b = (int)((1.0f - b_amount)*b);
			}
			else
			{
				b = (int)(b + b_amount*(float)(255-b));
			}
		}
		break;

	}
}

///////////////////////////////////////////////////////////////

//Name: apply_filter_to_pixels ()

//Info: This does all the work for the filters. It gets the pixels

//	from the surface. Then it gets the bits per pixel of the current

//	DirectDraw environment. It goes through each pixel, ripping

//	the rgb components. Next, it applies the filter and rebuilds

//	the pixel. Calling ReLoadImage will restore the surface to its

//	original state.

//Params:	type - filter type

//			r_amount, g_amount, b_amount - amount of intensity of filter

//Returns: none

///////////////////////////////////////////////////////////////


void MasterGraphicEx::apply_filter_to_pixels (const int type, const float r_amount, const float g_amount, const float b_amount)
{
	//check for a valid surface before we do anything

	if (surf == 0L)
	{
		//check surf->IsLost () also would be a good idea!

		return;
	}
	//get the bit count

	int bit_count = get_bit_count ();
	//amount of bytes between end of scanline and start of next

	int pitch;
	//temporary place holders for color information

	int r, g, b, a;

	//temporary position variable

	int x;
	//verify the bit count

	if (bit_count == -1)
	{
		return;
	}
	
	//get the pixels of this graphic

	DDSURFACEDESC surface_desc = get_surface_desc ();

	//verify we have any pixels at all

	if (surface_desc.lpSurface == 0L)
	{
		return;
	}


	//i only support 15, 16, 24 & 32 bit conversions

	switch (bit_count)
	{
		////////////////////////////////////////////////////////////////

		//15/16 bit case:

		//This is a nice bit pattern to work with, small (2 bytes) and

		//it has good color. Basically, we rip the color, apply filter

		//and reassemble image. The 15/16 pixel reading is a little 

		//buggy (not comparing color keys properly and reading pixels

		// a little off)

		///////////////////////////////////////////////////////////////

		case 15:
		{
			//get pixels from surface

			unsigned short* pixels_2_bytes = 
				(unsigned short*)surface_desc.lpSurface;
			
			//get extra bytes

			pitch = surface_desc.lPitch - w*2;
			//convert bytest to pixels

			pitch /= 2;
			
			//go through pixel by pixel

			for (int i = 0; i < h; i++)
			{
				//go to beginning of next line

				x = i*w + i*pitch;

				for (int j = 0; j < w; j++)
				{
				//extract color (we are ORing with 7 for a higher intensity

				//conversion

				r = ((pixels_2_bytes[x + j]>>10)&BIT_5_MASK)<<3;
				if (r)
					r |= 0x7;
				g = ((pixels_2_bytes[x + j]>>5)&BIT_5_MASK)<<3;
				if (g)
					g |= 0x7;
				b = (pixels_2_bytes[x + j]&BIT_5_MASK)<<3;
				if (b)
					b |= 0x7;

				//dont apply the color filter to the transparent color

				if (!ckey || (RGB (r, g, b) != (color_key)))
				{		
					//apply filter

					filter (type, r, g, b, r_amount, g_amount, b_amount);
					//rebuild pixel

					pixels_2_bytes[x + j] = (unsigned short) (((r&0xF8)<<7) | ((g&0xF8)<<2) | (b>>3));
				}
				}
			}
		}
		break;

		case 16:
		{
			//get extra bytes

			pitch = surface_desc.lPitch - w*2;
			//convert bytest to pixels

			pitch /= 2;
			
			//get pixels from surface

			unsigned short* pixels_2_bytes = 
				(unsigned short*)surface_desc.lpSurface;

			for (int i = 0; i < h; i++)
			{
				//get beginning position of scanline

				x = i*w + i*pitch;
				for (int j = 0; j < w; j++)
				{
					
					//extract colors

					r = ((pixels_2_bytes[x + j]>>11)&BIT_5_MASK)<<3;
					if (r)
						r |= 7;
					g = ((pixels_2_bytes[x + j]>>5)&BIT_6_MASK)<<2;
					if (g)
						g |= 7;
					b = (pixels_2_bytes[x + j]&BIT_5_MASK)<<3;
					if (b)
						b |= 7;

					if (!ckey || (RGB (r, g, b) != (color_key)))
					{
					filter (type, r, g, b, r_amount, g_amount, b_amount);

					//rebuild pixel

					pixels_2_bytes[x + j] = (unsigned short) (((r&0xF8)<<8) | ((g&0xF8)<<3) | (b>>3));
					}
			
				}
			}


		}
		break;

		////////////////////////////////////////////////////////////////

		//24 bit case:

		//This is usually the worst one to deal with because 24 bit 

		//color is stored as 3 bytes, too big for a short, too small 

		//for an int. In addition, as a 24 bit DirectDraw surface,

		//there are 8 (DDSURFACE->lPitch / 2) bytes appended to each

		//image scanline. So we have to skip operation on these unused

		//bytes. Note, there can be optimizations here, this was a 

		//first attempt

		///////////////////////////////////////////////////////////////

		
		case 24:
		{

			//cast surface_pixels as a byte array

			unsigned char* pixels_1_byte 
				= (unsigned char*)surface_desc.lpSurface;

			//this formula took a while to figure out,

			//but it works (pitch is the amount of bytes

			//from the end a scanline, to the beginning

			//of the next

			pitch = surface_desc.lPitch - 3*w;


			//cycle through the byte array, 3(rgb) at a time

			
			//go 1 scanline at a time

			for (int i = 0; i < h; i++)
			{
				//go through each 3 byte pixel of the scanline

				//WARNING: there is extra crap appended to each

				//scanline and we cant apply the filter to those!

				
				x = (i*3*w) + (i*pitch);

				for (int j = 0; j < (w*3); j+=3)
				{
				//get current position in pixel array

				//pitch is the amount of extra bytes 

				//3*w is the width of actual image in bytes

				//j is the position in current line


				
				//get rgb values

				r = pixels_1_byte[x + j];
				g = pixels_1_byte[x + j + 1];
				b = pixels_1_byte[x + j + 2];
				
				if (!ckey || (RGB (r, g, b) != color_key))
				{				
				//apply filter

				filter (type, r, g, b, r_amount, g_amount, b_amount);

				//reassign the rgb values

				pixels_1_byte[x + j]= r;
				pixels_1_byte[x + j + 1] = g;
				pixels_1_byte[x + j + 2] = b;
				}
				}

			}

		}
		break;

		////////////////////////////////////////////////////////////////

		//32 bit case:

		//32 bit color is stored as 4 bytes, so it fits into an int.

		//In 32 bit, I read the pixel then extract the ARGB color components

		//from it. (a is the alpha channel, for translucency). Then, I apply

		//the filter and recreate the pixel. I am preserving the original

		//a value and not applying it to the filter.

		///////////////////////////////////////////////////////////////


		case 32:
		{

			char d[100];
			wsprintf (d, "pitch: %i\n", surface_desc.lPitch);
			OutputDebugString (d);
			
			//get extra bytes at end of line

			pitch = surface_desc.lPitch - (w*4);
			//convert bytes to pixels

			pitch /= 4;

			//4 bytes per pixel

			unsigned int* pixels_4_bytes = (unsigned int*)surface_desc.lpSurface;
			//go to each pixel

			for (int i = 0; i < h; i++)
			{	
				x = (i*w) + (i*pitch);
				for (int j = 0; j < w; j++)
				{

					//get color components

					a = pixels_4_bytes[x + j]>>24;
					r = (pixels_4_bytes[x + j]>>16)&BIT_8_MASK;
					g = (pixels_4_bytes[x + j]>>8)&BIT_8_MASK;
					b = pixels_4_bytes[x + j]&BIT_8_MASK;

					if (!ckey || (RGB (r, g, b) != color_key))
					{
					//apply filter

					filter (type, r, g, b, r_amount, g_amount, b_amount);

					//construct pixel and assign it to array

					pixels_4_bytes[x + j] = (unsigned int)((a<<24) | (r<<16) | ((g) << 8) | b);
					}
				}
			}

		}
		break;

	}


}

///////////////////////////////////////////////////////////////

//Name: get_bit_count ()

//Info: This method attempts to get a point to the pixels

//		of this graphic's DirectDraw Surface. This is a pointer

//		right to the video memory. Reading/Writing to video

//		memory is very slow. But, these filters are intended to

//		be applied only once to the graphic.

//Params: none

//Returns: -1 - failure

//		   15, 16, 24, 32 - bits per pixel of the DirectDraw environment

///////////////////////////////////////////////////////////////

int MasterGraphicEx::get_bit_count ()
{
	int bit_count;
	DDSURFACEDESC ddsd;
	ddsd.dwSize = sizeof (ddsd);

	//get display mode, I am using the DirectDraw object 

	//rather than the surface on purpose (they both should be the same)

	scr->lpDD->GetDisplayMode (&ddsd);
	//get the bit count

	bit_count = ddsd.ddpfPixelFormat.dwRGBBitCount;

	if (bit_count == 16)
	{
		//find out which 16 bit we have (5x5x5 or 5x6x5)

		if (ddsd.ddpfPixelFormat.dwGBitMask == (BIT_5_MASK<<5))
		{
			bit_count = 15;
		}
		else if (ddsd.ddpfPixelFormat.dwGBitMask == (BIT_6_MASK<<5))
		{
			bit_count = 16;
		}		
	}
	//we only support 15, 16, 24 & 32

	else if ((bit_count != 24) && (bit_count != 32))
	{
		bit_count = -1;
	}

	return bit_count;
}

///////////////////////////////////////////////////////////////

//Name: get_surface_pixels ()

//Info: This method attempts to get a point to the pixels

//		of this graphic's DirectDraw Surface. This is a pointer

//		right to the video memory. Reading/Writing to video

//		memory is very slow. But, these filters are intended to

//		be applied only once to the graphic.

//Params: none

//Returns: 0L - failure

//		   else - a pointer to the pixels of this graphic

///////////////////////////////////////////////////////////////

DDSURFACEDESC MasterGraphicEx::get_surface_desc ()
{
	DDSURFACEDESC OffSurfaceDescription;
	HRESULT rval;	

	//clear the offsurface description structure

	ZeroMemory (&OffSurfaceDescription, sizeof (OffSurfaceDescription));
	OffSurfaceDescription.dwSize = sizeof (OffSurfaceDescription);
	
	//get the surface description

	rval = surf->Lock (NULL,&OffSurfaceDescription,
											DDLOCK_SURFACEMEMORYPTR, NULL);
	
	
	//check if lock did its job

	if (rval != DD_OK)
	{
		//we have a problem, return NULL

		ZeroMemory (&OffSurfaceDescription, sizeof (OffSurfaceDescription));
	}


	//unlock the surface, or DX will crash

	surf->Unlock (0L);

	return (OffSurfaceDescription);
}

///////////////////////////////////////////////////////////////

//Name: constrain_amount ()

//Info: Constrains amount to the range low to high

//Params: amount - changes amount to the specified range

//			low - floor of range

//			high - ceiling of range

//Returns: none

///////////////////////////////////////////////////////////////


void MasterGraphicEx::constrain_amount (float& amount, const float low, const float high)
{
	if (amount < low)
	{
		amount = low;
	}

	if (amount > high)
	{
		amount = high;
	}
}

void MasterGraphicEx::SetColorKey (COLORREF r)
{
	color_key = r;
	MasterGraphic::SetColorKey (color_key);
}

///////////////////////////////////////////////////////////////

//Client interface for drawing methods

///////////////////////////////////////////////////////////////


void MasterGraphicEx::DisplayGraphic_draw (const int x, const int y)
{
	generic_draw (x, y, w, h, 0);
}

void MasterGraphicEx::DisplayGraphic_clip (const int x, const int y, 
										   const RECT* clip_rect)
{
	generic_draw_clip (x, y, w, h, clip_rect, 0);
}

void MasterGraphicEx::DisplayGraphic_scale (const int x, const int y, 
											const int dest_w, const int dest_h)
{

	generic_draw (x, y, dest_w, dest_h, 0);
}

void MasterGraphicEx::DisplayGraphic_flip (const int x, const int y, 
										   const bool horizontal)
{
	m_ddbltfx.dwDDFX = horizontal?DDBLTFX_MIRRORLEFTRIGHT:DDBLTFX_MIRRORUPDOWN;

	generic_draw (x, y, w, h, DDBLT_DDFX);
}

//it turns out that scaling and clipping requires a little 

//extra math... =(

void MasterGraphicEx::DisplayGraphic_scale_clip (const int x, const int y, const int dest_w, const int dest_h, const RECT* clip_rect)
{

	DWORD flags = DDBLT_WAIT;

	//flag for color key

	if (ckey)
	{
		flags|=DDBLT_KEYSRC; 
	}

	RECT dest, source;

	//image rect

	source.left=0;
	source.top=0;
	source.right=w;
	source.bottom=h;

	//destination rect

	dest.left = x;
	dest.top = y;
	dest.right = x + dest_w;
	dest.bottom = y + dest_h;

	//scaling and clipping math ... 

	float dx = float(w)/dest_w;
	float dy = float(h)/dest_h;

	if (dest.left < clip_rect->left)
	{
		source.left = int((clip_rect->left-dest.left)*dx+0.5);
		dest.left = clip_rect->left;
	}

	if (dest.top < clip_rect->top)
	{
		source.top = int((clip_rect->top-dest.top)*dy+0.5);
		dest.top = clip_rect->top;
	}

	if (dest.right > clip_rect->right)
	{
		source.right -= int((dest.right-clip_rect->right)*dx+0.5);
		dest.right = clip_rect->right;
	}

	if (dest.bottom > clip_rect->bottom)
	{
		source.bottom -= int((dest.bottom-clip_rect->bottom)*dy+0.5);
		dest.bottom = clip_rect->bottom;
	}

	if (dest.right<dest.left || dest.bottom<dest.top) return; // Out of clipping borders


	if (surf)
	{
	//Blt to screen

	HRESULT ddrval= scr->lpBack->Blt(&dest, surf, &source, flags, &m_ddbltfx);

	if(ddrval != DD_OK)
	{
		ReLoadGraphic();
	}
	}
}

void MasterGraphicEx::DisplayGraphic_flip_clip (const int x, const int y, const bool horizontal, const RECT* clip_rect)
{

	m_ddbltfx.dwDDFX = horizontal?DDBLTFX_MIRRORLEFTRIGHT:DDBLTFX_MIRRORUPDOWN;

	generic_draw_clip (x, y, w, h, clip_rect, DDBLT_DDFX);
}

///////////////////////////////////////////////////////////////

//Client interfaces to filters (self expanatory)

///////////////////////////////////////////////////////////////

void MasterGraphicEx::apply_grayscale ()
{
	apply_filter_to_pixels (GRAYSCALE);
}

void MasterGraphicEx::apply_complement ()
{
	apply_filter_to_pixels (COMPLEMENT);
}

void MasterGraphicEx::apply_brighten (float amount)
{
	constrain_amount (amount, 0.0f, 1.0f);
	apply_filter_to_pixels (BRIGHTEN, amount, amount, amount);
}

void MasterGraphicEx::apply_darken (float amount)
{	
	constrain_amount (amount, 0.0f, 1.0f);
	apply_filter_to_pixels (DARKEN, amount, amount, amount);
}

void MasterGraphicEx::apply_balance (float r_amount, float g_amount, float b_amount)
{
	constrain_amount (r_amount, -1.0f, 1.0f);
	constrain_amount (g_amount, -1.0f, 1.0f);
	constrain_amount (b_amount, -1.0f, 1.0f);

	apply_filter_to_pixels (BALANCE, r_amount, g_amount, b_amount);
}