Bilinear resizing - I fail at it

Started by
0 comments, last by A_SN 15 years, 6 months ago
I wrote a function which purpose is to resize an using bilinear filtering/interpolation given its old dimensions and the new dimensions, regardless of the aspect ratio. I decided to try and get to same code working for both regular linear interpolation (upsizing) and linear filtering (when downsizing) for that reason, because while an axis can be upsized the other one can be simultaneously downsized. It works well for downsizing, looks great and all right, and for upsizing it works OK until you realise it really looks like a bit of pre-smoothing + nearest neighbour interpolation. That puzzles me, I have no idea how it can make it look like it does a tad bit of blurring on the original image and then upsamples it with nearest neighbour interpolation. I would blame it on the way that I calculate the 'weight' variable, which works by adding the sum of each weight used for each of the 4 original neighbouring pixels for the calculation of each new interpolated pixel, however when I used a fixed 'weight' variable things are 'way wronger' than when I do it like this. I'm kind of stumped that I would fail at doing something as simple as bilinear interpolation, but I just cannot see what's wrong with what I do, despite spending a whole day on trying to fix it, so I need help.. Thanks a lot in advance. EDIT : Also, my code is a tad slow. Any thoughts on that? I could move to fixed point airthmetic when things work I guess, but I'm going to reuse the code anything for some float image resizing..
uint32_t *resize_image(rgb32_t *in, int32_t old_w, int32_t old_h, int32_t new_w, int32_t new_h)
{
	int32_t i, iy, ix, jy, jx;			// iterators
	int32_t jy_start, jy_stop, jx_start, jx_stop;	// bounds for the iteration of pixels from the original image
	rgb32_t *out;			// new image
	float pos_in_x, pos_in_y;	// position of the new image pixel in the old image
	float ratio_x, ratio_y;		// downsampling ratio (< 1.0 means enlargement)
	float radius_x, radius_y;	// radius of the triangle from the projected point in original units
	float weight;			// global weight to apply to all weights, globally
	float p_weight, p_weight_y;	// local pixel weight
	int32_t *p_weight_p = (int32_t *) &p_weight;		// integer pointer to the float p_weight
	int32_t *p_weight_yp = (int32_t *) &p_weight_y;		// integer pointer to the float p_weight_y
	float r, g, b;

	ratio_x = (float) (old_w-1) / (float) (new_w-1);	// resizing ratio
	ratio_y = (float) (old_h-1) / (float) (new_h-1);
	radius_x = ratio_x;					// how far from the new pixel should we look for old pixels to weight and add
	radius_y = ratio_y;
	if (radius_x<1.f)
		radius_x = 1.f;					// if we upsample
	if (radius_y<1.f)					// keep the radius to 1 so that we catch all the 4 neighbouring original pixels
		radius_y = 1.f;
	weight = 1.f / (radius_x * radius_y);			// product by which to weight to final sum for a pixel

	out = calloc (new_w * new_h, sizeof(rgb32_t));

	for (iy=0; iy<new_h; iy++)
	{
		pos_in_y = (float) iy * ratio_y;			// position of the new pixel (iy) in the old array
		jy_start = (int32_t) ceilf(pos_in_y - radius_y);	// only start at the first pixel that comes after the new pixel minus its radius
		if (jy_start < 0)		// bounds checking
			jy_start = 0;

		jy_stop = (int32_t) ceilf(pos_in_y + radius_y);		// stop at the last pixel before the new pixel + its radius
		if (jy_stop >= old_h)		// bounds checking
			jy_stop = old_h - 1;

		for (ix=0; ix<new_w; ix++)
		{
			pos_in_x = (float) ix * ratio_x;		// position of the new pixel (ix) in the old array
			jx_start = (int32_t) ceilf(pos_in_x - radius_x);
			if (jx_start < 0)
				jx_start = 0;
	
			jx_stop = (int32_t) ceilf(pos_in_x + radius_x);
			if (jx_stop >= old_w)
				jx_stop = old_w - 1;

			r = 0;		// reset the values of the new interpolated pixels for each colour channel
			g = 0;
			b = 0;

			weight = 0;

			for (jy=jy_start; jy<jy_stop; jy++)	// iteration through all the neighbouring pixels from the new interpolated pixel
			{
				p_weight_y = ((float) jy - pos_in_y) * (1.f/radius_y);	// normalised distance of the original neighbouring pixel from the new pixel
				*p_weight_yp &= 0x7FFFFFFF;	// absolute value by binary masking (sets the sign to 0)
				p_weight_y = 1.f - p_weight_y;		// f(x) = 1 - x; <-- the formula for linear interpolation

				for (jx=jx_start; jx<jx_stop; jx++)
				{
					p_weight = ((float) jx - pos_in_x) * (1.f/radius_x);	// same thing as above in the X axis
					*p_weight_p &= 0x7FFFFFFF;		// absolute value by binary masking (sets the sign to 0)
					p_weight = 1.f - p_weight;
					p_weight *= p_weight_y;		// we multiply the weights of each axis to obtain the 2D weight

					weight += p_weight;

					r += (float) in[jy*old_w + jx].r * p_weight;	// we multiply each original pixel value by its weight 
					g += (float) in[jy*old_w + jx].g * p_weight;	// and add it to the new value,
					b += (float) in[jy*old_w + jx].b * p_weight;	// for each channel
				}
			}

			weight = 1.f/weight;			// we do that to avoid having to do 3 divisions (1 div + 3 muls is faster than 3 divs, right?)

			out[iy*new_w + ix].r = r * weight;	// finally we weight the final sum and put it in the new image array
			out[iy*new_w + ix].g = g * weight;
			out[iy*new_w + ix].b = b * weight;
		}
	}

	return (uint32_t *) out;
}
[Edited by - A_SN on October 13, 2008 9:06:42 PM]
Advertisement
Nevermind I guess.. it looks like when I upsample in both dimensions I just need to set the 'weight' variable always to 1...

If anyone has any comments on the speed, they're still welcome :)

This topic is closed to new replies.

Advertisement