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