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}