Image Manipulation

Discuss programming topics for the various GPL'd game engine sources.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Image Manipulation

Post by Baker »

I have somewhat unusual ideas going on in my head about a Quake engine, not a Quake engine, map editor, image editor, model viewer and text editor all being the same thing.

Here are the beginnings of a very short a brief image manipulation ... err ... file. And it does not just an entire image, but also can do mere ranges.

Notice how short it is. And it uses inlining to keep things separated but clear and readable.

PIXOP_ALPHA_FROM_COLOR generates an alpha channel for an image based on the existing color.

Code: Select all

typedef enum
{
	PIXOP_NEGATIVE_IMAGE,
	PIXOP_GREYSCALE,
	PIXOP_SWAP_RED_GREEN,
	PIXOP_SWAP_GREEN_BLUE,
	PIXOP_SWAP_BLUE_RED,
	PIXOP_ALPHA_FROM_COLOR,
	PIXOP_REMOVE_RED,
	PIXOP_REMOVE_GREEN,
	PIXOP_REMOVE_BLUE,	
	
} pixelop;

Code: Select all

static inline byte Pix_Average_RGB (byte* red, byte* green, byte *blue)
{
	unsigned average	= ((unsigned)*red + *green + *blue) / 3;	// Average the colors
	average				= CLAMP (0, average, 255);		// Clamp to range just in case.  Shouldn't happen?
	return average;
}

static inline unsigned Pixel_From_Components (byte* red, byte* green, byte *blue, byte* alpha)
{
	return ((unsigned)*red + ((unsigned)*green << 8) + ((unsigned)*blue << 16) + ((unsigned)*alpha << 24));
}

static inline void Pixel_Component_Swap (byte* component1, byte* component2)
{
	byte extra = *component1;
	*component1 = *component2;
	*component2 = extra; 
}

static inline void Pixel_Perform_Operation (const int* pixelOperation, unsigned* myPixel)
{
	byte red					= (*myPixel      )  & 0xFF;
	byte green					= (*myPixel >> 8 )  & 0xFF;
	byte blue					= (*myPixel >> 16)  & 0xFF;
	byte alpha					= (*myPixel >> 24)  & 0xFF;
	
	switch (*pixelOperation)
	{
		case PIXOP_GREYSCALE:			red = green = blue = Pix_Average_RGB (&red, &green, &blue);			break;				
		case PIXOP_NEGATIVE_IMAGE:		red	= ~red; green = ~green; blue = ~ blue; /* ~ is bitwise NOT */	break;
		case PIXOP_SWAP_RED_GREEN:		Pixel_Component_Swap (&red, &green);								break;
		case PIXOP_SWAP_GREEN_BLUE:		Pixel_Component_Swap (&green, &blue);								break;
		case PIXOP_SWAP_BLUE_RED:		Pixel_Component_Swap (&blue, &red);									break;
		case PIXOP_ALPHA_FROM_COLOR:	alpha = Pix_Average_RGB (&red, &green, &blue);						break;
		case PIXOP_REMOVE_RED:			red = 0;															break;
		case PIXOP_REMOVE_GREEN:		green = 0;															break;
		case PIXOP_REMOVE_BLUE:			blue = 0;															break;				
		default:																							break;
	}
	
	*myPixel						= Pixel_From_Components (&red, &green, &blue, &alpha); 	
}

byte* Image_RGBA_Modify_Range (const int pixelOperation, const int x1, const int y1, const int x2, const int y2, byte *sourceDest, const int sourceDestSize, const int sourceDestWidth, const int sourceDestHeight)
{
	const int	pixelcount			= sourceDestWidth * sourceDestHeight;
	const int	bitsPerPixel		= sourceDestSize / pixelcount; 	// Choke if not RGBA?	
	unsigned*	buffer				= (unsigned*)sourceDest;
	
	for (int row = y1; row <= y2; row ++)
		for (int col = x1; col <= x2; col ++)
		{
			unsigned* thisPixel = buffer + (row  * sourceDestWidth + col);   //			unsigned* thisPixel = &buffer[row  * sourceDestWidth + col];
			Pixel_Perform_Operation (&pixelOperation, thisPixel);
		}
		
	return sourceDest;
}

byte* Image_RGBA_Modify (const int pixelOperation, byte *sourceDest, const int sourceDestSize, const int sourceDestWidth, const int sourceDestHeight)
{	
	const int	pixelcount			= sourceDestWidth * sourceDestHeight;
	const int	bitsPerPixel		= sourceDestSize / pixelcount; 	// Choke if not RGBA?
	unsigned*	thisPixel			= (unsigned*) sourceDest;
	
	for (int i = 0 ; i < pixelcount ; i++, thisPixel++)
		Pixel_Perform_Operation (&pixelOperation, thisPixel);
	
	return sourceDest;
}
Image

The origin of fair of this kind of goes like this. I wanted to load the above charset into my "non-Quake" engine (or whatever it is) project.

But the above image does not have an alpha mask. I spent maybe 45 minutes trying to figure out how to make an alpha channel in Gimp (I did it once for sure) off the existing image. Could not get it to work. :( [This is why I am disappointed with Gimp as free software ... it can be a pain to figure out how to do something, even with Google.]

I already had a bit of an "image library" going and found myself repeating the bulk of a body of functions over and over. I'm like "this is crazy".

Went with inlining and passing the vars as pointers, which I figured would make the inlining more natural.

Why did I want to load that pic? ---------> Next destination ---> OpenGL simple ascii text editor.

(Yes I have enlarge canvas, vertical flip and such already written. Need to determine how to do resample the "right way" .. where the right way may involve treating edges as a special case. I saw some of that in DirectQ.]
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
qbism
Posts: 1238
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Image Manipulation

Post by qbism »

And this was easier than Layer->Transparency->Add Alpha Channel ?! :D
I like PIXOP_ALPHA_FROM_COLOR. It represents many alpha conditions. It could replace large alpha tgas with smaller jpegs. Could add attenuation factor, where alpha is proportional to average squared for example. Also, PIXOP_ALPHA_FROM_RGB where a specific color becomes alpha.
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: Image Manipulation

Post by Spike »

glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
then you don't need an alpha channel.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

qbism wrote:And this was easier than Layer->Transparency->Add Alpha Channel ?! :D
Easier than spending 30 minutes each and every time I try to do something unobvious with it, yes. :D I can't figure out how to view the alpha channel or edit the alpha channel in GIMP either.
Spike wrote:glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
then you don't need an alpha channel.
Well, sure for that particular image :D
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
andrewj
Posts: 133
Joined: Mon Aug 30, 2010 3:29 pm
Location: Australia

Re: Image Manipulation

Post by andrewj »

qbism wrote:And this was easier than Layer->Transparency->Add Alpha Channel ?!
That merely adds an alpha channel to the image, it does not convert black pixels into transparent pixels while keeping white pixels solid.

The easiest way might be to use Colors -> Components -> Decompose, which creates a new image and on that image do Colors -> Components -> Compose and select RGBA as the color model.
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Re: Image Manipulation

Post by goldenboy »

I think Gimp has a color to alpha filter as well.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: Image Manipulation

Post by mh »

Most of the time I'd just do this kind of thing in a shader. GPUs are better at this work and the image won't need preprocessing.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Re: Image Manipulation

Post by revelator »

I think Gimp has a color to alpha filter as well.
it does :) but else i agree with mh a shader is way better and also allows more control, the downside is you need a backend for glsl or arb2.
Productivity is a state of mind.
qbism
Posts: 1238
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Image Manipulation

Post by qbism »

goldenboy wrote:I think Gimp has a color to alpha filter as well.
Yes, under colors menu.
mh wrote:Most of the time I'd just do this kind of thing in a shader. GPUs are better at this work and the image won't need preprocessing.
Even so, could be beneficial to winquake engines.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

qbism wrote:Even so, could be beneficial to winquake engines.
The current incarnation of this code is for RGBA and not for paletted images. So only an engine like FTEQW's past-life software renderer would benefit.

The way things are at the moment. Needless to say I care about 256 color paletted images for reasons too obvious to explain further ...
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

Code: Select all

byte quakePalette [] =
{
    0, 0, 0,
    15, 15, 15,
    31, 31, 31,
    47, 47, 47,
    63, 63, 63,
    75, 75, 75,
    91, 91, 91,
    107, 107, 107,
    123, 123, 123,
    139, 139, 139,
    155, 155, 155,
    171, 171, 171,
    187, 187, 187,
    203, 203, 203,
    219, 219, 219,
    235, 235, 235,
    15, 11, 7,
    23, 15, 11,
    31, 23, 11,
    39, 27, 15,
    47, 35, 19,
    55, 43, 23,
    63, 47, 23,
    75, 55, 27,
    83, 59, 27,
    91, 67, 31,
    99, 75, 31,
    107, 83, 31,
    115, 87, 31,
    123, 95, 35,
    131, 103, 35,
    143, 111, 35,
    11, 11, 15,
    19, 19, 27,
    27, 27, 39,
    39, 39, 51,
    47, 47, 63,
    55, 55, 75,
    63, 63, 87,
    71, 71, 103,
    79, 79, 115,
    91, 91, 127,
    99, 99, 139,
    107, 107, 151,
    115, 115, 163,
    123, 123, 175,
    131, 131, 187,
    139, 139, 203,
    0, 0, 0,
    7, 7, 0,
    11, 11, 0,
    19, 19, 0,
    27, 27, 0,
    35, 35, 0,
    43, 43, 7,
    47, 47, 7,
    55, 55, 7,
    63, 63, 7,
    71, 71, 7,
    75, 75, 11,
    83, 83, 11,
    91, 91, 11,
    99, 99, 11,
    107, 107, 15,
    7, 0, 0,
    15, 0, 0,
    23, 0, 0,
    31, 0, 0,
    39, 0, 0,
    47, 0, 0,
    55, 0, 0,
    63, 0, 0,
    71, 0, 0,
    79, 0, 0,
    87, 0, 0,
    95, 0, 0,
    103, 0, 0,
    111, 0, 0,
    119, 0, 0,
    127, 0, 0,
    19, 19, 0,
    27, 27, 0,
    35, 35, 0,
    47, 43, 0,
    55, 47, 0,
    67, 55, 0,
    75, 59, 7,
    87, 67, 7,
    95, 71, 7,
    107, 75, 11,
    119, 83, 15,
    131, 87, 19,
    139, 91, 19,
    151, 95, 27,
    163, 99, 31,
    175, 103, 35,
    35, 19, 7,
    47, 23, 11,
    59, 31, 15,
    75, 35, 19,
    87, 43, 23,
    99, 47, 31,
    115, 55, 35,
    127, 59, 43,
    143, 67, 51,
    159, 79, 51,
    175, 99, 47,
    191, 119, 47,
    207, 143, 43,
    223, 171, 39,
    239, 203, 31,
    255, 243, 27,
    11, 7, 0,
    27, 19, 0,
    43, 35, 15,
    55, 43, 19,
    71, 51, 27,
    83, 55, 35,
    99, 63, 43,
    111, 71, 51,
    127, 83, 63,
    139, 95, 71,
    155, 107, 83,
    167, 123, 95,
    183, 135, 107,
    195, 147, 123,
    211, 163, 139,
    227, 179, 151,
    171, 139, 163,
    159, 127, 151,
    147, 115, 135,
    139, 103, 123,
    127, 91, 111,
    119, 83, 99,
    107, 75, 87,
    95, 63, 75,
    87, 55, 67,
    75, 47, 55,
    67, 39, 47,
    55, 31, 35,
    43, 23, 27,
    35, 19, 19,
    23, 11, 11,
    15, 7, 7,
    187, 115, 159,
    175, 107, 143,
    163, 95, 131,
    151, 87, 119,
    139, 79, 107,
    127, 75, 95,
    115, 67, 83,
    107, 59, 75,
    95, 51, 63,
    83, 43, 55,
    71, 35, 43,
    59, 31, 35,
    47, 23, 27,
    35, 19, 19,
    23, 11, 11,
    15, 7, 7,
    219, 195, 187,
    203, 179, 167,
    191, 163, 155,
    175, 151, 139,
    163, 135, 123,
    151, 123, 111,
    135, 111, 95,
    123, 99, 83,
    107, 87, 71,
    95, 75, 59,
    83, 63, 51,
    67, 51, 39,
    55, 43, 31,
    39, 31, 23,
    27, 19, 15,
    15, 11, 7,
    111, 131, 123,
    103, 123, 111,
    95, 115, 103,
    87, 107, 95,
    79, 99, 87,
    71, 91, 79,
    63, 83, 71,
    55, 75, 63,
    47, 67, 55,
    43, 59, 47,
    35, 51, 39,
    31, 43, 31,
    23, 35, 23,
    15, 27, 19,
    11, 19, 11,
    7, 11, 7,
    255, 243, 27,
    239, 223, 23,
    219, 203, 19,
    203, 183, 15,
    187, 167, 15,
    171, 151, 11,
    155, 131, 7,
    139, 115, 7,
    123, 99, 7,
    107, 83, 0,
    91, 71, 0,
    75, 55, 0,
    59, 43, 0,
    43, 31, 0,
    27, 15, 0,
    11, 7, 0,
    0, 0, 255,
    11, 11, 239,
    19, 19, 223,
    27, 27, 207,
    35, 35, 191,
    43, 43, 175,
    47, 47, 159,
    47, 47, 143,
    47, 47, 127,
    47, 47, 111,
    47, 47, 95,
    43, 43, 79,
    35, 35, 63,
    27, 27, 47,
    19, 19, 31,
    11, 11, 15,
    43, 0, 0,
    59, 0, 0,
    75, 7, 0,
    95, 7, 0,
    111, 15, 0,
    127, 23, 7,
    147, 31, 7,
    163, 39, 11,
    183, 51, 15,
    195, 75, 27,
    207, 99, 43,
    219, 127, 59,
    227, 151, 79,
    231, 171, 95,
    239, 191, 119,
    247, 211, 139,
    167, 123, 59,
    183, 155, 55,
    199, 195, 55,
    231, 227, 87,
    127, 191, 255,
    171, 231, 255,
    215, 255, 255,
    103, 0, 0,
    139, 0, 0,
    179, 0, 0,
    215, 0, 0,
    255, 0, 0,
    255, 243, 147,
    255, 247, 199,
    255, 255, 255,
    159, 91, 83
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

Raw materials for planning: After looking over a few different ways of palette reduction, adapting Qwalk's (Sajt's model converter) way seems easiest:

Edit ... uh ... doesn't Super8 have something like this built-in??? So much engine stuff happening so fast ... if so, do not reinvent wheel ...

Code: Select all

/*
    QShed <http://www.icculus.org/qshed>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <string.h>

#include "global.h"
#include "image.h"

typedef enum image_format_e
{
	IMGFMT_MISSING,
	IMGFMT_UNRECOGNIZED,
	IMGFMT_PCX,
	IMGFMT_TGA,
	IMGFMT_JPG
} image_format_t;

static image_format_t get_image_format(const char *filename)
{
	const char *ext = strrchr(filename, '.');

	if (!ext)
		return IMGFMT_MISSING;

	if (!strcasecmp(ext, ".pcx"))
		return IMGFMT_PCX;
	if (!strcasecmp(ext, ".tga"))
		return IMGFMT_TGA;
	if (!strcasecmp(ext, ".jpg") || !strcasecmp(ext, ".jpeg"))
		return IMGFMT_JPG;

	return IMGFMT_UNRECOGNIZED;
}

image_rgba_t *image_load(mem_pool_t *pool, const char *filename, void *filedata, size_t filesize, char **out_error)
{
	switch (get_image_format(filename))
	{
	default:
	case IMGFMT_MISSING:
		return (void)(out_error && (*out_error = msprintf("missing file extension"))), NULL;
	case IMGFMT_UNRECOGNIZED:
		return (void)(out_error && (*out_error = msprintf("unrecognized file extension"))), NULL;
	case IMGFMT_PCX: return image_pcx_load(pool, filedata, filesize, out_error);
	case IMGFMT_TGA: return image_tga_load(pool, filedata, filesize, out_error);
	case IMGFMT_JPG: return image_jpg_load(pool, filedata, filesize, out_error);
	}
}

image_rgba_t *image_load_from_file(mem_pool_t *pool, const char *filename, char **out_error)
{
	void *filedata;
	size_t filesize;
	image_rgba_t *image;

	if (!loadfile(filename, &filedata, &filesize, out_error))
		return NULL;

	image = image_load(pool, filename, filedata, filesize, out_error);

	qfree(filedata);
	return image;
}

bool_t image_save(const char *filename, const image_rgba_t *image, char **out_error)
{
	bool_t (*savefunc)(const image_rgba_t *image, xbuf_t *xbuf, char **out_error) = NULL;
	xbuf_t *xbuf;

	switch (get_image_format(filename))
	{
	default:
	case IMGFMT_MISSING:
		return (void)(out_error && (*out_error = msprintf("missing file extension"))), false;
	case IMGFMT_UNRECOGNIZED:
		return (void)(out_error && (*out_error = msprintf("unrecognized file extension"))), false;
	case IMGFMT_PCX:
		return (void)(out_error && (*out_error = msprintf("cannot save 32-bit image to PCX"))), false;
	case IMGFMT_TGA:
		savefunc = image_tga_save;
		break;
	case IMGFMT_JPG:
		return (void)(out_error && (*out_error = msprintf("jpeg saving not implemented"))), false; /* FIXME - so implement it! */
	}

/* allocate write buffer and set it up to flush directly to the file */
	xbuf = xbuf_create_file(262144, filename, out_error);
	if (!xbuf)
		return false;

/* write image data */
	if (!(*savefunc)(image, xbuf, out_error))
	{
		xbuf_free(xbuf, NULL);
		return false;
	}

/* flush any remaining data to file and free the buffer */
	return xbuf_finish_file(xbuf, out_error);
}

bool_t image_paletted_save(const char *filename, const image_paletted_t *image, char **out_error)
{
	bool_t (*savefunc)(const image_paletted_t *image, xbuf_t *xbuf, char **out_error) = NULL;
	xbuf_t *xbuf;

	switch (get_image_format(filename))
	{
	default:
	case IMGFMT_MISSING:
		return (void)(out_error && (*out_error = msprintf("missing file extension"))), false;
	case IMGFMT_UNRECOGNIZED:
		return (void)(out_error && (*out_error = msprintf("unrecognized file extension"))), false;
	case IMGFMT_PCX:
		savefunc = image_pcx_save;
		break;
	case IMGFMT_TGA:
		return (void)(out_error && (*out_error = msprintf("cannot save 8-bit image to TGA"))), false;
	case IMGFMT_JPG:
		return (void)(out_error && (*out_error = msprintf("cannot save 8-bit image to JPEG"))), false;
	}

/* allocate write buffer and set it up to flush directly to the file */
	xbuf = xbuf_create_file(262144, filename, out_error);
	if (!xbuf)
		return false;

/* write image data */
	if (!(*savefunc)(image, xbuf, out_error))
	{
		xbuf_free(xbuf, NULL);
		return false;
	}

/* flush any remaining data to file and free the buffer */
	return xbuf_finish_file(xbuf, out_error);
}

image_rgba_t *image_alloc(mem_pool_t *pool, int width, int height)
{
	image_rgba_t *image;
	if (width < 1 || height < 1)
		return NULL;
	image = (image_rgba_t*)mem_alloc(pool, sizeof(image_rgba_t) + width * height * 4);
	if (!image)
		return NULL;
	image->width = width;
	image->height = height;
	image->pixels = (unsigned char*)(image + 1);
	return image;
}

void image_free(image_rgba_t **image)
{
	mem_free(*image);
	*image = NULL;
}

image_paletted_t *image_paletted_alloc(mem_pool_t *pool, int width, int height)
{
	image_paletted_t *image;
	if (width < 1 || height < 1)
		return NULL;
	image = (image_paletted_t*)mem_alloc(pool, sizeof(image_paletted_t) + width * height);
	if (!image)
		return NULL;
	image->width = width;
	image->height = height;
	image->pixels = (unsigned char*)(image + 1);
	return image;
}

void image_paletted_free(image_paletted_t **image)
{
	mem_free(*image);
	*image = NULL;
}

image_rgba_t *image_createfill(mem_pool_t *pool, int width, int height, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
	image_rgba_t *image;
	int i;

	image = image_alloc(pool, width, height);
	if (!image)
		return NULL;

	for (i = 0; i < width * height; i++)
	{
		image->pixels[i*4+0] = r;
		image->pixels[i*4+1] = g;
		image->pixels[i*4+2] = b;
		image->pixels[i*4+3] = a;
	}

	return image;
}

image_rgba_t *image_clone(mem_pool_t *pool, const image_rgba_t *source)
{
	image_rgba_t *image;

	if (!source)
		return NULL;

	image = image_alloc(pool, source->width, source->height);
	if (!image)
		return NULL;

	memcpy(image->pixels, source->pixels, source->width * source->height * 4);

	return image;
}

static unsigned char palettize_colour(const palette_t *palette, bool_t fullbright, const unsigned char rgb[3])
{
	int i, dist;
	int besti = -1, bestdist = 0;

	for (i = 0; i < 256; i++)
	{
		if (fullbright != !!(palette->fullbright_flags[i >> 5] & (1U << (i & 31))))
			continue;

		dist = 299 * abs(palette->rgb[i*3+0] - rgb[0]) + 587 * abs(palette->rgb[i*3+1] - rgb[1]) + 114 * abs(palette->rgb[i*3+2] - rgb[2]);

		if (besti == -1 || dist < bestdist)
		{
			besti = i;
			bestdist = dist;
		}
	}

	return (besti != -1) ? besti : 0;
}

image_paletted_t *image_palettize(mem_pool_t *pool, const palette_t *palette, const image_rgba_t *source_diffuse, const image_rgba_t *source_fullbright)
{
	bool_t palette_has_fullbrights;
	image_paletted_t *pimage;
	int i;

	if (!source_diffuse && !source_fullbright)
		return NULL;

	pimage = (image_paletted_t*)mem_alloc(pool, sizeof(image_paletted_t) + source_diffuse->width * source_diffuse->height);
	if (!pimage)
		return NULL;
	pimage->width = source_diffuse->width;
	pimage->height = source_diffuse->height;
	pimage->pixels = (unsigned char*)(pimage + 1);
	pimage->palette = *palette;

	palette_has_fullbrights = false;
	for (i = 0; i < 8; i++)
		if (palette->fullbright_flags[i])
			palette_has_fullbrights = true;

	if (source_diffuse && source_fullbright)
	{
		const unsigned char *in_diffuse = source_diffuse->pixels;
		const unsigned char *in_fullbright = source_fullbright->pixels;
		unsigned char *out = pimage->pixels;
		for (i = 0; i < pimage->width * pimage->height; i++, in_diffuse += 4, in_fullbright += 4, out++)
		{
			if (in_fullbright[0] || in_fullbright[1] || in_fullbright[2])
				*out = palettize_colour(palette, palette_has_fullbrights, in_fullbright);
			else
				*out = palettize_colour(palette, false, in_diffuse);
		}
	}
	else if (source_diffuse)
	{
		const unsigned char *in_diffuse = source_diffuse->pixels;
		unsigned char *out = pimage->pixels;
		for (i = 0; i < pimage->width * pimage->height; i++, in_diffuse += 4, out++)
			*out = palettize_colour(palette, false, in_diffuse);
	}
	else if (source_fullbright)
	{
		const unsigned char *in_fullbright = source_fullbright->pixels;
		unsigned char *out = pimage->pixels;
		for (i = 0; i < pimage->width * pimage->height; i++, in_fullbright += 4, out++)
			*out = palettize_colour(palette, palette_has_fullbrights, in_fullbright);
	}

	return pimage;
}

/* FIXME - rewrite the minification code... it's ugly, probably slow, and maybe also has some weighting issues */
image_rgba_t *image_resize(mem_pool_t *pool, const image_rgba_t *source, int newwidth, int newheight)
{
	image_rgba_t *intermediate;
	image_rgba_t *image;
	int x, y, i;

	intermediate = image_alloc(pool, newwidth, source->height);
	if (!intermediate)
		return NULL;

	image = image_alloc(pool, newwidth, newheight);
	if (!image)
	{
		image_free(&intermediate);
		return NULL;
	}

/* horizontal resize */
	if (newwidth > source->width)
	{
		for (x = 0; x < newwidth; x++)
		{
			float fx = (float)x * (float)source->width / (float)newwidth;
			int x1 = (int)fx;
			int x2 = min(x1 + 1, source->width - 1);
			float fx2 = fx - (float)x1;
			float fx1 = 1.0f - fx2;
			int ifx1 = (int)(fx1 * 256.0f);
			int ifx2 = (int)(fx2 * 256.0f);
			unsigned char *out = intermediate->pixels + x * 4;
			const unsigned char *in1 = source->pixels + x1 * 4;
			const unsigned char *in2 = source->pixels + x2 * 4;

			for (y = 0; y < source->height; y++, out += newwidth * 4, in1 += source->width * 4, in2 += source->width * 4)
				for (i = 0; i < 4; i++)
					out[i] = (in1[i] * ifx1 + in2[i] * ifx2 + 127) >> 8;
		}
	}
	else if (newwidth < source->width)
	{
		for (y = 0; y < source->height; y++)
		for (x = 0; x < newwidth; x++)
		{
			const unsigned char *in = source->pixels + y * source->width * 4;
			float colour[4], count;
			float fxx = (x * source->width) / (float)newwidth;
			int x1 = ((x - 1) * source->width + newwidth / 2) / newwidth;
			int x2 = ((x + 1) * source->width + newwidth / 2) / newwidth;

			x1 = max(x1, 0);
			x2 = min(x2, source->width - 1);

			colour[0] = colour[1] = colour[2] = colour[3] = 0;
			count = 0;

			for (i = x1; i <= x2; i++)
			{
				float dist;

				if (i < fxx)
					dist = (fxx - x1) - (fxx - i);
				else if (i > fxx)
					dist = (x2 - fxx) - (i - fxx);
				else
					dist = max(x2 - fxx, fxx - x1);

				dist *= dist; /* square it to increase sharpness a little */

				colour[0] += dist * in[i*4+0];
				colour[1] += dist * in[i*4+1];
				colour[2] += dist * in[i*4+2];
				colour[3] += dist * in[i*4+3];
				count += dist;
			}

			if (!count)
				count = 1; /* i don't THINK this should happen... */

			for (i = 0; i < 4; i++)
				intermediate->pixels[(y*newwidth+x)*4+i] = (unsigned char)(colour[i] / count);
		}
	}
	else
	{
		memcpy(intermediate->pixels, source->pixels, source->width * source->height * 4);
	}

/* vertical resize */
	if (newheight > source->height)
	{
		for (y = 0; y < newheight; y++)
		{
			float fy = (float)y * (float)source->height / (float)newheight;
			int y1 = (int)fy;
			int y2 = min(y1 + 1, source->height - 1);
			float fy2 = fy - (float)y1;
			float fy1 = 1.0f - fy2;
			int ify1 = (int)(fy1 * 256.0f);
			int ify2 = (int)(fy2 * 256.0f);
			unsigned char *out = image->pixels + y * newwidth * 4;
			const unsigned char *in1 = intermediate->pixels + y1 * newwidth * 4;
			const unsigned char *in2 = intermediate->pixels + y2 * newwidth * 4;

			for (x = 0; x < newwidth; x++, out += 4, in1 += 4, in2 += 4)
				for (i = 0; i < 4; i++)
					out[i] = (in1[i] * ify1 + in2[i] * ify2 + 127) >> 8;
		}
	}
	else if (newheight < source->height)
	{
		for (y = 0; y < newheight; y++)
		for (x = 0; x < newwidth; x++)
		{
			float colour[4], count;
			float fyy = (y * source->height) / (float)newheight;
			int y1 = ((y - 1) * source->height + newheight / 2) / newheight;
			int y2 = ((y + 1) * source->height + newheight / 2) / newheight;

			y1 = max(y1, 0);
			y2 = min(y2, source->height - 1);

			colour[0] = colour[1] = colour[2] = colour[3] = 0;
			count = 0;

			for (i = y1; i <= y2; i++)
			{
				const unsigned char *in = intermediate->pixels + (i * newwidth + x) * 4;
				float dist;

				if (i < fyy)
					dist = (fyy - y1) - (fyy - i);
				else if (i > fyy)
					dist = (y2 - fyy) - (i - fyy);
				else
					dist = max(y2 - fyy, fyy - y1);

				dist *= dist; /* square it to increase sharpness a little */

				colour[0] += dist * in[0];
				colour[1] += dist * in[1];
				colour[2] += dist * in[2];
				colour[3] += dist * in[3];
				count += dist;
			}

			if (!count)
				count = 1; /* i don't THINK this should happen... */

			for (i = 0; i < 4; i++)
				image->pixels[(y*newwidth+x)*4+i] = (unsigned char)(colour[i] / count);
		}
	}
	else
	{
		memcpy(image->pixels, intermediate->pixels, newwidth * newheight * 4);
	}

	image_free(&intermediate);

	return image;
}

/* pad an image to a larger size. the edge pixels will be repeated instead of filled with black, to avoid any unwanted
 * bleeding if mipmapped and/or rendered with texture filtering. */
image_rgba_t *image_pad(mem_pool_t *pool, const image_rgba_t *source, int width, int height)
{
	image_rgba_t *image;
	const unsigned char *inpixels;
	unsigned char *outp;
	int x, y;

	if (width < source->width || height < source->height)
		return NULL;

	image = image_alloc(pool, width, height);
	if (!image)
		return NULL;

	inpixels = source->pixels;
	outp = image->pixels;

	for (y = 0; y < source->height; y++)
	{
		memcpy(outp, inpixels, source->width * 4);
		outp += source->width * 4;
		inpixels += source->width * 4;

		for (x = source->width; x < width; x++, outp += 4)
		{
			outp[0] = outp[-4];
			outp[1] = outp[-3];
			outp[2] = outp[-2];
			outp[3] = outp[-1];
		}
	}
	for (; y < height; y++)
	{
		memcpy(outp, outp - width * 4, width * 4);
		outp += width * 4;
	}

	return image;
}

void image_drawpixel(image_rgba_t *image, int x, int y, unsigned char r, unsigned char g, unsigned char b)
{
	unsigned char *ptr;
	if (x < 0 || x >= image->width)
		return;
	if (y < 0 || y >= image->height)
		return;
	ptr = image->pixels + (y * image->width + x) * 4;
	ptr[0] = r;
	ptr[1] = g;
	ptr[2] = b;
	ptr[3] = 255;
}

void image_drawline(image_rgba_t *image, int x1, int y1, int x2, int y2, unsigned char r, unsigned char g, unsigned char b)
{
	int i, dx, dy, dxabs, dyabs, sdx, sdy, px, py;

	if (x1 < 0 && x2 < 0) return;
	if (y1 < 0 && y2 < 0) return;
	if (x1 >= image->width && x2 >= image->width) return;
	if (y1 >= image->height && y2 >= image->height) return;

	dx = x2 - x1;
	dy = y2 - y1;

	dxabs = abs(dx);
	dyabs = abs(dy);

	sdx = (dx < 0) ? -1 : ((dx > 0) ? 1 : 0);
	sdy = (dy < 0) ? -1 : ((dy > 0) ? 1 : 0);

	px = x1;
	py = y1;

	image_drawpixel(image, px, py, r, g, b);

	if (dxabs >= dyabs) /* line is more horizontal than vertical */
	{
		int y = dxabs >> 1;

		for (i = 0; i < dxabs; i++)
		{
			y += dyabs;
			if (y >= dxabs)
			{
				y -= dxabs;
				py += sdy;
			}
			px += sdx;
			image_drawpixel(image, px, py, r, g, b);
		}
	}
	else /* line is more vertical than horizontal */
	{
		int x = dyabs >> 1;

		for (i = 0; i < dyabs; i++)
		{
			x += dxabs;
			if (x >= dyabs)
			{
				x -= dyabs;
				px += sdx;
			}
			py += sdy;
			image_drawpixel(image, px, py, r, g, b);
		}
	}
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

Code: Select all

	PIXOP_QUAKE_COLORS_NO_FULLBRIGHT,
Converts RGBA to RGBA with colors to Quake palette (the non-fullbright ones).


Image

Code: Select all

#include "quakepalette.h"
static void Pixel_Apply_Best_Palette_Index (byte* red, byte* green, byte *blue, byte* myPalette, int* myPaletteNumColors)
{
	int				bestColorIndex		=  -1;
	int				bestColorDistance	=  -1;
	const byte*		currentPaletteIndex	= myPalette;
	
	for (int i = 0; i < *myPaletteNumColors; i++, currentPaletteIndex += 3 )
	{
		const int		redDistance		= *red   - currentPaletteIndex[0];
		const int		greenDistance	= *green - currentPaletteIndex[1];
		const int		blueDistance	= *blue  - currentPaletteIndex[2];
		const unsigned	ColorDistance	= SQUARED(redDistance) + SQUARED(greenDistance) + SQUARED(blueDistance); // Could sqrt this but no reason to do so
		
		if (ColorDistance < bestColorDistance)
		{
			bestColorDistance	= ColorDistance;
			bestColorIndex		= i;
		}
	}
	
	*red	= myPalette[bestColorIndex * 3 + 0];
	*green	= myPalette[bestColorIndex * 3 + 1];
	*blue	= myPalette[bestColorIndex * 3 + 2];
	
}

static void Pixel_Apply_Best_Quake_NoFullbright (byte* red, byte* green, byte *blue)
{
	int numNoFullBrightColors = 224;
	Pixel_Apply_Best_Palette_Index (red, green, blue, quakePalette, &numNoFullBrightColors);
}
quakepalette.h

Code: Select all

/***********************************************************************************************************\
 *****
 **
 **	quakepalette.h:	Quake palette
 **
 *****
 \***********************************************************************************************************/



#ifndef _QUAKEPALETTE_H
#define _QUAKEPALETTE_H

byte quakePalette [] =
{
      0,   0,   0,
     15,  15,  15,
     31,  31,  31,
     47,  47,  47,
     63,  63,  63,
     75,  75,  75,
     91,  91,  91,
    107, 107, 107,
    123, 123, 123,
    139, 139, 139,
    155, 155, 155,
    171, 171, 171,
    187, 187, 187,
    203, 203, 203,
    219, 219, 219,
    235, 235, 235,
     15,  11,   7,
     23,  15,  11,
     31,  23,  11,
     39,  27,  15,
     47,  35,  19,
     55,  43,  23,
     63,  47,  23,
     75,  55,  27,
     83,  59,  27,
     91,  67,  31,
     99,  75,  31,
    107,  83,  31,
    115,  87,  31,
    123,  95,  35,
    131, 103,  35,
    143, 111,  35,
     11,  11,  15,
     19,  19,  27,
     27,  27,  39,
     39,  39,  51,
     47,  47,  63,
     55,  55,  75,
     63,  63,  87,
     71,  71, 103,
     79,  79, 115,
     91,  91, 127,
     99,  99, 139,
	107, 107, 151,
    115, 115, 163,
    123, 123, 175,
    131, 131, 187,
    139, 139, 203,
      0,   0,   0,
      7,   7,   0,
     11,  11,   0,
     19,  19,   0,
     27,  27,   0,
     35,  35,   0,
     43,  43,   7,
     47,  47,   7,
     55,  55,   7,
     63,  63,   7,
     71,  71,   7,
     75,  75,  11,
     83,  83,  11,
     91,  91,  11,
     99,  99,  11,
    107, 107,  15,
      7,   0,   0,
     15,   0,   0,
     23,   0,   0,
     31,   0,   0,
     39,   0,   0,
     47,   0,   0,
     55,   0,   0,
     63,   0,   0,
     71,   0,   0,
     79,   0,   0,
     87,   0,   0,
     95,   0,   0,
    103,   0,   0,
    111,   0,   0,
    119,   0,   0,
    127,   0,   0,
     19,  19,   0,
     27,  27,   0,
     35,  35,   0,
     47,  43,   0,
     55,  47,   0,
     67,  55,   0,
     75,  59,   7,
     87,  67,   7,
     95,  71,   7,
    107,  75,  11,
    119,  83,  15,
    131,  87,  19,
    139,  91,  19,
    151,  95,  27,
    163,  99,  31,
    175, 103,  35,
     35,  19,   7,
     47,  23,  11,
     59,  31,  15,
     75,  35,  19,
     87,  43,  23,
     99,  47,  31,
    115,  55,  35,
    127,  59,  43,
    143,  67,  51,
    159,  79,  51,
    175,  99,  47,
    191, 119,  47,
    207, 143,  43,
    223, 171,  39,
    239, 203,  31,
    255, 243,  27,
     11,   7,   0,
     27,  19,   0,
     43,  35,  15,
     55,  43,  19,
     71,  51,  27,
     83,  55,  35,
     99,  63,  43,
    111,  71,  51,
    127,  83,  63,
    139,  95,  71,
    155, 107,  83,
    167, 123,  95,
    183, 135, 107,
    195, 147, 123,
    211, 163, 139,
    227, 179, 151,
    171, 139, 163,
    159, 127, 151,
    147, 115, 135,
    139, 103, 123,
    127,  91, 111,
    119,  83,  99,
    107,  75,  87,
     95,  63,  75,
     87,  55,  67,
     75,  47,  55,
     67,  39,  47,
     55,  31,  35,
     43,  23,  27,
     35,  19,  19,
     23,  11,  11,
     15,   7,   7,
    187, 115, 159,
    175, 107, 143,
    163,  95, 131,
    151,  87, 119,
    139,  79, 107,
    127,  75,  95,
    115,  67,  83,
    107,  59,  75,
     95,  51,  63,
     83,  43,  55,
     71,  35,  43,
     59,  31,  35,
     47,  23,  27,
     35,  19,  19,
     23,  11,  11,
     15,   7,   7,
    219, 195, 187,
    203, 179, 167,
    191, 163, 155,
    175, 151, 139,
    163, 135, 123,
    151, 123, 111,
    135, 111,  95,
    123,  99,  83,
    107,  87,  71,
     95,  75,  59,
     83,  63,  51,
     67,  51,  39,
     55,  43,  31,
     39,  31,  23,
     27,  19,  15,
     15,  11,   7,
    111, 131, 123,
    103, 123, 111,
     95, 115, 103,
     87, 107,  95,
     79,  99,  87,
     71,  91,  79,
     63,  83,  71,
     55,  75,  63,
     47,  67,  55,
     43,  59,  47,
     35,  51,  39,
     31,  43,  31,
     23,  35,  23,
     15,  27,  19,
     11,  19,  11,
      7,  11,   7,
    255, 243,  27,
    239, 223,  23,
    219, 203,  19,
    203, 183,  15,
    187, 167,  15,
    171, 151,  11,
    155, 131,   7,
    139, 115,   7,
    123,  99,   7,
    107,  83,   0,
     91,  71,   0,
     75,  55,   0,
     59,  43,   0,
     43,  31,   0,
     27,  15,   0,
     11,   7,   0,
      0,   0, 255,
     11,  11, 239,
     19,  19, 223,
     27,  27, 207,
     35,  35, 191,
     43,  43, 175,
     47,  47, 159,
     47,  47, 143,
     47,  47, 127,
     47,  47, 111,
     47,  47,  95,
     43,  43,  79,
     35,  35,  63,
     27,  27,  47,
     19,  19,  31,
     11,  11,  15,
     43,   0,   0,
     59,   0,   0,
     75,   7,   0,
     95,   7,   0,
    111,  15,   0,
    127,  23,   7,
    147,  31,   7,
    163,  39,  11,
    183,  51,  15,
    195,  75,  27,
    207,  99,  43,
    219, 127,  59,
    227, 151,  79,
    231, 171,  95,
    239, 191, 119,
    247, 211, 139,
    167, 123,  59,
    183, 155,  55,
    199, 195,  55,
    231, 227,  87,
    127, 191, 255,
    171, 231, 255,
    215, 255, 255,
    103,   0,   0,
    139,   0,   0,
    179,   0,   0,
    215,   0,   0,
    255,   0,   0,
    255, 243, 147,
    255, 247, 199,
    255, 255, 255,
    159,  91,  83,
};

#endif // _QUAKEPALETTE_H
[And I just noticed a weird oversight that actually works for a really funny reason and I think I'll just keep that in there. ColorDistance < bestColorDistance ---> but bestColorDistance starts as -1. Still works of all things probably because ColorDistance is unsigned int so -1 evaluates to a very large number. ]
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
andrewj
Posts: 133
Joined: Mon Aug 30, 2010 3:29 pm
Location: Australia

Re: Image Manipulation

Post by andrewj »

Baker wrote:[And I just noticed a weird oversight that actually works for a really funny reason and I think I'll just keep that in there. ColorDistance < bestColorDistance ---> but bestColorDistance starts as -1. Still works of all things probably because ColorDistance is unsigned int so -1 evaluates to a very large number. ]
That is probably compiler-dependent behavior, not something you want to rely on, so fix that crappy part of the code.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

andrewj wrote:That is probably compiler-dependent behavior, not something you want to rely on, so fix that crappy part of the code.
unsigned bestColorDistance = 0xFFFFFFFF;
:D

Code: Select all

typedef enum
{
	TRANSFORM_FLIP_VERTICAL,
	TRANSFORM_FLIP_HORIZONTAL,
	TRANSFORM_ROTATE_CLOCKWISE90,
	TRANSFORM_ROTATE_COUNTERCLOCKWISE90,	
	TRANSFORM_ROTATE_180,
} transform_simple_op;

Code: Select all

byte* Image_RGBA_Transform (const int transformOperation, byte *source, const int sourceSize, int* sourceWidth, int* sourceHeight)
{	
	const int	pixelcount			= *sourceWidth * *sourceHeight;
	const int	bitsPerPixel		= sourceSize / pixelcount; 	// Choke if not RGBA?
	const fbool swapWidthHeight		= (transformOperation == TRANSFORM_ROTATE_CLOCKWISE90 || transformOperation == TRANSFORM_ROTATE_COUNTERCLOCKWISE90);
	
	unsigned*	oldImage			= (unsigned*)source;
	const int	oldWidth			= *sourceWidth;
	const int	oldHeight			= *sourceHeight;

	unsigned*	newImage			= Memory_malloc(sourceSize, "New Buffer");
	int			newWidth			= oldWidth;
	int			newHeight			= oldHeight;
	
	unsigned*	newPixel			= newImage;
	
	if (swapWidthHeight)
	{
		newWidth	= oldHeight;
		newHeight	= oldWidth;
	}
									   
									   
	for (int row = 0; row < oldHeight; row ++)
		for (int col = 0; col < oldWidth; col ++, newPixel ++)
		{
			int oldrow, oldcol;
			switch (transformOperation)
			{
				case TRANSFORM_ROTATE_COUNTERCLOCKWISE90:
													oldrow = col;					oldcol = (oldHeight - 1) - row;	break;
				case TRANSFORM_ROTATE_CLOCKWISE90:	oldrow = (oldWidth - 1) - col;	oldcol = row;					break;
				case TRANSFORM_FLIP_VERTICAL:		oldrow = (oldHeight - 1) - row;	oldcol = col;					break;
				case TRANSFORM_FLIP_HORIZONTAL:		oldrow = row;					oldcol = (oldWidth - 1) - col;	break; 
				case TRANSFORM_ROTATE_180:			oldrow = (oldHeight - 1) - row;	oldcol = (oldWidth - 1) - col;	break;
				default:																							break;
			}
			*newPixel = oldImage[oldrow * oldWidth + oldcol];
		}

	
	// Destroy old image
	Memory_free (source);
	
	*sourceWidth	= newWidth;
	*sourceHeight	= newHeight;
	return (byte*) newImage;
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Post Reply