Macromedia Director MX04: Great looking Image Scaling Code

Recently I wrote about looking for a better way to scale images in Director. Well, Josh Chunick came to my rescue on Direct-L (actually Robotduck posted Josh’s code, but Josh posted about it shortly after).

The lingo code is great and performs faster than I thought lingo would. I’ve made some changes to it to improve it for my needs, and I’ll post the code below, with notes and samples afterwards:
on imageResample(theImage, newWidth, newHeight, sharpenAmount)
  -- Smooth Image Resampling Code
  -- WAS: Bilinear Image Resampling Code
  -- ©2005 by Josh Chunick (josh@chunick.com)
  -- code optimizations by Hanford Lemoore ( http://blog.hanfordlemoore.com )
  -- code optimizations by Thomas Mavrofides
  -- This code is free to use in commercial applications
  -- or however you want.If you use this code you
  -- must keep the comments, including this message,
  -- intact. Feel free to add any changes or make improvements.
  -- ======================================
  -- NOTES:
  -- 1/25/06 Hanford Lemoore:
  -- Removed the lookup table;  it was often slower, esp. with large images
  --
  -- Changed RGB colors to lists. This allows for floating operations to yield
  -- closer-to-the-original colors, including white, especially with “untouched” pixels.
  --
  -- Added code to use copypixels when the scale is more than 1/2, as copypixels
  -- looks better. This is why I renamed the function; since it’s not always bilinear.
  --
  -- fixed a scale bug where the new art was off by 1 pixel both dimensions
  --
  -- Incorporated Sharpening, but I commented it out to improve speed.

  newImage = image(newWidth, newHeight, 32)
  oldWidth = theImage.width
  oldHeight = theImage.height

  -- return the original image if the scale factor is 1
  if newImage.rect = theImage.rect then return theImage

  --HL: use copypixels to scale it if it’s shrinking more than 1/2
  -- Bilinear scaling it not very good when shrinking images more than 1/2 their size,
  -- especially on images with fine lines, and copypixels is better.
  if newimage.width < (theimage.width/2) or  newimage.height < (theimage.height/2) then
    newimage.copypixels(theimage,newimage.rect,theimage.rect)
    return newimage
  end if

  -- get the ratio between the old image and the new one
  xScale = (oldWidth) / float(newWidth)
  yScale = (oldHeight) / float(newHeight)

  -- draw each pixel in the new image
  repeat with y = 1 to newHeight

    -- generate the y calculation variables
    dstY = (y—1) * yScale
    interplY = bitXor(dstY, 0)
    calcY = dstY—interplY

    repeat with x = 1 to newWidth
      -- generate the x calculation variables
      dstX = (x—1) * xScale
      interplX = bitXor(dstX, 0)
      calcX = dstX—interplX

      -- get the 4 pixels around the interpolated one

      -- HL: color()s are converted to lists; allows floating point calcs, giving more accurate color results
      -- Slower, but without that, true white can’t be reached in the destination image.
      irgb = theImage.getPixel(interplX,interply)
      if irgb=0 then irgb= color(0,0,0)
      theColour1 = [irgb.red,irgb.green,irgb.blue]
      irgb = theImage.getPixel(interplX + 1,interplY)
      if irgb=0 then irgb= color(0,0,0)
      theColour2 = [irgb.red,irgb.green,irgb.blue]
      irgb = theImage.getPixel(interplX,interplY + 1)
      if irgb=0 then irgb= color(0,0,0)
      theColour3 = [irgb.red,irgb.green,irgb.blue]
      irgb = theImage.getPixel(interplX + 1,interplY + 1)
      if irgb=0 then irgb= color(0,0,0)
      theColour4 = [irgb.red,irgb.green,irgb.blue]

      -- calculate the new colour
      newColor1 = theColour1 * (1—calcY) + theColour3 * calcY
      newColor2 = theColour2 * (1—calcY) + theColour4 * calcY
      newColor= newColor1 * (1—calcX) + newColor2 * calcX

      --    --HL: Uncomment these lines if you want sharpen to be an option
      --      if not(voidp(sharpenAmount)) then
      --        newcolor = newcolor + sharpenAmount * (theColour3—theColour1)
      --      end if

      newcolor = color(newcolor[1],newcolor[2],newcolor[3])

      --Set this pixel into the new image
      newImage.setPixel(point(x—1,y—1), newColor)

    end repeat
  end repeat
  return newImage
end

Notes


In testing the original Bilinear resampling code I discovered that Bilinear doesn’t always work so well on fine details like thin diagonal lines. In fact, for certain scales Director’s own scaling code looked way smoother and cleaner than bilinear. Upon further research I discovered the problem, Bilinear scaling is optimized for scaling an image to less than half it’s size. The code I was using was averaging colors from surrounding pixels. When scaling is more than 1/2, pixel data is being thrown out without being factored into the new image.

Check out the details in the images below. The original was 1024 by 768, and these are scaled down to 25%. Notice the dropouts in the powerlines in the Bilinear version. Notice the aliasing in the letters. The Copypixels version looks much better.

Bilinear Resampling to 25%:
Bilinear resampling to 25% it's original size

Copypixels Resampling to 25%:
Copypixels resampling an image to 25%

Director has the opposite problem. Scaling something in Director more than 50% looks quite good, even on thin lines and images with lots of detail. But when it’s scaling is less than 1/2, it appears as though Director shifts half (or more) of it pixels instead of resampling them. This causes bumps and unevenness when a shifted row of pixels meets up against a resampled row. You can see samples of this problem in my previous blog entry.

My solution was to add code to use copypixels when scaling more than 50%, and to use bilinear resampling when less than 50%.

Other Changes


I removed a lookup table. I did many tests with and without the lookup table, comparing times, and I found it was slower.

I changed over the code to use floating point calculations for color. I needed my new image to have colors that matched the original whenever possible, and I discovered that when I scaled an entirely white image with the original code, that the white turned to an off-white of color(253,253,253). Converting it over to floating point slowed things down, but I needed the color accuracy.

Finally, I fixed an offset bug that was causing the new image to be off by 1 pixel in both dimensions. I discovered this when I was comparing the bilinear results to copypixels results and saw that the pixels looked slightly shifted. I confirmed it when I commented out the shortcut code that aborted the resample if the source and dest were both the same size. Turned out a 1-to-1 resampling produced blurry results.

I’m pretty happy with the resulting code. As per Josh’s original edict, feel free to use it for your own purposes and let me know if you make changes or improvements. I’d love to hear about it.

Samples


Original Image:
Director Image Resampler Code

Bilinear Resample:
Director Image Resampler Code

Copypixels Resample:
Director Image Resampler Code


2 Responses to “Macromedia Director MX04: Great looking Image Scaling Code”

  1. blog.hanfordlemoore » Blog Archive » Scaling images in Macromedia Director MX04 Says:

    [...] User Interface , Art, Design, Games « Previous Entry Next Entry » [...]

  2. tortugaloka Says:

    Very usefull article you got here. I’ve used it and got good results. May I sugest you add support for images with transparency ? If you attempt to use this with an image that uses information in the alpha channel, a black area will be drawin instead.

    Regards!

Leave a Reply

For security, enter the word TURING below:
Comments RSS feed