1 package org.newdawn.slick; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.net.URL; 6 7 import org.newdawn.slick.opengl.Texture; 8 9 import javax.annotation.Nonnull; 10 import javax.annotation.Nullable; 11 12 /** 13 * A sheet of sprites that can be drawn individually 14 * 15 * @author Kevin Glass 16 */ 17 public class SpriteSheet extends Image { 18 /** The width of a single element in pixels */ 19 private int tw; 20 /** The height of a single element in pixels */ 21 private int th; 22 /** The margin of the image */ 23 private int margin = 0; 24 /** Subimages */ 25 private Image[][] subImages; 26 /** The spacing between tiles */ 27 private int spacing; 28 /** The target image for this sheet */ 29 private Image target; 30 31 /** 32 * Create a new sprite sheet based on a image location 33 * 34 * @param ref The URL to the image to use 35 * @param tw The width of the tiles on the sheet 36 * @param th The height of the tiles on the sheet 37 * @throws SlickException Indicates a failure to read image data 38 * @throws IOException Indicates the URL could not be opened 39 */ 40 public SpriteSheet(@Nonnull URL ref,int tw,int th) throws SlickException, IOException { 41 this(new Image(ref.openStream(), ref.toString(), false), tw, th); 42 } 43 44 /** 45 * Create a new sprite sheet based on a image location 46 * 47 * @param image The image to based the sheet of 48 * @param tw The width of the tiles on the sheet 49 * @param th The height of the tiles on the sheet 50 */ 51 public SpriteSheet(@Nonnull Image image,int tw,int th) { 52 super(image); 53 54 this.target = image; 55 this.tw = tw; 56 this.th = th; 57 58 // call init manually since constructing from an image will have previously initialised 59 // from incorrect values 60 initImpl(); 61 } 62 63 /** 64 * Create a new sprite sheet based on a image location 65 * 66 * @param image The image to based the sheet of 67 * @param tw The width of the tiles on the sheet 68 * @param th The height of the tiles on the sheet 69 * @param spacing The spacing between tiles 70 * @param margin The magrin around the tiles 71 */ 72 private SpriteSheet(@Nonnull Image image, int tw, int th, int spacing, int margin) { 73 super(image); 74 75 this.target = image; 76 this.tw = tw; 77 this.th = th; 78 this.spacing = spacing; 79 this.margin = margin; 80 81 // call init manually since constructing from an image will have previously initialised 82 // from incorrect values 83 initImpl(); 84 } 85 86 /** 87 * Create a new sprite sheet based on a image location 88 * 89 * @param image The image to based the sheet of 90 * @param tw The width of the tiles on the sheet 91 * @param th The height of the tiles on the sheet 92 * @param spacing The spacing between tiles 93 */ 94 public SpriteSheet(@Nonnull Image image,int tw,int th,int spacing) { 95 this(image,tw,th,spacing,0); 96 } 97 98 /** 99 * Create a new sprite sheet based on a image location 100 * 101 * @param ref The location of the sprite sheet to load 102 * @param tw The width of the tiles on the sheet 103 * @param th The height of the tiles on the sheet 104 * @param spacing The spacing between tiles 105 * @throws SlickException Indicates a failure to load the image 106 */ 107 public SpriteSheet(String ref,int tw,int th, int spacing) throws SlickException { 108 this(ref,tw,th,null,spacing); 109 } 110 111 /** 112 * Create a new sprite sheet based on a image location 113 * 114 * @param ref The location of the sprite sheet to load 115 * @param tw The width of the tiles on the sheet 116 * @param th The height of the tiles on the sheet 117 * @throws SlickException Indicates a failure to load the image 118 */ 119 public SpriteSheet(String ref,int tw,int th) throws SlickException { 120 this(ref,tw,th,null); 121 } 122 123 /** 124 * Create a new sprite sheet based on a image location 125 * 126 * @param ref The location of the sprite sheet to load 127 * @param tw The width of the tiles on the sheet 128 * @param th The height of the tiles on the sheet 129 * @param col The colour to treat as transparent 130 * @throws SlickException Indicates a failure to load the image 131 */ 132 private SpriteSheet(String ref, int tw, int th, @Nullable Color col) throws SlickException { 133 this(ref, tw, th, col, 0); 134 } 135 136 /** 137 * Create a new sprite sheet based on a image location 138 * 139 * @param ref The location of the sprite sheet to load 140 * @param tw The width of the tiles on the sheet 141 * @param th The height of the tiles on the sheet 142 * @param col The colour to treat as transparent 143 * @param spacing The spacing between tiles 144 * @throws SlickException Indicates a failure to load the image 145 */ 146 private SpriteSheet(String ref, int tw, int th, @Nullable Color col, int spacing) throws SlickException { 147 super(ref, false, FILTER_NEAREST, col); 148 149 this.target = this; 150 this.tw = tw; 151 this.th = th; 152 this.spacing = spacing; 153 } 154 155 /** 156 * Create a new sprite sheet based on a image location 157 * 158 * @param name The name to give to the image in the image cache 159 * @param ref The stream from which we can load the image 160 * @param tw The width of the tiles on the sheet 161 * @param th The height of the tiles on the sheet 162 * @throws SlickException Indicates a failure to load the image 163 */ 164 public SpriteSheet(String name, @Nonnull InputStream ref,int tw,int th) throws SlickException { 165 super(ref,name,false); 166 167 this.target = this; 168 this.tw = tw; 169 this.th = th; 170 } 171 172 /** 173 * @see org.newdawn.slick.Image#initImpl() 174 */ 175 void initImpl() { 176 if (subImages != null) { 177 return; 178 } 179 180 int tilesAcross = ((getWidth()-(margin*2) - tw) / (tw + spacing)) + 1; 181 int tilesDown = ((getHeight()-(margin*2) - th) / (th + spacing)) + 1; 182 if ((getHeight() - th) % (th+spacing) != 0) { 183 tilesDown++; 184 } 185 186 subImages = new Image[tilesAcross][tilesDown]; 187 for (int x=0;x<tilesAcross;x++) { 188 for (int y=0;y<tilesDown;y++) { 189 subImages[x][y] = getSprite(x,y); 190 } 191 } 192 } 193 194 /** 195 * Get the sub image cached in this sprite sheet 196 * 197 * @param x The x position in tiles of the image to get 198 * @param y The y position in tiles of the image to get 199 * @return The subimage at that location on the sheet 200 */ 201 public Image getSubImage(int x, int y) { 202 init(); 203 204 if ((x < 0) || (x >= subImages.length)) { 205 throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y); 206 } 207 if ((y < 0) || (y >= subImages[0].length)) { 208 throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y); 209 } 210 211 return subImages[x][y]; 212 } 213 214 /** 215 * Create a new sub-image for a particular cell on the sprite sheet. This is generally 216 * used internally, getSubImage should be used instead. 217 * 218 * @param x The x position of the cell on the sprite sheet 219 * @param y The y position of the cell on the sprite sheet 220 * @return The single image from the sprite sheet 221 */ 222 @Nonnull 223 Image getSprite(int x, int y) { 224 target.init(); 225 initImpl(); 226 227 if ((x < 0) || (x >= subImages.length)) { 228 throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y); 229 } 230 if ((y < 0) || (y >= subImages[0].length)) { 231 throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y); 232 } 233 234 return target.getSubImage(x*(tw+spacing) + margin, y*(th+spacing) + margin,tw,th); 235 } 236 237 /** 238 * Get the number of sprites across the sheet 239 * 240 * @return The number of sprites across the sheet 241 */ 242 public int getHorizontalCount() { 243 target.init(); 244 initImpl(); 245 246 return subImages.length; 247 } 248 249 /** 250 * Get the number of sprites down the sheet 251 * 252 * @return The number of sprite down the sheet 253 */ 254 public int getVerticalCount() { 255 target.init(); 256 initImpl(); 257 258 return subImages[0].length; 259 } 260 261 /** 262 * Render a sprite when this sprite sheet is in use, 263 * using the tile width and height given at SpriteSheet 264 * construction. 265 * 266 * @see #startUse() 267 * @see #endUse() 268 * 269 * @param x The x position to render the sprite at 270 * @param y The y position to render the sprite at 271 * @param sx The x location of the cell to render 272 * @param sy The y location of the cell to render 273 */ 274 public void renderInUse(int x,int y,int sx,int sy) { 275 renderInUse(x, y, tw, th, sx, sy); 276 } 277 278 /** 279 * Render a sprite when this sprite sheet is in use, applying 280 * a scale transform. 281 * 282 * @see #startUse() 283 * @see #endUse() 284 * 285 * @param x The x position to render the sprite at 286 * @param y The y position to render the sprite at 287 * @param width the new width for the sprite 288 * @param height the new height for the sprite 289 * @param sx The x location of the cell to render 290 * @param sy The y location of the cell to render 291 */ 292 void renderInUse(int x, int y, int width, int height, int sx, int sy) { 293 subImages[sx][sy].drawEmbedded(x, y, width, height); 294 } 295 296 /** 297 * Render a sprite when this sprite sheet is in use, applying 298 * a rotation. The sub-image's center of rotation (by default 299 * the center of the image) will be used, and the width and 300 * height will be that of the tile width / tile height as 301 * given during SpriteSheet construction. 302 * 303 * @see #startUse() 304 * @see #endUse() 305 * 306 * @param x The x position to render the sprite at 307 * @param y The y position to render the sprite at 308 * @param rotation the rotation to apply to the embedded image 309 * @param sx The x location of the cell to render 310 * @param sy The y location of the cell to render 311 */ 312 public void renderInUse(int x, int y, float rotation, int sx, int sy) { 313 renderInUse(x, y, tw, th, rotation, sx, sy); 314 } 315 316 /** 317 * Render a sprite when this sprite sheet is in use, applying 318 * a scale and rotation. The sub-image's center of rotation (by default 319 * the center of the image) will be used and scaled accordingly. 320 * 321 * @see #startUse() 322 * @see #endUse() 323 * 324 * @param x The x position to render the sprite at 325 * @param y The y position to render the sprite at 326 * @param width the new width for the sprite 327 * @param height the new height for the sprite 328 * @param rotation the rotation to apply to the embedded image 329 * @param sx The x location of the cell to render 330 * @param sy The y location of the cell to render 331 */ 332 void renderInUse(int x, int y, int width, int height, float rotation, int sx, int sy) { 333 subImages[sx][sy].drawEmbedded(x, y, width, height, rotation); 334 } 335 336 /** 337 * @see org.newdawn.slick.Image#endUse() 338 */ 339 public void endUse() { 340 if (target == this) { 341 super.endUse(); 342 return; 343 } 344 target.endUse(); 345 } 346 347 /** 348 * @see org.newdawn.slick.Image#startUse() 349 */ 350 public void startUse() { 351 if (target == this) { 352 super.startUse(); 353 return; 354 } 355 target.startUse(); 356 } 357 358 /** 359 * @see org.newdawn.slick.Image#setTexture(org.newdawn.slick.opengl.Texture) 360 */ 361 public void setTexture(Texture texture) { 362 if (target == this) { 363 super.setTexture(texture); 364 return; 365 } 366 target.setTexture(texture); 367 } 368 369 public void renderInUse(int x, int y, int sx, int sy, byte transform) { 370 subImages[sx][sy].drawEmbedded(x, y, tw, th, transform); 371 } 372 373 }