001 package com.croftsoft.core.animation.painter;
002
003 import java.awt.*;
004 import java.awt.geom.Rectangle2D;
005 import javax.swing.*;
006
007 import com.croftsoft.core.animation.ComponentPainter;
008 import com.croftsoft.core.lang.NullArgumentException;
009
010 /*********************************************************************
011 * Tiles the Icon across the Component.
012 *
013 * <p>
014 * Supports a palette of up to 256 different icons.
015 * </p>
016 *
017 * @version
018 * 2003-07-11
019 * @since
020 * 2002-02-14
021 * @author
022 * <a href="http://www.CroftSoft.com/">David Wallace Croft</a>
023 *********************************************************************/
024
025 public final class TilePainter
026 implements ComponentPainter
027 //////////////////////////////////////////////////////////////////////
028 //////////////////////////////////////////////////////////////////////
029 {
030
031 private final Shape tileShape;
032
033 private final int tileWidth;
034
035 private final int tileHeight;
036
037 private final Icon [ ] tileIcons;
038
039 private final byte [ ] [ ] tileMap;
040
041 private final Rectangle clipBounds;
042
043 private final Rectangle originalClipBounds;
044
045 //
046
047 private int offsetX;
048
049 private int offsetY;
050
051 //////////////////////////////////////////////////////////////////////
052 // constructor methods
053 //////////////////////////////////////////////////////////////////////
054
055 /*********************************************************************
056 * Main constructor.
057 *
058 * @param offsetX
059 *
060 * Shifts the tile pattern horizontally.
061 * When there is only one tile icon that is being repeated,
062 * reasonable values are between 0 and the icon width less 1.
063 * Example: values of 0 to 39 for an icon width of 40.
064 *
065 * @param offsetY
066 *
067 * Shifts the tile pattern vertically.
068 * When there is only one tile icon that is being repeated,
069 * reasonable values are between 0 and the icon height less 1.
070 * Example: values of 0 to 39 for an icon height of 40.
071 *
072 * @param tileShape
073 *
074 * The area of the Component where the tiles will be painted.
075 * If the tileShape is null, component.getBounds() will be used.
076 *********************************************************************/
077 public TilePainter (
078 int offsetX,
079 int offsetY,
080 Icon [ ] tileIcons,
081 byte [ ] [ ] tileMap,
082 Dimension tileSize,
083 Shape tileShape )
084 //////////////////////////////////////////////////////////////////////
085 {
086 this.offsetX = offsetX;
087
088 this.offsetY = offsetY;
089
090 NullArgumentException.check ( this.tileIcons = tileIcons );
091
092 NullArgumentException.check ( this.tileMap = tileMap );
093
094 if ( tileIcons.length < 1 )
095 {
096 throw new IllegalArgumentException ( "tileIcons.length < 1" );
097 }
098
099 if ( tileIcons.length > 256 )
100 {
101 throw new IllegalArgumentException ( "tileIcons.length > 256" );
102 }
103
104 for ( int i = 0; i < tileIcons.length; i++ )
105 {
106 if ( tileIcons [ i ] == null )
107 {
108 throw new IllegalArgumentException (
109 "tileIcons[" + i + "] == null" );
110 }
111 }
112
113 if ( tileMap.length < 1 )
114 {
115 throw new IllegalArgumentException ( "tileMap.length < 1" );
116 }
117
118 int tilesWide = tileMap [ 0 ].length;
119
120 if ( tilesWide < 1 )
121 {
122 throw new IllegalArgumentException ( "tileMap[0].length < 1" );
123 }
124
125 for ( int row = 0; row < tileMap.length; row++ )
126 {
127 if ( tileMap [ row ].length != tilesWide )
128 {
129 throw new IllegalArgumentException (
130 "tileMap[" + row + "].length != tileMap[0].length" );
131 }
132
133 for ( int column = 0; column < tileMap [ row ].length; column++ )
134 {
135 int paletteIndex = 0xFF & tileMap [ row ] [ column ];
136
137 if ( paletteIndex >= tileIcons.length )
138 {
139 throw new IllegalArgumentException (
140 "tileMap[" + row + "][" + column + "] >= tileIcons.length" );
141 }
142 }
143 }
144
145 if ( tileSize == null )
146 {
147 tileWidth = tileIcons [ 0 ].getIconWidth ( );
148
149 tileHeight = tileIcons [ 0 ].getIconHeight ( );
150 }
151 else
152 {
153 tileWidth = tileSize.width;
154
155 tileHeight = tileSize.height;
156 }
157
158 if ( ( tileWidth < 1 )
159 || ( tileHeight < 1 ) )
160 {
161 throw new IllegalArgumentException (
162 "tileWidth < 1 or tileHeight < 1" );
163 }
164
165 this.tileShape = tileShape;
166
167 clipBounds = new Rectangle ( );
168
169 originalClipBounds = new Rectangle ( );
170 }
171
172 /*********************************************************************
173 * Convenience constructor.
174 *********************************************************************/
175 public TilePainter (
176 int offsetX,
177 int offsetY,
178 Icon icon,
179 Shape tileShape )
180 //////////////////////////////////////////////////////////////////////
181 {
182 this (
183 offsetX,
184 offsetY,
185 new Icon [ ] { icon },
186 new byte [ ] [ ] { { 0 } },
187 ( Dimension ) null,
188 tileShape );
189 }
190
191 /*********************************************************************
192 * Convenience constructor.
193 *
194 * <p><code>this ( 0, 0, icon, null );</code></p>
195 *********************************************************************/
196 public TilePainter ( Icon icon )
197 //////////////////////////////////////////////////////////////////////
198 {
199 this ( 0, 0, icon, null );
200 }
201
202 //////////////////////////////////////////////////////////////////////
203 // accessor methods
204 //////////////////////////////////////////////////////////////////////
205
206 public int getOffsetX ( ) { return offsetX; }
207
208 public int getOffsetY ( ) { return offsetY; }
209
210 public int getTileWidth ( ) { return tileWidth; }
211
212 public int getTileHeight ( ) { return tileHeight; }
213
214 public int getTileRows ( ) { return tileMap.length; }
215
216 public int getTileColumns ( ) { return tileMap [ 0 ].length; }
217
218 //////////////////////////////////////////////////////////////////////
219 // mutator methods
220 //////////////////////////////////////////////////////////////////////
221
222 public void setOffsetX ( int offsetX ) { this.offsetX = offsetX; }
223
224 public void setOffsetY ( int offsetY ) { this.offsetY = offsetY; }
225
226 //////////////////////////////////////////////////////////////////////
227 //////////////////////////////////////////////////////////////////////
228
229 public int getTileRow ( Point mousePoint )
230 //////////////////////////////////////////////////////////////////////
231 {
232 int row = floorDivision ( mousePoint.y, offsetY, tileHeight );
233
234 row = row % getTileRows ( );
235
236 if ( row < 0 )
237 {
238 row += getTileRows ( );
239 }
240
241 return row;
242 }
243
244 public int getTileColumn ( Point mousePoint )
245 //////////////////////////////////////////////////////////////////////
246 {
247 int column = floorDivision ( mousePoint.x, offsetX, tileWidth );
248
249 column = column % getTileColumns ( );
250
251 if ( column < 0 )
252 {
253 column += getTileColumns ( );
254 }
255
256 return column;
257 }
258
259 //////////////////////////////////////////////////////////////////////
260 //////////////////////////////////////////////////////////////////////
261
262 public void paint (
263 JComponent component,
264 Graphics2D graphics )
265 //////////////////////////////////////////////////////////////////////
266 {
267 graphics.getClipBounds ( clipBounds );
268
269 if ( tileShape != null )
270 {
271 if ( !tileShape.intersects ( clipBounds ) )
272 {
273 return;
274 }
275
276 graphics.setClip ( tileShape );
277
278 originalClipBounds.setBounds ( clipBounds );
279
280 Rectangle2D.intersect (
281 originalClipBounds, tileShape.getBounds2D ( ), clipBounds );
282 }
283
284 int minX = clipBounds.x;
285
286 int maxX = clipBounds.x + clipBounds.width - 1;
287
288 int minY = clipBounds.y;
289
290 int maxY = clipBounds.y + clipBounds.height - 1;
291
292 int minColumn = floorDivision ( minX, offsetX, tileWidth );
293
294 int maxColumn = floorDivision ( maxX, offsetX, tileWidth );
295
296 int minRow = floorDivision ( minY, offsetY, tileHeight );
297
298 int maxRow = floorDivision ( maxY, offsetY, tileHeight );
299
300 int rows = getTileRows ( );
301
302 int columns = getTileColumns ( );
303
304 for ( int row = minRow; row <= maxRow; row++ )
305 {
306 int r = row % rows;
307
308 if ( r < 0 )
309 {
310 r += rows;
311 }
312
313 byte [ ] rowTileData = tileMap [ r ];
314
315 for ( int column = minColumn; column <= maxColumn; column++ )
316 {
317 int c = column % columns;
318
319 if ( c < 0 )
320 {
321 c += columns;
322 }
323
324 tileIcons [ 0xFF & rowTileData [ c ] ].paintIcon (
325 component,
326 graphics,
327 column * tileWidth + offsetX,
328 row * tileHeight + offsetY);
329 }
330 }
331
332 if ( tileShape != null )
333 {
334 graphics.setClip ( originalClipBounds );
335 }
336 }
337
338 //////////////////////////////////////////////////////////////////////
339 //////////////////////////////////////////////////////////////////////
340
341 private static int floorDivision (
342 int point,
343 int zero,
344 int length )
345 //////////////////////////////////////////////////////////////////////
346 {
347 if ( point >= zero )
348 {
349 return ( point - zero ) / length;
350 }
351
352 return ( point - zero + 1 ) / length - 1;
353 }
354
355 //////////////////////////////////////////////////////////////////////
356 //////////////////////////////////////////////////////////////////////
357 }