001package com.github.sarxos.webcam;
002
003import static java.awt.RenderingHints.KEY_ANTIALIASING;
004import static java.awt.RenderingHints.KEY_INTERPOLATION;
005import static java.awt.RenderingHints.KEY_RENDERING;
006import static java.awt.RenderingHints.VALUE_ANTIALIAS_OFF;
007import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
008import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR;
009import static java.awt.RenderingHints.VALUE_RENDER_SPEED;
010
011import java.awt.AlphaComposite;
012import java.awt.BasicStroke;
013import java.awt.Color;
014import java.awt.Dimension;
015import java.awt.FontMetrics;
016import java.awt.Graphics;
017import java.awt.Graphics2D;
018import java.awt.GraphicsConfiguration;
019import java.awt.GraphicsEnvironment;
020import java.awt.RenderingHints;
021import java.awt.image.BufferedImage;
022import java.beans.PropertyChangeEvent;
023import java.beans.PropertyChangeListener;
024import java.util.HashMap;
025import java.util.Locale;
026import java.util.Map;
027import java.util.ResourceBundle;
028import java.util.concurrent.Executors;
029import java.util.concurrent.RejectedExecutionException;
030import java.util.concurrent.ScheduledExecutorService;
031import java.util.concurrent.ThreadFactory;
032import java.util.concurrent.TimeUnit;
033import java.util.concurrent.atomic.AtomicBoolean;
034import java.util.concurrent.atomic.AtomicInteger;
035
036import javax.swing.JPanel;
037import javax.swing.SwingUtilities;
038
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042
043/**
044 * Simply implementation of JPanel allowing users to render pictures taken with
045 * webcam.
046 * 
047 * @author Bartosz Firyn (SarXos)
048 */
049public class WebcamPanel extends JPanel implements WebcamListener, PropertyChangeListener {
050
051        /**
052         * This enum is to control of how image will be drawn in the panel bounds.
053         * 
054         * @author Sylwia Kauczor
055         */
056        public static enum DrawMode {
057
058                /**
059                 * Do not resize image - paint it as it is. This will make the image to
060                 * go off out the bounds if panel is smaller than image size.
061                 */
062                NONE,
063
064                /**
065                 * Will resize image to the panel bounds. This mode does not care of the
066                 * image scale, so the final image may be disrupted.
067                 */
068                FILL,
069
070                /**
071                 * Will fir image into the panel bounds. This will resize the image and
072                 * keep both x and y scale factor.
073                 */
074                FIT,
075        }
076
077        /**
078         * Interface of the painter used to draw image in panel.
079         * 
080         * @author Bartosz Firyn (SarXos)
081         */
082        public static interface Painter {
083
084                /**
085                 * Paints panel without image.
086                 * 
087                 * @param g2 the graphics 2D object used for drawing
088                 */
089                void paintPanel(WebcamPanel panel, Graphics2D g2);
090
091                /**
092                 * Paints webcam image in panel.
093                 * 
094                 * @param g2 the graphics 2D object used for drawing
095                 */
096                void paintImage(WebcamPanel panel, BufferedImage image, Graphics2D g2);
097        }
098
099        /**
100         * Default painter used to draw image in panel.
101         * 
102         * @author Bartosz Firyn (SarXos)
103         * @author Sylwia Kauczor
104         */
105        public class DefaultPainter implements Painter {
106
107                /**
108                 * Webcam device name.
109                 */
110                private String name = null;
111
112                /**
113                 * Lat repaint time, uset for debug purpose.
114                 */
115                private long lastRepaintTime = -1;
116
117                /**
118                 * Buffered image resized to fit into panel drawing area.
119                 */
120                private BufferedImage resizedImage = null;
121
122                @Override
123                public void paintPanel(WebcamPanel owner, Graphics2D g2) {
124
125                        assert owner != null;
126                        assert g2 != null;
127
128                        Object antialiasing = g2.getRenderingHint(KEY_ANTIALIASING);
129
130                        g2.setRenderingHint(KEY_ANTIALIASING, isAntialiasingEnabled() ? VALUE_ANTIALIAS_ON : VALUE_ANTIALIAS_OFF);
131                        g2.setBackground(Color.BLACK);
132                        g2.fillRect(0, 0, getWidth(), getHeight());
133
134                        int cx = (getWidth() - 70) / 2;
135                        int cy = (getHeight() - 40) / 2;
136
137                        g2.setStroke(new BasicStroke(2));
138                        g2.setColor(Color.LIGHT_GRAY);
139                        g2.fillRoundRect(cx, cy, 70, 40, 10, 10);
140                        g2.setColor(Color.WHITE);
141                        g2.fillOval(cx + 5, cy + 5, 30, 30);
142                        g2.setColor(Color.LIGHT_GRAY);
143                        g2.fillOval(cx + 10, cy + 10, 20, 20);
144                        g2.setColor(Color.WHITE);
145                        g2.fillOval(cx + 12, cy + 12, 16, 16);
146                        g2.fillRoundRect(cx + 50, cy + 5, 15, 10, 5, 5);
147                        g2.fillRect(cx + 63, cy + 25, 7, 2);
148                        g2.fillRect(cx + 63, cy + 28, 7, 2);
149                        g2.fillRect(cx + 63, cy + 31, 7, 2);
150
151                        g2.setColor(Color.DARK_GRAY);
152                        g2.setStroke(new BasicStroke(3));
153                        g2.drawLine(0, 0, getWidth(), getHeight());
154                        g2.drawLine(0, getHeight(), getWidth(), 0);
155
156                        String str = null;
157
158                        final String strInitDevice = rb.getString("INITIALIZING_DEVICE");
159                        final String strNoImage = rb.getString("NO_IMAGE");
160                        final String strDeviceError = rb.getString("DEVICE_ERROR");
161
162                        if (!errored) {
163                                str = starting ? strInitDevice : strNoImage;
164                        } else {
165                                str = strDeviceError;
166                        }
167
168                        FontMetrics metrics = g2.getFontMetrics(getFont());
169                        int w = metrics.stringWidth(str);
170                        int h = metrics.getHeight();
171
172                        int x = (getWidth() - w) / 2;
173                        int y = cy - h;
174
175                        g2.setFont(getFont());
176                        g2.setColor(Color.WHITE);
177                        g2.drawString(str, x, y);
178
179                        if (name == null) {
180                                name = webcam.getName();
181                        }
182
183                        str = name;
184
185                        w = metrics.stringWidth(str);
186                        h = metrics.getHeight();
187
188                        g2.drawString(str, (getWidth() - w) / 2, cy - 2 * h);
189                        g2.setRenderingHint(KEY_ANTIALIASING, antialiasing);
190                }
191
192                @Override
193                public void paintImage(WebcamPanel owner, BufferedImage image, Graphics2D g2) {
194
195                        assert owner != null;
196                        assert image != null;
197                        assert g2 != null;
198
199                        int pw = getWidth();
200                        int ph = getHeight();
201                        int iw = image.getWidth();
202                        int ih = image.getHeight();
203
204                        Object antialiasing = g2.getRenderingHint(KEY_ANTIALIASING);
205                        Object rendering = g2.getRenderingHint(KEY_RENDERING);
206
207                        g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF);
208                        g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_SPEED);
209                        g2.setBackground(Color.BLACK);
210                        g2.setColor(Color.BLACK);
211                        g2.fillRect(0, 0, pw, ph);
212
213                        // resized image position and size
214                        int x = 0;
215                        int y = 0;
216                        int w = 0;
217                        int h = 0;
218
219                        switch (drawMode) {
220                                case NONE:
221                                        w = image.getWidth();
222                                        h = image.getHeight();
223                                        break;
224                                case FILL:
225                                        w = pw;
226                                        h = ph;
227                                        break;
228                                case FIT:
229                                        double s = Math.max((double) iw / pw, (double) ih / ph);
230                                        double niw = iw / s;
231                                        double nih = ih / s;
232                                        double dx = (pw - niw) / 2;
233                                        double dy = (ph - nih) / 2;
234                                        w = (int) niw;
235                                        h = (int) nih;
236                                        x = (int) dx;
237                                        y = (int) dy;
238                                        break;
239                        }
240
241                        if (resizedImage != null) {
242                                resizedImage.flush();
243                        }
244
245                        if (w == image.getWidth() && h == image.getHeight() && !mirrored) {
246                                resizedImage = image;
247                        } else {
248
249                                GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
250                                GraphicsConfiguration gc = genv.getDefaultScreenDevice().getDefaultConfiguration();
251
252                                Graphics2D gr = null;
253                                try {
254
255                                        resizedImage = gc.createCompatibleImage(pw, ph);
256                                        gr = resizedImage.createGraphics();
257                                        gr.setComposite(AlphaComposite.Src);
258
259                                        for (Map.Entry<RenderingHints.Key, Object> hint : imageRenderingHints.entrySet()) {
260                                                gr.setRenderingHint(hint.getKey(), hint.getValue());
261                                        }
262
263                                        gr.setBackground(Color.BLACK);
264                                        gr.setColor(Color.BLACK);
265                                        gr.fillRect(0, 0, pw, ph);
266
267                                        int sx1, sx2, sy1, sy2; // source rectangle coordinates
268                                        int dx1, dx2, dy1, dy2; // destination rectangle coordinates
269
270                                        dx1 = x;
271                                        dy1 = y;
272                                        dx2 = x + w;
273                                        dy2 = y + h;
274
275                                        if (mirrored) {
276                                                sx1 = iw;
277                                                sy1 = 0;
278                                                sx2 = 0;
279                                                sy2 = ih;
280                                        } else {
281                                                sx1 = 0;
282                                                sy1 = 0;
283                                                sx2 = iw;
284                                                sy2 = ih;
285                                        }
286
287                                        gr.drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null);
288
289                                } finally {
290                                        if (gr != null) {
291                                                gr.dispose();
292                                        }
293                                }
294                        }
295
296                        g2.drawImage(resizedImage, 0, 0, null);
297
298                        if (isFPSDisplayed()) {
299
300                                String str = String.format("FPS: %.1f", webcam.getFPS());
301
302                                int sx = 5;
303                                int sy = ph - 5;
304
305                                g2.setFont(getFont());
306                                g2.setColor(Color.BLACK);
307                                g2.drawString(str, sx + 1, sy + 1);
308                                g2.setColor(Color.WHITE);
309                                g2.drawString(str, sx, sy);
310                        }
311
312                        if (isImageSizeDisplayed()) {
313
314                                String res = String.format("%d\u2A2F%d px", iw, ih);
315
316                                FontMetrics metrics = g2.getFontMetrics(getFont());
317                                int sw = metrics.stringWidth(res);
318                                int sx = pw - sw - 5;
319                                int sy = ph - 5;
320
321                                g2.setFont(getFont());
322                                g2.setColor(Color.BLACK);
323                                g2.drawString(res, sx + 1, sy + 1);
324                                g2.setColor(Color.WHITE);
325                                g2.drawString(res, sx, sy);
326                        }
327
328                        if (isDisplayDebugInfo()) {
329
330                                if (lastRepaintTime < 0) {
331                                        lastRepaintTime = System.currentTimeMillis();
332                                } else {
333
334                                        long now = System.currentTimeMillis();
335                                        String res = String.format("DEBUG: repaints per second: %.1f", (double) 1000 / (now - lastRepaintTime));
336                                        lastRepaintTime = now;
337                                        g2.setFont(getFont());
338                                        g2.setColor(Color.BLACK);
339                                        g2.drawString(res, 6, 16);
340                                        g2.setColor(Color.WHITE);
341                                        g2.drawString(res, 5, 15);
342                                }
343                        }
344
345                        g2.setRenderingHint(KEY_ANTIALIASING, antialiasing);
346                        g2.setRenderingHint(KEY_RENDERING, rendering);
347                }
348        }
349
350        private static final class PanelThreadFactory implements ThreadFactory {
351
352                private static final AtomicInteger number = new AtomicInteger(0);
353
354                @Override
355                public Thread newThread(Runnable r) {
356                        Thread t = new Thread(r, String.format("webcam-panel-scheduled-executor-%d", number.incrementAndGet()));
357                        t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
358                        t.setDaemon(true);
359                        return t;
360                }
361        }
362
363        /**
364         * This runnable will do nothing more than repaint panel.
365         */
366        private static final class SwingRepainter implements Runnable {
367
368                private WebcamPanel panel = null;
369
370                public SwingRepainter(WebcamPanel panel) {
371                        this.panel = panel;
372                }
373
374                @Override
375                public void run() {
376                        panel.repaint();
377                }
378        }
379
380        /**
381         * S/N used by Java to serialize beans.
382         */
383        private static final long serialVersionUID = 1L;
384
385        /**
386         * Logger.
387         */
388        private static final Logger LOG = LoggerFactory.getLogger(WebcamPanel.class);
389
390        /**
391         * Minimum FPS frequency.
392         */
393        public static final double MIN_FREQUENCY = 0.016; // 1 frame per minute
394
395        /**
396         * Maximum FPS frequency.
397         */
398        private static final double MAX_FREQUENCY = 50; // 50 frames per second
399
400        /**
401         * Thread factory used by execution service.
402         */
403        private static final ThreadFactory THREAD_FACTORY = new PanelThreadFactory();
404
405        public static final Map<RenderingHints.Key, Object> DEFAULT_IMAGE_RENDERING_HINTS = new HashMap<RenderingHints.Key, Object>();
406        static {
407                DEFAULT_IMAGE_RENDERING_HINTS.put(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
408                DEFAULT_IMAGE_RENDERING_HINTS.put(KEY_RENDERING, VALUE_RENDER_SPEED);
409                DEFAULT_IMAGE_RENDERING_HINTS.put(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
410        }
411
412        /**
413         * This runnable will do nothing more than repaint panel.
414         */
415        private final Runnable repaint = new SwingRepainter(this);
416
417        /**
418         * Rendering hints to be used when painting image to be displayed.
419         */
420        private Map<RenderingHints.Key, Object> imageRenderingHints = new HashMap<RenderingHints.Key, Object>(DEFAULT_IMAGE_RENDERING_HINTS);
421
422        /**
423         * Scheduled executor acting as timer.
424         */
425        private ScheduledExecutorService executor = null;
426
427        /**
428         * Image updater reads images from camera and force panel to be repainted.
429         * 
430         * @author Bartosz Firyn (SarXos)
431         */
432        private class ImageUpdater implements Runnable {
433
434                /**
435                 * Repaint scheduler schedule panel updates.
436                 * 
437                 * @author Bartosz Firyn (sarxos)
438                 */
439                private class RepaintScheduler extends Thread {
440
441                        /**
442                         * Repaint scheduler schedule panel updates.
443                         */
444                        public RepaintScheduler() {
445                                setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
446                                setName(String.format("repaint-scheduler-%s", webcam.getName()));
447                                setDaemon(true);
448                        }
449
450                        @Override
451                        public void run() {
452
453                                // do nothing when not running
454                                if (!running.get()) {
455                                        return;
456                                }
457
458                                repaintPanel();
459
460                                // loop when starting, to wait for images
461                                while (starting) {
462                                        try {
463                                                Thread.sleep(50);
464                                        } catch (InterruptedException e) {
465                                                throw new RuntimeException(e);
466                                        }
467                                }
468
469                                // schedule update when webcam is open, otherwise schedule
470                                // second scheduler execution
471
472                                try {
473
474                                        // FPS limit means that panel rendering frequency is
475                                        // limited to the specific value and panel will not be
476                                        // rendered more often then specific value
477
478                                        if (webcam.isOpen()) {
479
480                                                // TODO: rename FPS value in panel to rendering
481                                                // frequency
482
483                                                if (isFPSLimited()) {
484                                                        executor.scheduleAtFixedRate(updater, 0, (long) (1000 / frequency), TimeUnit.MILLISECONDS);
485                                                } else {
486                                                        executor.scheduleWithFixedDelay(updater, 100, 1, TimeUnit.MILLISECONDS);
487                                                }
488                                        } else {
489                                                executor.schedule(this, 500, TimeUnit.MILLISECONDS);
490                                        }
491                                } catch (RejectedExecutionException e) {
492
493                                        // executor has been shut down, which means that someone
494                                        // stopped panel / webcam device before it was actually
495                                        // completely started (it was in "starting" timeframe)
496
497                                        LOG.warn("Executor rejected paint update");
498                                        LOG.trace("Executor rejected paint update because of", e);
499
500                                        return;
501                                }
502                        }
503                }
504
505                /**
506                 * Update scheduler thread.
507                 */
508                private Thread scheduler = null;
509
510                /**
511                 * Is repainter running?
512                 */
513                private AtomicBoolean running = new AtomicBoolean(false);
514
515                /**
516                 * Start repainter. Can be invoked many times, but only first call will
517                 * take effect.
518                 */
519                public void start() {
520                        if (running.compareAndSet(false, true)) {
521                                executor = Executors.newScheduledThreadPool(1, THREAD_FACTORY);
522                                scheduler = new RepaintScheduler();
523                                scheduler.start();
524                        }
525                }
526
527                /**
528                 * Stop repainter. Can be invoked many times, but only first call will
529                 * take effect.
530                 * 
531                 * @throws InterruptedException
532                 */
533                public void stop() throws InterruptedException {
534                        if (running.compareAndSet(true, false)) {
535                                executor.shutdown();
536                                executor.awaitTermination(5000, TimeUnit.MILLISECONDS);
537                                scheduler.join();
538                        }
539                }
540
541                @Override
542                public void run() {
543                        try {
544                                update();
545                        } catch (Throwable t) {
546                                errored = true;
547                                WebcamExceptionHandler.handle(t);
548                        }
549                }
550
551                /**
552                 * Perform single panel area update (repaint newly obtained image).
553                 */
554                private void update() {
555
556                        // do nothing when updater not running, when webcam is closed, or
557                        // panel repainting is paused
558
559                        if (!running.get() || !webcam.isOpen() || paused) {
560                                return;
561                        }
562
563                        // get new image from webcam
564
565                        BufferedImage tmp = webcam.getImage();
566                        boolean repaint = true;
567
568                        if (tmp != null) {
569
570                                // ignore repaint if image is the same as before
571                                if (image == tmp) {
572                                        repaint = false;
573                                }
574
575                                errored = false;
576                                image = tmp;
577                        }
578
579                        if (repaint) {
580                                repaintPanel();
581                        }
582                }
583        }
584
585        /**
586         * Resource bundle.
587         */
588        private ResourceBundle rb = null;
589
590        /**
591         * The mode of how the image will be resized to fit into panel bounds.
592         * Default is {@link DrawMode#FIT}
593         * 
594         * @see DrawMode
595         */
596        private DrawMode drawMode = DrawMode.FIT;
597
598        /**
599         * Frames requesting frequency.
600         */
601        private double frequency = 5; // FPS
602
603        /**
604         * Is frames requesting frequency limited? If true, images will be fetched
605         * in configured time intervals. If false, images will be fetched as fast as
606         * camera can serve them.
607         */
608        private boolean frequencyLimit = false;
609
610        /**
611         * Display FPS.
612         */
613        private boolean frequencyDisplayed = false;
614
615        /**
616         * Display image size.
617         */
618        private boolean imageSizeDisplayed = false;
619
620        /**
621         * Is antialiasing enabled (true by default).
622         */
623        private boolean antialiasingEnabled = true;
624
625        /**
626         * Webcam object used to fetch images.
627         */
628        private final Webcam webcam;
629
630        /**
631         * Repainter is used to fetch images from camera and force panel repaint
632         * when image is ready.
633         */
634        private final ImageUpdater updater;
635
636        /**
637         * Image currently being displayed.
638         */
639        private BufferedImage image = null;
640
641        /**
642         * Webcam is currently starting.
643         */
644        private volatile boolean starting = false;
645
646        /**
647         * Painting is paused.
648         */
649        private volatile boolean paused = false;
650
651        /**
652         * Is there any problem with webcam?
653         */
654        private volatile boolean errored = false;
655
656        /**
657         * Webcam has been started.
658         */
659        private final AtomicBoolean started = new AtomicBoolean(false);
660
661        /**
662         * Default painter.
663         */
664        private final Painter defaultPainter = new DefaultPainter();
665
666        /**
667         * Painter used to draw image in panel.
668         * 
669         * @see #setPainter(Painter)
670         * @see #getPainter()
671         */
672        private Painter painter = defaultPainter;
673
674        /**
675         * Preferred panel size.
676         */
677        private Dimension defaultSize = null;
678
679        /**
680         * If debug info should be displayed.
681         */
682        private boolean displayDebugInfo = false;
683
684        /**
685         * Is image mirrored.
686         */
687        private boolean mirrored = false;
688
689        /**
690         * Creates webcam panel and automatically start webcam.
691         * 
692         * @param webcam the webcam to be used to fetch images
693         */
694        public WebcamPanel(Webcam webcam) {
695                this(webcam, true);
696        }
697
698        /**
699         * Creates new webcam panel which display image from camera in you your
700         * Swing application.
701         * 
702         * @param webcam the webcam to be used to fetch images
703         * @param start true if webcam shall be automatically started
704         */
705        public WebcamPanel(Webcam webcam, boolean start) {
706                this(webcam, null, start);
707        }
708
709        /**
710         * Creates new webcam panel which display image from camera in you your
711         * Swing application. If panel size argument is null, then image size will
712         * be used. If you would like to fill panel area with image even if its size
713         * is different, then you can use {@link WebcamPanel#setFillArea(boolean)}
714         * method to configure this.
715         * 
716         * @param webcam the webcam to be used to fetch images
717         * @param size the size of panel
718         * @param start true if webcam shall be automatically started
719         * @see WebcamPanel#setFillArea(boolean)
720         */
721        public WebcamPanel(Webcam webcam, Dimension size, boolean start) {
722
723                if (webcam == null) {
724                        throw new IllegalArgumentException(String.format("Webcam argument in %s constructor cannot be null!", getClass().getSimpleName()));
725                }
726
727                this.defaultSize = size;
728                this.webcam = webcam;
729                this.updater = new ImageUpdater();
730                this.rb = WebcamUtils.loadRB(WebcamPanel.class, getLocale());
731
732                setDoubleBuffered(true);
733
734                addPropertyChangeListener("locale", this);
735
736                if (size == null) {
737                        Dimension r = webcam.getViewSize();
738                        if (r == null) {
739                                r = webcam.getViewSizes()[0];
740                        }
741                        setPreferredSize(r);
742                } else {
743                        setPreferredSize(size);
744                }
745
746                if (start) {
747                        start();
748                }
749        }
750
751        /**
752         * Set new painter. Painter is a class which pains image visible when
753         * 
754         * @param painter the painter object to be set
755         */
756        public void setPainter(Painter painter) {
757                this.painter = painter;
758        }
759
760        /**
761         * Get painter used to draw image in webcam panel.
762         * 
763         * @return Painter object
764         */
765        public Painter getPainter() {
766                return painter;
767        }
768
769        @Override
770        protected void paintComponent(Graphics g) {
771                Graphics2D g2 = (Graphics2D) g;
772                if (image == null) {
773                        painter.paintPanel(this, g2);
774                } else {
775                        painter.paintImage(this, image, g2);
776                }
777        }
778
779        /**
780         * Open webcam and start rendering.
781         */
782        public void start() {
783
784                if (!started.compareAndSet(false, true)) {
785                        return;
786                }
787
788                webcam.addWebcamListener(this);
789
790                LOG.debug("Starting panel rendering and trying to open attached webcam");
791
792                updater.start();
793
794                starting = true;
795
796                try {
797                        if (!webcam.isOpen()) {
798                                errored = !webcam.open();
799                        }
800                } catch (WebcamException e) {
801                        errored = true;
802                        throw e;
803                } finally {
804                        starting = false;
805                        repaintPanel();
806                }
807        }
808
809        /**
810         * Stop rendering and close webcam.
811         */
812        public void stop() {
813
814                if (!started.compareAndSet(true, false)) {
815                        return;
816                }
817
818                webcam.removeWebcamListener(this);
819
820                LOG.debug("Stopping panel rendering and closing attached webcam");
821
822                try {
823                        updater.stop();
824                } catch (InterruptedException e) {
825                        throw new RuntimeException(e);
826                }
827
828                image = null;
829
830                try {
831                        if (webcam.isOpen()) {
832                                errored = !webcam.close();
833                        }
834                } catch (WebcamException e) {
835                        errored = true;
836                        throw e;
837                } finally {
838                        repaintPanel();
839                }
840        }
841
842        /**
843         * Repaint panel in Swing asynchronous manner.
844         */
845        private void repaintPanel() {
846                SwingUtilities.invokeLater(repaint);
847        }
848
849        /**
850         * Pause rendering.
851         */
852        public void pause() {
853                if (paused) {
854                        return;
855                }
856
857                LOG.debug("Pausing panel rendering");
858
859                paused = true;
860        }
861
862        /**
863         * Resume rendering.
864         */
865        public void resume() {
866
867                if (!paused) {
868                        return;
869                }
870
871                LOG.debug("Resuming panel rendering");
872
873                paused = false;
874        }
875
876        /**
877         * Is frequency limit enabled?
878         * 
879         * @return True or false
880         */
881        public boolean isFPSLimited() {
882                return frequencyLimit;
883        }
884
885        /**
886         * Enable or disable frequency limit. Frequency limit should be used for
887         * <b>all IP cameras working in pull mode</b> (to save number of HTTP
888         * requests). If true, images will be fetched in configured time intervals.
889         * If false, images will be fetched as fast as camera can serve them.
890         * 
891         * @param frequencyLimit
892         */
893        public void setFPSLimited(boolean frequencyLimit) {
894                this.frequencyLimit = frequencyLimit;
895        }
896
897        /**
898         * Get rendering frequency in FPS (equivalent to Hz).
899         * 
900         * @return Rendering frequency
901         */
902        public double getFPSLimit() {
903                return frequency;
904        }
905
906        /**
907         * Set rendering frequency (in Hz or FPS). Minimum frequency is 0.016 (1
908         * frame per minute) and maximum is 25 (25 frames per second).
909         * 
910         * @param fps the frequency
911         */
912        public void setFPSLimit(double fps) {
913                if (fps > MAX_FREQUENCY) {
914                        fps = MAX_FREQUENCY;
915                }
916                if (fps < MIN_FREQUENCY) {
917                        fps = MIN_FREQUENCY;
918                }
919                this.frequency = fps;
920        }
921
922        /**
923         * Is displaying of some debug information enabled.
924         * 
925         * @return True if debug information are enabled, false otherwise
926         */
927        public boolean isDisplayDebugInfo() {
928                return displayDebugInfo;
929        }
930
931        /**
932         * Display some debug information on image surface.
933         * 
934         * @param displayDebugInfo the value to control debug information
935         */
936        public void setDisplayDebugInfo(boolean displayDebugInfo) {
937                this.displayDebugInfo = displayDebugInfo;
938        }
939
940        /**
941         * This method return true in case if camera FPS is set to be displayed on
942         * panel surface. Default value returned is false.
943         * 
944         * @return True if camera FPS is set to be displayed on panel surface
945         * @see #setFPSDisplayed(boolean)
946         */
947        public boolean isFPSDisplayed() {
948                return frequencyDisplayed;
949        }
950
951        /**
952         * This method is to control if camera FPS should be displayed on the webcam
953         * panel surface.
954         * 
955         * @param displayed the value to control if camera FPS should be displayed
956         */
957        public void setFPSDisplayed(boolean displayed) {
958                this.frequencyDisplayed = displayed;
959        }
960
961        /**
962         * This method will return true in case when panel is configured to display
963         * image size. The string will be printed in the right bottom corner of the
964         * panel surface.
965         * 
966         * @return True in case if panel is configured to display image size
967         */
968        public boolean isImageSizeDisplayed() {
969                return imageSizeDisplayed;
970        }
971
972        /**
973         * Configure panel to display camera image size to be displayed.
974         * 
975         * @param imageSizeDisplayed
976         */
977        public void setImageSizeDisplayed(boolean imageSizeDisplayed) {
978                this.imageSizeDisplayed = imageSizeDisplayed;
979        }
980
981        /**
982         * Turn on/off antialiasing.
983         * 
984         * @param antialiasing the true to enable, false to disable antialiasing
985         */
986        public void setAntialiasingEnabled(boolean antialiasing) {
987                this.antialiasingEnabled = antialiasing;
988        }
989
990        /**
991         * @return True is antialiasing is enabled, false otherwise
992         */
993        public boolean isAntialiasingEnabled() {
994                return antialiasingEnabled;
995        }
996
997        /**
998         * Is webcam panel repainting starting.
999         * 
1000         * @return True if panel is starting
1001         */
1002        public boolean isStarting() {
1003                return starting;
1004        }
1005
1006        /**
1007         * Is webcam panel repainting started.
1008         * 
1009         * @return True if panel repainting has been started
1010         */
1011        public boolean isStarted() {
1012                return started.get();
1013        }
1014
1015        public boolean isFitArea() {
1016                return drawMode == DrawMode.FIT;
1017        }
1018
1019        /**
1020         * This method will change the mode of panel area painting so the image will
1021         * be resized and will keep scale factor to fit into drawable panel bounds.
1022         * When set to false, the mode will be reset to {@link DrawMode#NONE} so
1023         * image will be drawn as it is.
1024         * 
1025         * @param fitArea the fit area mode enabled or disabled
1026         */
1027        public void setFitArea(boolean fitArea) {
1028                this.drawMode = fitArea ? DrawMode.FIT : DrawMode.NONE;
1029        }
1030
1031        /**
1032         * Image will be resized to fill panel area if true. If false then image
1033         * will be rendered as it was obtained from webcam instance.
1034         * 
1035         * @param fillArea shall image be resided to fill panel area
1036         */
1037        public void setFillArea(boolean fillArea) {
1038                this.drawMode = fillArea ? DrawMode.FILL : DrawMode.NONE;
1039        }
1040
1041        /**
1042         * Get value of fill area setting. Image will be resized to fill panel area
1043         * if true. If false then image will be rendered as it was obtained from
1044         * webcam instance.
1045         * 
1046         * @return True if image is being resized, false otherwise
1047         */
1048        public boolean isFillArea() {
1049                return drawMode == DrawMode.FILL;
1050        }
1051
1052        /**
1053         * Get default painter used to draw panel.
1054         * 
1055         * @return Default painter
1056         */
1057        public Painter getDefaultPainter() {
1058                return defaultPainter;
1059        }
1060
1061        @Override
1062        public void propertyChange(PropertyChangeEvent evt) {
1063                Locale lc = (Locale) evt.getNewValue();
1064                if (lc != null) {
1065                        rb = WebcamUtils.loadRB(WebcamPanel.class, lc);
1066                }
1067        }
1068
1069        @Override
1070        public void webcamOpen(WebcamEvent we) {
1071
1072                // if default size has not been provided, then use the one from webcam
1073                // device (this will be current webcam resolution)
1074
1075                if (defaultSize == null) {
1076                        setPreferredSize(webcam.getViewSize());
1077                }
1078        }
1079
1080        @Override
1081        public void webcamClosed(WebcamEvent we) {
1082                stop();
1083        }
1084
1085        @Override
1086        public void webcamDisposed(WebcamEvent we) {
1087                stop();
1088        }
1089
1090        @Override
1091        public void webcamImageObtained(WebcamEvent we) {
1092                // do nothing
1093        }
1094
1095        /**
1096         * This method returns true if image mirroring is enabled. The default value
1097         * is false.
1098         * 
1099         * @return True if image is mirrored, false otherwise
1100         */
1101        public boolean isMirrored() {
1102                return mirrored;
1103        }
1104
1105        /**
1106         * Decide whether or not the image from webcam painted on panel surface will
1107         * be mirrored. The image from camera itself is not modified.
1108         * 
1109         * @param mirrored the parameter to control if image should be mirrored
1110         */
1111        public void setMirrored(boolean mirrored) {
1112                this.mirrored = mirrored;
1113        }
1114}