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