1 /** 2 * The chess framework library. 3 * More information is available at http://www.jinchess.com/. 4 * Copyright (C) 2006 Alexander Maryanovsky. 5 * All rights reserved. 6 * 7 * The chess framework library is free software; you can redistribute 8 * it and/or modify it under the terms of the GNU Lesser General Public License 9 * as published by the Free Software Foundation; either version 2 of the 10 * License, or (at your option) any later version. 11 * 12 * The chess framework library is distributed in the hope that it will 13 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 15 * General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public License 18 * along with the chess framework library; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21 22 package free.chess; 23 24 import java.awt.*; 25 import java.awt.Component; 26 import java.awt.Graphics; 27 import java.awt.Image; 28 import java.awt.Toolkit; 29 import java.io.IOException; 30 import java.net.URL; 31 import java.util.HashSet; 32 import java.util.Iterator; 33 import java.util.Properties; 34 import java.util.Set; 35 36 import free.util.IOUtilities; 37 import free.util.ImageUtilities; 38 39 40 41 /** 42 * A <code>BoardPainter</code> which paints the board with two images - one for 43 * light squares an one for dark squares. 44 */ 45 46 public class SquareImagesBoardPainter implements ResourceBoardPainter{ 47 48 49 50 /** 51 * The constant for specifying image scaling mode. 52 * In this mode, the square images are simply scaled to fit the square. 53 * This means that all squares will look the same. For this option, you should 54 * provide relatively small images, around 60x60. 55 */ 56 57 public static final int SCALE_MODE = 0; 58 59 60 61 /** 62 * The constant for specifying image slicing mode. 63 * If the square images are larger than the squares, each square will 64 * be drawn using a contain a different, unscaled portion of the image. 65 * If the square images are smaller than the squares, they will be tiled 66 * inside each square. 67 * For this option you should provide either large images - 200x200 to 300x300, 68 * or small, tileable images. 69 */ 70 71 public static final int SLICE_MODE = 1; 72 73 74 75 /** 76 * A delegate board painter we use while the board image is being loaded. 77 */ 78 79 private static final BoardPainter whileLoadingDelegate = new DefaultBoardPainter(); 80 81 82 83 /** 84 * True if the board image is to be loaded asynchronously, and in the meanwhile, 85 * the delegate should be used. 86 */ 87 88 private static volatile boolean asyncImageLoad = false; 89 90 91 92 /** 93 * The light square image. When <code>SquareImagesBoardPainter</code> is used 94 * as a <code>ResourceBoardPainter</code>, this remains <code>null</code> 95 * until the image data is fully loaded. 96 */ 97 98 private Image lightImage = null; 99 100 101 102 /** 103 * The dark square image. When <code>SquareImagesBoardPainter</code> is used 104 * as a <code>ResourceBoardPainter</code>, this remains <code>null</code> 105 * until the image data is fully loaded. 106 */ 107 108 private Image darkImage = null; 109 110 111 112 /** 113 * The mode of image usage, either {@link #SCALE_MODE} or {@link #SLICE_MODE}. 114 */ 115 116 private int mode; 117 118 119 120 /** 121 * A version of the light square image, scaled according to the last paint 122 * request. 123 */ 124 125 private Image scaledLightImage = null; 126 127 128 129 /** 130 * A version of the dark square image, scaled according to the last paint 131 * request. 132 */ 133 134 private Image scaledDarkImage = null; 135 136 137 138 /** 139 * When <code>SquareImagesBoardPainter</code> is used as a 140 * <code>ResourceBoardPainter</code>, this specified the URL of the light 141 * square image. Otherwise, it remains null. 142 */ 143 144 private URL lightImageUrl = null; 145 146 147 148 /** 149 * When <code>SquareImagesBoardPainter</code> is used as a 150 * <code>ResourceBoardPainter</code>, this specified the URL of the dark 151 * square image. Otherwise, it remains null. 152 */ 153 154 private URL darkImageUrl = null; 155 156 157 158 /** 159 * The <code>ImageDataReceiver</code>, if any, currently waiting on board 160 * image data to load. 161 */ 162 163 private ImageDataReceiver imageDataReceiver = null; 164 165 166 167 /** 168 * A no-arg constructor so that this <code>SquareImagesBoardPainter</code> can 169 * be used as a <code>ResourceBoardPainter</code>. 170 */ 171 SquareImagesBoardPainter()172 public SquareImagesBoardPainter(){ 173 174 } 175 176 177 178 /** 179 * <p>Creates a new <code>SquareImagesBoardPainter</code> which paints light 180 * squares with <code>lightImage</code> and dark squares with 181 * <code>darkImage</code>. <code>mode</code> is either {@link #SCALE_MODE} or 182 * {@link #SLICE_MODE}. 183 */ 184 SquareImagesBoardPainter(Image lightImage, Image darkImage, int mode)185 public SquareImagesBoardPainter(Image lightImage, Image darkImage, int mode){ 186 switch (mode){ 187 case SCALE_MODE: 188 case SLICE_MODE: 189 break; 190 default: 191 throw new IllegalArgumentException("Unknown image usage mode: " + mode); 192 } 193 194 this.lightImage = lightImage; 195 this.darkImage = darkImage; 196 this.mode = mode; 197 } 198 199 200 201 /** 202 * Sets whether the square images are to be loaded asynchronously and a simpler 203 * board painter delegate used while they load. Asynchronous loading is off 204 * by default. 205 */ 206 setAsyncImageLoad(boolean asyncImageLoad)207 public static void setAsyncImageLoad(boolean asyncImageLoad){ 208 SquareImagesBoardPainter.asyncImageLoad = asyncImageLoad; 209 } 210 211 212 213 /** 214 * Since <code>SquareImagesBoardPainter</code>s are immutable, simply returns 215 * <code>this</code>. 216 */ 217 freshInstance()218 public BoardPainter freshInstance(){ 219 return this; 220 } 221 222 223 224 /** 225 * Loads the painter from the specified URL. The file structure at the URL is 226 * described below. 227 * <p>A properties file named "definition" must be located at the URL. 228 * That file should contain the following properties: 229 * <ul> 230 * <li><code>ext</code>: Specifies the extension (type) of the 231 * image(s) - gif, png, etc. If this is omitted, "gif" is assumed. 232 * <li><code>imageUseMode</code>: Specifies the way the images are used. 233 * Possible values are "scale" and "slice". If omitted, defaults to 234 * "slice". 235 * </ul> 236 * Two images named <code>light.[ext]</code> and <code>dark.[ext]</code> must 237 * be located at the URL, where "ext" is the value of the <code>ext</code> 238 * property. 239 */ 240 load(URL url)241 public void load(URL url) throws IOException{ 242 if (lightImageUrl != null) 243 throw new IllegalStateException("This SquareImagesBoardPainter has already been loaded"); 244 245 URL defURL = new URL(url, "definition"); 246 247 Properties def = IOUtilities.loadProperties(defURL, true); 248 if (def == null) 249 def = new Properties(); 250 251 if (def.getProperty("scaleSquares") != null) // Backward compatibility 252 mode = new Boolean(def.getProperty("scaleSquares", "false")).booleanValue() ? SCALE_MODE : SLICE_MODE; 253 else{ 254 String modeString = def.getProperty("imageUseMode", "slice"); 255 mode = "slice".equals(modeString) ? SLICE_MODE : SCALE_MODE; 256 } 257 258 String ext = def.getProperty("ext", "gif"); 259 lightImageUrl = new URL(url, "light." + ext); 260 darkImageUrl = new URL(url, "dark." + ext); 261 } 262 263 264 265 /** 266 * If the square images are already loaded, prepares their scaled version 267 * (if needed) for the specified board size and returns <code>true</code>. 268 * Otherwise, starts loading them. If asynchronous loading is enabled, the 269 * loading is done in a background thread, otherwise, waits until the loading 270 * is done. 271 * Returns whether the images are ready. 272 */ 273 prepareSquareImages(int width, int height, Component target)274 protected synchronized boolean prepareSquareImages(int width, int height, Component target){ 275 if (lightImage == null){ 276 if (imageDataReceiver != null){ // Already being loaded 277 imageDataReceiver.addComponentToRepaint(target); 278 return false; 279 } 280 281 if (asyncImageLoad){ 282 imageDataReceiver = new ImageDataReceiver(target); 283 IOUtilities.loadAsynchronously(new URL[]{lightImageUrl, darkImageUrl}, null, imageDataReceiver, true); 284 return false; 285 } 286 else{ 287 imageDataReceiver = new ImageDataReceiver(null); 288 IOUtilities.loadSynchronously(new URL[]{lightImageUrl, darkImageUrl}, null, imageDataReceiver, true); 289 if ((lightImage == null) || (darkImage == null)) 290 return false; 291 292 scaleSquareImages(width, height); 293 return true; 294 } 295 } 296 else{ 297 scaleSquareImages(width, height); 298 return true; 299 } 300 } 301 302 303 304 /** 305 * Scales the square image to the specified board size. 306 */ 307 scaleSquareImages(int width, int height)308 private void scaleSquareImages(int width, int height){ 309 if (mode == SCALE_MODE){ 310 int squareWidth = width/8; 311 int squareHeight = height/8; 312 if ((scaledLightImage != null) && 313 (scaledLightImage.getWidth(null) == squareWidth) && 314 (scaledLightImage.getHeight(null) == squareHeight)) 315 return; 316 317 scaledLightImage = lightImage.getScaledInstance(squareWidth, squareHeight, Image.SCALE_SMOOTH); 318 scaledDarkImage = darkImage.getScaledInstance(squareWidth, squareHeight, Image.SCALE_SMOOTH); 319 320 ImageUtilities.preload(scaledLightImage); 321 ImageUtilities.preload(scaledDarkImage); 322 } 323 else if (mode == SLICE_MODE){ 324 325 } 326 } 327 328 329 330 /** 331 * Paints the board at the given location on the given Graphics scaled to 332 * the given size. 333 */ 334 paintBoard(Graphics g, Component component, int x, int y, int width, int height)335 public void paintBoard(Graphics g, Component component, int x, int y, int width, int height){ 336 if (prepareSquareImages(width, height, component)){ 337 Rectangle clipRect = g.getClipBounds(); 338 Rectangle drawnRect = new Rectangle(x, y, width/8, height/8); 339 340 if (mode == SCALE_MODE){ 341 for (int file = 0; file < 8; file++, drawnRect.x += width/8){ 342 drawnRect.y = y; 343 for (int rank = 7; rank >= 0; rank--, drawnRect.y += height/8){ 344 if (!drawnRect.intersects(clipRect)) 345 continue; 346 347 Image image = (file+rank) % 2 == 0 ? scaledDarkImage : scaledLightImage; 348 g.drawImage(image, drawnRect.x, drawnRect.y, component); 349 } 350 } 351 } 352 else if (mode == SLICE_MODE){ 353 int lwidth = lightImage.getWidth(null); 354 int lheight = lightImage.getHeight(null); 355 int dwidth = darkImage.getWidth(null); 356 int dheight = darkImage.getHeight(null); 357 for (int file = 0; file < 8; file++, drawnRect.x += width/8){ 358 drawnRect.y = y; 359 for (int rank = 7; rank >= 0; rank--, drawnRect.y += height/8){ 360 if (!drawnRect.intersects(clipRect)) 361 continue; 362 363 Image image; 364 int iwidth, iheight; 365 if ((file+rank) % 2 == 0){ 366 image = darkImage; 367 iwidth = dwidth; 368 iheight = dheight; 369 } 370 else{ 371 image = lightImage; 372 iwidth = lwidth; 373 iheight = lheight; 374 } 375 376 g.setClip(clipRect.intersection(drawnRect)); 377 int imgX = (file*iwidth) % drawnRect.width; 378 int imgY = ((7-rank)*iheight) % drawnRect.height; 379 for (int offx = drawnRect.x; offx - imgX < drawnRect.x+drawnRect.width; offx += iwidth) 380 for (int offy = drawnRect.y; offy - imgY < drawnRect.y+drawnRect.height; offy += iheight) 381 g.drawImage(image, offx - imgX, offy - imgY, component); 382 } 383 } 384 } 385 g.setClip(clipRect); 386 } 387 else 388 whileLoadingDelegate.paintBoard(g, component, x, y, width, height); 389 } 390 391 392 393 394 /** 395 * The receiver of square image data. Responsible for creating the square images. 396 */ 397 398 private class ImageDataReceiver implements IOUtilities.DataReceiver{ 399 400 401 402 /** 403 * The set of components to repaint once the images are loaded. 404 */ 405 406 private final Set componentsToRepaint = new HashSet(2); 407 408 409 410 /** 411 * Creates a new <code>ImageDataReceiver</code> with the specified component 412 * to repaint once the image data is loaded. 413 */ 414 ImageDataReceiver(Component component)415 public ImageDataReceiver(Component component){ 416 componentsToRepaint.add(component); 417 } 418 419 420 421 /** 422 * Adds the specified component to the set of components to repaint once 423 * loading the image is done. 424 */ 425 addComponentToRepaint(Component component)426 public void addComponentToRepaint(Component component){ 427 componentsToRepaint.add(component); 428 } 429 430 431 432 /** 433 * Called when the image data has been loaded. Creates the square images. 434 */ 435 dataRead(URL [] urls, Object id, byte [][] data, IOException [] exceptions)436 public void dataRead(URL [] urls, Object id, byte [][] data, IOException [] exceptions){ 437 // If there are any exceptions, we simply quit - this will cause 438 // the painter to keep using the delegate to paint the board. 439 for (int i = 0; i < exceptions.length; i++) 440 if (exceptions[i] != null) 441 return; 442 443 lightImage = Toolkit.getDefaultToolkit().createImage(data[0]); 444 darkImage = Toolkit.getDefaultToolkit().createImage(data[1]); 445 446 ImageUtilities.preload(lightImage); 447 ImageUtilities.preload(darkImage); 448 449 for (Iterator i = componentsToRepaint.iterator(); i.hasNext();){ 450 Component component = (Component)i.next(); 451 if (component != null) 452 component.repaint(); 453 } 454 } 455 456 457 458 } 459 460 461 462 } 463