Image Manipulation

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

Re: Image Manipulation

Post by Baker »

To do list:

HARD

* Rotate by angle (Hard?)
* color quantization with X colors (i.e. reduce color depth to X and select X colors to represent entire image)

EASY

* Resample
* Crop
* "Enlarge Canvas"
* Gamma Adjust
* Contrast Adjust
* Count colors used
* cut
* paste at x,y
* generic color swap without tolerance
* create thumbnail (basically same as resample)

MEDIUM

* fill at x,y with color
* generic color swap with some sort of tolerance
* HSL stuff (hue saturation luminance) hue = atan2(2 * r - g - b, cube root of 3 * (g - b))
* Draw fixed width font at x, y; draw fixed width font at r, c

[Other thoughts: how to define a complicated selection. Thoughts on drawing a line, circle, freehand with a brush]
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 »

Count the colors used in an image:

Code: Select all

int Image_RGBA_Info_NumColorsUsed (const byte *source, const int sourceSize, const int sourceWidth, const int sourceHeight)
{
	unsigned	colorsTable[sourceWidth*sourceHeight]; // Max possible colors is num pixels in image
	int			numColorsTable = 0;
	unsigned*	currentPixel = (unsigned*) source;

	for (int row = 0; row < sourceHeight; row ++)
		for (int col = 0; col < sourceWidth; col ++, currentPixel ++)
		{
			unsigned thisPixelColor = *currentPixel & 0x00FFFFFF; // Strip alpha
			// Find currentPixel in colorsTable
			int existingColor;
			for (existingColor = 0; existingColor < numColorsTable; existingColor ++)
				if (colorsTable[existingColor] == thisPixelColor) break;
			
			// If color was not found, add it the end of the table and increase numColorsTable
			if (existingColor == numColorsTable) colorsTable[numColorsTable ++] = thisPixelColor;
		}
	
	return numColorsTable;
}
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 »

Paste image. Accepts any X, Y range including ranges with no actual overlap with main image. Effectively crop and enlarge canvas type of operations are this as well.

Code: Select all

byte* Image_RGBA_PasteAt (const int pasteop, const int x, const int y, 
						  const byte* source, const int sourceSize, const int sourceWidth, const int sourceHeight,
						        byte* dest, const int destSize, const int destWidth, const int destHeight)
{
	// In an unclipped world, this is the answer
	const int	realx1	= x;						
	const int   realy1	= y;
	const int	realx2	= (x + sourceWidth  - 1);
	const int 	realy2	= (y + sourceHeight - 1);
	
	// Range checking
	const fbool xOverlap= 0 <= realx2 && realx1 <= (destWidth  - 1); // Some source X overlaps with dest X
	const fbool yOverlap= 0 <= realy2 && realy1 <= (destHeight - 1); // Some source Y overlaps with dest Y
	
	if (xOverlap && yOverlap)
	{
		const int clippedx1	= FLOOR   (realx1, 0);					// Clip the range of operation to actual surface of dest
		const int clippedy1	= FLOOR   (realy1, 0);	
		const int clippedx2	= CEILING (realx2, destWidth  - 1 );	// realx2 > destWidth  ? destWidth  : realx2;
		const int clippedy2	= CEILING (realy2, destHeight - 1 );
		
		for (int destRow = clippedy1, sourceRow = 0; destRow <= clippedy2; destRow ++, sourceRow ++)
		{
			unsigned* sourcePixel	= (unsigned*)&source[ 4* (sourceRow * sourceWidth + 0)        ]; // Because a full paste starts at col 0
			unsigned* destPixel		= (unsigned*)&dest  [ 4* (destRow   * destWidth   + clippedx1)];
			
			// Could memcpy and even cut some vars out of for loop, but I think it has explanatory value ...
			for (int destCol = clippedx1, sourceCol = 0; destCol <= clippedx2; destCol ++, sourceCol ++, destPixel ++, sourcePixel ++)
				*destPixel = *sourcePixel;
		}
	}
	
	return dest;
}
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 »

I've spent some time off and on writing my own resample function after discovering how shitty the quick ones in Quake engines actually are. Sure I looked over the bicubic and bilinear ones that exist out there, but I kinda wanted to get my hands dirty so I have the capability of writing more advanced functions in the future if needed (even though I am most certainly rewriting one of those, yet I can't tell which one but it is probably bilinear but more precise and slower than the quick and dirty Quake resample).

My first experiment involved mapping an original pixel unto remapped pixels, which may or may not be any sort of convenient 1 to some integer or convenient fraction relationship.

Then it occurred to me that for rotating an image, the calculations get a bit more fun. And you can go with remapping the pixel as a circle onto several other pixel -- which involves some tremendous fun trying to calculate circle/rect overlap --- or just do more simple rect and rect mapping.

Curiously, the more you mess around with 2D or 3D math more intriguing it gets. And a bit addictive.

Part 1:

Code: Select all

byte* Image_RGBA_Resample (const byte* source, const int sourceSize, const int sourceWidth, const int sourceHeight,
						   byte* dest,   const int destSize,   const int destWidth,   const int destHeight)
{
	unsigned* sourceImage		= (unsigned*) source;
	unsigned* destImage			= (unsigned*) dest;
	// Ok ... we are going to do this "right".  Right as in slow and super accurate.  Logical.
	// Imagining we have unlimited CPU and there is no such thing as time.
	
	// Yes we are allocating 5 floats here.
	// Red, green, blue, alpha and "share" to interpolate with high precision.
	float* tempBuffer			= Memory_malloc (destWidth * destHeight * sizeof(float) * 5, "Temp buffer to paint");
	
	const float PixelRemapWidth		= (float)destWidth  / sourceWidth;
	const float PixelRemapHeight	= (float)destHeight / sourceHeight;
	
	for (int sourceRow = 0; sourceRow < sourceHeight; sourceRow ++)
		for (int sourceCol = 0; sourceCol < sourceWidth; sourceCol ++)
		{
			unsigned* srcColor = sourceImage + (sourceRow  * sourceWidth + sourceCol);
			Pixel_Project_As_Rect (srcColor, &sourceRow, &sourceCol, &PixelRemapWidth, &PixelRemapHeight, tempBuffer, destImage, &destSize, &destWidth, &destHeight);
		}

	Memory_free (tempBuffer);
	return dest;
}
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 ..
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: Image Manipulation

Post by Spike »

would it not be better to calculate the pixels in the dest, rather than projecting the source? then you won't have issues with gaps when eg rotating/scaling up.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

Spike wrote:would it not be better to calculate the pixels in the dest, rather than projecting the source? then you won't have issues with gaps when eg rotating/scaling up.
I calculate the fractions so I don't have gaps. Maybe I should mentally picture the inverse like you suggest and see if it simplifies. Still, scaling up can occur from either perspective depending on if it is an upscale or downscale or ... both (i.e. image to double width, half height). So I'm kinda thinking in theory that there is no "better point-of-view". Everything is relative.
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 »

Oh what a major pain that was ...

The resampled image results are A++++, though. And I've written this so it can be used by rotate. In theory, if I add a z-buffer I could probably do (non-realtime) 3D rendering with it ... like rotate a 2D image around an axis or all of them, perspective calcs or whatever. Since it makes all the remapping calculations per pixel, non-standard "deformations" are possible.

Code: Select all

// We aren't optimized here at all so don't even bother with an inline attempt.
static float Pixel_Project_As_Rect_Dest_Pixel_Fraction (const float currentDestCoord, const float rangeStart, const float rangeEnd)
{
	fbool isFirstofRange		= (currentDestCoord + 0) == floorf(rangeStart); // 3 == floorf(3.25) --> 3 == 3
	fbool isLastofRange			= (currentDestCoord + 1) ==  ceilf(rangeEnd  ); // 
	float destFraction; //0.5 to 1 and we are at 0.
	
		 if (isFirstofRange && isLastofRange)	destFraction =  rangeEnd - rangeStart;
	else if (isLastofRange)						destFraction =    - currentDestCoord + rangeEnd ;   // 0 - 4 + 4.25 =                 0.25 or    - 4 + 5 = 1
	else if (isFirstofRange)					destFraction =  1 + currentDestCoord - rangeStart ; // 1 + 3 - 3.25 = 1.00 - (-.25) = 0.75 or  1 + 3 - 3 = 1
	else										destFraction =  1;

	return destFraction;
}

static float Deplete_Amount_From (const float amount, float* sourceOfAmount)
{
	*sourceOfAmount -= amount;
	return amount;
}

static float Interpolate_Add_Weight_Source_Dest_Count (const float addWeight, const float* source,  float* dest, const int count)
{
	const float oldWeight		= dest[count];
	const float newWeight		= oldWeight + addWeight;
	
	for (int component = 0; component < count; component ++)
		dest[component] = (dest[component] * oldWeight + source[component] * addWeight) / newWeight;
		
	return newWeight;
}

static inline float Pixel_Project_As_Rect (const unsigned* pixelColor, const int* sourceRow, const int* sourceCol, 
										   const float paintGallons, const float* pixelScaleWidth, const float* pixelScaleHeight, float* floatBuckets, unsigned* destImage, const int* destSize, const int* destWidth, const int *destHeight)
{
	const float Pixel_X1			= *sourceCol * *pixelScaleWidth;	// pixelScaleWidth is how much paint to spray per pixel
	const float Pixel_Y1			= *sourceRow * *pixelScaleHeight;	// Like if 0.5 then a pixel in range can get at most
	const float Pixel_X2			= Pixel_X1   + *pixelScaleWidth;		// a half impact.
	const float Pixel_Y2			= Pixel_Y1	 + *pixelScaleHeight;	// But double scale can get max 1 per pixel 
	
	float paintBucket				= paintGallons;
	
	float colorComponentsPct[4]; // RGBA
	Pixel_Components_Percents_From_Pixel(pixelColor, &colorComponentsPct[0], &colorComponentsPct[1], &colorComponentsPct[2], &colorComponentsPct[3]);
	
	// We have no optimization for row first here so don't bother ... And only first and last of range can have a frac
	for (int virtualDestCol = floorf(Pixel_X1); virtualDestCol < Pixel_X2; virtualDestCol ++)
	{
		float columnFraction		= Pixel_Project_As_Rect_Dest_Pixel_Fraction (virtualDestCol, Pixel_X1, Pixel_X2);

		for (int virtualDestRow = floorf(Pixel_Y1); virtualDestRow < Pixel_Y2; virtualDestRow ++)
		{
			float rowFraction		= Pixel_Project_As_Rect_Dest_Pixel_Fraction (virtualDestRow, Pixel_Y1, Pixel_Y2);
			
			// Actual pixel action here ...
			float VirtualPaintPct	= Deplete_Amount_From (columnFraction * rowFraction, &paintBucket);
			
			float* mixBucket		= floatBuckets      + (virtualDestRow * (*destWidth) + virtualDestCol)*5;
			unsigned*  destPixel	= destImage			+ virtualDestRow * (*destWidth) + virtualDestCol;

			// Mix
			mixBucket[4]			= Interpolate_Add_Weight_Source_Dest_Count (VirtualPaintPct, colorComponentsPct, mixBucket, 4);
			Pixel_Components_Percents_To_Pixel(destPixel, &mixBucket[0], &mixBucket[1], &mixBucket[2], &mixBucket[3]);
			
		}
	}
	
	return paintBucket;
}


byte* Image_RGBA_Resample (const byte* source, const int sourceSize, const int sourceWidth, const int sourceHeight,
						   byte* dest,   const int destSize,   const int destWidth,   const int destHeight)
{
	unsigned* sourceImage		= (unsigned*) source;
	unsigned* destImage			= (unsigned*) dest;
	// Ok ... we are going to do this "right".  Imagining we have unlimited CPU and there is no such thing as time.
	
	// Yes we are allocating 5 floats here.   Red, green, blue, alpha and "share" to interpolate with high precision.
	float* tempBuffer			= Memory_calloc (destWidth * destHeight, sizeof(float) * 5, "Temp buffer to paint");
	// Zero fill! ???
	
	const float PixelScaleWidth		= (float)destWidth  / sourceWidth;
	const float PixelScaleHeight	= (float)destHeight / sourceHeight;
	const float gallonsPaintPerPix	= PixelScaleWidth * PixelScaleHeight;
	
	for (int sourceRow = 0; sourceRow < sourceHeight; sourceRow ++)
		for (int sourceCol = 0; sourceCol < sourceWidth; sourceCol ++)
		{
			unsigned*	srcColor			= sourceImage + (sourceRow  * sourceWidth + sourceCol);
			float		pixelPaintGallons	= gallonsPaintPerPix;
			Pixel_Project_As_Rect (srcColor, &sourceRow, &sourceCol, pixelPaintGallons, &PixelScaleWidth, &PixelScaleHeight, tempBuffer, destImage, &destSize, &destWidth, &destHeight);
			
			// pixel Paint gallons better be 0!
		}
	
	// Validate tempBuffer[4] = 1 in all cases or very close considering floating point precision
	Memory_free(tempBuffer);
	return dest;
}
Image

One of Moon[Drunk]'s charsets resampled from 512/512 to 333 width and 333 height. With alpha channel. A small amount of floating point error (13th row ... top of some letters do not look entirely complete.) is visible in one of the rows. Not 100% sure how to address that atm ... somewhere checking boundaries of a pixel needs rounded slightly. Or maybe that row is just a tough luck case in a situation where 1:1 or 1:(int) X ratio is not possible.

Pretty much every Quake engine renders text or replacement HUD gfx really poorly under certain console resolutions. Probably not necessary ...
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 »

Rotating a 2D image turns out to involve some complexities.

This is just so I can adjust the painting factor per pixel for a rotated rectangle.

Code: Select all

		const float matrix2d[2][2]		= { cosf(DEGREES_TO_RADIANS(degrees)), -sinf(DEGREES_TO_RADIANS(degrees)),
										sinf(DEGREES_TO_RADIANS(degrees)),  cosf(DEGREES_TO_RADIANS(degrees))};
		const float originX = sourceWidth  / 2;
		const float originY = sourceHeight / 2;
		const float x1 = 0 - originX;
		const float y1 = 0 - originY;
		const float x2 = sourceWidth  - originX;
		const float y2 = sourceHeight - originY;
		
		const float rectCorners[4][2] = {	matrix2d[0][0] * x1 + matrix2d[0][1] * y1, matrix2d[1][0] * x1 + matrix2d[1][1] * y1,
											matrix2d[0][0] * x2 + matrix2d[0][1] * y1, matrix2d[1][0] * x2 + matrix2d[1][1] * y1,
											matrix2d[0][0] * x2 + matrix2d[0][1] * y2, matrix2d[1][0] * x2 + matrix2d[1][1] * y2,
											matrix2d[0][0] * x2 + matrix2d[0][1] * y2, matrix2d[1][0] * x2 + matrix2d[1][1] * y2};
			
		const float minsx = Least (rectCorners[0][0], rectCorners[1][0], rectCorners[2][0], rectCorners[3][0]);
		const float minsy = Least (rectCorners[0][1], rectCorners[1][1], rectCorners[2][1], rectCorners[3][1]);
		const float maxsy = Most  (rectCorners[0][0], rectCorners[1][0], rectCorners[2][0], rectCorners[3][0]);	
		const float maxsy = Most  (rectCorners[0][1], rectCorners[1][1], rectCorners[2][1], rectCorners[3][1]);
On the positive size, I can reuse most of this concept for creating an enlarged cheapo cube bounding box collisions in my possible "map editor that you can run around the map" concept.
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 »

Might need these (source):

Code: Select all

void glFrustumf (GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far)
{
	GLfloat m [16];
	GLfloat _2n = 2.0 * near;
	GLfloat _1over_rml = 1.0 / (right - left);
	GLfloat _1over_fmn = 1.0 / (far - near);
	GLfloat _1over_tmb = 1.0 / (top - bottom);

	m[0] = _2n * _1over_rml;
	m[1] = 0.0;
	m[2] = 0.0;
	m[3] = 0.0;

	m[4] = 0.0;
	m[5] = _2n * _1over_tmb;
	m[6] = 0.0;
	m[7] = 0.0;

	m[8] = (right + left) * _1over_rml;
	m[9] = (top + bottom) * _1over_tmb;
	m[10] = (-(far + near)) * _1over_fmn;
	m[11] = -1.0;

	m[12] = 0.0;
	m[13] = 0.0;
	m[14] = -(_2n * far * _1over_fmn);
	m[15] = 0.0;

	glMultMatrixf(m);
}

Code: Select all

void glOrthof (GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far)
{
	GLfloat m [16];
	GLfloat rml = right - left;
	GLfloat fmn = far - near;
	GLfloat tmb = top - bottom;
	GLfloat _1over_rml;
	GLfloat _1over_fmn;
	GLfloat _1over_tmb;

	if (rml == 0.0 || fmn == 0.0 || tmb == 0.0) {
		GLERROR(GL_INVALID_VALUE);
		return;
	}

	_1over_rml = 1.0 / rml;
	_1over_fmn = 1.0 / fmn;
	_1over_tmb = 1.0 / tmb;

	m[0] = 2.0 * _1over_rml;
	m[1] = 0.0;
	m[2] = 0.0;
	m[3] = 0.0;

	m[4] = 0.0;
	m[5] = 2.0 * _1over_tmb;
	m[6] = 0.0;
	m[7] = 0.0;

	m[8] = 0.0;
	m[9] = 0.0;
	m[10] = -2.0 * _1over_fmn;
	m[11] = 0.0;

	m[12] = -(right + left) *  _1over_rml;
	m[13] = -(top + bottom) *  _1over_tmb;
	m[14] = -(far + near) * _1over_fmn;
	m[15] = 1.0;

	glMultMatrixf(m);
}
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 ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: Image Manipulation

Post by mh »

glOrtho is actually completely unnecessary with most hardware released this century:

Code: Select all

	!!ARBvp1.0
	MAD result.position, vertex.attrib[0], program.env[0], {-1, 1, 0, 0};

Code: Select all

	qglProgramEnvParameter4fARB (GL_VERTEX_PROGRAM_ARB, 0, (2.0f / vid.width2d), -(2.0f / vid.height2d), 0, 1);
Who knows - if you're drawing lots and lots and lots of 2D stuff this will exchange 4 DP4 instructions for one MAD per-vertex, which may translate into a small but worthwhile gain.
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
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

I'm kind of trying to see inside of what is really happening in both software rendering and hardware rendering, I've been working out maths most of the day. I'm understanding the software renderer and hardware renderer, seeing what the matrixes are doing with the calculations, etc. So I'm kinda of going for the "unshielded" approach and instead of avoiding the hard things, I'm specifically engaging them.

Yeah this slows me down for the moment, but if I'd rather be in a position where I can, say, look at some clipping or collision or rotation or transformation code and see what is going on directly.
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 »

Rotation transformation. :D

This isn't rendering the image sideways. This is altering the image itself. This requires a bit of 2D math and some 2D matrix action and a few other maths that otherwise was quite a bit of math effort to work through.

Image

Code: Select all

// We've got the X and the Y and we could supply the matrix.  Still.  How are we going to 
static inline float PixelProjectTransform (const float matrix2d[2][2], const int sourceWidth, const int sourceHeight, const unsigned* pixelColor, const int* sourceRow, const int* sourceCol, 
										   const float paintGallons, float* floatBuckets, unsigned* destImage, const int* destSize, const int* destWidth, const int *destHeight)
{
//	const float Pixel_X1			= *sourceCol * *pixelScaleWidth;	// pixelScaleWidth is how much paint to spray per pixel
//	const float Pixel_Y1			= *sourceRow * *pixelScaleHeight;	// Like if 0.5 then a pixel in range can get at most
//	const float Pixel_X2			= Pixel_X1   + *pixelScaleWidth;		// a half impact.
//	const float Pixel_Y2			= Pixel_Y1	 + *pixelScaleHeight;	// But double scale can get max 1 per pixel 
	
	// Do as standard rect.
	// Center
	const float originX = sourceWidth  / 2.0f;
	const float originY = sourceHeight / 2.0f;		
	const float Centered_X1			= (*sourceCol - originX);
	const float Centered_Y1			= (*sourceRow - originY);
	const float Centered_X2			= Centered_X1 + 1;
	const float Centered_Y2			= Centered_Y1 + 1;

	// Rotate transformation ...
	const float rectCorners[4][2] = {	matrix2d[0][0] * Centered_X1 + matrix2d[0][1] * Centered_Y1, matrix2d[1][0] * Centered_X1 + matrix2d[1][1] * Centered_Y1,
										matrix2d[0][0] * Centered_X2 + matrix2d[0][1] * Centered_Y1, matrix2d[1][0] * Centered_X2 + matrix2d[1][1] * Centered_Y1,
										matrix2d[0][0] * Centered_X2 + matrix2d[0][1] * Centered_Y2, matrix2d[1][0] * Centered_X2 + matrix2d[1][1] * Centered_Y2,
										matrix2d[0][0] * Centered_X1 + matrix2d[0][1] * Centered_Y2, matrix2d[1][0] * Centered_X1 + matrix2d[1][1] * Centered_Y2};
	// ^^ transformed corners
	
	// transformed corners turned into expanded rect
	const float Pixel_Centered_X1	= MathOp4f(MATHOP_LEAST, rectCorners[0][0], rectCorners[1][0], rectCorners[2][0], rectCorners[3][0]) ;
	const float Pixel_Centered_Y1	= MathOp4f(MATHOP_LEAST, rectCorners[0][1], rectCorners[1][1], rectCorners[2][1], rectCorners[3][1]) ;
	const float Pixel_Centered_X2	= MathOp4f(MATHOP_MOST,  rectCorners[0][0], rectCorners[1][0], rectCorners[2][0], rectCorners[3][0]) ;	
	const float Pixel_Centered_Y2	= MathOp4f(MATHOP_MOST,  rectCorners[0][1], rectCorners[1][1], rectCorners[2][1], rectCorners[3][1]) ;				
	
	const float Pixel_X1			= originX + Pixel_Centered_X1;
	const float Pixel_Y1			= originY + Pixel_Centered_Y1;
	const float Pixel_X2			= originX + Pixel_Centered_X2;
	const float Pixel_Y2			= originY + Pixel_Centered_Y2;

	
	float paintBucket				= paintGallons;
	
	float colorComponentsPct[4]; // RGBA
	Pixel_Components_Percents_From_Pixel (pixelColor, &colorComponentsPct[0], &colorComponentsPct[1], &colorComponentsPct[2], &colorComponentsPct[3]);
	
	// We have no optimization for row first here so don't bother ... And only first and last of range can have a frac
	for (int virtualDestCol = floorf(Pixel_X1); virtualDestCol < Pixel_X2; virtualDestCol ++)
	{
		float columnFraction		= Pixel_Project_As_Rect_Dest_Pixel_Fraction (virtualDestCol, Pixel_X1, Pixel_X2);
		if (virtualDestCol < 0 || virtualDestCol > (*destWidth - 1)) continue;
		
		
		for (int virtualDestRow = floorf(Pixel_Y1); virtualDestRow < Pixel_Y2; virtualDestRow ++)
		{
			float rowFraction		= Pixel_Project_As_Rect_Dest_Pixel_Fraction (virtualDestRow, Pixel_Y1, Pixel_Y2);
			
			// Actual pixel action here ...
			float VirtualPaintPct	= Deplete_Amount_From (columnFraction * rowFraction, &paintBucket);
			
			float* mixBucket		= floatBuckets      + (virtualDestRow * (*destWidth) + virtualDestCol)*5;
			unsigned*  destPixel	= destImage			+ virtualDestRow * (*destWidth) + virtualDestCol;

			if (virtualDestRow < 0 || virtualDestRow > (*destHeight - 1)) continue;
			
			// Mix
			mixBucket[4]			= Interpolate_Add_Weight_Source_Dest_Count (VirtualPaintPct, colorComponentsPct, mixBucket, 4);
			Pixel_Components_Percents_To_Pixel(destPixel, &mixBucket[0], &mixBucket[1], &mixBucket[2], &mixBucket[3]);
			
		}
	}
	
	return paintBucket;
}


byte* Image_RGBA_RotateScale (const float degrees, const byte* source, const int sourceSize, const int sourceWidth, const int sourceHeight,
						   byte* dest,   const int destSize,   const int destWidth,   const int destHeight)
{
	unsigned* sourceImage		= (unsigned*) source;
	unsigned* destImage			= (unsigned*) dest;
	// Ok ... we are going to do this "right".  Imagining we have unlimited CPU and there is no such thing as time.
	
	// Yes we are allocating 5 floats here.   Red, green, blue, alpha and "share" to interpolate with high precision.
	float* tempBuffer			= Memory_calloc (destWidth * destHeight, sizeof(float) * 5, "Temp buffer to paint");
	// Zero fill! ???
	
	const float PixelScaleWidth		= (float)destWidth  / sourceWidth;
	const float PixelScaleHeight	= (float)destHeight / sourceHeight;
	const float matrix2d[2][2]		= { cosf(DEGREES_TO_RADIANS(degrees)), -sinf(DEGREES_TO_RADIANS(degrees)),
										sinf(DEGREES_TO_RADIANS(degrees)),  cosf(DEGREES_TO_RADIANS(degrees))};
	
	const float gallonsPaintPerPix	= PixelScaleWidth * PixelScaleHeight;
//	const float paintScale			= 
	float rotatedRectArea;
	{
		const float originX = sourceWidth  / 2;
		const float originY = sourceHeight / 2;
		const float x1 = 0 - originX;
		const float y1 = 0 - originY;
		const float x2 = sourceWidth  - originX;
		const float y2 = sourceHeight - originY;
		
		const float rectCorners[4][2] = {	matrix2d[0][0] * x1 + matrix2d[0][1] * y1, matrix2d[1][0] * x1 + matrix2d[1][1] * y1,
											matrix2d[0][0] * x2 + matrix2d[0][1] * y1, matrix2d[1][0] * x2 + matrix2d[1][1] * y1,
											matrix2d[0][0] * x2 + matrix2d[0][1] * y2, matrix2d[1][0] * x2 + matrix2d[1][1] * y2,
											matrix2d[0][0] * x1 + matrix2d[0][1] * y2, matrix2d[1][0] * x1 + matrix2d[1][1] * y2};
			
		const float minsx = floorf (MathOp4f(MATHOP_LEAST, rectCorners[0][0], rectCorners[1][0], rectCorners[2][0], rectCorners[3][0]) );
		const float minsy = floorf (MathOp4f(MATHOP_LEAST, rectCorners[0][1], rectCorners[1][1], rectCorners[2][1], rectCorners[3][1]) );
		const float maxsx = ceilf  (MathOp4f(MATHOP_MOST,  rectCorners[0][0], rectCorners[1][0], rectCorners[2][0], rectCorners[3][0]) );	
		const float maxsy = ceilf  (MathOp4f(MATHOP_MOST,  rectCorners[0][1], rectCorners[1][1], rectCorners[2][1], rectCorners[3][1]) );				
		
		rotatedRectArea = RectArea (minsx, minsy, maxsx, maxsy);
	}
	
	for (int sourceRow = 0; sourceRow < sourceHeight; sourceRow ++)
		for (int sourceCol = 0; sourceCol < sourceWidth; sourceCol ++)
		{
			unsigned*	srcColor			= sourceImage + (sourceRow  * sourceWidth + sourceCol);
			float		pixelPaintGallons	= gallonsPaintPerPix;
			
			PixelProjectTransform (matrix2d, sourceWidth, sourceHeight, srcColor, &sourceRow, &sourceCol, pixelPaintGallons, tempBuffer, destImage, &destSize, &destWidth, &destHeight);
			
			// pixel Paint gallons better be 0!
		}
	
	// Validate tempBuffer[4] = 1 in all cases or very close considering floating point precision
	Memory_free(tempBuffer);
	return dest;
}
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 »

This is rather interesting and I need to stockpile it before I forget (not that I don't have 15 sheets of paper sitting around of me trying to wrap my head around this). For trapezoid with points A, B, C, D where going clockwise around from top/left -> top/right-> bottom/right -> bottom/left.

Matrix:

Code: Select all

X = [ BX - AX         (CX - DX) - (BX - AX)            ]
      [---------- +  --------------------------  +  AX  ]
      [  X2 - X1        CY-BY                                ]
      [                                                              ]
Y =  [(CY - BY) - (DY - AY)    (DY - AY)              ]
      [-----------------------  +  ------------  +  AY  ]
      [  BX - AX                         Y2 - Y1             ]
This creates the right kind of matrix to map a rect to ANY trapezoid. All the answers I've seen anywhere on the internet --- and I searched a ton --- were all convoluted messes involving several steps and matrix calculations or derivates and such. I didn't think that sounded right. I pictured that any trapezoid has to have a fixed X slope that depends on X and Y and likewise any Y slope would have a fixed slope depending on X and Y. Fortunately, I was right. With this formula many calculations immediately become possible including perspective calculations, mapping a rect to trapezoid, snatching and "untrapezoiding" a rect. It probably took me 3 hours of drawing some trapezoids and trying to visualize how to calculate any given X/Y on them, and a lot of that was after looking at 40 pages from Google and not liking anything I saw. This question has been asked ton of times on the internet, but the answer really is just basic algebra of all things.
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 ..
metlslime
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Re: Image Manipulation

Post by metlslime »

Baker wrote:With this formula many calculations immediately become possible including perspective calculations...
I don't have time to check your work or anything, but if you are talking about perspective texture mapping, it might be good to verify which of these two projections you are doing:

Image

Only the one on the right is appropriate for texture mapping in 3d space, even though the one on the left is perfectly fine if you are simply distorting a 2D rectangle in 2D space.

(no idea if the image tag will work for others, here is the URL if it doesn't: http://i19.photobucket.com/albums/b193/ ... ection.png)
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Image Manipulation

Post by Baker »

metlslime wrote:Only the one on the right is appropriate for texture mapping in 3d space, even though the one on the left is perfectly fine if you are simply distorting a 2D rectangle in 2D space.
I'm kind of rediscovering geometry a bit.

I should use correct terminology as that is important.

Yes, this an affine transformation. Not perspective.
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