View Javadoc
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 }