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}