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