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 }