source: trunk/src/org/expeditee/gio/swing/SwingImageManager.java@ 1097

Last change on this file since 1097 was 1097, checked in by davidb, 6 years ago

Newly structured files from Corey's work on logic/graphics separation

File size: 14.8 KB
Line 
1package org.expeditee.gio.swing;
2
3import java.awt.Graphics;
4import java.awt.Graphics2D;
5import java.awt.GraphicsEnvironment;
6import java.awt.Rectangle;
7import java.awt.Toolkit;
8import java.awt.image.BufferedImage;
9import java.awt.image.CropImageFilter;
10import java.awt.image.FilteredImageSource;
11import java.awt.image.ImageObserver;
12import java.awt.image.MemoryImageSource;
13import java.awt.image.PixelGrabber;
14import java.awt.image.VolatileImage;
15import java.io.File;
16import java.io.IOException;
17import java.net.HttpURLConnection;
18import java.net.URL;
19import java.util.HashMap;
20
21import javax.imageio.ImageIO;
22
23import org.expeditee.core.Colour;
24import org.expeditee.core.Image;
25import org.expeditee.core.bounds.AxisAlignedBoxBounds;
26import org.expeditee.gio.ImageManager;
27import org.expeditee.gui.DisplayController;
28
29import com.sun.pdfview.PDFPage;
30
31/**
32 * The Swing version of the image manager. Mappings are maintained through
33 * the Image handle number so that handles can be garbage-collected i.e. this
34 * manager doesn't hold any reference to the handle object itself. Image.finalize()
35 * tells this manager when the handle is being collected so the mapping can be
36 * removed.
37 *
38 * @author cts16
39 */
40public class SwingImageManager extends ImageManager {
41
42 /** Singleton instance. */
43 private static SwingImageManager _instance;
44
45 /** Singleton instantiator. */
46 public static SwingImageManager getInstance()
47 {
48 if (_instance == null) _instance = new SwingImageManager();
49
50 return _instance;
51 }
52
53 /** Mapping from image handles to actual internal images. */
54 private HashMap<Long, java.awt.Image> _imageMap;
55
56 /** Mapping from image handles to image animators (for animated images e.g. GIFs). */
57 private HashMap<Long, SelfAnimatingImageObserver> _animatorMap;
58
59 /** Constructor. */
60 private SwingImageManager()
61 {
62 // Initialise the maps
63 _imageMap = new HashMap<Long, java.awt.Image>();
64 _animatorMap = new HashMap<Long, SelfAnimatingImageObserver>();
65 }
66
67 @Override
68 public org.expeditee.core.Image createImage(int width, int height, boolean hintUseAcceleratedMemory)
69 {
70 // Ensure width and height are sane
71 if (width <= 0 || height <= 0) return null;
72
73 java.awt.Image swingImage;
74
75 if (hintUseAcceleratedMemory) {
76 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
77 swingImage = ge.getDefaultScreenDevice().getDefaultConfiguration().createCompatibleVolatileImage(width, height);
78 } else {
79 swingImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
80 }
81
82 return register(swingImage, false);
83 }
84
85 @Override
86 public org.expeditee.core.Image createImage(int width, int height, int[] pixelData)
87 {
88 // Ensure width and height are sane
89 if (width <= 0 || height <= 0) return null;
90
91 // Make sure the caller has provided enough pixel data
92 if (pixelData == null || pixelData.length < (width * height)) return null;
93
94 // Create the Swing image
95 java.awt.Image swingImage;
96 MemoryImageSource memory_image = new MemoryImageSource(width, height, pixelData, 0, width);
97 swingImage = Toolkit.getDefaultToolkit().createImage(memory_image);
98
99 // Register the image and return its handle
100 return register(swingImage, false);
101 }
102
103 @Override
104 public org.expeditee.core.Image createImage(int width, int height, PDFPage page)
105 {
106 // Ensure width and height are sane
107 if (width <= 0 || height <= 0) return null;
108
109 // Ensure we have a valid PDFPage
110 if (page == null) return null;
111
112 // Get the Swing image of the page
113 java.awt.Image swingImage = page.getImage(width, height, new Rectangle(0, 0, (int)page.getBBox().getWidth(), (int)page.getBBox().getHeight()), null, true, true);
114
115 // Register it and return the handle
116 return register(swingImage, false);
117 }
118
119 /**
120 * Registers the given swing image with the Expeditee image system. Not available from the abstract interface.
121 *
122 * @param image
123 * The image to register.
124 *
125 * @return The internal image handle.
126 */
127 public org.expeditee.core.Image createImage(java.awt.Image image)
128 {
129 return register(image, false);
130 }
131
132 @Override
133 public org.expeditee.core.Image createImageAsCroppedCopy(org.expeditee.core.Image orig, int x, int y, int width, int height)
134 {
135 // Ensure width and height are sane
136 if (width <= 0 || height <= 0) return null;
137
138 // Make sure the original is a valid image
139 java.awt.Image swingOrig = getInternalImage(orig);
140 if (swingOrig == null) return null;
141
142 // Make sure the given coordinate are within the bounds of the image
143 AxisAlignedBoxBounds selectedArea = new AxisAlignedBoxBounds(x, y, width, height);
144 if (!orig.getBounds().completelyContains(selectedArea)) return null;
145
146 // Create a new image that is a cropped version of the original
147 java.awt.Image swingImage = Toolkit.getDefaultToolkit().createImage(
148 new FilteredImageSource(swingOrig.getSource(),
149 new CropImageFilter(x, y, width, height)
150 )
151 );
152
153 // Register and return the handle
154 return register(swingImage, false);
155 }
156
157 @Override
158 public org.expeditee.core.Image getImage(URL url)
159 {
160 // Make sure url is valid
161 if (url == null) return null;
162
163 // Get the image from the given URL
164 java.awt.Image swingImage = Toolkit.getDefaultToolkit().createImage(url);
165 if (swingImage == null) return null;
166
167 // Register and return the handle
168 return register(swingImage, true);
169 }
170
171 @Override
172 public org.expeditee.core.Image getImage(String filename)
173 {
174 // Make sure the filename is valid
175 if (filename == null) return null;
176
177 // Get the image from the given file
178 java.awt.Image swingImage = Toolkit.getDefaultToolkit().createImage(filename);
179 if (swingImage == null) return null;
180
181 // Register and return the handle
182 return register(swingImage, true);
183 }
184
185 @Override
186 public org.expeditee.core.Image getImage(HttpURLConnection connection) throws IOException
187 {
188 // Make sure the connection is valid
189 if (connection == null) return null;
190
191 // Spoofing a widely accepted User Agent, since some sites refuse to serve non-webbrowser clients
192 connection.setRequestProperty("User-Agent", "Mozilla/5.0");
193
194 // Read the image from the connection
195 BufferedImage swingImage = ImageIO.read(connection.getInputStream());
196 if (swingImage == null) return null;
197
198 // Register and return the handle
199 return register(swingImage, false);
200 }
201
202 @Override
203 public synchronized void releaseImage(org.expeditee.core.Image image)
204 {
205 // Make sure the handle corresponds to a valid Swing image
206 java.awt.Image swingImage = getInternalImage(image);
207 if (swingImage == null) return;
208
209 // Release and forget about the Swing image
210 swingImage.flush();
211 _imageMap.remove(image.getHandle());
212
213 // If this image is animated, stop the animator
214 SelfAnimatingImageObserver obs = getAnimator(image);
215 if (obs != null) {
216 obs.deactivate();
217 _animatorMap.remove(image.getHandle());
218 }
219 }
220
221 @Override
222 public synchronized boolean isImageValid(Image image)
223 {
224 // Null image is invalid
225 if (image == null) return false;
226
227 // Image is valid if it's in the map
228 return _imageMap.containsKey(image.getHandle());
229 }
230
231 @Override
232 public int getWidth(org.expeditee.core.Image image)
233 {
234 // Make sure the image is valid
235 java.awt.Image swingImage = getInternalImage(image);
236 if (swingImage == null) return Image.INVALID_SIZE;
237
238 // Create a blocking observer so we don't return without an answer
239 BlockingImageObserver obs = new BlockingImageObserver(ImageObserver.WIDTH);
240
241 // Try and get the width now if it's available
242 int width = swingImage.getWidth(obs);
243
244 // If it's not available yet, block until it is
245 if (width == Image.INVALID_SIZE) {
246 obs.attemptWait();
247 width = obs.width;
248 }
249
250 return width;
251 }
252
253 @Override
254 public int getHeight(org.expeditee.core.Image image)
255 {
256 // Make sure the image is valid
257 java.awt.Image swingImage = getInternalImage(image);
258 if (swingImage == null) return Image.INVALID_SIZE;
259
260 // Create a blocking observer so we don't return without an answer
261 BlockingImageObserver obs = new BlockingImageObserver(ImageObserver.HEIGHT);
262
263 // Try and get the height now if it's available
264 int height = swingImage.getHeight(obs);
265
266 // If it's not available yet, block until it is
267 if (height == Image.INVALID_SIZE) {
268 obs.attemptWait();
269 height = obs.height;
270 }
271
272 return height;
273 }
274
275 @Override
276 public Colour[] getPixels(org.expeditee.core.Image image, int x, int y, int width, int height)
277 {
278 // Make sure we have a valid image
279 java.awt.Image swingImage = getInternalImage(image);
280 if (swingImage == null) return null;
281
282 // Make sure width and height are reasonable
283 if (width <= 0 || height <= 0) return null;
284
285 // Make sure the given coordinate are within the bounds of the image
286 AxisAlignedBoxBounds selectedArea = new AxisAlignedBoxBounds(x, y, width, height);
287 if (!image.getBounds().completelyContains(selectedArea)) return null;
288
289 // Create an pixel grabber to get the pixel values
290 int[] pixels = new int[width * height];
291 PixelGrabber grabber = new PixelGrabber(swingImage, x, y, width, height, pixels, 0, width);
292
293 // Block until we've grabbed the pixels
294 boolean pixelsGrabbed = false;
295 while(!pixelsGrabbed) {
296 try {
297 grabber.grabPixels(0);
298 pixelsGrabbed = true;
299 } catch (InterruptedException e) {
300 }
301 }
302
303 // Return the colour of the grabbed pixel
304 Colour[] ret = new Colour[width * height];
305 for (int i = 0; i < (width * height); i++) {
306 ret[i] = Colour.fromARGB32BitPacked(pixels[i]);
307 }
308 return ret;
309 }
310
311 @Override
312 public void setPixel(org.expeditee.core.Image image, int x, int y, Colour c)
313 {
314 // Make sure we have a valid image
315 java.awt.Image swingImage = getInternalImage(image);
316 if (swingImage == null) return;
317
318 // Make sure the given coordinate is inside the image
319 if (!image.getBounds().contains(x, y)) return;
320
321 // Draw to the pixel
322 Graphics g = swingImage.getGraphics();
323 g.setColor(SwingConversions.toSwingColor(c));
324 g.fillRect(x, y, 1, 1);
325 }
326
327 @Override
328 public boolean writeImageToDisk(org.expeditee.core.Image image, String format, File file) throws IOException
329 {
330 // Validate parameters
331 if (format == null || file == null) return false;
332
333 // Make sure we have a valid image
334 java.awt.Image swingImage = getInternalImage(image);
335 if (swingImage == null) return false;
336
337 // Write the image to disk
338 if (swingImage instanceof BufferedImage) {
339 return ImageIO.write((BufferedImage) swingImage, format, file);
340 } else {
341 assert(swingImage instanceof VolatileImage);
342 return ImageIO.write(((VolatileImage) swingImage).getSnapshot(), format, file);
343 }
344 }
345
346 /** Gets the graphics context for drawing on this image. */
347 public Graphics2D getImageGraphics(Image image)
348 {
349 // Make sure we have a valid image
350 java.awt.Image swingImage = getInternalImage(image);
351 if (swingImage == null) return null;
352
353 // Get the graphics context for the image
354 try {
355 if (swingImage instanceof VolatileImage) {
356 return ((VolatileImage) swingImage).createGraphics();
357 } else {
358 assert (swingImage instanceof BufferedImage);
359 return ((BufferedImage) swingImage).createGraphics();
360 }
361 } catch (Exception e) {
362 e.printStackTrace(System.out);
363 return null;
364 }
365 }
366
367 /** Gets the Swing image associated with the given handle, or null if none is. */
368 public synchronized java.awt.Image getInternalImage(org.expeditee.core.Image image)
369 {
370 // Make sure we have a valid image
371 if (!isImageValid(image)) return null;
372
373 // Return the internal image
374 return _imageMap.get(image.getHandle());
375 }
376
377 /** Creates and returns a handle for the given Swing image to use for future reference. */
378 private synchronized org.expeditee.core.Image register(java.awt.Image swingImage, boolean fromDisk)
379 {
380 // TODO: Add animators. cts16
381 // TODO: Only add animators for images we know are animated. cts16
382
383 // Create a new handle for the image
384 org.expeditee.core.Image image = Image.get(fromDisk);
385
386 // Put the handle and image in the map
387 _imageMap.put(image.getHandle(), swingImage);
388
389 // Return the handle
390 return image;
391 }
392
393 /**
394 * Gets an animator for the given image. Will reuse an old one if it exists,
395 * or create one if not.
396 */
397 public synchronized SelfAnimatingImageObserver getAnimator(org.expeditee.core.Image image)
398 {
399 SelfAnimatingImageObserver obs = _animatorMap.get(image.getHandle());
400 if (obs == null) {
401 obs = new SelfAnimatingImageObserver();
402 _animatorMap.put(image.getHandle(), obs);
403 }
404 return _animatorMap.get(image.getHandle());
405 }
406
407 /** TODO: Comment. cts16 */
408 public java.awt.image.BufferedImage getBufferedImageForImage(org.expeditee.core.Image image)
409 {
410 if (image == null) return null;
411
412 java.awt.Image swingImage = getInternalImage(image);
413 if (swingImage instanceof VolatileImage) {
414 return ((VolatileImage) swingImage).getSnapshot();
415 } else if (swingImage instanceof sun.awt.image.ToolkitImage) {
416 return ((sun.awt.image.ToolkitImage) swingImage).getBufferedImage();
417 } else {
418 assert(swingImage instanceof BufferedImage);
419 return (BufferedImage) swingImage;
420 }
421 }
422
423 /**
424 * Image observer which blocks the calling thread until image is fully loaded.
425 */
426 public static class BlockingImageObserver implements ImageObserver
427 {
428 private int _waitingOn;
429 private boolean _done = false;
430
431 public int x, y, width, height;
432
433 public BlockingImageObserver()
434 {
435 this(ImageObserver.ALLBITS);
436 }
437
438 public BlockingImageObserver(int waitingOn)
439 {
440 _waitingOn = waitingOn;
441 }
442
443 @Override
444 public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y, int width, int height) {
445
446 if ((infoflags & _waitingOn) == _waitingOn) {
447 this.x = x;
448 this.y = y;
449 this.width = width;
450 this.height = height;
451 synchronized (this) {
452 _done = true;
453 notify();
454 }
455 return true;
456 }
457
458 return false;
459 }
460
461 public void attemptWait()
462 {
463 synchronized (this) {
464 while (!_done) {
465 try {
466 wait();
467 } catch (InterruptedException e) {
468
469 }
470 }
471 }
472 }
473
474 }
475
476 /**
477 * Image observer which refreshes the display to show a new frame from an animated image.
478 */
479 public static class SelfAnimatingImageObserver implements ImageObserver
480 {
481 private boolean _active;
482
483 public SelfAnimatingImageObserver()
484 {
485 _active = true;
486 }
487
488 public void activate()
489 {
490 _active = true;
491 }
492
493 public void deactivate()
494 {
495 _active = false;
496 }
497
498 @Override
499 public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y, int width, int height)
500 {
501 if (!_active) return false;
502
503 DisplayController.requestRefresh(false);
504
505 return true;
506 }
507
508 }
509
510 /**
511 * Image observer which refreshes the display once an image finishes loading.
512 */
513 public static class LoadCompletedImageObserver implements ImageObserver
514 {
515 @Override
516 public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y, int width, int height)
517 {
518 if ((infoflags & ImageObserver.ALLBITS) != 0) {
519 DisplayController.requestRefresh(false);
520 return false;
521 }
522
523 return true;
524 }
525 }
526
527}
Note: See TracBrowser for help on using the repository browser.