[comp.graphics] how to rotate bitmapped images?

mikem@uhccux.uhcc.hawaii.edu (Mike Morton) (11/05/88)

A friend and I are looking for information on how to rotate a bitmapped
image by an arbitrary angle (not something nice like 90 degrees).
Unfortunately, we'd like to find a fast algorithm; is this possible?
A description or reference to something published would be great.

Please email to me, as I don't normally read this group.  I'll post a
summary.

 -- Mike Morton // P.O. Box 11378, Honolulu, HI  96828, (808) 676-6966 HST
      Internet: msm@ceta.ics.hawaii.edu
    (anagrams): Mr. Machine Tool; Ethical Mormon; Chosen Immortal; etc.

cm26+@andrew.cmu.edu (Curt McDowell) (11/08/88)

/*
 * How to Rotate Bitmaps:  Example
 *
 * By Curt McDowell, Carnegie Mellon University
 *
 * I posted before on how wonderful CORDIC is.  But this is a
 * very good opportunity to convince the rest of you!!
 *
 * CORDIC is the only imaginable way to accomplish this
 * task.  If anyone thinks I'm wrong, please tell me about your
 * method!  How does PostScript do it?
 *
 * Do not attempt to compile this right away, since it requires
 * my own bitmap graphics package.  However the modifications
 * for using other bitmap packages are trivial since only
 * the ability to set, reset, and test single points is necessary.
 */

#include "gr.h"
#include "ras.h"

/*
 * CORDIC works better if everything is done shifted left.
 * The scaling provides extra bits of accuracy on the right,
 * without affecting the amount of rotation.  So it's like
 * fixed-point arithmetic.  The shifting used for scaling takes
 * 1 operation on most computers (or 0 on some like Z-80).
 */

#define SCALE 16

/*
 * What I do is first decide what area of the screen I
 * want to rotate.  In this example it's a large circle
 * in the center of the screen.  I scan the area and,
 * for each pixel that is on, enter its coordinates in a
 * large array of pixel (x,y)'s.  This is the sole purpose
 * of initpoints().
 *
 * To make it faster I've gone down to the bitmap.
 * You could use any method you want.  For a square
 * area, e.g.:
 *
 *   numpoints = 0;
 *   for (y = 0; y < MAXY; y++)
 *       for (x = 0; x < MAXX; x++)
 *           if (PointSet(x, y)) {
 *               arrayX[numpoints] = x << SCALE;
 *               arrayY[numpoints] = y << SCALE;
 *               numpoints++;
 *           }
 *
 * Also notice I'm scaling the points left by 16 bits as
 * I read them in.  I'll simply scale them right when plotting
 * the rotated image.  Simple.  We're done worrying about
 * scaling!
 */

#define MAXPOINTS 150000
long px[MAXPOINTS], py[MAXPOINTS];
int numpoints;

long cx, cy;
struct ras *screen;

#ifdef MSBLEFT
#define LeftBitOn(b) (b & MSBit)
#define ShiftLeft(b) (b <<= 1)
#else
#define LeftBitOn(b) (b & LSBit)
#define ShiftLeft(b) (b >>= 1)
#endif

initpoints(radius)
int radius;
{
    register unsigned long *p, b;
    int lineCount, colCount;
    register int bitCount;
    int curx, cury, curySquared;
    int radiusSquared = radius * radius;

    numpoints = 0;
    lineCount = screen->height;
    p = gr_framebuf;
    cury = -cy;

    /* Don't worry about this stuff.  It's a faster way of reading */
    /* the pixels from a circle. */

    while (lineCount--) {
        curySquared = cury * cury;
        curx = -cx;
        colCount = screen->width / 32;
        while (colCount--) {
            bitCount = 32;
            b = *p;
            while (bitCount--) {
                if (curx * curx + curySquared < radiusSquared &&
                  LeftBitOn(b)) {
                    px[numpoints] = curx << SCALE;
                    py[numpoints] = cury << SCALE;
                    numpoints++;
                }
                ShiftLeft(b);
                curx++;
            }
            p++;
        }
        cury++;
    }
}

/*
 * This routine right here is the amazing one.
 * It erases the previous image and simultaneously
 * draws the new rotated one, using XOR plotting mode!
 *
 * By far the most time being spent in this routine is
 * in the plotting, NOT the rotation!
 *
 * Of course, you could remove plotting from this routine
 * altogether.  Then this routine reduces to something
 * ridiculously small which rotates an array of points by
 * some angle.  Amazing!  You can use any scheme you want
 * for erasing the old image and replotting the new one, if
 * that's what you need.
 *
 * Can anyone think of a better way to store the pixels than
 * the (x, y) coordinates?  Maybe something independent of the
 * pixel density?
 */

cycle(xs, ys, count)
register long *xs, *ys;
register int count;
{
    while (count--) {
        /* Turn off point from old location */

        ras_tog(screen, cx + (*xs >> SCALE), cy + (*ys >> SCALE));

        /* Simple CORDIC rotation by atn(2 ^ -k).  E.g., */
        /* k=4 is rotation by ~ 3.57 deg CW */
        /* k=5 is rotation by ~ 1.79 deg CW */
        /* To rotate CCW, toggle subtraction and addition! */
        /* Below is a rotation by 1.79 deg (k=5). */

        *xs -= *ys >> 5;
        *ys += *xs >> 5;

        /* Set point at new location.  Wow! */

        ras_tog(screen, cx + (*xs >> SCALE), cy + (*ys >> SCALE));

        xs++;
        ys++;
    }
}

/*
 * But, you say, you can only rotate by these weird
 * arctangent angles.  Well, the next obvious thing to do
 * is use a few CORDIC rotations in a row to get any
 * angle.  No problem since each rotation is only two
 * shifts, an add, and a subtract!  E.g.:
 *
 *      *xs -= *ys >> 5;
 *      *ys += *xs >> 5;
 *      *xs -= *ys >> 4;
 *      *ys += *xs >> 4;
 *
 * This would rotate atn(2^-4) + atn(2^-5) or about
 * 3.57 + 1.79 = 5.36 degrees.  Since your scale can get as
 * big as you want, you can get any angle to any accuracy
 * using rotations of the right sizes.
 * You can also mix in CCW rotations if it helps.
 */

main()
{
    srand(time(0));

    /* Open screen... */

    gr_init();

    screen =
      ras_dpy((u_short *) gr_framebuf,
      gr_screenwidth, gr_screenheight);

    cx = screen->width / 2;
    cy = screen->height / 2;

    /* Read in initial points from a big circle on the screen. */
    /* It reads what happens to be on the screen when */
    /* this program is run. */

    initpoints(screen->height * 7 / 16);

    /* Now -- repeatedly rotate a little, erasing and */
    /* redrawing the image! */

    while (1)
        cycle(px, py, numpoints);
}

/*
 * BUT WAIT, that's not all!  CORDIC rotation is stable.
 * This means you can sit there rotating the same set of
 * points repeatedly forever, and they will NOT diverge or
 * converge.  Although a few of them may shift a pixel or so.
 *
 * In the unlikely event that you make a million bucks
 * because of seeing this, send money and/or employment
 * literature to:
 *
 *   Curt McDowell
 *   Box 499
 *   5115 Margaret Morrison St.
 *   Pittsburgh, PA 15213
 *
 *   Arpa mail: cm26@andrew.cmu.edu
 */