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