John John - 1 month ago 40
C++ Question

Algorithm to downscale / interpolate an image using buffers?

I'm trying to downscale an image from a large size (as large as 960x960) to possibly as small as 32x32. I have the following code I use to get the raw pixels:

Image* img = new Image();
img->initWithImageFile(fileNameWithPath);
int x=3;
if(img->hasAlpha()){
x=4;
}

unsigned char *data = new unsigned char[img->getDataLen()*x];
data = img->getData();
// [0][0] => Left-Top Pixel !
// But cocos2d Location Y-axis is Bottom(0) to Top(max)

//This is for changing pixels in the original image
//Skip this loop if there are no changes to be made (converting to grayscale etc).
for(int i=0;i<img->getWidth();i++)
{
for(int j=0;j<img->getHeight();j++)
{
unsigned char *pixel = data + (i + j * img->getWidth()) * x;

// You can see/change pixels' RGBA value(0-255) here !
unsigned char r = *pixel;
unsigned char g = *(pixel + 1);
unsigned char b = *(pixel + 2) ;
unsigned char a = *(pixel + 3);

//pixel[2] = 255; //Example: Setting the blue component to 255
}
}


I can create an output image by doing:

int width = scale*img->getWidth();
int height = scale*img->getHeight();
Image* scaledImage = new Image();
auto dataLen = width * height * x * sizeof(unsigned char);
auto data2 = static_cast<unsigned char*>(malloc(dataLen));
scaledImage->initWithRawData(data2, dataLen, width, height, 8);


And I can set the individual pixels of the output image by doing:

unsigned char *pixel2 = data2 + (i + j * width) * x;


The question is how to average / interpolate the pixels from the original image efficiently (using minimum cpu and memory, with the preference to use more cpu if necessary and less memory).

Challenges:


  1. The downscaled image and the original image may not be perfect
    multiples.

  2. The downscaled image can be as small as 0.1 of the
    original image size.

  3. most images will be downscaled to 0.4 to 0.1 of the original image, so these are the most important ranges.



EDIT: If you think some other interpolation algorithm would be better suited (instead of bilinear) then I am open to it. The challenge is writing an efficient algorithm to average / interpolate the individual pixels..

How do I interpolate the individual pixels of the original image ?

Answer

Memory isn't an issue, you need an input buffer with the original image and an output buffer with the rescaled image, you don't need any more.

Bilinear interpolation isn't very suitable for downsampling by a large factor as it doesn't really differ from nearest sampling. You only interpolate four neighbouring pixels and so are vulnerable to aliasing effects, particularly on computer-generated images.

This function uses the averaging method.

 /*
  resize an image using the averaging method.
  Note that dwidth and dheight must be smaller than or equal to swidth, sheight.

 */
void sprshrink(unsigned char *dest, int dwidth, int dheight, unsigned char *src, int swidth, int sheight)
{
  int x, y;
  int i, ii;
  float red, green, blue, alpha;
  float xfrag, yfrag, xfrag2, yfrag2;
  float xt, yt, dx, dy;
  int xi, yi;


  dx = ((float)swidth)/dwidth;
  dy = ((float)sheight)/dheight;

  for(yt= 0, y=0;y<dheight;y++, yt += dy)
  {
    yfrag = ceil(yt) - yt;
    if(yfrag == 0)
      yfrag = 1;
    yfrag2 = yt+dy - (float) floor(yt + dy);
    if(yfrag2 == 0 && dy != 1.0f)
      yfrag2 = 1;

    for(xt = 0, x=0;x<dwidth;x++, xt+= dx)
    {
      xi = (int) xt;
      yi = (int) yt;
      xfrag = (float) ceil(xt) - xt;
     if(xfrag == 0)
       xfrag = 1;
      xfrag2 = xt+dx - (float) floor(xt+dx);
     if(xfrag2 == 0 && dx != 1.0f)
        xfrag2 = 1;
      red = xfrag * yfrag * src[(yi*swidth+xi)*4];
      green =  xfrag * yfrag * src[(yi*swidth+xi)*4+1];
      blue =   xfrag * yfrag * src[(yi*swidth+xi)*4+2];
      alpha =  xfrag * yfrag * src[(yi*swidth+xi)*4+3];

      for(i=0; xi + i + 1 < xt+dx-1; i++)
      {
        red += yfrag * src[(yi*swidth+xi+i+1)*4];
        green += yfrag * src[(yi*swidth+xi+i+1)*4+1];
        blue += yfrag * src[(yi*swidth+xi+i+1)*4+2];
        alpha += yfrag * src[(yi*swidth+xi+i+1)*4+3];
      } 

      red += xfrag2 * yfrag * src[(yi*swidth+xi+i+1)*4];
      green += xfrag2 * yfrag * src[(yi*swidth+xi+i+1)*4+1];
      blue += xfrag2 * yfrag * src[(yi*swidth+xi+i+1)*4+2];
      alpha += xfrag2 * yfrag * src[(yi*swidth+xi+i+1)*4+3];


      for(i=0; yi+i+1 < yt +dy-1 && yi + i+1 < sheight;i++)
      {
        red += xfrag * src[((yi+i+1)*swidth+xi)*4];
        green += xfrag * src[((yi+i+1)*swidth+xi)*4+1];
        blue += xfrag * src[((yi+i+1)*swidth+xi)*4+2];
        alpha += xfrag * src[((yi+i+1)*swidth+xi)*4+3];

        for (ii = 0; xi + ii + 1 < xt + dx - 1 && xi + ii + 1 < swidth; ii++)
        {
          red += src[((yi+i+1)*swidth+xi+ii+1)*4];
          green += src[((yi+i+1)*swidth+xi+ii+1)*4+1];
          blue += src[((yi+i+1)*swidth+xi+ii+1)*4+2];
          alpha += src[((yi+i+1)*swidth+xi+ii+1)*4+3];
        }

          if (yi + i + 1 < sheight && xi + ii + 1 < swidth)
          {
              red += xfrag2 * src[((yi+i+1)*swidth+xi+ii+1)*4];
              green += xfrag2 * src[((yi+i+1)*swidth+xi+ii+1)*4+1];
              blue += xfrag2 * src[((yi+i+1)*swidth+xi+ii+1)*4+2];
              alpha += xfrag2 * src[((yi+i+1)*swidth+xi+ii+1)*4+3];
          }
      }

      if (yi + i + 1 < sheight)
      {
          red += xfrag * yfrag2 * src[((yi + i + 1)*swidth + xi) * 4];
          green += xfrag * yfrag2 * src[((yi + i + 1)*swidth + xi) * 4 + 1];
          blue += xfrag * yfrag2 * src[((yi + i + 1)*swidth + xi) * 4 + 2];
          alpha += xfrag * yfrag2 * src[((yi + i + 1)*swidth + xi) * 4 + 3];

          for (ii = 0; xi + ii + 1 < xt + dx - 1 && xi + ii + 1 < swidth; ii++)
          {
              red += yfrag2 * src[((yi + i + 1)*swidth + xi + ii + 1) * 4];
              green += yfrag2 * src[((yi + i + 1)*swidth + xi + ii + 1) * 4 + 1];
              blue += yfrag2 * src[((yi + i + 1)*swidth + xi + ii + 1) * 4 + 2];
              alpha += yfrag2 * src[((yi + i + 1)*swidth + xi + ii + 1) * 4 + 3];
          }
      }

      if (yi + i + 1 < sheight && xi + ii + 1 < swidth)
      {
          red += xfrag2 * yfrag2 * src[((yi + i + 1)*swidth + xi + ii + 1) * 4];
          green += xfrag2 * yfrag2 * src[((yi + i + 1)*swidth + xi + ii + 1) * 4 + 1];
          blue += xfrag2 * yfrag2 * src[((yi + i + 1)*swidth + xi + ii + 1) * 4 + 2];
          alpha += xfrag2 * yfrag2 * src[((yi + i + 1)*swidth + xi + ii + 1) * 4 + 3];
      }


     red /= dx * dy;
     green /= dx * dy;
     blue /= dx * dy;
     alpha /= dx * dy;

     red = clamp(red, 0, 255);
     green = clamp(green, 0, 255);
     blue = clamp(blue, 0, 255);
     alpha = clamp(alpha, 0, 255);

     dest[(y*dwidth+x)*4] = (unsigned char) red;
     dest[(y*dwidth+x)*4+1] = (unsigned char) green;
     dest[(y*dwidth+x)*4+2] = (unsigned char) blue;
     dest[(y*dwidth+x)*4+3] = (unsigned char) alpha;
    }
  }


}

It is maintained in the Baby X resource compiler on github.

https://github.com/MalcolmMcLean/babyxrc