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