1 /**
2  * The chess framework library.
3  * More information is available at http://www.jinchess.com/.
4  * Copyright (C) 2002 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.image.FilteredImageSource;
26 import java.awt.image.ImageFilter;
27 import java.awt.image.RGBImageFilter;
28 import java.io.IOException;
29 import java.net.URL;
30 import java.util.*;
31 
32 import free.util.IOUtilities;
33 import free.util.ImageUtilities;
34 import free.util.TextUtilities;
35 
36 
37 /**
38  * An implementation of <code>PiecePainter</code> which paints images.
39  */
40 
41 public final class ImagePiecePainter implements ResourcePiecePainter{
42 
43 
44 
45   /**
46    * The painter we delegate to while loading images.
47    */
48 
49   private static final PiecePainter whileLoadingDelegate = new DefaultPiecePainter();
50 
51 
52 
53   /**
54    * The ImageFilter we use to create shaded images.
55    */
56 
57   private static final ImageFilter SHADING_FILTER = new ShadingFilter();
58 
59 
60 
61   /**
62    * True if piece images are to be loaded asynchronously, and in the meanwhile,
63    * the delegate should be used.
64    */
65 
66   private static volatile boolean asyncImageLoad = false;
67 
68 
69 
70   /**
71    * An array whose indices specify the size of the images and whose values
72    * are maps mapping <code>Piece</code>s to either <code>Image</code>s or
73    * <code>URL</code>s, if the image isn't loaded yet.
74    */
75 
76   private Map [] pieceImages;
77 
78 
79 
80   /**
81    * Same as pieceImages only for shaded images.
82    */
83 
84   private Map [] shadedPieceImages;
85 
86 
87 
88   /**
89    * Maps the square sizes for which images are currently being loaded to the
90    * <code>ImageDataReceiver</code>s waiting on the data.
91    */
92 
93   private final Map imageDataReceivers = new HashMap(2);
94 
95 
96 
97   /**
98    * A no-arg constructor, so that this piece painter can be used as a
99    * <code>ResourcePiecePainter</code>.
100    */
101 
ImagePiecePainter()102   public ImagePiecePainter(){
103 
104   }
105 
106 
107 
108   /**
109    * Creates a new <code>ImagePiecePainter</code> with the specified piece
110    * images. The given <code>Map</code> should map <code>Integer</code>s
111    * specifying the size of the piece images to <code>Maps</code>s which in
112    * turn map <code>Piece</code>s to piece <code>Image</code>s.
113    */
114 
ImagePiecePainter(Map pieceImages)115   public ImagePiecePainter(Map pieceImages){
116 
117     // Find the largest size
118     int maxSize = 0;
119     Iterator sizes = pieceImages.keySet().iterator();
120     while (sizes.hasNext()){
121       int size = ((Integer)sizes.next()).intValue();
122       if (size <= 0)
123         throw new IllegalArgumentException("Image sizes must be positive");
124 
125       if (size > maxSize)
126         maxSize = size;
127     }
128 
129     if (maxSize == 0)
130       throw new IllegalArgumentException("No sizes in the hashtable");
131 
132     this.pieceImages = new Map[maxSize + 1];
133     this.shadedPieceImages = new Map[maxSize + 1];
134 
135     // Fill the array
136     sizes = pieceImages.keySet().iterator();
137     while (sizes.hasNext()){
138       Integer size = (Integer)sizes.next();
139       int sizeInt = size.intValue();
140 
141       Map images = (Map)pieceImages.get(size);
142       int imagesCount = images.size();
143       this.pieceImages[sizeInt] = new HashMap(imagesCount);
144       this.shadedPieceImages[sizeInt] = new HashMap(imagesCount);
145 
146       Iterator pieces = images.keySet().iterator();
147       while (pieces.hasNext()){
148         Object key = pieces.next();
149         Image image = (Image)images.get(key);
150         Image shadedImage = shadeImage(image);
151 
152         this.pieceImages[sizeInt].put(key, image);
153         this.shadedPieceImages[sizeInt].put(key, shadedImage);
154       }
155     }
156   }
157 
158 
159 
160   /**
161    * Sets whether piece images are to be loaded asynchronously and a simpler
162    * piece painter delegate used while they load. Asynchronous loading is off
163    * by default.
164    */
165 
setAsyncImageLoad(boolean asyncImageLoad)166   public static void setAsyncImageLoad(boolean asyncImageLoad){
167     ImagePiecePainter.asyncImageLoad = asyncImageLoad;
168   }
169 
170 
171 
172   /**
173    * Creates a shaded version of the specified image.
174    */
175 
shadeImage(Image image)176   private static Image shadeImage(Image image){
177     return Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), SHADING_FILTER));
178   }
179 
180 
181 
182   /**
183    * Since <code>ImagePiecePainter</code>s are immutable, simply returns
184    * <code>this</code>.
185    */
186 
freshInstance()187   public PiecePainter freshInstance(){
188     return this;
189   }
190 
191 
192 
193   /**
194    * Loads the piece images from the specified URL. The structure at the
195    * specified url is described below.
196    * A properties file named "definition" must be located at the base URL.
197    * That file should contain the following three properties:
198    * <ul>
199    *   <li><code>image.type</code>: Specifies the extension (type) of the
200    *       images - gif, png etc. If this is not specified, "gif" is assumed.
201    *   <li><code>size.pref</code>: Specifies the preferred size of the piece
202    *       set, in pixels.
203    *   <li><code>size.list</code>: A list of sizes of all the available piece
204    *       images, in increasing order, separated by spaces.
205    * </ul>
206    * Directories with names corresponding to the sizes must be present at the
207    * base URL and in those directories resources named
208    * <code>[color char][piece char].[extension]</code> where
209    * <code>[color char]</code> is either 'w' or 'b' (for black or white),
210    * <code>[piece char]</code> is one of 'k', 'q', 'r', 'b', 'n'
211    * or 'p'. The images for all twelve pieces must be present there and they
212    * must have the correct size.
213    */
214 
load(URL baseUrl)215   public void load(URL baseUrl) throws IOException{
216     if (pieceImages != null)
217       throw new IllegalStateException("This ImagePiecePainter has already been loaded");
218 
219     URL defURL = new URL(baseUrl, "definition");
220     Properties def = IOUtilities.loadProperties(defURL, true);
221     if (def == null)
222       throw new IOException("Unable to load " + defURL);
223 
224     String ext = def.getProperty("ext", "gif");
225     int [] sizes = TextUtilities.parseIntList(def.getProperty("size.list"), " ");
226 
227     this.pieceImages = new HashMap[sizes[sizes.length - 1] + 1];
228     this.shadedPieceImages = new HashMap[sizes[sizes.length - 1] + 1];
229 
230     Piece [] pieces = new Piece[]{
231         ChessPiece.WHITE_KING, ChessPiece.BLACK_KING,
232         ChessPiece.WHITE_QUEEN, ChessPiece.BLACK_QUEEN,
233         ChessPiece.WHITE_ROOK, ChessPiece.BLACK_ROOK,
234         ChessPiece.WHITE_BISHOP, ChessPiece.BLACK_BISHOP,
235         ChessPiece.WHITE_KNIGHT, ChessPiece.BLACK_KNIGHT,
236         ChessPiece.WHITE_PAWN, ChessPiece.BLACK_PAWN};
237 
238     String [] pieceNames =
239       new String[]{"wk", "bk", "wq", "bq", "wr", "br", "wb", "bb", "wn", "bn", "wp", "bp"};
240 
241     for (int i = 0; i < sizes.length; i++){
242       int size = sizes[i];
243 
244       Map normal = new HashMap(15);
245       Map shaded = new HashMap(15);
246 
247       for (int j = 0; j < pieces.length; j++){
248         URL imageUrl = new URL(baseUrl, size + "/" + pieceNames[j] + "." + ext);
249         normal.put(pieces[j], imageUrl);
250         shaded.put(pieces[j], imageUrl);
251       }
252 
253       this.pieceImages[size] = normal;
254       this.shadedPieceImages[size] = shaded;
255     }
256   }
257 
258 
259 
260   /**
261    * Returns the size of provided images which is the best fit for the
262    * specified square size.
263    */
264 
bestFitImageSize(int squareSize)265   protected int bestFitImageSize(int squareSize){
266     if (squareSize <= 0)
267       throw new IllegalArgumentException("Image size must be positive");
268 
269     if (squareSize >= pieceImages.length)
270       return pieceImages.length - 1;
271 
272     if (pieceImages[squareSize] != null)
273       return squareSize;
274 
275     for (int i = squareSize; i > 0; i--)
276       if (pieceImages[i] != null)
277         return i;
278 
279     for (int i = squareSize+1; i < pieceImages.length; i++)
280       if (pieceImages[i] != null)
281         return i;
282 
283     throw new Error("This can't happen");
284   }
285 
286 
287 
288   /**
289    * If already loaded, returns the piece images at the specified size.
290    * Otherwise, starts loading them (if async loading is enabled, in a
291    * background thread, in the meanwhile, returning <code>null</code>),
292    * and once done, repaints the specified component.
293    */
294 
loadPieces(int squareSize, boolean shaded, Component target)295   protected synchronized Map loadPieces(int squareSize, boolean shaded, Component target){
296     int imageSize = bestFitImageSize(squareSize);
297     Map images = (shaded ? shadedPieceImages : pieceImages)[imageSize];
298     if (images.values().iterator().next() instanceof Image) // Already loaded
299       return images;
300     else{
301       ImageDataReceiver receiver = (ImageDataReceiver)imageDataReceivers.get(new Integer(imageSize));
302       if (receiver != null){ // We're already loading the images
303         receiver.addComponentToRepaint(target);
304         return null;
305       }
306 
307       Set entrySet = images.entrySet();
308       Piece [] pieces = new Piece[entrySet.size()];
309       URL [] urls = new URL[entrySet.size()];
310       Iterator entries = entrySet.iterator();
311       for (int i = 0; i < pieces.length; i++){
312         Map.Entry entry = (Map.Entry)entries.next();
313         pieces[i] = (Piece)entry.getKey();
314         urls[i] = (URL)entry.getValue();
315       }
316 
317       Map normalImages = pieceImages[imageSize];
318       Map shadedImages = shadedPieceImages[imageSize];
319       receiver = new ImageDataReceiver(asyncImageLoad ? target : null, imageSize, normalImages, shadedImages);
320       imageDataReceivers.put(new Integer(imageSize), receiver);
321 
322       if (asyncImageLoad){
323         IOUtilities.loadAsynchronously(urls, pieces, receiver, true);
324         return null;
325       }
326       else{
327         IOUtilities.loadSynchronously(urls, pieces, receiver, true);
328         return shaded ? shadedImages : normalImages;
329       }
330     }
331   }
332 
333 
334 
335 
336   /**
337    * Paints the image of the given piece at the given coordinates on the given
338    * Graphics object scaled to the given size.
339    */
340 
paintPiece(Piece piece, Graphics g, Component component, Rectangle rect, boolean shaded)341   public void paintPiece(Piece piece, Graphics g, Component component, Rectangle rect,
342       boolean shaded){
343 
344     int x = rect.x;
345     int y = rect.y;
346     int width = rect.width;
347     int height = rect.height;
348 
349     int size = width > height ? height : width;
350 
351     Map pieces = loadPieces(size, shaded, component);
352     if (pieces == null){
353       whileLoadingDelegate.paintPiece(piece, g, component, rect, shaded);
354       return;
355     }
356 
357     Image pieceImage = (Image)pieces.get(piece);
358     int pieceWidth = pieceImage.getWidth(null);
359     int pieceHeight = pieceImage.getHeight(null);
360 
361     g.drawImage(pieceImage, x + (width - pieceWidth)/2, y + (height - pieceHeight)/2, component);
362   }
363 
364 
365 
366 
367   /**
368    * The <code>ImageFilter</code> we use to create shaded piece images.
369    */
370 
371   private static class ShadingFilter extends RGBImageFilter{
372 
filterRGB(int x, int y, int rgb)373     public int filterRGB(int x, int y, int rgb){
374       int alpha = (rgb >> 24) & 0xff;
375       int r = (rgb >> 16) & 0xff;
376       int g = (rgb >> 8) & 0xff;
377       int b = rgb & 0xff;
378 
379       r = (r + 128*2)/3;
380       g = (g + 128*2)/3;
381       b = (b + 128*2)/3;
382 
383       return (alpha << 24) | (r << 16) | (g << 8) | b;
384     }
385 
386   }
387 
388 
389 
390   /**
391    * The receiver of loaded image data. Responsible for creating and correctly
392    * mapping the piece images.
393    */
394 
395   private class ImageDataReceiver implements IOUtilities.DataReceiver{
396 
397 
398 
399     /**
400      * The map of pieces to normal images.
401      */
402 
403     private final Map normalImages;
404 
405 
406 
407     /**
408      * The map of pieces to shaded images.
409      */
410 
411     private final Map shadedImages;
412 
413 
414 
415     /**
416      * The size of the images we're loading.
417      */
418 
419     private final int imageSize;
420 
421 
422 
423     /**
424      * The components to repaint when the loading is done.
425      */
426 
427     private final Set componentsToRepaint = new HashSet(2);
428 
429 
430 
431     /**
432      * Creates a new <code>ImageDataReceiver</code> with the specified maps to
433      * fill and component to repaint when loading is done.
434      */
435 
ImageDataReceiver(Component componentToRepaint, int imageSize, Map normalImages, Map shadedImages)436     public ImageDataReceiver(Component componentToRepaint, int imageSize, Map normalImages, Map shadedImages){
437       this.normalImages = normalImages;
438       this.shadedImages = shadedImages;
439       this.imageSize = imageSize;
440       componentsToRepaint.add(componentToRepaint);
441     }
442 
443 
444 
445     /**
446      * Adds a component to the set of components to repaint once all the images
447      * are loaded.
448      */
449 
addComponentToRepaint(Component component)450     public void addComponentToRepaint(Component component){
451       componentsToRepaint.add(component);
452     }
453 
454 
455 
456     /**
457      * Called when the image data has been loaded. Creates and maps the piece
458      * images.
459      */
460 
dataRead(URL [] urls, Object id, byte [][] data, IOException[] exceptions)461     public void dataRead(URL [] urls, Object id, byte [][] data, IOException[] exceptions){
462       // If there are any exceptions, we simply quit - this will cause
463       // the painter to keep using the delegate to paint pieces.
464       for (int i = 0; i < exceptions.length; i++)
465         if (exceptions[i] != null)
466           return;
467 
468       synchronized(ImagePiecePainter.this){
469         Toolkit toolkit = Toolkit.getDefaultToolkit();
470         Piece [] pieces = (Piece[])id;
471         for (int i = 0; i < data.length; i++){
472           Image normalImage = toolkit.createImage(data[i]);
473           Image shadedImage = shadeImage(normalImage);
474 
475           ImageUtilities.preload(normalImage);
476           ImageUtilities.preload(shadedImage);
477 
478           normalImages.put(pieces[i], normalImage);
479           shadedImages.put(pieces[i], shadedImage);
480         }
481 
482         imageDataReceivers.remove(new Integer(imageSize));
483 
484         for (Iterator i = componentsToRepaint.iterator(); i.hasNext();){
485           Component component = (Component)i.next();
486           if (component != null)
487             component.repaint();
488         }
489       }
490 
491 
492 
493     }
494 
495 
496 
497 
498   }
499 
500 
501 
502 }
503