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="https://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         }