Skip to content
Snippets Groups Projects
  • Deucе's avatar
    f9808ca5
    Use a double for scaling instead of an integer · f9808ca5
    Deucе authored
    All bitmap drivers now support arbitrary scaling, and the scaling
    factor is a double, allowing arbitrary window scaling in all bitmap
    modes (making nelgin happy).
    
    While we're here, fix bugs in horizontal interpolation and X window
    resizeing.
    f9808ca5
    History
    Use a double for scaling instead of an integer
    Deucе authored
    All bitmap drivers now support arbitrary scaling, and the scaling
    factor is a double, allowing arbitrary window scaling in all bitmap
    modes (making nelgin happy).
    
    While we're here, fix bugs in horizontal interpolation and X window
    resizeing.
scale.c 20.34 KiB
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

#include "ciolib.h"
#include "scale.h"
#include "xbr.h"

static void pointy_scale3(uint32_t* src, uint32_t* dest, int width, int height);
static void pointy_scale5(uint32_t* src, uint32_t* dest, int width, int height);
static void pointy_scale_odd(uint32_t* src, uint32_t* dest, int width, int height, int mult);
static void interpolate_height(uint32_t* src, uint32_t* dst, int width, int height, int newheight);
static void interpolate_width(uint32_t* src, uint32_t* dst, int width, int height, int newwidth);
static void multiply_scale(uint32_t* src, uint32_t* dst, int width, int height, int xmult, int ymult);

static struct graphics_buffer *free_list;

/*
 * Corrects width/height to have the specified aspect ratio
 * any fit inside the specified rectangle
 */
void
aspect_fix_wc(int *x, int *y, bool wc, int aspect_width, int aspect_height)
{
	int bestx, besty;

	if (aspect_width == 0 || aspect_height == 0)
		return;
	bestx = lround((double)*y * aspect_width / aspect_height);
	besty = lround((double)*x * aspect_height / aspect_width);

	if (wc)
		*y = besty;
	else
		*x = bestx;
}

/*
 * Corrects width/height to have the specified aspect ratio
 * any fit inside the specified rectangle
 */
void
aspect_fix_inside(int *x, int *y, int aspect_width, int aspect_height)
{
	int bestx, besty;

	if (aspect_width == 0 || aspect_height == 0)
		return;
	bestx = lround((double)*y * aspect_width / aspect_height);
	besty = lround((double)*x * aspect_height / aspect_width);

	if (besty <= *y)
		*y = besty;
	else if (bestx <= *x)
		*x = bestx;
	else {
		fprintf(stderr, "Unable to fix %dx%d at ratio %d:%d (best %d, %d)\n", *x, *y, aspect_width, aspect_height, bestx, besty);
	}
}

/*
 * Corrects width/height to have the specified aspect ratio
 */
void
aspect_fix(int *x, int *y, int aspect_width, int aspect_height)
{
	int bestx, besty;

	// Nothing we can do here...
	if (aspect_width == 0 || aspect_height == 0)
		return;
	bestx = lround((double)*y * aspect_width / aspect_height);
	besty = lround((double)*x * aspect_height / aspect_width);

	if (bestx < *x && besty > 0)
		*y = besty;
	else
		*x = bestx;
}

/*
 * Corrects width/height to have the specified aspect ratio
 */
void
aspect_fix_low(int *x, int *y, int aspect_width, int aspect_height)
{
	int bestx, besty;

	// Nothing we can do here...
	if (aspect_width == 0 || aspect_height == 0)
		return;
	bestx = lround((double)*y * aspect_width / aspect_height);
	besty = lround((double)*x * aspect_height / aspect_width);

	if (bestx < *x && bestx > 0)
		*x = bestx;
	else
		*y = besty;
}

/*
 * Given a width/height of a source image, adjust it to match the current aspect
 * ratio.  Will not reduce either number
 */
void
aspect_correct(int *x, int *y, int aspect_width, int aspect_height)
{
	int width = *x;
	int height;

	if (!aspect_height || !aspect_width)
		return;
	height = lround((double)(width * aspect_height) / aspect_width);
	if (height < *y) {
		height = *y;
		width = lround(((double)height * aspect_width) / aspect_height);
	}

	*x = width;
	*y = height;
}

/*
 * Essentially the opposite of the above.  Given an output width/height, translates to
 * the size of the source image.
 *
 * Note that this is much trickier as the "source" bitmap may have been integer scaled
 * differently in both directions... so what this does is reverse the aspect ratio
 * calculation, then find the next lowest even multiple of the mode bitmap size.
 */
void
aspect_reverse(int *x, int *y, int scrnwidth, int scrnheight, int aspect_width, int aspect_height)
{
	int width = *x;
	int height = *y;
	int cheight;
	int cwidth;

	if (!aspect_height || !aspect_width) {
		width = scrnwidth * (*x / scrnwidth);
		if (width < scrnwidth)
			width = scrnwidth;
		height = scrnheight * (*x / scrnheight);
		if (height < scrnheight)
			height = scrnheight;
		return;
	}
	// First, find the "controlling" dimension... the one that won't be scaled (ie: the one that gets smaller)
	cwidth = lround((double)(height * aspect_width) / aspect_height * scrnwidth / scrnheight);
	cheight = lround((double)(width * aspect_height) / aspect_width * scrnheight / scrnwidth);
	if (cwidth > width) {
		// Width controls, so this is simply finding the largest width multiple that fits in the box
		width = scrnwidth * (*x / scrnwidth);
		if (width < scrnwidth)
			width = scrnwidth;

		// Now we need to find the largest bitmap height that would fit in the output height
		// So, we scale the height to bitmap size...
		height = lround((double)*y / ((double)scrnwidth / scrnheight) * ((double)aspect_width / aspect_height));
		// And do the same calculation...
		height = lround((double)scrnheight * ((double)height / scrnheight));
	}
	else if (cheight > height) {
		// Height controls
		height = scrnheight * (*x / scrnheight);
		if (height < scrnheight)
			height = scrnheight;

		width = lround((double)*x / ((double)scrnheight / scrnwidth) * ((double)aspect_height / aspect_width));
		width = lround((double)scrnwidth * ((double)width / scrnwidth));
	}

	*x = width;
	*y = height;
}

struct graphics_buffer *
get_buffer(void)
{
	struct graphics_buffer* ret = NULL;

	if (free_list) {
		ret = free_list;
		free_list = free_list->next;
		ret->next = NULL;
		return ret;
	}

	ret = calloc(1, sizeof(struct graphics_buffer));
	return ret;
}

void
release_buffer(struct graphics_buffer *buf)
{
	if (buf == NULL)
		return;
	buf->next = free_list;
	free_list = buf;
}

void
calc_scaling_factors(int *x, int *y, int winwidth, int winheight, int aspect_width, int aspect_height, int scrnwidth, int scrnheight)
{
	aspect_fix_low(&winwidth, &winheight, aspect_width, aspect_height);
	aspect_reverse(&winwidth, &winheight, scrnwidth, scrnheight, aspect_width, aspect_height);
	*x = winwidth / scrnwidth;
	*y = winheight / scrnheight;
	if (*x < 1 || *x > 14)
		*x = 1;
	if (*y < 1 || *y > 14)
		*y = 1;
}

struct graphics_buffer *
do_scale(struct rectlist* rect, int fwidth, int fheight)
{
	struct graphics_buffer* ret1 = get_buffer();
	struct graphics_buffer* ret2 = get_buffer();
	int pointymult = 1;
	int pointy5 = 0;
	int pointy3 = 0;
	int xbr2 = 0;
	int xbr4 = 0;
	int ymult = 1;
	int xmult = 1;
	int total_xscaling = 1;
	int total_yscaling = 1;
	struct graphics_buffer *ctarget;
	struct graphics_buffer *csrc;
	uint32_t* nt;
	bool swapxy = false;

	int xscale = fwidth / rect->rect.width;
	int yscale = fheight / rect->rect.height;

	if (xscale > yscale) {
		swapxy = true;
		total_xscaling = xscale;
		xscale = yscale;
		yscale = total_xscaling;
	}
	total_xscaling = xscale;
	xscale = 1;
	total_yscaling = yscale;
	yscale = 1;
	// If x/y scaling isn't a simple multiple, block scale everything...
	if ((total_yscaling % total_xscaling) == 0) {
		if (!(cio_api.options & CONIO_OPT_BLOCKY_SCALING)) {
			if ((total_xscaling & 1) == 1 && total_xscaling > 5) {
				pointymult = total_xscaling;
				total_xscaling /= pointymult;
				xscale *= pointymult;
				total_yscaling /= pointymult;
				yscale *= pointymult;
			}
			while (total_xscaling > 1 && ((total_xscaling % 5) == 0) && ((total_yscaling % 5) == 0)) {
				pointy5++;
				total_xscaling /= 5;
				xscale *= 5;
				total_yscaling /= 5;
				yscale *= 5;
			}
			while (total_xscaling > 1 && ((total_xscaling % 3) == 0) && ((total_yscaling % 3) == 0)) {
				pointy3++;
				total_xscaling /= 3;
				xscale *= 3;
				total_yscaling /= 3;
				yscale *= 3;
			}
			if (ciolib_r2yptr != NULL && ciolib_y2rptr != NULL) {
				while (total_xscaling > 1 && ((total_xscaling % 4) == 0) && ((total_yscaling % 4) == 0)) {
					xbr4++;
					total_xscaling /= 4;
					xscale *= 4;
					total_yscaling /= 4;
					yscale *= 4;
				}
				while (total_xscaling > 1 && ((total_xscaling % 2) == 0) && ((total_yscaling % 2) == 0)) {
					xbr2++;
					total_xscaling /= 2;
					xscale *= 2;
					total_yscaling /= 2;
					yscale *= 2;
				}
			}
		}
	}

	xmult = total_xscaling;
	xscale *= xmult;
	total_xscaling = 1;

	ymult = total_yscaling;
	yscale *= ymult;
	total_yscaling = 1;

	if (swapxy) {
		int tmp;

		tmp = ymult;
		ymult = xmult;
		xmult = tmp;

		tmp = xscale;
		xscale = yscale;
		yscale = tmp;
	}

	// Now make sure target is big enough...
	size_t needsz = fwidth * fheight * sizeof(uint32_t);
	if (needsz > ret1->sz) {
		nt = realloc(ret1->data, needsz);
		if (nt == NULL) {
			release_buffer(ret1);
			release_buffer(ret2);
			return NULL;
		}
		ret1->data = nt;
		ret1->sz = needsz;
	}

	if (needsz > ret2->sz) {
		nt = realloc(ret2->data, needsz);
		if (nt == NULL) {
			release_buffer(ret1);
			release_buffer(ret2);
			return NULL;
		}
		ret2->data = nt;
		ret2->sz = needsz;
	}

	// Copy rect into first buffer
	// TODO: Unify bitmap rects and scaling buffers so this can just whomp on over.
	csrc = ret1;
	ctarget = ret2;
	memcpy(csrc->data, rect->data, rect->rect.width * rect->rect.height * sizeof(rect->data[0]));
	csrc->w = rect->rect.width;
	csrc->h = rect->rect.height;

#if 0
fprintf(stderr, "Plan:\n"
"start:       %dx%d\n"
"pointymulti: %d\n"
"pointy5:     %d\n"
"pointy3:     %d\n"
"xBR4:        %d\n"
"xBR2:        %d\n"
"Multiply:    %dx%d\n"
"hinterp:     %zu -> %zu\n"
"winterp:     %zu -> %zu\n",
csrc->w, csrc->h, pointymult, pointy5, pointy3, xbr4, xbr2, xmult, ymult, csrc->h * yscale, fheight, csrc->w * xscale, fwidth);
#endif
	// And scale...
	if (ymult != 1 || xmult != 1) {
		multiply_scale(csrc->data, ctarget->data, csrc->w, csrc->h, xmult, ymult);
		ctarget->w = csrc->w * xmult;
		ctarget->h = csrc->h * ymult;
		ymult = 1;
		xmult = 1;
		csrc = ctarget;
		if (ctarget == ret1)
			ctarget = ret2;
		else
			ctarget = ret1;
	}
	if (pointymult > 1 && pointymult & 1) {
		pointy_scale_odd(csrc->data, ctarget->data, csrc->w, csrc->h, pointymult);
		ctarget->w = csrc->w * pointymult;
		ctarget->h = csrc->h * pointymult;
		pointymult = 1;
		csrc = ctarget;
		if (ctarget == ret1)
			ctarget = ret2;
		else
			ctarget = ret1;
	}
	while (pointy5 > 0) {
		pointy_scale5(csrc->data, ctarget->data, csrc->w, csrc->h);
		pointy5--;
		ctarget->w = csrc->w * 5;
		ctarget->h = csrc->h * 5;
		csrc = ctarget;
		if (ctarget == ret1)
			ctarget = ret2;
		else
			ctarget = ret1;
	}
	while (pointy3 > 0) {
		pointy_scale3(csrc->data, ctarget->data, csrc->w, csrc->h);
		pointy3--;
		ctarget->w = csrc->w * 3;
		ctarget->h = csrc->h * 3;
		csrc = ctarget;
		if (ctarget == ret1)
			ctarget = ret2;
		else
			ctarget = ret1;
	}
	while (xbr4 > 0) {
		xbr_filter(csrc->data, ctarget->data, csrc->w, csrc->h, 4);
		xbr4--;
		ctarget->w = csrc->w * 4;
		ctarget->h = csrc->h * 4;
		csrc = ctarget;
		if (ctarget == ret1)
			ctarget = ret2;
		else
			ctarget = ret1;
	}
	while (xbr2 > 0) {
		xbr_filter(csrc->data, ctarget->data, csrc->w, csrc->h, 2);
		xbr2--;
		ctarget->w = csrc->w * 2;
		ctarget->h = csrc->h * 2;
		csrc = ctarget;
		if (ctarget == ret1)
			ctarget = ret2;
		else
			ctarget = ret1;
	}

	// And finally, interpolate if needed
	if (ciolib_r2yptr != NULL && ciolib_y2rptr != NULL) {
		if (fheight != csrc->h) {
			interpolate_height(csrc->data, ctarget->data, csrc->w, csrc->h, fheight);
			ctarget->h = fheight;
			ctarget->w = csrc->w;
			csrc = ctarget;
			if (ctarget == ret1)
				ctarget = ret2;
			else
				ctarget = ret1;
		}
	
		if (fwidth != csrc->w) {
			interpolate_width(csrc->data, ctarget->data, csrc->w, csrc->h, fwidth);
			ctarget->h = csrc->h;
			ctarget->w = fwidth;
			csrc = ctarget;
			if (ctarget == ret1)
				ctarget = ret2;
			else
				ctarget = ret1;
		}
	}

	release_buffer(ctarget);
	return csrc;
}

static void
pointy_scale_odd(uint32_t* src, uint32_t* dest, int width, int height, int mult)
{
	int x, y;
	uint32_t* s;
	uint32_t* d;
	int prevline, prevcol, nextline, nextcol;
	int i, j;
	int mid = mult / 2;
	int multoff = mult - 1;
	int dline = width * mult;
	int dbott;
	int dstripe = dline * mult;

	s = src;
	d = dest;
	prevline = 0;
	nextline = width;
	for (y = 0; y < height; y++) {
		if (y == height - 1)
			nextline = 0;
		prevcol = 0;
		nextcol = 1;
		for (x = 0; x < width; x++) {
			if (x == width - 1)
				nextcol = 0;

			for (i = 0; i < mid; i++) {
				d = &dest[dstripe * y + dline * i + x * mult];
				dbott = dline * (multoff - i * 2);

				for (j = 0; j < mid - i; j++) {
					if (s[prevline + prevcol] == s[0]) {
						d[j] = s[0];
					}
					else if (s[prevline] == s[prevcol]) {
						d[j] = s[prevcol];
					}
					else {
						d[j] = s[0];
					}

					if (s[prevline + nextcol] == s[0]) {
						d[multoff - j] = s[0];
					}
					else if (s[prevline] == s[nextcol]) {
						d[multoff - j] = s[nextcol];
					}
					else {
						d[multoff - j] = s[0];
					}

					if (s[prevcol + nextline] == s[0]) {
						d[dbott + j] = s[0];
					}
					else if(s[prevcol] == s[nextline]) {
						d[dbott + j] = s[prevcol];
					}
					else {
						d[dbott + j] = s[0];
					}

					if (s[nextcol + nextline] == s[0]) {
						d[dbott + multoff - j] = s[0];
					}
					else if (s[nextcol] == s[nextline]) {
						d[dbott + multoff - j] = s[nextcol];
					}
					else {
						d[dbott + multoff - j] = s[0];
					}
				}

				// And the rest is always kept the same
				for (; j < mid; j++) {
					d[j] = s[0];
					d[multoff - j] = s[0];
					d[dbott + j] = s[0];
					d[dbott + multoff - j] = s[0];
				}

				// And the middle dot.
				d[j] = s[0];
				d[dbott + j] = s[0];
			}

			d = &dest[dstripe * y + dline * i + x * mult];

			for (j = 0; j < mid; j++) {
				d[j] = s[0];
				d[multoff - j] = s[0];
			}
			d[j] = s[0];

			s++;

			if (x == 0)
				prevcol = -1;
		}
		if (y == 0)
			prevline = -width;
	}
}

static void
pointy_scale5(uint32_t* src, uint32_t* dest, int width, int height)
{
	int x, y;
	uint32_t* s;
	uint32_t* d;
	int w5 = width * 5;
	int w10 = width * 10;
	int w15 = width * 15;
	int w20 = width * 20;
	int prevline, prevcol, nextline, nextcol;

	s = src;
	d = dest;
	prevline = 0;
	nextline = width;
	for (y = 0; y < height; y++) {
		if (y == height - 1)
			nextline = 0;
		prevcol = 0;
		nextcol = 1;
		for (x = 0; x < width; x++) {
			if (x == width - 1)
				nextcol = 0;

			if (s[prevline + prevcol] == s[0]) {
				d[0] = s[0];
				d[1] = s[0];
				d[w5] = s[0];
			}
			else if (s[prevcol] == s[prevline]) {
				d[0] = s[prevcol];
				d[1] = s[prevcol];
				d[w5] = s[prevcol];
			}
			else {
				d[0] = *s;
				d[1] = *s;
				d[w5] = *s;
			}

			// Top-middle stays OG.
			d[2] = *s;
			d[w5+1] = *s;
			d[w5+2] = *s;
			d[w5+3] = *s;

			// And so on around the outside (round the outside)
			if (s[prevline + nextcol] == s[0]) {
				d[3] = s[0];
				d[4] = s[0];
				d[w5 + 4] = s[0];
			}
			else if (s[nextcol] == s[prevline]) {
				d[3] = s[nextcol];
				d[4] = s[nextcol];
				d[w5 + 4] = s[nextcol];
			}
			else {
				d[3] = s[0];
				d[4] = s[0];
				d[w5 + 4] = s[0];
			}

			d[w10] = *s;
			d[w10+1] = *s;
			d[w10+2] = *s;
			d[w10+3] = *s;
			d[w10+4] = *s;

			if (s[prevcol + nextline] == s[0]) {
				d[w15] = s[0];
				d[w20] = s[0];
				d[w20 + 1] = s[0];
			}
			else if(s[prevcol] == s[nextline]) {
				d[w15] = s[prevcol];
				d[w20] = s[prevcol];
				d[w20 + 1] = s[prevcol];
			}
			else {
				d[w15] = s[0];
				d[w20] = s[0];
				d[w20 + 1] = s[0];
			}

			d[w15 + 1] = *s;
			d[w15 + 2] = *s;
			d[w15 + 3] = *s;
			d[w20 + 2] = *s;

			if (s[nextcol + nextline] == s[0]) {
				d[w15 + 4] = s[0];
				d[w20 + 3] = s[0];
				d[w20 + 4] = s[0];
			}
			else if (s[nextcol] == s[nextline]) {
				d[w15 + 4] = s[nextcol];
				d[w20 + 3] = s[nextcol];
				d[w20 + 4] = s[nextcol];
			}
			else {
				d[w15 + 4] = s[0];
				d[w20 + 3] = s[0];
				d[w20 + 4] = s[0];
			}

			d += 5;
			s++;

			if (x == 0)
				prevcol = -1;
		}
		d += w20;
		if (y == 0)
			prevline = -width;
	}
}

static void
pointy_scale3(uint32_t* src, uint32_t* dest, int width, int height)
{
	int x, y;
	uint32_t* s;
	uint32_t* d;
	int w3 = width * 3;
	int w6 = width * 6;
	int prevline, prevcol, nextline, nextcol;

	s = src;
	d = dest;
	prevline = 0;
	nextline = width;
	for (y = 0; y < height; y++) {
		if (y == height - 1)
			nextline = 0;
		prevcol = 0;
		nextcol = 1;
		for (x = 0; x < width; x++) {
			if (x == width - 1)
				nextcol = 0;

			// Top-left is filled if both left and top are the same.
			if (s[prevline + prevcol] == s[0])
				d[0] = s[0];
			else if (s[prevcol] == s[prevline])
				d[0] = s[prevcol];
			else
				d[0] = *s;

			// Top-middle stays OG.
			d[1] = *s;

			// And so on around the outside (round the outside)
			if (s[prevline + nextcol] == s[0])
				d[2] = s[0];
			else if (s[nextcol] == s[prevline])
				d[2] = s[nextcol];
			else
				d[2] = *s;

			d[w3] = *s;
			d[w3 + 1] = *s;
			d[w3 + 2] = *s;

			if (s[prevcol + nextline] == s[0])
				d[w6] = s[0];
			else if(s[prevcol] == s[nextline])
				d[w6] = s[prevcol];
			else
				d[w6] = *s;

			d[w6 + 1] = *s;

			if (s[nextcol + nextline] == s[0])
				d[w6 + 2] = s[0];
			else if (s[nextcol] == s[nextline])
				d[w6 + 2] = s[nextcol];
			else
				d[w6 + 2] = s[0];

			d += 3;
			s++;

			if (x == 0)
				prevcol = -1;
		}
		d += w6;
		if (y == 0)
			prevline = -width;
	}
}

static uint32_t
blend(const uint32_t c1, const uint32_t c2, uint16_t weight)
{
	uint8_t yuv1[4];
	uint8_t yuv2[4];
	uint8_t yuv3[4];
	const uint16_t iw = 65535 - weight;

	*(uint32_t *)yuv1 = ciolib_r2yptr[c1];
	*(uint32_t *)yuv2 = ciolib_r2yptr[c2];
#ifdef __BIG_ENDIAN__
	yuv3[0] = 0;
	yuv3[1] = (yuv1[1] * iw + yuv2[1] * weight) / 65535;
	yuv3[2] = (yuv1[2] * iw + yuv2[2] * weight) / 65535;
	yuv3[3] = (yuv1[3] * iw + yuv2[3] * weight) / 65535;
#else
	yuv3[3] = 0;
	yuv3[2] = (yuv1[2] * iw + yuv2[2] * weight) / 65535;
	yuv3[1] = (yuv1[1] * iw + yuv2[1] * weight) / 65535;
	yuv3[0] = (yuv1[0] * iw + yuv2[0] * weight) / 65535;
#endif

	return ciolib_y2rptr[*(uint32_t*)yuv3];
}

/*
 * This does non-integer *width* scaling.  It does not scale in the other
 * direction.  This does the interpolation using Y'UV to prevent dimming of
 * pixels.
 */
static void
interpolate_width(uint32_t* src, uint32_t* dst, int width, int height, int newwidth)
{
	int x, y;
	const double mult = (double)width / newwidth;
	uint32_t *s = dst;

	for (x = 0; x < newwidth; x++) {
		// First, calculate which two pixels this is between.
		const double xpos = mult * x;
		const int xposi = xpos;
		const uint16_t weight = xpos * 65536;
		dst = &s[x];
		for (y = 0; y < height; y++) {
			if (weight == 0) {
				// Exact match!
				*dst = src[width * y + xposi];
			}
			else {
				// Now pick the two pixels
				const uint32_t pix1 = src[y * width + xposi];
				uint32_t pix2;
				if (xposi < width - 1)
					pix2 = src[y * width + xposi + 1];
				else
					pix2 = src[y * width + xposi];
				if (pix1 == pix2)
					*dst = pix1;
				else {
					*dst = blend(pix1, pix2, weight);
				}
			}
			dst += newwidth;
		}
	}
}

/*
 * This does non-integer *height* scaling.  It does not scale in the other
 * direction.  This does the interpolation using Y'UV to prevent dimming of
 * pixels.
 */
static void
interpolate_height(uint32_t* src, uint32_t* dst, int width, int height, int newheight)
{
	int x, y;
	const double mult = (double)height / newheight;
	double ypos = 0;
	int last_yposi = 0;
	int ywn = width;
	static uint32_t *nline = NULL;
	static uint32_t *tline = NULL;
	static size_t nsz = 0;
	static size_t tsz = 0;
	uint32_t *stmp;

	if (nsz < width * 4) {
		stmp = realloc(nline, width * 4);
		if (stmp == NULL)
			goto fail;
		nline = stmp;
		nsz = width * 4;
	}
	if (tsz < width * 4) {
		stmp = realloc(tline, width * 4);
		if (stmp == NULL)
			goto fail;
		tline = stmp;
		tsz = width * 4;
	}

	memcpy(tline, src, width * sizeof(*tline));
	memcpy(nline, src + width, width * sizeof(*tline));
	for (y = 0; y < newheight; y++) {
		const int yposi = ypos;
		const uint16_t weight = ypos * 65536;
		if (yposi != last_yposi) {
			ywn += width;
			last_yposi = yposi;
			stmp = tline;
			tline = nline;
			nline = stmp;
			memcpy(nline, &src[ywn], nsz);
		}
		if (weight == 0 || yposi >= height - 1) {
			memcpy(dst, tline, tsz);
			dst += width;
		}
		else {
			for (x = 0; x < width; x++) {
				// Now pick the two pixels
				const uint32_t pix1 = tline[x];
				const uint32_t pix2 = nline[x];
				if (pix1 == pix2)
					*dst = pix1;
				else
					*dst = blend(pix1, pix2, weight);
				dst++;
			}
		}
		ypos += mult;
	}

	return;
fail:
	free(nline);
	free(tline);
	nline = NULL;
	tline = NULL;
	nsz = 0;
	tsz = 0;
	memcpy(src, dst, width * height * sizeof(*src));
	fprintf(stderr, "Allocation failure in interpolate_height()!");
}

static void
multiply_scale(uint32_t* src, uint32_t* dst, int width, int height, int xmult, int ymult)
{
	int x, y;
	int mx, my;
	uint32_t* slstart;

	for (y = 0; y < height; y++) {
		slstart = src;
		for (my = 0; my < ymult; my++) {
			src = slstart;
			for (x = 0; x < width; x++) {
				for (mx = 0; mx < xmult; mx++) {
					*dst = *src;
					dst++;
				}
				src++;
			}
		}
	}
}