1 | package org.expeditee.gio.swing;
|
---|
2 |
|
---|
3 | import java.awt.Graphics;
|
---|
4 | import java.awt.Graphics2D;
|
---|
5 | import java.awt.GraphicsEnvironment;
|
---|
6 | import java.awt.Rectangle;
|
---|
7 | import java.awt.Toolkit;
|
---|
8 | import java.awt.image.BufferedImage;
|
---|
9 | import java.awt.image.CropImageFilter;
|
---|
10 | import java.awt.image.FilteredImageSource;
|
---|
11 | import java.awt.image.ImageObserver;
|
---|
12 | import java.awt.image.MemoryImageSource;
|
---|
13 | import java.awt.image.PixelGrabber;
|
---|
14 | import java.awt.image.VolatileImage;
|
---|
15 | import java.io.File;
|
---|
16 | import java.io.IOException;
|
---|
17 | import java.net.HttpURLConnection;
|
---|
18 | import java.net.URL;
|
---|
19 | import java.util.HashMap;
|
---|
20 |
|
---|
21 | import javax.imageio.ImageIO;
|
---|
22 |
|
---|
23 | import org.expeditee.core.Colour;
|
---|
24 | import org.expeditee.core.Image;
|
---|
25 | import org.expeditee.core.bounds.AxisAlignedBoxBounds;
|
---|
26 | import org.expeditee.gio.ImageManager;
|
---|
27 | import org.expeditee.gui.DisplayController;
|
---|
28 |
|
---|
29 | import 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 | */
|
---|
40 | public 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 | }
|
---|