This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

| Photoshop .tga Alpha Fix Submitted by

I recently stumbled upon a problem with the .tga output of Photoshop with 32-bit alpha-channel images. Photoshop seemed to replace all fully transparent pixels in the image with white color, regardless of what color they were before becoming translucent. This is usually no problem, but when using such images as textures, the white color together with texture filtering causes some weird, ugly seams.
I don't know whether this is just a problem with the Photoshop 7.0 I use at university or other Photoshop versions or imaging programs.
Anyways, I wrote a small Windows tool which iteratively recolours all fully transparent pixels in a targa to the average color of all of their (nontransparent) neighbours. The tool is not the most optimized app there is, nor is the source clean or well written, but its fairly easy to use, and it does its job.
The appended zip file contains the program source, a .Net workspace and a precompiled .exe file. I'm not sure however which files I need to bundle with my apps since I switched to the .Net 2003 compiler suite. So, when any missing dll is reported, please tell me.
Well, now for using the tool in projects of your own, you may both use the precompiled version as well as the source code for everything you want. However, I'm always happy about mail telling me that it wasn't or was useful to you.
Cheers,
- Wernaeh

Currently browsing [TgaTool.zip] (46,248 bytes) - [TgaFix.cpp] - (11,053 bytes)

    //////////////////////////////////////////////////////////////////////////
    //
    //  Tga image translucency fixer
    //  Created by Wernaeh
    //
    //////////////////////////////////////////////////////////////////////////

    // Windows stuff
    #include "Windows.h"

    // Files
    #include <fstream>

    // For shortening stuff, directly include
    // the std namespace
    using namespace std;

    // The global exit flag for the message loop
    bool bExit = false;

    // The main programm window
    HWND hMainWin = NULL;

    // The name of the current targa file
    char *pFileName = NULL;

    // The current targa file data
    char *pPixels = NULL;
    int iResX = 0;
    int iResY = 0;
    char cBypp = 0;

    // The default (supported) targa header
    const char cTgaHeader[12] =
    	{ 0, 0, 2, 0,  0, 0, 0, 0,  0, 0, 0, 0 };

    // Present a message box with the specified text
    // indicating that some problem occured.
    void MsgBox(const char *text)
    {
    	MessageBox(hMainWin, text, "Problem:",
    			   MB_OK | MB_ICONEXCLAMATION);
    }

    // Load a targa file with the given file name
    // This method returns false if the targa file can not
    // be modified, but the error is ignorable. This includes
    // invalid file types, or tga's with corrupt headers.
    // If everything is ok with the file, the global image data
    // vars are filled.
    bool LoadTga()
    {
    	ifstream file;

    	// Open the targa file
    	file.open(pFileName, ios::in | ios::binary | ios::_Nocreate);

    	if (!file)
    	{
    		MsgBox("Failed to open file for reading");
    		return false;
    	}

    	// Extract the file header
    	char header[12];

    	if (!file.read(header, sizeof(char) * 12))
    	{
    		MsgBox("Corrupt tga header");
    		return false;
    	}

    	// Only support uncompressed targas
    	if (memcmp(header, cTgaHeader, 12) != 0)
    	{
    		MsgBox("Unsupported targa header");
    		return false;
    	}

    	// Extract file resolution
    	short resx;
    	short resy;

    	if (!file.read((char*)&resx, sizeof(short)) ||
    		!file.read((char*)&resy, sizeof(short)))
    	{
    		MsgBox("Corrupt targa resolution");
    		return false;
    	}

    	// Check for valid resolution
    	if (resx <= 0 || resx > 2048 ||
    		resy <= 0 || resy > 2048)
    	{
    		MsgBox("Targa resolution not supported");
    		return false;
    	}

    	// Extract bitdepth
    	char bitdepth;

    	if (!file.read(&bitdepth, sizeof(char)))
    	{
    		MsgBox("Corrupt targa bitdepth");
    		return false;
    	}

    	// Only open targas with 24 or 32 bits per pixel
    	if (bitdepth != 32 && bitdepth != 24)
    	{
    		MsgBox("Targa bitdepth not supported");
    		return false;
    	}

    	// Jump over last part of header
    	file.seekg(sizeof(char), ios::cur);

    	// Apply image sizing and depth information
    	iResX = resx;
    	iResY = resy;
    	cBypp = bitdepth / 8;

    	// Reserve image memory
    	pPixels =
    		(char*)malloc(iResX * iResY * cBypp * sizeof(char));

    	if (!pPixels)
    	{
    		throw("Failed to allocate pixel memory");
    	}

    	// Load actual image from file
    	if (!file.read(pPixels, iResX * iResY * cBypp * sizeof(char)))
    	{
    		MsgBox("Unexpected end in image");
    		return false;
    	}

    	// Close file again
    	file.close();

    	return true;
    }

    // Process the targa image in memory
    void ProcessImage()
    {
    	// No need to process targa files without
    	// alpha channel
    	if (cBypp != 4)
    	{
    		return;
    	}

    	// Create process map
    	char *map =
    		(char*)malloc(iResX * iResY * sizeof(char));

    	if (!map)
    	{
    		throw("Failed to alloc temporary memory");
    	}

    	// Initialize process map
    	int pixel = 3;

    	for (int i = 0;
    		 i < iResX * iResY;
    		 ++i)
    	{
    		if (pPixels[pixel] != 0)
    		{
    			map[i] = 0;
    		}
    		else
    		{
    			map[i] = 2;
    		}

    		pixel += 4;
    	}

    	// Correct pixels until each one has been
    	// treated.
    	bool anyfound;

    	do
    	{
    		anyfound = false;

    		// Set all pixels neighbouring to at least
    		// a single treated pixel to the average of
    		// all treated neighbours.
    		int index = 0;

    		for (int i = 0;
    			 i < iResY;
    			 ++i)
    		{
    			for (int j = 0;
    				 j < iResX;
    				 ++j)
    			{

    				// Only look at untreated pixels
    				if (map[index] == 2)
    				{

    					// Build coords for neighbor pixels
    					int xnext = j + 1;
    					if (xnext >= iResX)
    					{
    						xnext -= iResX;
    					}

    					int xprev = j - 1;
    					if (xprev < 0)
    					{
    						xprev += iResX;
    					}

    					// Y coords for neighbors
    					int ynext = i + 1;
    					if (ynext >= iResY)
    					{
    						ynext -= iResY;
    					}

    					int yprev = i - 1;
    					if (yprev < 0)
    					{
    						yprev += iResY;
    					}

    					// Build neighbour indices
    					int indices[8] =
    						{ ynext * iResX + xnext,
    						  ynext * iResX + j,
    						  ynext * iResX + xprev,
    						  i * iResX + xnext,
    						  i * iResX + xprev,
    						  yprev * iResX + xnext,
    						  yprev * iResX + j,
    						  yprev * iResX + xprev };

    					// Temporary accumulators
    					int accumr = 0;
    					int accumg = 0;
    					int accumb = 0;
    					int accnt = 0;

    					// Accumulate all completed neighbors
    					for (int k = 0;
    						 k < 8;
    						 ++k)
    					{
    						if (map[indices[k]] == 0)
    						{
    							accumr += pPixels[indices[k] * 4];
    							accumg += pPixels[indices[k] * 4 + 1];
    							accumb += pPixels[indices[k] * 4 + 2];
    							++accnt;
    						}
    					}

    					// Assign result of accumulation and update
    					// the process map
    					if (accnt != 0)
    					{
    						pPixels[index * 4] = accumr / accnt;
    						pPixels[index * 4 + 1] = accumg / accnt;
    						pPixels[index * 4 + 2] = accumb / accnt;

    						map[index] = 1;
    					}
    				}

    				++index;
    			}
    		}

    		// Mark all treated pixels as completed
    		// Also raise anyfound flag if any pixel
    		// was treated.
    		for (int i = 0;
    			 i < iResX * iResY;
    			 ++i)
    		{
    			if (map[i] == 1)
    			{
    				map[i] = 0;
    				anyfound = true;
    			}
    		}
    	}
    	while (anyfound);

    	// Release process map
    	free(map);
    }

    // Write the targa file back to disk
    void SaveTga()
    {
    	ofstream file;

    	// Open the file for output
    	file.open(pFileName, ios::out | ios::binary | ios::trunc);

    	if (!file)
    	{
    		MsgBox("Failed to open file for saving");
    		return;
    	}

    	// Create some temporaries
    	char bitdepth = cBypp * 8;
    	char bituse = 0;

    	// Write out all targa image data
    	file.write(cTgaHeader, 12 * sizeof(char));

    	file.write((char*)&iResX, sizeof(short));
    	file.write((char*)&iResY, sizeof(short));
    	file.write(&bitdepth, sizeof(char));
    	file.write(&bituse, sizeof(char));

    	file.write(pPixels, iResX * iResY * cBypp * sizeof(char));

    	// Close file again
    	file.close();
    }

    // Release all image data
    void ReleaseImage()
    {
    	iResX = 0;
    	iResY = 0;
    	cBypp = 0;

    	if (pPixels)
    	{
    		free(pPixels);
    		pPixels = NULL;
    	}
    }

    // Process a single file dropped into the main window
    void ProcessFile()
    {
    	// Load the targa file
    	if (LoadTga())
    	{
    		// Fix the image data
    		ProcessImage();

    		// Write it back into the file
    		SaveTga();
    	}

    	// Release all image data again
    	ReleaseImage();
    }

    // Process a number of files dropped into the window
    void ProcessDrop(HDROP drop)
    {
    	// Retrieve file count dropped
    	int fcount =
    		DragQueryFile(drop, 0xffffffff, NULL, 0);

    	for (int i = 0;
    		 i < fcount;
    		 ++i)
    	{
    		// Allocate buffer for file name
    		int namlen =
    			DragQueryFile(drop, i, NULL, 0) + 1;

    		pFileName =
    			(char*)malloc(sizeof(char) * namlen);

    		if (!pFileName)
    		{
    			throw("Failed to allocate memory");
    		}

    		// Retrieve file name
    		DragQueryFile(drop, i, pFileName, namlen);

    		// Proces the specified file
    		ProcessFile();

    		// Release name buffer
    		free(pFileName);
    	}

    	// Clean up memory occupied by the drop
    	DragFinish(drop);

    	// Bring up a message box to indicate
    	// that we finished
    	MessageBox(hMainWin, "Files fixed",
    			   "Finished", MB_OK);
    }

    // The window procedure
    LRESULT CALLBACK WndProc
    	(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    	// Handle incoming message
    	switch (uMsg)
    	{
    		case WM_CLOSE:
    			// Force program to terminate
    			bExit = true;
    		return 0;

    		case WM_DROPFILES:
    			// Some files were dropped, so process them
    			ProcessDrop((HDROP)wParam);
    		return 0;
    	}

    	// Let default window procedure handle all
    	// unhandled messages.
    	return
    		DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    // Run the actual program
    void Run()
    {
    	// Repeat main loop until exit flag is raised
    	while (!bExit)
    	{

    		// Handle all incoming windows messages
    		MSG msg;

    		while (PeekMessage(&msg, hMainWin, 0, 0, PM_REMOVE))
    		{
    			TranslateMessage(&msg);
    			DispatchMessage(&msg);
    		}
    	}
    }

    // The programm creation method
    void Create()
    {
    	// Create program window class
    	WNDCLASS wndcls =
    		{ 0, &WndProc, 0, 0, GetModuleHandle(NULL), NULL,
    		  LoadCursor(NULL, IDC_ARROW), GetSysColorBrush(COLOR_3DFACE),
    		  NULL, "TgaFixWindow" };

    	if (RegisterClass(&wndcls) == 0)
    	{
    		throw("Failed to register window class");
    	}

    	// Create program window
    	hMainWin = CreateWindowEx
    		(WS_EX_APPWINDOW | WS_EX_ACCEPTFILES | WS_EX_TOPMOST |
    		 WS_EX_CLIENTEDGE, "TgaFixWindow", "TgaFix",
    		 WS_CAPTION | WS_BORDER | WS_POPUP | WS_SYSMENU,
    		 50, 50, 150, 100, NULL, NULL, GetModuleHandle(NULL), NULL);

    	if (hMainWin == NULL)
    	{
    		throw("Failed to create window");
    	}

    	// Create the static "drag items here" text
    	HWND tempwin = CreateWindow
    		("STATIC", "Drag TGA files here", WS_CHILD | WS_VISIBLE,
    		 22, 20, 100, 100, hMainWin, NULL, GetModuleHandle(NULL), NULL);

    	if (tempwin == NULL)
    	{
    		throw("Failed to create static window text");
    	}

    	// Make window visible
    	ShowWindow(hMainWin, SW_SHOW);
    }

    // Programm shutdown method
    void Destroy()
    {
    	// Release image data in the case of an exceptional
    	// shutdown.
    	ReleaseImage();

    	// Destroy programm window
    	if (hMainWin)
    	{
    		DestroyWindow(hMainWin);
    	}

    	// Unregister window class
    	UnregisterClass("TgaFixWindow", GetModuleHandle(NULL));
    }

    // This programm is intended to fix the problems occuring with
    // alpha channel images on certain imaging applications. Photoshop
    // for example sets all completely translucent pixels to white, which
    // later on gives problems with mipmapping. This tga fixer allows the
    // user to specifiy a number of files which then are all repaired.
    // Repair is performed in that all fully transparent pixels are recolored
    // with the average color of their (non translucent) neighbours.
    // This process continues iteratively until all translucent pixels have
    // been recolored appropriately. Note since this is only a tool, the
    // code ain't as clean as it should perhaps be.
    int CALLBACK WinMain
    	(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance,
    	 IN LPSTR lpCmdLine, IN int nShowCmd)
    {
    	try
    	{
    		// Setup out program on startup
    		Create();

    		try
    		{
    			// Run program
    			Run();
    		}
    		catch (const char *except)
    		{
    			MsgBox(except);
    		}

    		// Clean up after we finished
    		Destroy();
    	}
    	catch (const char *except)
    	{
    		MsgBox(except);
    	}
    }

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.