1. /**
  2.  * @author Captain Awesome (http://www.javagaming.org/index.php?action=profile;u=28320)
  3.  * You may use this sprite-class (or parts of it) in any way you want, as long
  4.  * as you don't remove this notice and give me credit for my work.
  5.  *
  6.  * The only thing I didn't make myself is the splitImage(); method,
  7.  * which I found (and copied) from: http://www.javalobby.org/articles/ultimate-image/#13
  8.  *
  9.  * The reference to the BufferedImage you use in the constructor is not kept
  10.  * since the Sprite creates it's own optimized BufferedImage.
  11.  *
  12.  */
  13.  
  14. package testgame;
  15.  
  16. import java.awt.Color;
  17. import java.awt.Graphics;
  18. import java.awt.Graphics2D;
  19. import java.awt.GraphicsConfiguration;
  20. import java.awt.GraphicsEnvironment;
  21. import java.awt.Image;
  22. import java.awt.Rectangle;
  23. import java.awt.Toolkit;
  24. import java.awt.Transparency;
  25. import java.awt.image.BufferedImage;
  26. import java.awt.image.FilteredImageSource;
  27. import java.awt.image.ImageFilter;
  28. import java.awt.image.ImageProducer;
  29. import java.awt.image.PixelGrabber;
  30. import java.awt.image.RGBImageFilter;
  31. import java.io.Serializable;
  32. import java.util.logging.Level;
  33. import java.util.logging.Logger;
  34.  
  35. public class Sprite implements Cloneable, Serializable {
  36.     public Sprite(BufferedImage img) {
  37.         spriteImg = toCompatibleImage(img);
  38.     }
  39.  
  40.     @Override
  41.     public Sprite clone() {
  42.         try {
  43.             return (Sprite) super.clone();
  44.         }catch (CloneNotSupportedException e) {
  45.             System.out.println("Clone failed.");
  46.             return null;
  47.         }
  48.     }
  49.  
  50.     /*
  51.      * Starts the animation of the sprite.
  52.      * The user must then call continueAnimation() in order to animate the sprite.
  53.      */
  54.     public void setAnimation(int sleep) {
  55.         sleepTime = sleep;
  56.         currentSleepFrame = 0;
  57.         runAnim = true;
  58.     }
  59.  
  60.     public boolean isAnimating() {
  61.         return runAnim;
  62.     }
  63.  
  64.     /*
  65.      * Stops the animation
  66.      */
  67.     public void stopAnimation() {
  68.         runAnim=false;
  69.     }
  70.  
  71.  
  72.     /*
  73.      * Paints the sprite. If splitSprite has been used,
  74.      * it will paint the current frame.
  75.      */
  76.     public void paint(Graphics g) {
  77.         g.drawImage(this.getImage(), this.getRealX(), this.getRealY(), null);
  78.     }
  79.  
  80.     //Continues an animation
  81.     public void continueAnimation() {
  82.         if(isAnimating() && currentSleepFrame >= sleepTime) {
  83.             currentSleepFrame = 0;
  84.             this.nextFrame();
  85.         }else {
  86.             currentSleepFrame ++;
  87.         }
  88.     }
  89.  
  90.  
  91.     /*
  92.      *  Paints the original Sprite if setAnimation has been used
  93.      */
  94.     public void paintOrig(Graphics g) {
  95.         g.drawImage(spriteImg, x, y, null);
  96.     }
  97.  
  98.     /*
  99.      * Sets the position based on the parameters
  100.      */
  101.     public void setPosition(int x, int y) {
  102.         this.x=x;
  103.         this.y=y;
  104.     }
  105.  
  106.     //Defines which reference pixel (i.e where the image will be placed on the x/y coordinates)
  107.     public void setRefPixel(int x, int y) {
  108.         refX = x;
  109.         refY = y;
  110.     }
  111.  
  112.     /*
  113.      * Creates an animation of the current sprite, @param cols & rows decides
  114.      * how many columnss and rows the sprite should be split into
  115.      * It then modifies the @var frameSequence to hold every spritenumber in animImg
  116.      */
  117.     public void splitSprite(int cols, int rows) {
  118.         this.cols = cols;
  119.         this.rows = rows;
  120.  
  121.         animImg = splitImage(spriteImg, cols, rows);
  122.         frameSequence = new int[animImg.length];
  123.         for(int i=0;i<animImg.length;i++)frameSequence[i]=i;
  124.     }
  125.  
  126.     public void setFrame(int frame) {
  127.         currentFrame = frame;
  128.     }
  129.  
  130.     /*
  131.      * Edits the framesequence (alters the animation), 2 versions
  132.      * The first sets a name to use with getFrameSequence();
  133.      * The other one just sets the framesequence
  134.      */
  135.     public void setFrameSequence(int[] sequence, String name) {
  136.         frameSequence = sequence;
  137.         currentFrame = 0;
  138.  
  139.         frameSequenceName = name;
  140.     }
  141.  
  142.     public void setFrameSequence(int[] sequence) {
  143.         frameSequence = sequence;
  144.         currentFrame = 0;
  145.  
  146.         frameSequenceName = "UNDEFINED";
  147.     }
  148.  
  149.     public String getFrameSequence() {
  150.         return frameSequenceName;
  151.     }
  152.  
  153.     public int[] getFrames() {
  154.         return this.frameSequence;
  155.     }
  156.  
  157.     /*
  158.      * Goes to the next frame in the animation
  159.      */
  160.     public void nextFrame() {
  161.         currentFrame++;
  162.         if(currentFrame>=frameSequence.length)currentFrame=0;
  163.     }
  164.  
  165.     public int getFrame() {
  166.         return currentFrame;
  167.     }
  168.  
  169.     public int getSize() {
  170.         if(animImg!=null)return animImg.length;
  171.         else return 1;
  172.     }
  173.  
  174.     /*
  175.      * Splits the image to create an animation
  176.      */
  177.     private static BufferedImage[] splitImage(BufferedImage img, int cols, int rows) {
  178.         int w = img.getWidth()/cols;
  179.         int h = img.getHeight()/rows;
  180.         int num = 0;
  181.         BufferedImage imgs[] = new BufferedImage[cols*rows];
  182.        
  183.         for(int y = 0; y < rows; y++) {
  184.             for(int x = 0; x < cols; x++) {
  185.                 if(num==imgs.length)break;
  186.                 imgs[num] = createCompatibleImage(w, h);
  187.                 // Tell the graphics to draw only one block of the image
  188.                 Graphics2D g = imgs[num].createGraphics();
  189.                 g.drawImage(img, 0, 0, w, h, w*x, h*y, w*x+w, h*y+h, null);
  190.                 g.dispose();
  191.                 num++;
  192.             }
  193.         }
  194.  
  195.         return imgs;
  196.     }
  197.  
  198.     //Creates a BufferedImage that is optimized for this system.
  199.     private static BufferedImage createCompatibleImage(int width, int height) {
  200.         GraphicsConfiguration gfx = GraphicsEnvironment.
  201.                     getLocalGraphicsEnvironment().getDefaultScreenDevice().
  202.                     getDefaultConfiguration();
  203.  
  204.         return gfx.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
  205.     }
  206.  
  207.     private static BufferedImage toCompatibleImage(BufferedImage image)  {
  208.             //Create a new compatible image
  209.             BufferedImage bimg = createCompatibleImage(image.getWidth(), image.getHeight());
  210.  
  211.             //Get the graphics of the image and paint the original image onto it.
  212.             Graphics2D g = (Graphics2D) bimg.getGraphics();
  213.             g.drawImage(image, 0, 0, null);
  214.             g.dispose();
  215.  
  216.             //Return the new, compatible image.
  217.             return bimg;
  218.     }
  219.  
  220.  
  221.     /*
  222.      * Collision detection between the current sprite and another sprite
  223.      */
  224.     public boolean collidesWith(Sprite otherSprite, boolean pixelPerfect) {
  225.         boolean isColliding=false;
  226.  
  227.         Rectangle r1 = getBounds(this);
  228.         Rectangle r2 = getBounds(otherSprite);
  229.        
  230.         if(r1.intersects(r2)) {
  231.             if(pixelPerfect) {
  232.                 isColliding = pixelPerfectCollision(otherSprite, r1, r2);
  233.             }else {
  234.                 isColliding = true;
  235.             }
  236.         }
  237.  
  238.         return isColliding;
  239.     }
  240.  
  241.  
  242.     /*
  243.      *  pixelPerfectCollision(); first determines the area where the sprites collides
  244.      *  AKA the collision-rectangle. It then grabs the pixels from both sprites
  245.      *  which are inside the rectangle. It then checks every pixel from the arrays
  246.      *  given by grabPixels();, and if 2 pixels at the same position are opaque,
  247.      *  (alpha value over 0) it will return true. Otherwise it will return false.
  248.      */
  249.     private boolean pixelPerfectCollision(Sprite sprite, Rectangle r1, Rectangle r2) {
  250.         int cornerTopX=-1;
  251.         int cornerTopY=-1;
  252.  
  253.         int cornerBottomX = 1;
  254.         int cornerBottomY = 1;
  255.        
  256.  
  257.         /*
  258.          * Get the X-values for the two coordinates where the sprites collide
  259.          * Seriously, don't use the for loop, I don't know what I was thinking.
  260.          * Solution found below.
  261.          */
  262. //        for(int i=0;i<r1.getWidth();i++) {
  263. //            if(r1.getX()+i >= r2.getX() & r1.getX()+i < r2.getX()+r2.getWidth()) {
  264. //                if(cornerTopX==-1)cornerTopX = (int) (r1.getX() + i);
  265. //                cornerBottomX = (int) (r1.getX() + i);
  266. //            }
  267. //        }
  268.  
  269.         cornerTopX = (r1.x>r2.x)?r1.x:r2.x;
  270.         cornerBottomX = ((r1.x+r1.width) < (r2.x+r2.width))?(r1.x+r1.width):(r2.x+r2.width);
  271.  
  272.         /*
  273.          * Get the Y-values for the two coordinates where the sprites collide
  274.          * Solution found below.
  275.          */
  276. //        for(int i=0;i<r1.getHeight();i++) {
  277. //            if(r1.getY()+i >= r2.getY() & r1.getY()+i < r2.getY()+r2.getHeight()) {
  278. //                if(cornerTopY==-1)cornerTopY = (int) (r1.getY() + i);
  279. //                cornerBottomY = (int) (r1.getY() + i);
  280. //            }
  281. //        }
  282.  
  283.         cornerTopY = (r1.y>r2.y)?r1.y:r2.y;
  284.         cornerBottomY = ((r1.y+r1.height) < (r2.y+r2.height))?(r1.y+r1.height):(r2.y+r2.height);
  285.  
  286.         //Determine the width and height of the collision rectangle
  287.         int width=cornerBottomX-cornerTopX;
  288.         int height=cornerBottomY-cornerTopY;
  289.  
  290.         //Create arrays to hold the pixels
  291.         int[] pixels1 = new int[width*height];
  292.         int[] pixels2 = new int[width*height];
  293.  
  294.         //Create the pixelgrabber and fill the arrays
  295.         PixelGrabber pg1 = new PixelGrabber(getImage(), cornerTopX-getRealX(), cornerTopY-getRealY(), width, height, pixels1, 0, width);
  296.         PixelGrabber pg2 = new PixelGrabber(sprite.getImage(), cornerTopX-sprite.getRealX(), cornerTopY-sprite.getRealY(), width, height, pixels2, 0, width);
  297.  
  298.         //Grab the pixels
  299.         try {
  300.             pg1.grabPixels();
  301.             pg2.grabPixels();
  302.         } catch (InterruptedException ex) {
  303.             Logger.getLogger(Sprite.class.getName()).log(Level.SEVERE, null, ex);
  304.         }
  305.  
  306.         //Check if pixels at the same spot from both arrays are not transparent.
  307.         for(int i=0;i<pixels1.length;i++) {
  308.             int a = (pixels1[i] >>> 24) & 0xff;
  309.             int a2 = (pixels2[i] >>> 24) & 0xff;
  310.  
  311.             /* Awesome, we found two pixels in the same spot that aren't
  312.              * completely transparent! Thus the sprites are colliding!
  313.              */
  314.             if(a > 0 && a2 > 0) return true;
  315.            
  316.         }
  317.        
  318.         return false;
  319.     }
  320.  
  321.     //Invokes transparency on the selected color
  322.     public void invokeTransparency(Color color) {
  323.         spriteImg = makeTransparent(spriteImg, color);
  324.  
  325.         if(this.cols > 0 & this.rows > 0)this.splitSprite(this.cols, this.rows);
  326.  
  327.     }
  328.  
  329.  
  330.     public void invokeTransparency(Color color, int newAlphaValue) {
  331.         spriteImg = makeTransparent(spriteImg, color, newAlphaValue);
  332.         if(this.cols > 0 & this.rows > 0)this.splitSprite(this.cols, this.rows);
  333.     }
  334.  
  335.     public static BufferedImage makeTransparent(BufferedImage img, final Color color) {
  336.         ImageFilter filter = new RGBImageFilter() {
  337.  
  338.             public int markerRGB = color.getRGB() | 0xFF000000;
  339.            
  340.             @Override
  341.             public final int filterRGB(int x, int y, int rgb) {
  342.                 if((rgb | 0xFF000000)==markerRGB)return 0x00FFFFFF & rgb;
  343.                 else return rgb;
  344.             }
  345.         };
  346.  
  347.         ImageProducer ip = new FilteredImageSource(img.getSource(), filter);
  348.  
  349.         Image temp = Toolkit.getDefaultToolkit().createImage(ip);
  350.  
  351.  
  352.         BufferedImage bufImg = createCompatibleImage(img.getWidth(), img.getHeight());
  353.         Graphics2D g = bufImg.createGraphics();
  354.         g.drawImage(temp, 0, 0, null);
  355.         g.dispose();
  356.  
  357.         return bufImg;
  358.     }
  359.  
  360.     public static BufferedImage makeTransparent(BufferedImage img, final Color color, final int newColor) {
  361.         ImageFilter filter = new RGBImageFilter() {
  362.  
  363.             public int markerRGB = color.getRGB() | 0xFF000000;
  364.  
  365.             @Override
  366.             public final int filterRGB(int x, int y, int rgb) {
  367.                 if((rgb | 0xFF000000)==markerRGB) {
  368.                     return newColor & rgb;
  369.                 }else {
  370.                     return rgb;
  371.                 }
  372.             }
  373.         };
  374.  
  375.         ImageProducer ip = new FilteredImageSource(img.getSource(), filter);
  376.  
  377.         Image temp = Toolkit.getDefaultToolkit().createImage(ip);
  378.  
  379.  
  380.         BufferedImage bufImg = createCompatibleImage(img.getWidth(), img.getHeight());
  381.         Graphics2D g = bufImg.createGraphics();
  382.         g.drawImage(temp, 0, 0, null);
  383.         g.dispose();
  384.  
  385.         return bufImg;
  386.     }
  387.  
  388.     /*
  389.      *Returns the width of the current sprite
  390.      */
  391.     public int getWidth() {
  392.         return this.getImage().getWidth();
  393.     }
  394.  
  395.     /*
  396.      * Returns the height of the sprite
  397.      * */
  398.     public int getHeight() {
  399.         return this.getImage().getHeight();
  400.     }
  401.  
  402.     /*
  403.      * Returns the X-position of the sprite
  404.      * getRealX() returns the X-Position of the Sprite's upper-left corner
  405.      */
  406.     public int getX() {
  407.         return x;
  408.     }
  409.  
  410.     public int getRefX() {
  411.         return refX;
  412.     }
  413.  
  414.     public int getRealX() {
  415.         return x-refX;
  416.     }
  417.  
  418.     /*
  419.      * Returns the Y-position of the sprite
  420.      * getRealY() returns the Y-position of the Sprite's upper-left corner
  421.      */
  422.     public int getY() {
  423.         return y;
  424.     }
  425.  
  426.     public int getRefY() {
  427.         return refY;
  428.     }
  429.  
  430.      public int getRealY() {
  431.          return y-refY;
  432.      }
  433.  
  434.     /*
  435.      * Returns the boundaries for the sprite, used for collision detection
  436.      */
  437.     public static Rectangle getBounds(Sprite sprite) {
  438.         return new Rectangle(sprite.getRealX(), sprite.getRealY(), sprite.getWidth(), sprite.getHeight());
  439.     }
  440.  
  441.  
  442.     /* Returns the image this sprite is using (if it was split, it will return
  443.      * the current frame. Else it will return the whole image.)
  444.      */
  445.      public BufferedImage getImage() {
  446.         if(animImg!=null && currentFrame<frameSequence.length)return animImg[frameSequence[currentFrame]];
  447.         else return spriteImg;
  448.     }
  449.  
  450.     //Returns the whole image, no matter if it has been split or not.
  451.     public BufferedImage getOrigImage() {
  452.         return spriteImg;
  453.     }
  454.  
  455.     //Flips the sprite (horizontal/vertical)
  456.     public void flipHorizontal() {
  457.         int w = this.getOrigImage().getWidth();
  458.         int h = this.getOrigImage().getHeight();
  459.  
  460.         BufferedImage bimg = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
  461.         Graphics2D g = bimg.createGraphics();
  462.  
  463.         g.drawImage(this.getOrigImage(), 0, 0, w, h, w, 0, 0, h, null);
  464.         g.dispose();
  465.        
  466.         this.spriteImg = toCompatibleImage(bimg);
  467.  
  468.         if(this.rows > 0 & this.cols > 0)animImg = splitImage(spriteImg, cols, rows);
  469.     }
  470.  
  471.     public void flipVertical() {
  472.         int w = this.getOrigImage().getWidth();
  473.         int h = this.getOrigImage().getHeight();
  474.  
  475.         BufferedImage bimg = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
  476.         Graphics2D g = bimg.createGraphics();
  477.  
  478.         g.drawImage(this.getOrigImage(), 0, 0, w, h, 0, h, w, 0, null);
  479.         g.dispose();
  480.  
  481.         this.spriteImg = toCompatibleImage(bimg);
  482.  
  483.         if(this.rows > 0 & this.cols > 0)animImg = splitImage(spriteImg, cols, rows);
  484.     }
  485.  
  486.  
  487.     //Call one of these methods after the sprite has been de-serialized.
  488.     public void reloadSprite(BufferedImage img) {
  489.         this.spriteImg = img;
  490.         if(this.rows > 0 & this.cols > 0)animImg = splitImage(spriteImg, cols, rows);
  491.     }
  492.  
  493.     //Getting the cols and rows
  494.     public int getCols() {
  495.         return this.cols;
  496.     }
  497.  
  498.     public int getRows() {
  499.         return this.rows;
  500.     }
  501.  
  502.     public static BufferedImage duplicateAndReverse(BufferedImage bimg) {
  503.         BufferedImage temp = createCompatibleImage(bimg.getWidth()*2, bimg.getHeight());
  504.  
  505.         int w = bimg.getWidth();
  506.         int h = bimg.getHeight();
  507.  
  508.         Graphics2D g = temp.createGraphics();
  509.  
  510.         g.drawImage(bimg, 0, 0, null);
  511.  
  512.         g.drawImage(bimg, w, 0, w*2, h, w, 0, 0, h, null);
  513.         g.dispose();
  514.  
  515.         return temp;
  516.     }
  517.    
  518.     private transient BufferedImage spriteImg;
  519.     private transient BufferedImage[] animImg;
  520.  
  521.     private int x;
  522.     private int y;
  523.  
  524.     private int refX;
  525.     private int refY;
  526.  
  527.     private int frameSequence[] = {0};
  528.     private int currentFrame = 0;
  529.  
  530.     private int sleepTime;
  531.     private int currentSleepFrame;
  532.     private boolean runAnim = false;
  533.  
  534.     private int cols = 0;
  535.     private int rows = 0;
  536.  
  537.     private String frameSequenceName = "ORIG";
  538.  
  539.     //For serialization
  540.     private static final long serialVersionUID = 1L;
  541.  
  542. }