001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image;
031
032import java.awt.Color;
033import java.awt.Component;
034import java.awt.Container;
035import java.awt.Dimension;
036import java.awt.FlowLayout;
037import java.awt.FontMetrics;
038import java.awt.Graphics;
039import java.awt.Graphics2D;
040import java.awt.GraphicsEnvironment;
041import java.awt.GridLayout;
042import java.awt.RenderingHints;
043import java.awt.event.ComponentAdapter;
044import java.awt.event.ComponentEvent;
045import java.awt.event.MouseEvent;
046import java.awt.event.MouseListener;
047import java.awt.event.MouseMotionListener;
048import java.awt.event.WindowAdapter;
049import java.awt.event.WindowEvent;
050import java.awt.image.BufferedImage;
051import java.util.ArrayList;
052import java.util.Arrays;
053import java.util.Collection;
054import java.util.HashMap;
055import java.util.Map;
056
057import javax.swing.JComponent;
058import javax.swing.JFrame;
059import javax.swing.JScrollPane;
060import javax.swing.SwingUtilities;
061
062import org.openimaj.image.DisplayUtilities.ImageComponent.ImageComponentListener;
063import org.openimaj.image.pixel.ConnectedComponent;
064import org.openimaj.image.processor.connectedcomponent.render.BlobRenderer;
065import org.openimaj.math.geometry.point.Point2d;
066import org.openimaj.math.geometry.point.Point2dImpl;
067import org.openimaj.math.geometry.shape.Polygon;
068import org.openimaj.math.geometry.shape.Rectangle;
069
070/**
071 * Static methods for displaying images using Swing.
072 *
073 * In addition to normal windows, the class also supports "named windows" which
074 * can be referred to by name.
075 *
076 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
077 */
078public class DisplayUtilities {
079        private static int windowCount = 0;
080
081        private static int windowOpenCount = 0;
082
083        private static Map<String, JFrame> namedWindows = new HashMap<String, JFrame>();
084
085        /**
086         * Get the number of open windows
087         *
088         * @return number of open windows
089         */
090        public static int openWindowCount() {
091                return DisplayUtilities.windowOpenCount;
092        }
093
094        /**
095         * Display an image with the default name
096         *
097         * @param image
098         *            the image
099         * @return frame containing the image
100         */
101        public static JFrame display(final Image<?, ?> image) {
102                return DisplayUtilities.display(image, "Image: "
103                                + DisplayUtilities.windowCount);
104        }
105
106        /**
107         * Display an image with the default name
108         *
109         * @param image
110         *            the image
111         * @return frame containing the image
112         */
113        public static JFrame display(final BufferedImage image) {
114                return DisplayUtilities.display(image, "Image: "
115                                + DisplayUtilities.windowCount);
116        }
117
118        /**
119         * Display an image with the given title
120         *
121         * @param image
122         *            the image
123         * @param title
124         *            the title
125         * @return frame containing the image
126         */
127        public static JFrame display(final Image<?, ?> image, final String title) {
128                return DisplayUtilities.display(
129                                ImageUtilities.createBufferedImageForDisplay(image), title,
130                                image);
131        }
132
133        /**
134         * Display an image with the default name No additional functionality, such as
135         * zooming, is enabled.
136         *
137         * @param image
138         *            the image
139         * @return frame containing the image
140         */
141        public static JFrame displaySimple(final Image<?, ?> image) {
142                return DisplayUtilities.displaySimple(image, "Image: "
143                                + DisplayUtilities.windowCount);
144        }
145
146        /**
147         * Display an image with the default name. No additional functionality, such as
148         * zooming, is enabled.
149         *
150         * @param image
151         *            the image
152         * @return frame containing the image
153         */
154        public static JFrame displaySimple(final BufferedImage image) {
155                return DisplayUtilities.displaySimple(image, "Image: "
156                                + DisplayUtilities.windowCount);
157        }
158
159        /**
160         * Display an image with the given title. No additional functionality, such as
161         * zooming, is enabled.
162         *
163         * @param image
164         *            the image
165         * @param title
166         *            the title
167         * @return frame containing the image
168         */
169        public static JFrame displaySimple(final Image<?, ?> image,
170                        final String title)
171        {
172                return DisplayUtilities.displaySimple(
173                                ImageUtilities.createBufferedImageForDisplay(image), title,
174                                image);
175        }
176
177        private static BufferedImage getImage(final JFrame frame) {
178                if (frame == null)
179                        return null;
180
181                if (frame.getContentPane().getComponentCount() > 0
182                                && frame.getContentPane().getComponent(0) instanceof ImageComponent)
183                {
184                        return ((ImageComponent) frame.getContentPane().getComponent(0)).image;
185                }
186
187                return null;
188        }
189
190        /**
191         * Display an image in the given frame
192         *
193         * @param image
194         *            the image
195         * @param frame
196         *            the frame
197         * @return the frame
198         */
199        public static JFrame display(final Image<?, ?> image, final JFrame frame) {
200                final BufferedImage bimg = DisplayUtilities.getImage(frame);
201                return DisplayUtilities.display(
202                                ImageUtilities.createBufferedImageForDisplay(image, bimg),
203                                frame);
204        }
205
206        /**
207         * Set the position of a named window.
208         *
209         * @param name
210         *            The window name
211         * @param x
212         *            the x position
213         * @param y
214         *            the y position
215         */
216        public static void positionNamed(final String name, final int x,
217                        final int y)
218        {
219                final JFrame w = DisplayUtilities.createNamedWindow(name);
220                w.setBounds(x, y, w.getWidth(), w.getHeight());
221        }
222
223        /**
224         * Update the image that is being displayed in the given named window.
225         *
226         * @param name
227         *            The named window
228         * @param newImage
229         *            The new image to display
230         * @param title
231         *            The window title
232         */
233        public static void updateNamed(final String name,
234                        final Image<?, ?> newImage, final String title)
235        {
236                final JFrame w = DisplayUtilities.createNamedWindow(name, title, true);
237                final BufferedImage bimg = DisplayUtilities.getImage(w);
238
239                ((ImageComponent) w.getContentPane().getComponent(0))
240                                .setImage(ImageUtilities.createBufferedImageForDisplay(
241                                                newImage, bimg));
242        }
243
244        /**
245         * Create a named window with a title that is also the name
246         *
247         * @param name
248         * @return the window
249         */
250        public static JFrame createNamedWindow(final String name) {
251                return DisplayUtilities.createNamedWindow(name, name, false);
252        }
253
254        /**
255         * Create a named window with a title
256         *
257         * @param name
258         * @param title
259         * @return the window
260         */
261        public static JFrame createNamedWindow(final String name,
262                        final String title)
263        {
264                return DisplayUtilities.createNamedWindow(name, title, false);
265        }
266
267        /**
268         * Create a named window that auto resizes
269         *
270         * @param name
271         * @param title
272         * @param autoResize
273         * @return the window
274         */
275        public static JFrame createNamedWindow(final String name,
276                        final String title, final boolean autoResize)
277        {
278                if (DisplayUtilities.namedWindows.containsKey(name))
279                        return DisplayUtilities.namedWindows.get(name);
280                final JFrame frame = DisplayUtilities.makeDisplayFrame(title, 0, 0,
281                                null);
282                ((ImageComponent) frame.getContentPane().getComponent(0)).autoResize = autoResize;
283                ((ImageComponent) frame.getContentPane().getComponent(0)).autoPack = autoResize;
284                DisplayUtilities.namedWindows.put(name, frame);
285                return frame;
286        }
287
288        /**
289         * Display an image in the given frame by name (will be created if not already
290         * done so using {@link #createNamedWindow(String)}
291         *
292         * @param image
293         *            the image
294         * @param name
295         *            the name of the frame
296         * @return the frame
297         */
298        public static JFrame displayName(final Image<?, ?> image, final String name) {
299                if (GraphicsEnvironment.isHeadless())
300                        return null;
301
302                final JFrame frame = DisplayUtilities.createNamedWindow(name);
303                final BufferedImage bimg = DisplayUtilities.getImage(frame);
304                return DisplayUtilities.display(
305                                ImageUtilities.createBufferedImageForDisplay(image, bimg),
306                                frame, image);
307        }
308
309        /**
310         * Display an image in the given frame by name (will be created if not already
311         * done so using {@link #createNamedWindow(String)}
312         *
313         * @param image
314         *            the image
315         * @param name
316         *            the name of the frame
317         * @param autoResize
318         *            should the frame resize to fit its contents
319         * @return the frame
320         */
321        public static JFrame displayName(final Image<?, ?> image,
322                        final String name, final boolean autoResize)
323        {
324                if (GraphicsEnvironment.isHeadless())
325                        return null;
326
327                final JFrame frame = DisplayUtilities.createNamedWindow(name, name,
328                                autoResize);
329                final BufferedImage bimg = DisplayUtilities.getImage(frame);
330                return DisplayUtilities.display(
331                                ImageUtilities.createBufferedImageForDisplay(image, bimg),
332                                frame, image);
333        }
334
335        /**
336         * An image viewer that displays and image and allows zooming and panning of
337         * images.
338         * <p>
339         * When allowZooming is TRUE, clicking in the image will zoom in. CTRL-click in
340         * the image to zoom out.
341         *
342         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
343         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
344         */
345        public static class ImageComponent extends JComponent
346                        implements
347                                MouseListener,
348                                MouseMotionListener
349        {
350                /**
351                 * Listener for zoom and pan events
352                 *
353                 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
354                 * @created 25 Jul 2012
355                 * @version $Author$, $Revision$, $Date$
356                 */
357                public static interface ImageComponentListener {
358                        /**
359                         * Called when the image has been zoomed to the new zoom factor.
360                         *
361                         * @param newScaleFactor
362                         *            The new zoom factor
363                         */
364                        public void imageZoomed(double newScaleFactor);
365
366                        /**
367                         * Called when the image has been panned to a new position.
368                         *
369                         * @param newX
370                         *            The new X position
371                         * @param newY
372                         *            The new Y position
373                         */
374                        public void imagePanned(double newX, double newY);
375                }
376
377                /** */
378                private static final long serialVersionUID = 1L;
379
380                /** The image being displayed */
381                protected BufferedImage image;
382
383                /** The original image being displayed. Used for pixel interrogation */
384                protected Image<?, ?> originalImage;
385
386                /** Whether to auto resize the component to the content size */
387                private boolean autoResize = false;
388
389                /** Whether to pack the component on resize */
390                private boolean autoPack = false;
391
392                /** Whether to size the image to fit within the component's given size */
393                private boolean autoFit = false;
394
395                /** When using autoFit, whether to keep the aspect ratio constant */
396                private boolean keepAspect = true;
397
398                /** Draw a grid where there is no image */
399                private boolean drawTransparencyGrid = false;
400
401                /** Whether to draw the mouse over pixel colour on the next paint */
402                private boolean drawPixelColour = false;
403
404                /** Whether to show pixel colours on mouse over */
405                private boolean showPixelColours = true;
406
407                /** Whether to show the XY coordinate of the mouse */
408                private boolean showXY = true;
409
410                /** Whether to allow zooming */
411                private boolean allowZooming = true;
412
413                /** Whether to allow dragging */
414                private boolean allowDragging = true;
415
416                /** Gives the image-coord point in the centre of the image */
417                private double drawX = 0;
418
419                /** Gives the image-coord point in the centre of the image */
420                private double drawY = 0;
421
422                /** Gives the image scale */
423                private double scaleFactorX = 1;
424
425                /** Gives the image scale */
426                private double scaleFactorY = 1;
427
428                /** The last location of the drag - x-coordinate */
429                private int dragStartX = 0;
430
431                /** The last location of the drag - y-coordinate */
432                private int dragStartY = 0;
433
434                /** The x-coordinate of the pixel being displayed */
435                private int pixelX = 0;
436
437                /** The y-coordinate of the pixel being displayed */
438                private int pixelY = 0;
439
440                /** The current mouse coordinate */
441                private int mouseX = 0;
442
443                /** The current mouse coordinate */
444                private int mouseY = 0;
445
446                /** The current pixel colour */
447                private Float[] currentPixelColour = null;
448
449                /** List of listeners */
450                private final ArrayList<ImageComponentListener> listeners = new ArrayList<ImageComponentListener>();
451
452                /** The last displayed image */
453                private BufferedImage displayedImage = null;
454
455                /**
456                 * Default constructor
457                 */
458                public ImageComponent() {
459                        this(false, false);
460                }
461
462                /**
463                 * Default constructor. Allows setting of the autoResize parameter which if true
464                 * changes the size of the component to fit the contents.
465                 *
466                 * @param autoResize
467                 *            automatically resize the component to the content size
468                 */
469                public ImageComponent(final boolean autoResize) {
470                        this(autoResize, true);
471                }
472
473                /**
474                 * Construct with given image
475                 *
476                 * @param image
477                 *            the image
478                 */
479                public ImageComponent(final BufferedImage image) {
480                        this(true, true);
481                        this.setImage(image);
482                }
483
484                /**
485                 * Default constructor. Allows setting of the autoResize parameter which if true
486                 * changes the size of the component to fit the contents, and the autoPack
487                 * parameter which automatically packs the containers root (if its a JFrame)
488                 * whenever it is resized.
489                 *
490                 * @param autoResize
491                 *            automatically resize the component to the content size
492                 * @param autoPack
493                 *            automatically pack the root component on resize
494                 */
495                public ImageComponent(final boolean autoResize, final boolean autoPack) {
496                        this(1f, autoResize, autoPack);
497                }
498
499                /**
500                 * Default constructor. Allows setting of the autoResize parameter which if true
501                 * changes the size of the component to fit the contents, and the autoPack
502                 * parameter which automatically packs the containers root (if its a JFrame)
503                 * whenever it is resized.
504                 *
505                 * @param initialScale
506                 *            initial scale of the image
507                 * @param autoResize
508                 *            automatically resize the component to the content size
509                 * @param autoPack
510                 *            automatically pack the root component on resize
511                 */
512                public ImageComponent(final float initialScale,
513                                final boolean autoResize, final boolean autoPack)
514                {
515                        this.autoPack = autoPack;
516                        this.autoResize = autoResize;
517                        this.scaleFactorX = initialScale;
518                        this.scaleFactorY = initialScale;
519
520                        this.addMouseListener(this);
521                        this.addMouseMotionListener(this);
522
523                        // Add a component listener so that we can detect when the
524                        // component has been resized so that we can update
525                        this.addComponentListener(new ComponentAdapter() {
526                                @Override
527                                public void componentResized(final ComponentEvent e) {
528                                        ImageComponent.this.calculateScaleFactorsToFit(
529                                                        ImageComponent.this.image, ImageComponent.this.getBounds());
530                                };
531                        });
532                }
533
534                /**
535                 * Add the given listener to this image component.
536                 *
537                 * @param l
538                 *            The listener to add
539                 */
540                public void addImageComponentListener(final ImageComponentListener l) {
541                        this.listeners.add(l);
542                }
543
544                /**
545                 * Remove the given listener from this image component.
546                 *
547                 * @param l
548                 *            The listener to remove.
549                 */
550                public void removeImageComponentListener(final ImageComponentListener l) {
551                        this.listeners.remove(l);
552                }
553
554                /**
555                 * Set whether to allow zooming.
556                 *
557                 * @param allowZoom
558                 *            TRUE to allow zooming
559                 */
560                public void setAllowZoom(final boolean allowZoom) {
561                        this.allowZooming = allowZoom;
562                        if (allowZoom)
563                                this.autoFit = false;
564                }
565
566                /**
567                 * Set whether to allow panning.
568                 *
569                 * @param allowPan
570                 *            TRUE to allow panning
571                 */
572                public void setAllowPanning(final boolean allowPan) {
573                        this.allowDragging = allowPan;
574                        if (allowPan)
575                                this.autoFit = false;
576                }
577
578                /**
579                 * Set whether to allow drawing of the transparency grid.
580                 *
581                 * @param drawGrid
582                 *            TRUE draws the grid
583                 */
584                public void setTransparencyGrid(final boolean drawGrid) {
585                        this.drawTransparencyGrid = drawGrid;
586                        this.repaint();
587                }
588
589                /**
590                 * Set whether to show pixel colours or not.
591                 *
592                 * @param showPixelColours
593                 *            TRUE to show pixel colours
594                 */
595                public void setShowPixelColours(final boolean showPixelColours) {
596                        this.showPixelColours = showPixelColours;
597                        this.repaint();
598                }
599
600                /**
601                 * Set whether to show the XY position of the mouse curson or not
602                 *
603                 * @param showXYPosition
604                 *            TRUE to show XY position
605                 */
606                public void setShowXYPosition(final boolean showXYPosition) {
607                        this.showXY = showXYPosition;
608                        this.repaint();
609                }
610
611                /**
612                 * Set the image to draw
613                 *
614                 * @param image
615                 *            the image
616                 */
617                public void setImage(final BufferedImage image) {
618                        this.image = image;
619
620                        if (this.autoFit) {
621                                this.calculateScaleFactorsToFit(image, this.getBounds());
622                        } else if (this.autoResize) {
623                                // If the component isn't the right shape, we'll resize the
624                                // component.
625                                if (image.getWidth() != this.getWidth() ||
626                                                image.getHeight() != this.getHeight())
627                                {
628                                        this.setPreferredSize(new Dimension(
629                                                        (int) (image.getWidth() * this.scaleFactorX),
630                                                        (int) (image.getHeight() * this.scaleFactorY)));
631                                        this.setSize(new Dimension(
632                                                        (int) (image.getWidth() * this.scaleFactorX),
633                                                        (int) (image.getHeight() * this.scaleFactorY)));
634                                }
635
636                                final Component c = SwingUtilities.getRoot(this);
637                                if (c == null)
638                                        return;
639                                c.validate();
640
641                                if (c instanceof JFrame && this.autoPack) {
642                                        final JFrame f = (JFrame) c;
643                                        f.pack();
644                                }
645                        }
646
647                        if (this.showPixelColours)
648                                // This forces a repaint if showPixelColours is true
649                                this.updatePixelColours();
650                        else
651                                this.repaint();
652                }
653
654                /**
655                 * Given an image, will calculate two scale factors for the X and Y dimensions
656                 * of the image, such that the image will fit within the bounds.
657                 *
658                 * @param image
659                 *            The image to fit
660                 * @param bounds
661                 *            The bounds to fit within
662                 */
663                private void calculateScaleFactorsToFit(final BufferedImage image,
664                                final java.awt.Rectangle bounds)
665                {
666                        if (image == null || bounds == null)
667                                return;
668
669                        if (this.autoFit) {
670                                // If we can stretch the image it's pretty simple.
671                                if (!this.keepAspect) {
672                                        this.scaleFactorX = bounds.width / (double) image.getWidth();
673                                        this.scaleFactorY = bounds.height / (double) image.getHeight();
674                                }
675                                // Otherwise we need to find the ratios to fit while keeping
676                                // aspect
677                                else {
678                                        this.scaleFactorX = this.scaleFactorY = Math.min(
679                                                        bounds.width / (double) image.getWidth(),
680                                                        bounds.height / (double) image.getHeight());
681                                }
682                        }
683                }
684
685                /**
686                 * Move the image to the given position (image coordinates)
687                 *
688                 * @param x
689                 *            The x image coordinate
690                 * @param y
691                 *            The y image coordinate
692                 */
693                public void moveTo(final double x, final double y) {
694                        if (this.drawX != x || this.drawY != y) {
695                                this.drawX = x;
696                                this.drawY = y;
697                                this.repaint();
698
699                                for (final ImageComponentListener l : this.listeners)
700                                        l.imagePanned(x, y);
701                        }
702                }
703
704                /**
705                 * Set the scale factor to zoom to
706                 *
707                 * @param sf
708                 *            The scale factor
709                 */
710                public void zoom(final double sf) {
711                        this.scaleFactorX = this.scaleFactorY = sf;
712                        this.repaint();
713
714                        for (final ImageComponentListener l : this.listeners)
715                                l.imageZoomed(sf);
716                }
717
718                /**
719                 * Set the scale factor to draw the image in the x-direction. Allows the image
720                 * to be stretched or shrunk horizontally.
721                 *
722                 * @param sf
723                 *            The new scale factor
724                 */
725                public void setScaleFactorX(final double sf) {
726                        this.scaleFactorX = sf;
727                }
728
729                /**
730                 * Set the scale factor to draw the image in the y-direction. Allows the image
731                 * to be stretched or shrunk vertically.
732                 *
733                 * @param sf
734                 *            The new scale factor
735                 */
736                public void setScaleFactorY(final double sf) {
737                        this.scaleFactorY = sf;
738                }
739
740                /**
741                 * Set the scale factor to draw the image. Allows the image to be stretched or
742                 * shrunk both horizontall or vertically.
743                 *
744                 * @param sfx
745                 *            The new x scale factor
746                 * @param sfy
747                 *            The new y scale factor
748                 */
749                public void setScaleFactor(final double sfx, final double sfy) {
750                        this.setScaleFactorX(sfx);
751                        this.setScaleFactorY(sfy);
752                }
753
754                /**
755                 * If you want to be able to inspect the original image's pixel values (rather
756                 * than the generated BufferedImage) set the original image here. Use null to
757                 * enforce showing the BufferedImage pixel values. This does not set the
758                 * BufferedImage that is being used for the display.
759                 *
760                 * @param image
761                 *            The original image.
762                 */
763                public void setOriginalImage(final Image<?, ?> image) {
764                        this.originalImage = image;
765                }
766
767                /**
768                 * Make sure the x and y position we're drawing the image in is not going mad.
769                 */
770                private void sanitiseVars() {
771                        // Make sure we're not going out of the space
772                        // this.moveTo(
773                        // Math.max(
774                        // this.image.getWidth() / this.scaleFactorX / 2,
775                        // Math.min(
776                        // this.drawX,
777                        // this.image.getWidth()
778                        // - (this.getWidth() / 2 / this.scaleFactorX) ) ),
779                        // Math.max( this.image.getHeight() / this.scaleFactorY / 2,
780                        // Math.min(
781                        // this.drawY,
782                        // this.image.getHeight()
783                        // - (this.getHeight() / 2 / this.scaleFactorY) ) ) );
784                }
785
786                /**
787                 * {@inheritDoc}
788                 *
789                 * @see javax.swing.JComponent#paint(java.awt.Graphics)
790                 */
791                @Override
792                public void paint(final Graphics gfx) {
793                        // Create a double buffer into which we'll draw first.
794                        final BufferedImage img = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
795                        final Graphics2D g = (Graphics2D) img.getGraphics();
796
797                        if (this.drawTransparencyGrid) {
798                                final BufferedImage transparencyGrid = new BufferedImage(
799                                                this.getWidth(), this.getHeight(),
800                                                BufferedImage.TYPE_3BYTE_BGR);
801                                final Graphics tg = transparencyGrid.getGraphics();
802
803                                final int gridSizeX = (int) (20 * this.scaleFactorX);
804                                final int gridSizeY = (int) (20 * this.scaleFactorY);
805                                for (int y = 0; y < this.getHeight(); y += gridSizeY) {
806                                        for (int x = 0; x < this.getWidth(); x += gridSizeX) {
807                                                final int c = (x / gridSizeX + y / gridSizeY) % 2;
808                                                if (c == 0)
809                                                        tg.setColor(new Color(220, 220, 220));
810                                                else
811                                                        tg.setColor(Color.white);
812
813                                                tg.fillRect(x, y, gridSizeX, gridSizeY);
814                                        }
815                                }
816
817                                g.drawImage(transparencyGrid, 0, 0, null);
818                        }
819
820                        // Draw the image
821                        if (this.image != null) {
822                                // Scale and translate to the image drawing coordinates
823                                g.scale(this.scaleFactorX, this.scaleFactorY);
824                                g.translate(-this.drawX, -this.drawY);
825
826                                // Blat the image to the screen
827                                g.drawImage(this.image, 0, 0, this.image.getWidth(),
828                                                this.image.getHeight(), null);
829
830                                // Reset the graphics back to the original pixel-based coords
831                                g.translate(this.drawX, this.drawY);
832                                g.scale(1 / this.scaleFactorX, 1 / this.scaleFactorY);
833
834                                // If we're to show pixel colours and we're supposed to do it
835                                // on this time around...
836                                if ((this.showPixelColours || this.showXY)
837                                                && this.drawPixelColour)
838                                {
839                                        final StringBuffer pixelColourStrB = new StringBuffer();
840
841                                        if (this.showXY)
842                                                pixelColourStrB.append("[" + this.pixelX + ","
843                                                                + this.pixelY + "] ");
844
845                                        if (this.showPixelColours)
846                                                pixelColourStrB.append(Arrays
847                                                                .toString(this.currentPixelColour));
848
849                                        // Calculate the size to draw
850                                        final FontMetrics fm = g.getFontMetrics();
851                                        final int fw = fm.stringWidth(pixelColourStrB.toString());
852                                        final int fh = fm.getHeight() + fm.getDescent();
853                                        final int p = 4; // padding
854                                        final int dx = 0;
855                                        int dy = this.getHeight() - (fh + p);
856
857                                        // If the mouse is over where we want to put the box,
858                                        // we'll move the box to another corner
859                                        if (this.mouseX <= dx + fw + p && this.mouseX >= dx &&
860                                                        this.mouseY >= dy && this.mouseY <= dy + fh + p)
861                                                dy = 0;
862
863                                        // Draw a box
864                                        g.setColor(new Color(0, 0, 0, 0.5f));
865                                        g.fillRect(dx, dy, fw + p, fh + p);
866
867                                        // Draw the text
868                                        g.setColor(Color.white);
869                                        g.drawString(pixelColourStrB.toString(), dx + p / 2, dy
870                                                        + fm.getHeight() + p / 2);
871                                }
872                        }
873
874                        // Blat our offscreen image to the screen
875                        gfx.drawImage(img, 0, 0, null);
876
877                        // Store this displayed image
878                        this.displayedImage = img;
879                }
880
881                /**
882                 * {@inheritDoc}
883                 *
884                 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
885                 */
886                @Override
887                public void mouseClicked(final MouseEvent e) {
888                        if (e.getButton() == MouseEvent.BUTTON1 && this.allowZooming) {
889                                if (e.isControlDown()) {
890                                        // Scale the scalars down
891                                        this.scaleFactorX /= 2;
892                                        this.scaleFactorY /= 2;
893
894                                        final double moveX = this.drawX - e.getX() / this.scaleFactorX / 2;
895                                        final double moveY = this.drawY - e.getY() / this.scaleFactorY / 2;
896                                        if (this.allowDragging)
897                                                this.moveTo(moveX, moveY);
898                                        else
899                                                this.moveTo(0, 0);
900                                } else {
901                                        // Scale the scalars up
902                                        this.scaleFactorX *= 2;
903                                        this.scaleFactorY *= 2;
904
905                                        // Make sure we zoom in on the bit the user clicked on
906                                        if (this.allowDragging)
907                                                this.moveTo(
908                                                                this.drawX + e.getX() / this.scaleFactorX,
909                                                                this.drawY + e.getY() / this.scaleFactorY);
910                                        else
911                                                this.moveTo(0, 0);
912                                }
913
914                                // Make sure we're not going to draw out of bounds.
915                                this.sanitiseVars();
916
917                                this.repaint();
918                        }
919                }
920
921                @Override
922                public void mousePressed(final MouseEvent e) {
923                        if (this.allowDragging) {
924                                this.dragStartX = e.getX();
925                                this.dragStartY = e.getY();
926                        }
927                }
928
929                @Override
930                public void mouseReleased(final MouseEvent e) {
931                }
932
933                @Override
934                public void mouseEntered(final MouseEvent e) {
935                }
936
937                @Override
938                public void mouseExited(final MouseEvent e) {
939                        this.drawPixelColour = false;
940                        this.repaint();
941                }
942
943                @Override
944                public void mouseDragged(final MouseEvent e) {
945                        if (!this.allowDragging)
946                                return;
947
948                        final int diffx = e.getX() - this.dragStartX;
949                        final int diffy = e.getY() - this.dragStartY;
950
951                        if (diffx == 0 && diffy == 0)
952                                return;
953
954                        // Update the draw position
955                        this.moveTo(this.drawX - diffx / this.scaleFactorX,
956                                        this.drawY - diffy / this.scaleFactorY);
957
958                        // Reset the draggers
959                        this.dragStartX = e.getX();
960                        this.dragStartY = e.getY();
961
962                        // Make sure the drag stays within the bounds
963                        this.sanitiseVars();
964
965                        // Redraw the component
966                        this.repaint();
967                }
968
969                @Override
970                public void mouseMoved(final MouseEvent e) {
971                        if (this.image == null)
972                                return;
973
974                        // Convert the screen coords into image coords
975                        final double x = e.getX() / this.scaleFactorX + this.drawX;
976                        final double y = e.getY() / this.scaleFactorY + this.drawY;
977
978                        // If we're outside the image we don't print anything
979                        if (x >= this.image.getWidth() || y >= this.image.getHeight() ||
980                                        x < 0 || y < 0)
981                        {
982                                this.drawPixelColour = false;
983                                this.repaint();
984                                return;
985                        }
986
987                        // Pixel coordinates in the image
988                        this.pixelX = (int) x;
989                        this.pixelY = (int) y;
990
991                        this.mouseX = e.getX();
992                        this.mouseY = e.getY();
993
994                        this.updatePixelColours();
995                }
996
997                /**
998                 * Update the display of pixel colours
999                 */
1000                protected void updatePixelColours() {
1001                        if (this.showPixelColours && this.image != null) {
1002                                // If we don't have the original image, we'll just use the
1003                                // colours from the BufferedImage
1004                                if (this.originalImage == null) {
1005                                        final int colour = this.image.getRGB(this.pixelX, this.pixelY);
1006                                        this.currentPixelColour = new Float[3];
1007                                        this.currentPixelColour[0] = (float) ((colour & 0x00ff0000) >> 16);
1008                                        this.currentPixelColour[1] = (float) ((colour & 0x0000ff00) >> 8);
1009                                        this.currentPixelColour[2] = (float) ((colour & 0x000000ff));
1010                                } else {
1011                                        // If we're outside of the original image's coordinates,
1012                                        // we don't need to do anything else..
1013                                        if (this.pixelX >= this.originalImage.getWidth() || this.pixelX < 0 ||
1014                                                        this.pixelY >= this.originalImage.getHeight() || this.pixelY < 0)
1015                                                return;
1016
1017                                        // If we have the original image we get each of the bands
1018                                        // from it and update the current pixel colour member
1019                                        if (this.originalImage instanceof FImage) {
1020                                                final Object o = this.originalImage.getPixel(this.pixelX, this.pixelY);
1021                                                this.currentPixelColour = new Float[1];
1022                                                this.currentPixelColour[0] = (Float) o;
1023                                        } else if (this.originalImage instanceof MBFImage) {
1024                                                final MBFImage i = (MBFImage) this.originalImage;
1025                                                this.currentPixelColour = new Float[i.numBands()];
1026                                                for (int b = 0; b < i.numBands(); b++)
1027                                                        this.currentPixelColour[b] = i.getBand(b)
1028                                                                        .getPixel(this.pixelX, this.pixelY);
1029                                        }
1030                                }
1031
1032                                this.drawPixelColour = true;
1033                                this.repaint();
1034                        }
1035
1036                        if (this.showXY) {
1037                                this.drawPixelColour = true;
1038                                this.repaint();
1039                        }
1040                }
1041
1042                /**
1043                 * Sets whether to automatically size the image to fit within the bounds of the
1044                 * image component which is being sized externally. This shouldn't be used in
1045                 * combination with autoResize. When this method is called with TRUE, zooming
1046                 * and dragging are disabled.
1047                 *
1048                 * @param tf
1049                 *            TRUE to auto fit the image.
1050                 */
1051                public void setAutoFit(final boolean tf) {
1052                        this.autoFit = tf;
1053                        if (this.autoFit) {
1054                                this.allowZooming = false;
1055                                this.allowDragging = false;
1056                        }
1057                }
1058
1059                /**
1060                 * Sets whether to keep the aspect ratio of the image constant when the image is
1061                 * being autoFit into the component.
1062                 *
1063                 * @param tf
1064                 *            TRUE to keep the aspect ratio constant
1065                 */
1066                public void setKeepAspect(final boolean tf) {
1067                        this.keepAspect = tf;
1068                }
1069
1070                /**
1071                 * Sets whether to automatically resize the component to fit image (at it's
1072                 * given scale factor) within it. Note that in certain circumstances, where the
1073                 * image component is being sized by external forces (such as a layout manager),
1074                 * setting this to true can cause weird results where the image is pulled out
1075                 * and in constantly. This shouldn't be used in combination with autoFit.
1076                 *
1077                 * @param tf
1078                 *            TRUE to resize the component.
1079                 */
1080                public void setAutoResize(final boolean tf) {
1081                        this.autoResize = tf;
1082                }
1083
1084                /**
1085                 * Sets whether the component is to attempt to pack a frame into which it is
1086                 * added. If it is not in a frame this will have no effect. This allows the
1087                 * frame to resize with the component.
1088                 *
1089                 * @param tf
1090                 *            TRUE to auto pack the parent frame.
1091                 */
1092                public void setAutoPack(final boolean tf) {
1093                        this.autoPack = tf;
1094                }
1095
1096                /**
1097                 * Returns the current mouse position in pixels within the viewport. Will return
1098                 * the last known position if the mouse is no longer within the viewport.
1099                 *
1100                 * @return The position in pixels
1101                 */
1102                public Point2d getCurrentMousePosition() {
1103                        return new Point2dImpl(this.mouseX, this.mouseY);
1104                }
1105
1106                /**
1107                 * Returns the current mouse position in the coordinates of the image and is
1108                 * determined by the scaling factors and the position of the image within the
1109                 * viewport. If the mouse is no longer in the viewport, the last known mouse
1110                 * position will be returned.
1111                 *
1112                 * @return The position in image coordinates.
1113                 */
1114                public Point2d getCurrentMouseImagePosition() {
1115                        return new Point2dImpl(this.pixelX, this.pixelY);
1116                }
1117
1118                /**
1119                 * Returns the current pixel colour at the point of the mouse. The number of
1120                 * elements in the array will equal be 3, if no original has been supplied to
1121                 * the image component. The values will be between 0 and 255 and ordered red,
1122                 * green and blue. If the original has been supplied, then the number of
1123                 * elements will be equal to the number of bands in the original image and the
1124                 * values will be the original pixel values in the original image.
1125                 *
1126                 * @return The current pixel colour.
1127                 */
1128                public Float[] getCurrentPixelColour() {
1129                        return this.currentPixelColour;
1130                }
1131
1132                /**
1133                 * Returns the current displayed pixel colour (as an RGB encoded int) from the
1134                 * currently displayed image.
1135                 *
1136                 * @return The current displayed pixel colour.
1137                 */
1138                public int getCurrentDisplayedPixelColour() {
1139                        return this.displayedImage.getRGB(this.mouseX, this.mouseY);
1140                }
1141
1142                /**
1143                 * Returns the currently displaying image.
1144                 *
1145                 * @return The displayed image.
1146                 */
1147                public BufferedImage getDisplayedImage() {
1148                        return this.displayedImage;
1149                }
1150        }
1151
1152        /**
1153         * An extension of {@link ImageComponent} that scales the displayed image.
1154         *
1155         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
1156         *
1157         */
1158        public static class ScalingImageComponent extends ImageComponent {
1159                /**
1160                 *
1161                 */
1162                private static final long serialVersionUID = 1L;
1163                private boolean hq = false;
1164
1165                /**
1166                 * Construct the ScalingImageComponent with fast scaling enabled
1167                 */
1168                public ScalingImageComponent() {
1169                }
1170
1171                /**
1172                 * Construct the ScalingImageComponent, choosing between fast scaling or high
1173                 * quality scaling
1174                 *
1175                 * @param hq
1176                 *            true if high quality scaling is required.
1177                 */
1178                public ScalingImageComponent(boolean hq) {
1179                        this.hq = hq;
1180                }
1181
1182                @Override
1183                public void paint(final Graphics g) {
1184                        if (hq)
1185                                ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING,
1186                                                RenderingHints.VALUE_RENDER_QUALITY);
1187
1188                        final Component f = SwingUtilities.getRoot(this);
1189                        if (this.image != null)
1190                                g.drawImage(this.image, 0, 0, this.getWidth(),
1191                                                this.getHeight(), f);
1192                }
1193        }
1194
1195        /**
1196         * Display an image in the given frame
1197         *
1198         * @param image
1199         *            the image
1200         * @param frame
1201         *            the frame
1202         * @return the frame
1203         */
1204        public static JFrame display(final BufferedImage image, final JFrame frame) {
1205                return DisplayUtilities.display(image, frame, null);
1206        }
1207
1208        /**
1209         * Displays an image in the given named window
1210         *
1211         * @param image
1212         *            The image
1213         * @param name
1214         *            The name of the window
1215         * @return The frame that was created.
1216         */
1217        public static JFrame displayName(final BufferedImage image, final String name) {
1218                if (GraphicsEnvironment.isHeadless())
1219                        return null;
1220
1221                final JFrame f = DisplayUtilities.createNamedWindow(name);
1222                return DisplayUtilities.display(image, f);
1223        }
1224
1225        /**
1226         * Display an image in the given frame
1227         *
1228         * @param image
1229         *            the image
1230         * @param frame
1231         *            the frame
1232         * @param originalImage
1233         *            the original image
1234         * @return the frame
1235         */
1236        public static JFrame display(final BufferedImage image,
1237                        final JFrame frame, final Image<?, ?> originalImage)
1238        {
1239                if (frame == null)
1240                        return DisplayUtilities.makeDisplayFrame("Image: "
1241                                        + DisplayUtilities.windowCount, image.getWidth(),
1242                                        image.getHeight(), image);
1243
1244                if (frame.getContentPane().getComponentCount() > 0
1245                                && frame.getContentPane().getComponent(0) instanceof ImageComponent)
1246                {
1247                        final ImageComponent cmp = ((ImageComponent) frame.getContentPane()
1248                                        .getComponent(0));
1249                        if (!frame.isVisible()) {
1250                                final boolean ar = cmp.autoResize;
1251                                final boolean ap = cmp.autoPack;
1252                                cmp.autoResize = true;
1253                                cmp.autoPack = true;
1254                                cmp.setImage(image);
1255                                cmp.setOriginalImage(originalImage);
1256                                cmp.autoResize = ar;
1257                                cmp.autoPack = ap;
1258                                frame.setVisible(true);
1259                        } else {
1260                                cmp.setImage(image);
1261                                cmp.setOriginalImage(originalImage);
1262                        }
1263                } else {
1264                        frame.getContentPane().removeAll();
1265
1266                        final ImageComponent c = new ImageComponent(image);
1267                        c.setOriginalImage(originalImage);
1268
1269                        frame.add(c);
1270                        frame.pack();
1271                        frame.setVisible(true);
1272                }
1273                return frame;
1274        }
1275
1276        /**
1277         * Make a frame with the given title.
1278         *
1279         * @param title
1280         *            the title
1281         * @return the frame
1282         */
1283        public static JFrame makeFrame(final String title) {
1284                final JFrame f = new JFrame(title);
1285                f.setResizable(false);
1286                f.setUndecorated(false);
1287
1288                f.addWindowListener(new WindowAdapter() {
1289                        @Override
1290                        public void windowClosing(final WindowEvent evt) {
1291                                DisplayUtilities.windowOpenCount = DisplayUtilities.windowCount - 1;
1292                                f.dispose();
1293                        }
1294                });
1295                return f;
1296        }
1297
1298        /**
1299         * Display an image with the given title. No additional functionality, such as
1300         * zooming, is enabled.
1301         *
1302         * @param image
1303         *            the image
1304         * @param title
1305         *            the title
1306         * @return frame containing the image
1307         */
1308        public static JFrame displaySimple(final BufferedImage image,
1309                        final String title)
1310        {
1311                return DisplayUtilities.displaySimple(image, title, null);
1312        }
1313
1314        /**
1315         * Display an image with the given title
1316         *
1317         * @param image
1318         *            the image
1319         * @param title
1320         *            the title
1321         * @return frame containing the image
1322         */
1323        public static JFrame display(final BufferedImage image, final String title) {
1324                return DisplayUtilities.display(image, title, null);
1325        }
1326
1327        /**
1328         * Display an image with the given title. No additional functionality, such as
1329         * zooming, is enabled.
1330         *
1331         * @param image
1332         *            the image
1333         * @param title
1334         *            the title
1335         * @param originalImage
1336         *            original image
1337         * @return frame containing the image
1338         */
1339        public static JFrame displaySimple(final BufferedImage image,
1340                        final String title, final Image<?, ?> originalImage)
1341        {
1342                if (GraphicsEnvironment.isHeadless())
1343                        return null;
1344
1345                return DisplayUtilities.makeDisplayFrameSimple(title,
1346                                image.getWidth(), image.getHeight(), image, originalImage);
1347        }
1348
1349        /**
1350         * Get a frame that will display an image. No additional functionality, such as
1351         * zooming, is enabled.
1352         *
1353         * @param title
1354         *            the frame title
1355         * @param width
1356         *            the frame width
1357         * @param height
1358         *            the frame height
1359         * @param img
1360         *            the image to display
1361         * @param originalImage
1362         *            the original image
1363         * @return A {@link JFrame} that allows images to be displayed.
1364         */
1365        public static JFrame makeDisplayFrameSimple(final String title,
1366                        final int width, final int height, final BufferedImage img,
1367                        final Image<?, ?> originalImage)
1368        {
1369                final JFrame f = DisplayUtilities.makeFrame(title);
1370
1371                final ImageComponent c = new ImageComponent();
1372                if (img != null)
1373                        c.setImage(img);
1374                c.setOriginalImage(originalImage);
1375                c.setSize(width, height);
1376                c.setPreferredSize(new Dimension(c.getWidth(), c.getHeight()));
1377
1378                c.removeMouseListener(c);
1379                c.removeMouseMotionListener(c);
1380                c.setShowPixelColours(false);
1381                c.setShowXYPosition(false);
1382                c.setAllowZoom(false);
1383                c.setAutoscrolls(false);
1384                c.setAllowPanning(false);
1385
1386                f.add(c);
1387                f.pack();
1388                f.setVisible(img != null);
1389
1390                DisplayUtilities.windowCount++;
1391
1392                return f;
1393        }
1394
1395        /**
1396         * Display an image with the given title
1397         *
1398         * @param image
1399         *            the image
1400         * @param title
1401         *            the title
1402         * @param originalImage
1403         *            original image
1404         * @return frame containing the image
1405         */
1406        public static JFrame display(final BufferedImage image,
1407                        final String title, final Image<?, ?> originalImage)
1408        {
1409                if (GraphicsEnvironment.isHeadless())
1410                        return null;
1411
1412                return DisplayUtilities.makeDisplayFrame(title, image.getWidth(),
1413                                image.getHeight(), image, originalImage);
1414        }
1415
1416        /**
1417         * Get a frame that will display an image.
1418         *
1419         * @param title
1420         *            the frame title
1421         * @param width
1422         *            the frame width
1423         * @param height
1424         *            the frame height
1425         * @return A {@link JFrame} that allows images to be displayed.
1426         */
1427        public static JFrame makeDisplayFrame(final String title, final int width,
1428                        final int height)
1429        {
1430                return DisplayUtilities.makeDisplayFrame(title, width, height, null);
1431        }
1432
1433        /**
1434         * Get a frame that will display an image.
1435         *
1436         * @param title
1437         *            the frame title
1438         * @param width
1439         *            the frame width
1440         * @param height
1441         *            the frame height
1442         * @param img
1443         *            the image to display
1444         * @return A {@link JFrame} that allows images to be displayed.
1445         */
1446        public static JFrame makeDisplayFrame(final String title, final int width,
1447                        final int height, final BufferedImage img)
1448        {
1449                return DisplayUtilities.makeDisplayFrame(title, width, height, img,
1450                                null);
1451        }
1452
1453        /**
1454         * Get a frame that will display an image.
1455         *
1456         * @param title
1457         *            the frame title
1458         * @param width
1459         *            the frame width
1460         * @param height
1461         *            the frame height
1462         * @param img
1463         *            the image to display
1464         * @param originalImage
1465         *            the original image
1466         * @return A {@link JFrame} that allows images to be displayed.
1467         */
1468        public static JFrame makeDisplayFrame(final String title, final int width,
1469                        final int height, final BufferedImage img,
1470                        final Image<?, ?> originalImage)
1471        {
1472                final JFrame f = DisplayUtilities.makeFrame(title);
1473
1474                final ImageComponent c = new ImageComponent();
1475                if (img != null)
1476                        c.setImage(img);
1477                c.setOriginalImage(originalImage);
1478                c.setSize(width, height);
1479                c.setPreferredSize(new Dimension(c.getWidth(), c.getHeight()));
1480
1481                f.add(c);
1482                f.pack();
1483                f.setVisible(img != null);
1484
1485                DisplayUtilities.windowCount++;
1486
1487                return f;
1488        }
1489
1490        /**
1491         * Render a connected component and display it
1492         *
1493         * @param input
1494         *            the connected component
1495         * @return frame containing the rendered image
1496         */
1497        public static JFrame display(final ConnectedComponent input) {
1498                return DisplayUtilities.display(input, 1.0f);
1499        }
1500
1501        /**
1502         * Render a connected component with a given grey level and display it
1503         *
1504         * @param input
1505         *            the connected component
1506         * @param col
1507         *            the grey level
1508         * @return frame containing the rendered image
1509         */
1510        public static JFrame display(final ConnectedComponent input,
1511                        final float col)
1512        {
1513                final ConnectedComponent cc = input.clone();
1514
1515                final Rectangle bb = cc.calculateRegularBoundingBox();
1516
1517                // Render the mask, leaving a 10 px border
1518                cc.translate(10 - (int) bb.x, 10 - (int) bb.y);
1519                final FImage mask = new FImage((int) Math.max(bb.width + 20, 100),
1520                                (int) Math.max(bb.height + 20, 100));
1521                final BlobRenderer<Float> br = new BlobRenderer<Float>(mask, 1.0F);
1522                cc.process(br);
1523
1524                return DisplayUtilities.display(mask);
1525        }
1526
1527        /**
1528         * Render a polygon to an image and display it.
1529         *
1530         * @param input
1531         *            the polygon
1532         * @return the frame
1533         */
1534        public static JFrame display(final Polygon input) {
1535                return DisplayUtilities.display(input, 1.0f);
1536        }
1537
1538        /**
1539         * Render a polygon with a given grey level and display it
1540         *
1541         * @param input
1542         *            the polygon
1543         * @param col
1544         *            the grey level
1545         * @return frame containing the rendered image
1546         */
1547        public static JFrame display(final Polygon input, final float col) {
1548                final Polygon p = input.clone();
1549
1550                final Rectangle bb = p.calculateRegularBoundingBox();
1551
1552                // Render the mask, leaving a 1 px border
1553                p.translate(10 - bb.x, 10 - bb.y);
1554                final FImage mask = new FImage((int) (bb.width + 20),
1555                                (int) (bb.height + 20));
1556                mask.createRenderer().drawPolygon(p, col);
1557
1558                return DisplayUtilities.display(mask);
1559        }
1560
1561        /**
1562         * Display multiple images in an array
1563         *
1564         * @param title
1565         *            the frame title
1566         * @param images
1567         *            the images
1568         * @return the frame
1569         */
1570        public static JFrame display(final String title,
1571                        final Image<?, ?>... images)
1572        {
1573                final BufferedImage[] bimages = new BufferedImage[images.length];
1574
1575                for (int i = 0; i < images.length; i++)
1576                        bimages[i] = ImageUtilities
1577                                        .createBufferedImageForDisplay(images[i]);
1578
1579                return DisplayUtilities.display(title, bimages);
1580        }
1581
1582        /**
1583         * Display multiple images in a collection
1584         *
1585         * @param title
1586         *            the frame title
1587         * @param images
1588         *            the images
1589         * @return the frame
1590         */
1591        public static JFrame display(final String title,
1592                        final Collection<? extends Image<?, ?>> images)
1593        {
1594                final BufferedImage[] bimages = new BufferedImage[images.size()];
1595
1596                int i = 0;
1597                for (final Image<?, ?> img : images)
1598                        bimages[i++] = ImageUtilities
1599                                        .createBufferedImageForDisplay(img);
1600
1601                return DisplayUtilities.display(title, bimages);
1602        }
1603
1604        /**
1605         * Display multiple images in an array
1606         *
1607         * @param title
1608         *            the frame title
1609         * @param cols
1610         *            number of columns
1611         * @param images
1612         *            the images
1613         * @return the frame
1614         */
1615        public static JFrame display(final String title, final int cols,
1616                        final Image<?, ?>... images)
1617        {
1618                final JFrame f = new JFrame(title);
1619
1620                f.getContentPane().setLayout(new GridLayout(0, cols));
1621
1622                for (final Image<?, ?> image : images) {
1623                        if (image != null) {
1624                                final ImageComponent ic = new ImageComponent(
1625                                                ImageUtilities.createBufferedImageForDisplay(image));
1626                                ic.setOriginalImage(image);
1627                                f.getContentPane().add(ic);
1628                        }
1629                }
1630
1631                f.pack();
1632                f.setVisible(true);
1633
1634                return f;
1635        }
1636
1637        /**
1638         * Display multiple images in an array
1639         *
1640         * @param title
1641         *            the frame title
1642         * @param cols
1643         *            number of columns
1644         * @param images
1645         *            the images
1646         * @return the frame
1647         */
1648        public static JFrame displayLinked(final String title, final int cols,
1649                        final Image<?, ?>... images)
1650        {
1651                final JFrame f = new JFrame(title);
1652
1653                f.getContentPane().setLayout(new GridLayout(0, cols));
1654
1655                ImageComponent ic = null;
1656                for (final Image<?, ?> image : images) {
1657                        if (image != null) {
1658                                final ImageComponent ic2 = new ImageComponent(
1659                                                ImageUtilities.createBufferedImageForDisplay(image));
1660
1661                                if (ic != null) {
1662                                        ic.addImageComponentListener(new ImageComponentListener() {
1663                                                @Override
1664                                                public void imageZoomed(final double newScaleFactor) {
1665                                                        ic2.zoom(newScaleFactor);
1666                                                }
1667
1668                                                @Override
1669                                                public void imagePanned(final double newX,
1670                                                                final double newY)
1671                                                {
1672                                                        ic2.moveTo(newX, newY);
1673                                                }
1674                                        });
1675                                }
1676
1677                                ic2.setOriginalImage(image);
1678                                f.getContentPane().add(ic2);
1679
1680                                ic = ic2;
1681                        }
1682                }
1683
1684                f.pack();
1685                f.setVisible(true);
1686
1687                return f;
1688        }
1689
1690        /**
1691         * Display multiple images in an array of frames
1692         *
1693         * @param title
1694         *            the frame title
1695         * @param images
1696         *            the images
1697         * @return the frame
1698         */
1699        public static JFrame display(final String title,
1700                        final BufferedImage... images)
1701        {
1702                if (GraphicsEnvironment.isHeadless())
1703                        return null;
1704
1705                final JFrame f = new JFrame(title);
1706
1707                final int box_size = 200;
1708                final int n_images = images.length;
1709                final int n_boxes_x = 4;
1710                final int width = n_boxes_x * box_size;
1711                final int height = box_size * n_images / n_boxes_x;
1712
1713                f.addWindowListener(new WindowAdapter() {
1714                        @Override
1715                        public void windowClosing(final WindowEvent evt) {
1716                                DisplayUtilities.windowOpenCount = DisplayUtilities.windowCount - 1;
1717                                f.dispose();
1718                        }
1719                });
1720
1721                final Container scrollContainer = new Container();
1722                scrollContainer.setLayout(new FlowLayout());
1723
1724                final Container container = new Container();
1725                container.setSize(new Dimension(width, height));
1726                container.setPreferredSize(new Dimension(width, height));
1727                container.setLayout(new GridLayout(0, n_boxes_x));
1728                scrollContainer.add(container);
1729
1730                for (final BufferedImage img : images) {
1731                        final JComponent c = new JComponent() {
1732                                private static final long serialVersionUID = 1L;
1733
1734                                @Override
1735                                public void paint(final Graphics g) {
1736                                        final int cw = this.getWidth();
1737                                        final int ch = this.getHeight();
1738                                        if (img.getWidth() < cw && img.getHeight() < ch) {
1739                                                final int x = (cw - img.getWidth()) / 2;
1740                                                final int y = (ch - img.getHeight()) / 2;
1741                                                g.drawImage(img, x, y, img.getWidth(),
1742                                                                img.getHeight(), f);
1743                                        } else if (img.getWidth() > img.getHeight()) {
1744                                                final float sf = (float) cw / (float) img.getWidth();
1745                                                final int h = Math.round(sf * img.getHeight());
1746                                                g.drawImage(img, 0, (ch - h) / 2, cw, h, f);
1747                                        } else {
1748                                                final float sf = (float) ch / (float) img.getHeight();
1749                                                final int w = Math.round(sf * img.getWidth());
1750                                                g.drawImage(img, (cw - w) / 2, 0, w, ch, f);
1751                                        }
1752                                        // TODO: scale image proportionally and draw centered
1753
1754                                }
1755                        };
1756                        c.setSize(200, 200);
1757                        c.setPreferredSize(new Dimension(c.getWidth(), c.getHeight()));
1758                        container.add(c);
1759                }
1760                f.setSize(new Dimension(840, 600));
1761                f.setPreferredSize(new Dimension(840, 600));
1762
1763                f.getContentPane().add(new JScrollPane(scrollContainer));
1764
1765                f.pack();
1766                f.setVisible(true);
1767
1768                DisplayUtilities.windowCount++;
1769
1770                return f;
1771        }
1772
1773}