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

Last change on this file since 1428 was 1428, checked in by bln4, 5 years ago

Fixed bug with background color click on images that had been scaled.

File size: 15.1 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 @Override
120 public org.expeditee.core.Image createImage(byte[] rawImageData) {
121 // Get the image from the given file
122 java.awt.Image swingImage = Toolkit.getDefaultToolkit().createImage(rawImageData);
123 if (swingImage == null) return null;
124
125 // Register and return the handle
126 return register(swingImage, true);
127 }
128
129 /**
130 * Registers the given swing image with the Expeditee image system. Not available from the abstract interface.
131 *
132 * @param image
133 * The image to register.
134 *
135 * @return The internal image handle.
136 */
137 public org.expeditee.core.Image createImage(java.awt.Image image)
138 {
139 return register(image, false);
140 }
141
142 @Override
143 public org.expeditee.core.Image createImageAsCroppedCopy(org.expeditee.core.Image orig, int x, int y, int width, int height)
144 {
145 // Ensure width and height are sane
146 if (width <= 0 || height <= 0) return null;
147
148 // Make sure the original is a valid image
149 java.awt.Image swingOrig = getInternalImage(orig);
150 if (swingOrig == null) return null;
151
152 // Make sure the given coordinate are within the bounds of the image
153 AxisAlignedBoxBounds selectedArea = new AxisAlignedBoxBounds(x, y, width, height);
154 if (!orig.getBounds().completelyContains(selectedArea)) return null;
155
156 // Create a new image that is a cropped version of the original
157 java.awt.Image swingImage = Toolkit.getDefaultToolkit().createImage(
158 new FilteredImageSource(swingOrig.getSource(),
159 new CropImageFilter(x, y, width, height)
160 )
161 );
162
163 // Register and return the handle
164 return register(swingImage, false);
165 }
166
167 @Override
168 public org.expeditee.core.Image getImage(URL url)
169 {
170 // Make sure url is valid
171 if (url == null) return null;
172
173 // Get the image from the given URL
174 java.awt.Image swingImage = Toolkit.getDefaultToolkit().createImage(url);
175 if (swingImage == null) return null;
176
177 // Register and return the handle
178 return register(swingImage, true);
179 }
180
181 @Override
182 public org.expeditee.core.Image getImage(String filename)
183 {
184 // Make sure the filename is valid
185 if (filename == null) return null;
186
187 // Get the image from the given file
188 java.awt.Image swingImage = Toolkit.getDefaultToolkit().createImage(filename);
189 if (swingImage == null) return null;
190
191 // Register and return the handle
192 return register(swingImage, true);
193 }
194
195 @Override
196 public org.expeditee.core.Image getImage(HttpURLConnection connection) throws IOException
197 {
198 // Make sure the connection is valid
199 if (connection == null) return null;
200
201 // Spoofing a widely accepted User Agent, since some sites refuse to serve non-webbrowser clients
202 connection.setRequestProperty("User-Agent", "Mozilla/5.0");
203
204 // Read the image from the connection
205 BufferedImage swingImage = ImageIO.read(connection.getInputStream());
206 if (swingImage == null) return null;
207
208 // Register and return the handle
209 return register(swingImage, false);
210 }
211
212 @Override
213 public synchronized void releaseImage(org.expeditee.core.Image image)
214 {
215 // Make sure the handle corresponds to a valid Swing image
216 java.awt.Image swingImage = getInternalImage(image);
217 if (swingImage == null) return;
218
219 // Release and forget about the Swing image
220 swingImage.flush();
221 _imageMap.remove(image.getHandle());
222
223 // If this image is animated, stop the animator
224 SelfAnimatingImageObserver obs = getAnimator(image);
225 if (obs != null) {
226 obs.deactivate();
227 _animatorMap.remove(image.getHandle());
228 }
229 }
230
231 @Override
232 public synchronized boolean isImageValid(Image image)
233 {
234 // Null image is invalid
235 if (image == null) return false;
236
237 // Image is valid if it's in the map
238 return _imageMap.containsKey(image.getHandle());
239 }
240
241 @Override
242 public int getWidth(org.expeditee.core.Image image)
243 {
244 // Make sure the image is valid
245 java.awt.Image swingImage = getInternalImage(image);
246 if (swingImage == null) return Image.INVALID_SIZE;
247
248 // Create a blocking observer so we don't return without an answer
249 BlockingImageObserver obs = new BlockingImageObserver(ImageObserver.WIDTH);
250
251 // Try and get the width now if it's available
252 int width = swingImage.getWidth(obs);
253
254 // If it's not available yet, block until it is
255 if (width == Image.INVALID_SIZE) {
256 obs.attemptWait();
257 width = obs.width;
258 }
259
260 return width;
261 }
262
263 @Override
264 public int getHeight(org.expeditee.core.Image image)
265 {
266 // Make sure the image is valid
267 java.awt.Image swingImage = getInternalImage(image);
268 if (swingImage == null) return Image.INVALID_SIZE;
269
270 // Create a blocking observer so we don't return without an answer
271 BlockingImageObserver obs = new BlockingImageObserver(ImageObserver.HEIGHT);
272
273 // Try and get the height now if it's available
274 int height = swingImage.getHeight(obs);
275
276 // If it's not available yet, block until it is
277 if (height == Image.INVALID_SIZE) {
278 obs.attemptWait();
279 height = obs.height;
280 }
281
282 return height;
283 }
284
285 @Override
286 public Colour[] getPixels(org.expeditee.core.Image image, int x, int y, int width, int height) {
287 // Make sure we have a valid image
288 java.awt.Image swingImage = getInternalImage(image);
289 if (swingImage == null) return null;
290
291 // Make sure width and height are reasonable
292 if (width <= 0 || height <= 0) return null;
293
294 // Make sure the given coordinate are within the bounds of the image
295 AxisAlignedBoxBounds selectedArea = new AxisAlignedBoxBounds(x, y, width, height);
296 AxisAlignedBoxBounds bounds = image.getBounds();
297 if (!bounds.completelyContains(selectedArea)) {
298 return null;
299 }
300
301 // Create an pixel grabber to get the pixel values
302 int[] pixels = new int[width * height];
303 PixelGrabber grabber = new PixelGrabber(swingImage, x, y, width, height, pixels, 0, width);
304
305 // Block until we've grabbed the pixels
306 boolean pixelsGrabbed = false;
307 while(!pixelsGrabbed) {
308 try {
309 grabber.grabPixels(0);
310 pixelsGrabbed = true;
311 } catch (InterruptedException e) {
312 }
313 }
314
315 // Return the colour of the grabbed pixel
316 Colour[] ret = new Colour[width * height];
317 for (int i = 0; i < (width * height); i++) {
318 ret[i] = Colour.fromARGB32BitPacked(pixels[i]);
319 }
320 return ret;
321 }
322
323 @Override
324 public void setPixel(org.expeditee.core.Image image, int x, int y, Colour c)
325 {
326 // Make sure we have a valid image
327 java.awt.Image swingImage = getInternalImage(image);
328 if (swingImage == null) return;
329
330 // Make sure the given coordinate is inside the image
331 if (!image.getBounds().contains(x, y)) return;
332
333 // Draw to the pixel
334 Graphics g = swingImage.getGraphics();
335 g.setColor(SwingConversions.toSwingColor(c));
336 g.fillRect(x, y, 1, 1);
337 }
338
339 @Override
340 public boolean writeImageToDisk(org.expeditee.core.Image image, String format, File file) throws IOException
341 {
342 // Validate parameters
343 if (format == null || file == null) return false;
344
345 // Make sure we have a valid image
346 java.awt.Image swingImage = getInternalImage(image);
347 if (swingImage == null) return false;
348
349 // Write the image to disk
350 if (swingImage instanceof BufferedImage) {
351 return ImageIO.write((BufferedImage) swingImage, format, file);
352 } else {
353 assert(swingImage instanceof VolatileImage);
354 return ImageIO.write(((VolatileImage) swingImage).getSnapshot(), format, file);
355 }
356 }
357
358 /** Gets the graphics context for drawing on this image. */
359 public Graphics2D getImageGraphics(Image image)
360 {
361 // Make sure we have a valid image
362 java.awt.Image swingImage = getInternalImage(image);
363 if (swingImage == null) return null;
364
365 // Get the graphics context for the image
366 try {
367 if (swingImage instanceof VolatileImage) {
368 return ((VolatileImage) swingImage).createGraphics();
369 } else {
370 assert (swingImage instanceof BufferedImage);
371 return ((BufferedImage) swingImage).createGraphics();
372 }
373 } catch (Exception e) {
374 e.printStackTrace(System.out);
375 return null;
376 }
377 }
378
379 /** Gets the Swing image associated with the given handle, or null if none is. */
380 public synchronized java.awt.Image getInternalImage(org.expeditee.core.Image image)
381 {
382 // Make sure we have a valid image
383 if (!isImageValid(image)) return null;
384
385 // Return the internal image
386 return _imageMap.get(image.getHandle());
387 }
388
389 /** Creates and returns a handle for the given Swing image to use for future reference. */
390 private synchronized org.expeditee.core.Image register(java.awt.Image swingImage, boolean fromDisk)
391 {
392 // TODO: Add animators. cts16
393 // TODO: Only add animators for images we know are animated. cts16
394
395 // Create a new handle for the image
396 org.expeditee.core.Image image = Image.get(fromDisk);
397
398 // Put the handle and image in the map
399 _imageMap.put(image.getHandle(), swingImage);
400
401 // Return the handle
402 return image;
403 }
404
405 /**
406 * Gets an animator for the given image. Will reuse an old one if it exists,
407 * or create one if not.
408 */
409 public synchronized SelfAnimatingImageObserver getAnimator(org.expeditee.core.Image image)
410 {
411 SelfAnimatingImageObserver obs = _animatorMap.get(image.getHandle());
412 if (obs == null) {
413 obs = new SelfAnimatingImageObserver();
414 _animatorMap.put(image.getHandle(), obs);
415 }
416 return _animatorMap.get(image.getHandle());
417 }
418
419 /** TODO: Comment. cts16 */
420 public java.awt.image.BufferedImage getBufferedImageForImage(org.expeditee.core.Image image)
421 {
422 if (image == null) return null;
423
424 java.awt.Image swingImage = getInternalImage(image);
425 if (swingImage instanceof VolatileImage) {
426 return ((VolatileImage) swingImage).getSnapshot();
427 } else if (swingImage instanceof sun.awt.image.ToolkitImage) {
428 return ((sun.awt.image.ToolkitImage) swingImage).getBufferedImage();
429 } else {
430 assert(swingImage instanceof BufferedImage);
431 return (BufferedImage) swingImage;
432 }
433 }
434
435 /**
436 * Image observer which blocks the calling thread until image is fully loaded.
437 */
438 public static class BlockingImageObserver implements ImageObserver
439 {
440 private int _waitingOn;
441 private boolean _done = false;
442
443 public int x, y, width, height;
444
445 public BlockingImageObserver()
446 {
447 this(ImageObserver.ALLBITS);
448 }
449
450 public BlockingImageObserver(int waitingOn)
451 {
452 _waitingOn = waitingOn;
453 }
454
455 @Override
456 public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y, int width, int height) {
457
458 if ((infoflags & _waitingOn) == _waitingOn) {
459 this.x = x;
460 this.y = y;
461 this.width = width;
462 this.height = height;
463 synchronized (this) {
464 _done = true;
465 notify();
466 }
467 return true;
468 }
469
470 return false;
471 }
472
473 public void attemptWait()
474 {
475 synchronized (this) {
476 while (!_done) {
477 try {
478 wait();
479 } catch (InterruptedException e) {
480
481 }
482 }
483 }
484 }
485
486 }
487
488 /**
489 * Image observer which refreshes the display to show a new frame from an animated image.
490 */
491 public static class SelfAnimatingImageObserver implements ImageObserver
492 {
493 private boolean _active;
494
495 public SelfAnimatingImageObserver()
496 {
497 _active = true;
498 }
499
500 public void activate()
501 {
502 _active = true;
503 }
504
505 public void deactivate()
506 {
507 _active = false;
508 }
509
510 @Override
511 public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y, int width, int height)
512 {
513 if (!_active) return false;
514
515 DisplayController.requestRefresh(false);
516
517 return true;
518 }
519
520 }
521
522 /**
523 * Image observer which refreshes the display once an image finishes loading.
524 */
525 public static class LoadCompletedImageObserver implements ImageObserver
526 {
527 @Override
528 public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y, int width, int height)
529 {
530 if ((infoflags & ImageObserver.ALLBITS) != 0) {
531 DisplayController.requestRefresh(false);
532 return false;
533 }
534
535 return true;
536 }
537 }
538
539}
Note: See TracBrowser for help on using the repository browser.