Bilinear filtering of images

While playing around with Java’s crappy image-stuff, i wrote a simple method to zoom (downscaling is possible, but doesn’t make much sense using this algo) an image using a bilinear filter. It’s halfway optimized (using fixedpoint) and maybe somebody finds it usefull. It doesn’t really fit into anything else i’m doing, so i decided to release it here instead:

import java.awt.*;
import java.awt.image.*;

public class ImageUpscale {

  public static Image upscale(Image img, int width, int height) throws Exception {

    final int ACC = 512;
    final int ACCHX;
    final int ACCHY;
    final int ACCS = 9;
    final int ACCSD = ACCS << 1;
    final int ACCMASK = ~511;

    int sWidth = img.getWidth(null);
    int sHeight = img.getHeight(null);
    int length = sWidth * sHeight;
    int[] pixels = new int[length];
    PixelGrabber pg = new PixelGrabber(img, 0, 0, sWidth, sHeight, pixels, 0, sWidth);
    pg.grabPixels();

    BufferedImage dest = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    DataBufferInt data = (DataBufferInt) (dest.getRaster().getDataBuffer());
    int[] dPixels = data.getData();

    double xf = (double) sWidth / (double) width;
    double yf = (double) sHeight / (double) height;

    if (xf == (int) xf) {
         ACCHX = ACC >> 1;
    }
    else {
         ACCHX = 0;
    }

    if (yf == (int) yf) {
         ACCHY = ACC >> 1;
    }
    else {
         ACCHY = 0;
    }

    int xfI = (int) (xf * ACC);
    int yfI = (int) (yf * ACC);

    int sWidthACC = sWidth << ACCS;
    int sHeightACC = sHeight << ACCS;
    int fsy = 0;
    for (int y = 0; y < height; y++) {

      int floorY = fsy & ACCMASK;
      int ceilY = floorY + ACC;
      if (ceilY >= sHeightACC) {
        ceilY = floorY;
      }
      int fracY = (fsy & ~ACCMASK) + ACCHY;
      int oppY = ACC - fracY;
      int yPos = y * width;
      int fyW = (floorY * sWidth) >> ACCS;
      int cyW = (ceilY * sWidth) >> ACCS;

      int fsx = 0;
      int end = width + yPos;

      for (int x = yPos; x < end; x++) {

        int floorX = fsx & ACCMASK;
        int fracX = (fsx & ~ACCMASK) + ACCHX;
        int oppX = ACC - fracX;
        int ceilX = floorX + ACC;
        if (ceilX >= sWidthACC) {
          ceilX = floorX;
        }

        int c1 = pixels[ (floorX >> ACCS) + fyW];
        int c2 = pixels[ (ceilX >> ACCS) + fyW];
        int c3 = pixels[ (floorX >> ACCS) + cyW];
        int c4 = pixels[ (ceilX >> ACCS) + cyW];

        int c1r = (c1 >> 16) & 255;
        int c1g = (c1 >> 8) & 255;
        int c1b = c1 & 255;

        int c2r = (c2 >> 16) & 255;
        int c2g = (c2 >> 8) & 255;
        int c2b = c2 & 255;

        int c3r = (c3 >> 16) & 255;
        int c3g = (c3 >> 8) & 255;
        int c3b = c3 & 255;

        int c4r = (c4 >> 16) & 255;
        int c4g = (c4 >> 8) & 255;
        int c4b = c4 & 255;

        int cc1 = oppX * c1r + fracX * c2r;
        int cc2 = oppX * c3r + fracX * c4r;
        int r = (oppY * cc1 + fracY * cc2) >> ACCSD;

        cc1 = oppX * c1g + fracX * c2g;
        cc2 = oppX * c3g + fracX * c4g;
        int g = (oppY * cc1 + fracY * cc2) >> ACCSD;

        cc1 = oppX * c1b + fracX * c2b;
        cc2 = oppX * c3b + fracX * c4b;
        int b = (oppY * cc1 + fracY * cc2) >> ACCSD;

        dPixels[x] = (r << 16) | (g << 8) | b;
        fsx += xfI;
      }
      fsy += yfI;
    }
    return dest;
  }
}

wow I just played with that and it works really well! Very good job! :slight_smile:

Reminds me of some MMX code I once wrote.

Did the filtering with nearly [i]no[/i] overhead against a pure image copy by using memory wait times and such.

It was used for displaying smoke/fire effects in a game, where a texture has to be scale-copied to the screen. With MMX, it has been filtered … looked really nice!

What’s wrong with Java2D scaling? ???
Can’t see much difference?


private static final GraphicsConfiguration gc =
   GraphicsEnvironment.getLocalGraphicsEnvironment().
      getDefaultScreenDevice().getDefaultConfiguration();

public static BufferedImage resize(BufferedImage src, int w, int h) {
   int transparency = src.getColorModel().getTransparency();
   BufferedImage image = gc.createCompatibleImage(w, h, transparency);

   Graphics2D g = image.createGraphics();
   g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 
                      RenderingHints.VALUE_INTERPOLATION_BILINEAR);
   g.drawImage(src,
               0, 0, w, h, // destination
               0, 0, src.getWidth(), src.getHeight(), // source
               null);
   g.dispose();

   return image;
}

[quote]What’s wrong with Java2D scaling? ???
Can’t see much difference?
[/quote]
It’s not a much of a visible difference, but i had the problem, that Java’s stuff was crashing with out of memory in some cases, where mine will still work and my method is a little faster (but not that much…then again, it haven’t spend much time optimizing it). In addition, i wanted something that will work on simple arrays (for resizing textures and the framebuffer in jPCT) apart from all the Image-stuff.