Gaussian blur algorithm

Started by
5 comments, last by blueshogun96 14 years, 5 months ago
I'm working on my implementation of a gaussian blur algorithm. The code I'm using is based off of one from a gamedev.net article: http://www.gamedev.net/reference/programming/features/imageproc/page2.asp While the code didn't appear to be API specific, my implementation uses Direct3D. I'm not sure whether the problem lies within Direct3D or the algorithm itself. The reason I'm doing this is to create a glow effect as described in these documents: http://www.gamasutra.com/view/feature/2107/realtime_glow.php http://developer.nvidia.com/docs/IO/8230/D3DTutorial_EffectsNV.pdf Below is my code for performing the gaussian blur (just the algorithm itself):

.....

	// Perform the gaussian blur
	if( SUCCEEDED( pTempSurface->LockRect( &LockedRect, NULL, 0 ) ) )
	{
		pdwPixels = (DWORD*) LockedRect.pBits;
		Pitch = (INT) LockedRect.Pitch;

		// Horizontal blur
		for( int i = 1; i < Width - 1; i++ )
		{
			for( int j = 1; j < Height - 1; j++ )
			{
				// Clear colour fields
				dwRed = dwGreen = dwBlue = dwAlpha = 0;

				for( int k = 0; k < GaussWidth; k++ )
				{
					INT x = i - ( ( GaussWidth - 1 ) >> 1 ) + k;
					INT y = j;
					DWORD dwColour = pdwPixels[y*Pitch+x];

					DWORD a = ( dwColour >> 24 ) & 0xFF;
					DWORD r = ( dwColour >> 16 ) & 0xFF;
					DWORD g = ( dwColour >> 8 ) & 0xFF;
					DWORD b = ( dwColour >> 0 ) & 0xFF;

					dwAlpha += a * GaussFact[k];
					dwRed   += r * GaussFact[k];
					dwGreen += g * GaussFact[k];
					dwBlue  += b * GaussFact[k];
				}

				// Draw blurred pixel
				pdwPixels[j*Pitch+i] = D3DCOLOR_ARGB( dwAlpha/GaussSum, dwRed/GaussSum, dwGreen/GaussSum, dwBlue/GaussSum );
			}
		}

		// Vertical blur
		for( int i = 1; i < Width - 1; i++ )
		{
			for( int j = 1; j < Height - 1; j++ )
			{
				// Clear colour fields
				dwRed = dwGreen = dwBlue = dwAlpha = 0;

				for( int k = 0; k < GaussWidth; k++ )
				{
					INT x = i;
					INT y = j - ( ( GaussWidth - 1 ) >> 1 ) + k;
					DWORD dwColour = pdwPixels[y*Pitch+x];

					DWORD a = ( dwColour >> 24 ) & 0xFF;
					DWORD r = ( dwColour >> 16 ) & 0xFF;
					DWORD g = ( dwColour >> 8 ) & 0xFF;
					DWORD b = ( dwColour >> 0 ) & 0xFF;

					dwAlpha += a * GaussFact[k];
					dwRed   += r * GaussFact[k];
					dwGreen += g * GaussFact[k];
					dwBlue  += b * GaussFact[k];
				}

				dwAlpha /= GaussSum;
				dwRed   /= GaussSum;
				dwGreen /= GaussSum;
				dwBlue  /= GaussSum;

				// Draw blurred pixel
				pdwPixels[j*Pitch+i] = D3DCOLOR_ARGB( dwAlpha, dwRed, dwGreen, dwBlue );
			}
		}

		// Unlock the texture
		pTempSurface->UnlockRect();

.....

And here is the full source to the function I wrote to do this (including Direct3D code).

//--------------------------------------------------------------------------------------
// Name: DoGaussianBlur
// Desc: Performs the gaussian blur algorithm on the entire render target, then saves the
//		 output to a texture of the same size and format.  This code assumes the texture 
//		 is 32-bit (D3DFMT_A8R8G8B8).
//--------------------------------------------------------------------------------------
bool DoGaussianBlur( INT Width, INT Height, IDirect3DSurface9* pRTSurface, IDirect3DTexture9** ppTexture )
{
	IDirect3DSurface9* pTempSurface = NULL;
	DWORD	dwRed = 0, dwGreen = 0, dwBlue = 0, dwAlpha = 0;
	const INT GaussWidth = 7;
	INT		GaussFact[GaussWidth] = { 1, 6, 15, 20, 15, 6, 1 };
	INT		GaussSum = 64;
	D3DLOCKED_RECT LockedRect;
	DWORD*	pdwPixels = NULL;
	INT		Pitch = 0;

	// Verify this texture
	if( !pRTSurface )
		return false;

	// Create a temporary surface.
	HRESULT hr = lpD3DDevice->CreateOffscreenPlainSurface( Width, Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &pTempSurface, NULL );
	if( FAILED( hr ) )
	{
		OutputDebugStringA( "Error creating temporary surface!\n" );
		return false;
	}

	// Copy the contents of the render target to the newly created surface
	hr = lpD3DDevice->GetRenderTargetData( pRTSurface, pTempSurface );
	if( FAILED( hr ) )
	{
		OutputDebugStringA( "Error getting render target data!\n" );
		SAFE_RELEASE( pTempSurface );
		return false;
	}

	// Perform the gaussian blur
	if( SUCCEEDED( pTempSurface->LockRect( &LockedRect, NULL, 0 ) ) )
	{
		pdwPixels = (DWORD*) LockedRect.pBits;
		Pitch = (INT) LockedRect.Pitch;

		// Horizontal blur
		for( int i = 1; i < Width - 1; i++ )
		{
			for( int j = 1; j < Height - 1; j++ )
			{
				// Clear colour fields
				dwRed = dwGreen = dwBlue = dwAlpha = 0;

				for( int k = 0; k < GaussWidth; k++ )
				{
					INT x = i - ( ( GaussWidth - 1 ) >> 1 ) + k;
					INT y = j;
					DWORD dwColour = pdwPixels[y*Pitch+x];

					DWORD a = ( dwColour >> 24 ) & 0xFF;
					DWORD r = ( dwColour >> 16 ) & 0xFF;
					DWORD g = ( dwColour >> 8 ) & 0xFF;
					DWORD b = ( dwColour >> 0 ) & 0xFF;

					dwAlpha += a * GaussFact[k];
					dwRed   += r * GaussFact[k];
					dwGreen += g * GaussFact[k];
					dwBlue  += b * GaussFact[k];
				}

				// Draw blurred pixel
				pdwPixels[j*Pitch+i] = D3DCOLOR_ARGB( dwAlpha/GaussSum, dwRed/GaussSum, dwGreen/GaussSum, dwBlue/GaussSum );
			}
		}

		// Vertical blur
		for( int i = 1; i < Width - 1; i++ )
		{
			for( int j = 1; j < Height - 1; j++ )
			{
				// Clear colour fields
				dwRed = dwGreen = dwBlue = dwAlpha = 0;

				for( int k = 0; k < GaussWidth; k++ )
				{
					INT x = i;
					INT y = j - ( ( GaussWidth - 1 ) >> 1 ) + k;
					DWORD dwColour = pdwPixels[y*Pitch+x];

					DWORD a = ( dwColour >> 24 ) & 0xFF;
					DWORD r = ( dwColour >> 16 ) & 0xFF;
					DWORD g = ( dwColour >> 8 ) & 0xFF;
					DWORD b = ( dwColour >> 0 ) & 0xFF;

					dwAlpha += a * GaussFact[k];
					dwRed   += r * GaussFact[k];
					dwGreen += g * GaussFact[k];
					dwBlue  += b * GaussFact[k];
				}

				dwAlpha /= GaussSum;
				dwRed   /= GaussSum;
				dwGreen /= GaussSum;
				dwBlue  /= GaussSum;

				// Draw blurred pixel
				pdwPixels[j*Pitch+i] = D3DCOLOR_ARGB( dwAlpha, dwRed, dwGreen, dwBlue );
			}
		}

		// Unlock the texture
		pTempSurface->UnlockRect();
	}
	else
	{
		OutputDebugStringA( "Error locking surface!\n\n" );
		SAFE_RELEASE( pTempSurface );
		return false;
	}

	// Create a dynamic texture to write the blurred surface to.
	if( !ppTexture )
	{
		hr = lpD3DDevice->CreateTexture( Width, Height, 0, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, 
										 D3DPOOL_DEFAULT, ppTexture, NULL );
		if( FAILED( hr ) )
		{
			SAFE_RELEASE( pTempSurface );
			OutputDebugStringA( "Error creating copy texture!\n" );
			return false;
		}
	}

	DWORD* pdwSurface = NULL;
	DWORD* pdwTexture = NULL;
	D3DLOCKED_RECT LockedSurface, LockedTexture;
	bool bError = false;

	// Lock the temporary surface
	if( SUCCEEDED( pTempSurface->LockRect( &LockedSurface, NULL, 0 ) ) )
	{
		// Lock the texture
		if( SUCCEEDED( (*ppTexture)->LockRect( 0, &LockedTexture, NULL, 0 ) ) )
		{
			// Copy the data from the surface to the texture
			pdwSurface = (DWORD*) LockedSurface.pBits;
			pdwTexture = (DWORD*) LockedTexture.pBits;

			::CopyMemory( pdwTexture, pdwSurface, sizeof( pdwSurface ) );

			// Unlock
			(*ppTexture)->UnlockRect(0);
		}
		else
		{
			OutputDebugStringA( "Error locking duplicate texture!\n" );
			bError = true;
		}

		// Unlock
		pTempSurface->UnlockRect();
	}
	else
	{
		OutputDebugStringA( "Error locking temporary surface!\n" );
		bError = true;
	}

	return !bError;
}

The crash occurs when reading the first few pixels for modification. I followed the algorithm exactly (or at least to my best understanding). Is there a better way to do this? Or should I try another approach to the glow effect entirely? Thanks.
Advertisement
Is there a reason you're not doing it in a shader?
Quote:Original post by Haladria
Is there a reason you're not doing it in a shader?


Because I don't know how (unless there's a simpler way). Besides, I need a fixed-pipeline implementation either way (for OpenGL ES) later on.
Quote:Original post by blueshogun96
Quote:Original post by Haladria
Is there a reason you're not doing it in a shader?


Because I don't know how (unless there's a simpler way). Besides, I need a fixed-pipeline implementation either way (for OpenGL ES) later on.


No, there is no simple way I know of. You need to render a fullscreen quad with a guassian blur shader to a texture. (this should be separated in two passes for better performance) This is the shader I usually use:
http://www.gamerendering.com/2008/10/11/gaussian-blur-filter-shader/

It looks like the reason your code your code is crashing is that you're reading off the edge of the image at:

INT x = i - ( ( GaussWidth - 1 ) >> 1 ) + k;

In the case where i is 1 and k is 0 that will give you -2. It looks like that code was designed for a GaussWidth of 3, and won't work for any other value without modification.
Quote:Original post by Adam_42
(..) won't work for any other value without modification.


And those modifications include any one of:

* Changing your iteration ranges:
                int offset = ( GaussWidth - 1 ) >> 1;		for( int i = offset ; i < Width - offset ; i++ )		{			for( int j = offset ; j < Height - offset ; j++ )


* Not reading outside of the buffer:
DWORD dwColour = x >= 0 && x < Width && y >= 0 && y < Height ? pdwPixels[y*Pitch+x] : 0;


* Clamping your iteration values (the best idea, since you can then iterate over the entire image range):
INT x = std::max(0, std::min(Width, i - ( ( GaussWidth - 1 ) >> 1 ) + k));


INT y = std::max(0, std::min(Height, j - ( ( GaussWidth - 1 ) >> 1 ) + k));
Quote:Original post by Haladria
Quote:Original post by blueshogun96
Quote:Original post by Haladria
Is there a reason you're not doing it in a shader?


Because I don't know how (unless there's a simpler way). Besides, I need a fixed-pipeline implementation either way (for OpenGL ES) later on.


No, there is no simple way I know of. You need to render a fullscreen quad with a guassian blur shader to a texture. (this should be separated in two passes for better performance) This is the shader I usually use:
http://www.gamerendering.com/2008/10/11/gaussian-blur-filter-shader/


TBH, your fragment program makes things look much easier. I'll go ahead and try that instead and see if it works. I'm sure I can easily convert it to a HLSL pixel shader. Thanks.

Quote:Original post by mattd
Quote:Original post by Adam_42
(..) won't work for any other value without modification.


And those modifications include any one of:

* Changing your iteration ranges:
*** Source Snippet Removed ***

* Not reading outside of the buffer:
*** Source Snippet Removed ***

* Clamping your iteration values (the best idea, since you can then iterate over the entire image range):
*** Source Snippet Removed ***

*** Source Snippet Removed ***


I'll go ahead and try this too just in case, but I'll probably stick with the shader since it appears to be easier to use and maintain. Thanks again.

This topic is closed to new replies.

Advertisement