Mirroring an image

Hello all,

I was not sure of where to post this question, here or in the newbie forum. Anyway, here it goes:

I have a character’s sprite with all his animations (walking, jumping, etc). However, they are all onesided (character looking to the right). I was wondering what was the fastest way to mirror the image, if possible at runtime, in order to make the animations work both way.

You can use an AffineTransform when drawing the image (either at load time to duplicate or in game if you don’t want the extra memory use) but I don’t know how fast it’ll be. Simple mirroring on an axis should be trivial for graphics hardware, so its possible that it might get picked up on and be fast, or it might get mirrored in software and be a whole lot slower :frowning:

Oh no need for AffineTransform :slight_smile:

You can flip on the fly with drawImage (oh and it’s fast).

Take a look at the documentation:
http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Graphics.html

Hmmmm…so in drawImage I just need to specify an “inverted” rectangle and it will be flipped??? Duh for me!!! :-[

And I can do this with managed, accelerated, images?? Double duh :-[

Yep :slight_smile:

Are you dissapointed, that it’s so easy? ;D

Another easy thing is basic collision detection… you can do that with awt Rectangle eg:

if(shipRect.intersects(enemyRect))
//blam :slight_smile:

I just told you that because most beginners doesn’t know that… and it makes life so much easier for starters.

HA!! The collision is one tip that I already know!! ;D Well, at least I should know something, right? :slight_smile:

Thanks a lot!! Now I am a happy coder!! :slight_smile:

Well can we take this a step further? If this guy can face left and right with drawImage(), can he also face up and down from the same original image? The JDK docs say “scaled and flipped”, but doesn’t say anything about rotation. If you want your sprite to go 4 directions, it looks like you need two source images. One for up/down and the other for left/right.

A different question also begs palette-swapping. In my Pacman case, a ghost is blue when you eat a power pill. When he’s getting ready to turn back to normal he flashes white. Do I need new images for that, or can I say “Turn all the blue pixels white”? And will it be hardware-accelerated?

Didn’t mean to hijack a topic, but I figure my questions are in the same vein.

Michael Bishop

MBishop, I made a little test with the flipping from drawImage and it can also flip up/down. This is not necessarily a rotation, since rotations can be quite expensive, and in any degree… Anyway, it appears you don’t need separate images for different flips.

But, your questions about palette-swaping is really interesting, and I would also like to know the answer…

Do I need new images for that, or can I say “Turn all the
blue pixels white”? And will it be hardware-accelerated?

I usually do it the other way 'round. They are white and I ignore the colors (chanels) I don’t need.


private void createPlayerImages()
{
      int h=wPlayer.getHeight(null);
      int w=wPlayer.getWidth(null);
      int gpixels[] = new int[h * w];

      PixelGrabber pixelgrabber = new PixelGrabber(wPlayer, 0, 0, w, h, gpixels, 0, w);
      try
      {
            pixelgrabber.grabPixels();
      }
      catch(InterruptedException interruptedexception)
      {
            GUI.dm.printErr("interrupted waiting for pixels!");
      }
      if((pixelgrabber.getStatus() & ImageObserver.ERROR) != 0)
            GUI.dm.printErr("image fetch aborted or errored");

      int pixels[] = new int[h * w * 4];
      Image img = createImage(new MemoryImageSource(w * 4, h, ColorModel.getRGBdefault(), pixels, 0, w * 4));
      /*
      Number of bits:        32
      Red mask:              0x00ff0000
      Green mask:            0x0000ff00
      Blue mask:             0x000000ff
      Alpha mask:            0xff000000
      Color space:           sRGB
      isAlphaPremultiplied:  False
      Transparency:          Transparency.TRANSLUCENT
      transferType:          DataBuffer.TYPE_INT
      */
      for(int y=0;y<h;y++)
      {
            for(int x=0;x<w;x++)
            {
                  int pixel = gpixels[x+y*w];
                  int alpha = pixel & 0xff000000;
                  int red   = pixel & 0x00ff0000;
                  int green = pixel & 0x0000ff00;
                  int blue  = pixel & 0x000000ff;

                  pixels[x+w*0 + y*w*4]=alpha | red;         //red player
                  pixels[x+w*1 + y*w*4]=alpha | green;       //green player
                  pixels[x+w*2 + y*w*4]=alpha | blue;        //blue player
                  pixels[x+w*3 + y*w*4]=alpha | red | green; //yellow player
            }
      }

      if(img != null)
            img.flush();

      players = new BufferedImage[4];

      for(int i=0;i<4;i++)
      {
            players[i]= gc.createCompatibleImage(w,h,Transparency.TRANSLUCENT);
            Graphics2D tg = (Graphics2D)players[i].getGraphics();
            tg.setComposite(AlphaComposite.Src);
            tg.drawImage(img,
                  0,0,
                  w,h,
                  i*w,0,
                  i*w+w,h,
                  null);
            tg.dispose();
      }
}

After that method it’s like I just loaded 4 different sprites in different colors :slight_smile:

I grab the pixels of the white sprite, create a MemoryImageSource-Image wich is 4 times bigger, then I take each pixel apart into it’s channels (rgba), put em were they should be (red here the blue one there etc) and at the end I flush the image and copy each sprite into a BufferedImage.

Check this image: http://people.freenet.de/ki_onyx/ludo_mockup8.png
(The white sprite is the original all other sprites are generated by that method above. The whole thing is <8kb :))

If you need rotation you can use AffineTransform either to rotate in realtime or to create the amount of rotated images, you’ll need, at the beginning. However, creating em at the beginning is usually the way to go, because it’s faster and doesn’t create that much garbage.

That’s interesting, but the concept of working with MemoryImageSource and PixelGrabber is new to me. I get the idea, but what happens if you only want to manipulate a certain color? When ghosts are “energized” because Pacman ate a power pill, they go to blue ghosts with white faces. When they start flashing, they alternate between the above and WHITE ghosts with BLUE faces. Is there a way to say “Take this particular color and make it this other color”? Your example appears to be images with different shades of the SAME color; you don’t have multiple colors.

I also assume that since you’re painting it to a compatible Image, the result is hardware-accelerated or at least a candidate for it.

Thanks a bunch, this is pretty helpful.

Michael Bishop

As usual there are tons of ways to do it… all with the same result :slight_smile:

Take a look at the stuff you can do with setComposite.

Also if your background has always the same color (eg black) than you can do quite “cheap” stuff. Let’s say you want display text in different colors… create an Image wich is entrirely black and transparent text (1bit Image :)). In the game you just use fillRect and put the image ontop of that.

Here is an Image how that can look like:

And this is the 1bit charmask:

And ofcorse you can exchange a specific color with another… either by doing it similar to the way I did with the channels or by changing the palette (but I hadn’t done that so far in Java - at least not with single Images).

As far as changing a single color, here’s what I came up with:


public BufferedImage changeColor(BufferedImage inImage, Color oldColor,
                  Color newColor) {
            
            // Get the width and the height of the image we're going to change.
            int imageHeight = inImage.getHeight();
            int imageWidth = inImage.getWidth();
            
            // Get the old color we want to change in the integer (ARGB) form.
            int targetColor = oldColor.getRGB();
            
            // Create a single-dimension array that has enough room to hold all
            // the pixels of the changing image.
            int[] imagePixels = new int[imageWidth * imageHeight];

            // A PixelGrabber simply populates the above array with all the ARGB
            // values of the image we want to change.
            PixelGrabber pixelGrabber = new PixelGrabber(inImage, 0, 0,
                        imageWidth, imageHeight, imagePixels, 0, imageWidth);
            
            try {
                  pixelGrabber.grabPixels();
            }
            catch (InterruptedException iE) {
                  iE.printStackTrace();
                  return inImage;
            }
            
            // Make sure no errors occured.  If they do, just give back the
            // original image.
            if ((pixelGrabber.getStatus() & ImageObserver.ERROR) != 0) {
                  System.err.println("Pixel-grabbing aborted or errored.");
                  return inImage;
            }
            
            // Create another array.  This array will be another array of pixels,
            // but is intended to store the changed values.
            int[] newPixels = new int[imageHeight * imageWidth];
            
            // Create an Image made from a MemoryImageSource.  As we change the
            // values of the pixels, the resultant MemoryImageSource will change
            // thus the Image will change as well.
            Image newImage = thisToolkit.createImage(new MemoryImageSource(
                        imageWidth, imageHeight, ColorModel.getRGBdefault(),
                        newPixels, 0, imageWidth));
            
            // Iterate through our ENTIRE set of old pixels.
            for (int y = 0; y < imageHeight; y++) {
                  for (int x = 0; x < imageWidth; x++) {
                        
                        // Get the current pixel value and compare it against the value
                        // of the color we want to change.  If they're not the same,
                        // don't change the original value.
                        int currentPixel = imagePixels[x + y * imageWidth];
                        if (currentPixel != targetColor) {
                              newPixels[x + y * imageWidth] = currentPixel;
                        }
                        
                        // Otherwise, change the value to the NEW color value.
                        else {
                              newPixels[x + y * imageWidth] = newColor.getRGB();
                        }
                  }
            }
            
            // Flushing the Image in this case flushes any buffered pixel data to
            // ensure all our changes have been applied to the Image.
            if (newImage != null) {
                  newImage.flush();
            }
            
            // Now we want an Image to return to the user.  Of course we want it
            // to be a candidate for hardware-acceleration, so we use another
            // method of this class to ensure that.
            BufferedImage returnImage = createImage(imageWidth, imageHeight,
                        inImage.getColorModel().getTransparency());
            
            // Draw the new Image to our returnImage and, well, return it!
            Graphics2D returnGraphics = returnImage.createGraphics();
            returnGraphics.setComposite(AlphaComposite.Src);
            returnGraphics.drawImage(newImage, 0, 0, imageWidth, imageHeight,
                        0, 0, imageWidth, imageHeight, null);
            returnGraphics.dispose();
            return returnImage;
      }

Pretty self-explanatory; the createImage() call is a call to GraphicsConfiguration.createImage() and thisToolkit is a Toolkit. The only problem is that when you render your original image with anti-aliasing, you get different shades of a particular color. I need to modify that function to determine whether or not a color is a “shade” of the given color and apply the same shade of the new color. Aside from that, it works well. Thanks Onyx!

Michael Bishop

With BufferedImage you can use setRgb and getRgb instead of PixelGrabber and MemoryImageSource. They are easier to use :slight_smile:

One way to swap colors not using old 1.1. api’s is to use filtering operations.

Take a look at these turorials:
http://java.sun.com/docs/books/tutorial/2d/images/filtering.html
http://java.sun.com/j2se/1.4.2/docs/guide/2d/spec/j2d-image.html#wp63208

There’s also a lot of info found on here (guides, turorials):
http://java.sun.com/j2se/1.4.2/docs/guide/2d/index.html

One way to swap colors not using old 1.1. api’s is to use filtering operations.

Are there any benefits doin that the new way?

Changing the palette seems to be much easier… but in my case (the four player sprites)? Found it pretty easy and straightforward that way (and it doesnt even take a splitsecond).

However, thanks for the linkage. I’ll take a look the next time I need it :slight_smile:

OK, so to sum up for best performance and efficiency:

Let’s start with Pacman (since I’m working on it and it’s a situation everyone should be familiar with). Pacman himself has nine frames of animation. I have all 9 of these frames in a PNG file that are loaded as compatible images. That should be all I need because with the drawImage() function, I can “flip” him 90 degrees in any direction and still retain hardware-acceleration.

Then we move to maze tiles. All the unique ones are stored on the hard drive as PNG files and again are loaded as compatible images. Again, they can be turned 90 degrees with drawImage(). The difference here is that some mazes are different colors. So in the “pause” between loading levels, I can use the Java2D API to do a color-swap and store the new images in memory.

For ghosts, it’s trickier because they never flip 90 degrees; the only thing that moves are their eyes. Regardless, I have one strip of ghost animations. At load-time for the game, I take that strip, convert it into 4 different colors and associate each set of images to each ghost. It would be too expensive to swap colors and draw as necessary each frame, but that saves me from having 4 times as many ghosts stored on the hard drive.

Does this cover it, or am I missing something else? The gist of it is that color-swapping is done at load time; I will always have images of different color in memory. Rotation, or more specific, 90-degree turns do NOT need to be stored in memory because the particular flavor of drawImage retains hardware-acceleration unlike something like an AffineTransform. Correct?

Michael Bishop