package com.github.sarxos.webcam.addon.swt; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.awt.image.ComponentColorModel; import java.awt.image.WritableRaster; import java.util.ResourceBundle; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.WebcamEvent; import com.github.sarxos.webcam.WebcamException; import com.github.sarxos.webcam.WebcamExceptionHandler; import com.github.sarxos.webcam.WebcamListener; public class WebcamComposite extends Composite implements WebcamListener, PaintListener { private static final class CompositeThreadFactory implements ThreadFactory { private static final AtomicInteger number = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, String.format("webcam-composite-scheduled-executor-%d", number.incrementAndGet())); t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance()); t.setDaemon(true); return t; } } /** * Logger. */ private static final Logger LOG = LoggerFactory.getLogger(WebcamComposite.class); /** * Minimum FPS frequency. */ public static final double MIN_FREQUENCY = 0.016; // 1 frame per minute /** * Maximum FPS frequency. */ private static final double MAX_FREQUENCY = 50; // 50 frames per second /** * Thread factory used by execution service. */ private static final ThreadFactory THREAD_FACTORY = new CompositeThreadFactory(); /** * Scheduled executor acting as timer. */ private ScheduledExecutorService executor = null; /** * Image updater reads images from camera and force panel to be repainted. * * @author Bartosz Firyn (SarXos) */ private class ImageUpdater implements Runnable { /** * Repainter updates panel when it is being started. * * @author Bartosz Firyn (sarxos) */ private class RepaintScheduler extends Thread { public RepaintScheduler() { setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance()); setName(String.format("repaint-scheduler-%s", webcam.getName())); setDaemon(true); } @Override public void run() { if (!running.get()) { return; } Display.getDefault().syncExec(new Runnable() { @Override public void run() { redraw(); } }); while (starting) { try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } } if (webcam.isOpen()) { if (isFPSLimited()) { executor.scheduleAtFixedRate(updater, 0, (long) (1000 / frequency), TimeUnit.MILLISECONDS); } else { executor.scheduleWithFixedDelay(updater, 100, 1, TimeUnit.MILLISECONDS); } } else { executor.schedule(this, 500, TimeUnit.MILLISECONDS); } } } private Thread scheduler = new RepaintScheduler(); private AtomicBoolean running = new AtomicBoolean(false); private GC gc = null; public void start() { if (running.compareAndSet(false, true)) { executor = Executors.newScheduledThreadPool(1, THREAD_FACTORY); scheduler.start(); } } public void stop() { if (running.compareAndSet(true, false)) { executor.shutdown(); } } @Override public void run() { if (!running.get()) { return; } if (!webcam.isOpen()) { return; } if (paused) { return; } BufferedImage bi = null; try { bi = webcam.getImage(); } catch (Throwable t) { LOG.error("Exception when getting image", t); } if (bi == null) { LOG.debug("Image is null, ignore"); return; } ComponentColorModel model = (ComponentColorModel) bi.getColorModel(); PaletteData palette = new PaletteData(0x0000FF, 0x00FF00, 0xFF0000); ImageData data = new ImageData(bi.getWidth(), bi.getHeight(), model.getPixelSize(), palette); // this is valid because we are using a 3-byte data model without // transparent pixels data.transparentPixel = -1; WritableRaster raster = bi.getRaster(); int[] rgb = new int[3]; int x = 0; int y = 0; for (x = 0; x < data.width; x++) { for (y = 0; y < data.height; y++) { raster.getPixel(x, y, rgb); data.setPixel(x, y, palette.getPixel(new RGB(rgb[0], rgb[1], rgb[2]))); } } Image previous = image; try { image = new Image(Display.getDefault(), data); } finally { if (previous != null) { previous.dispose(); } } setBackgroundImage(image); Display.getDefault().syncExec(new Runnable() { @Override public void run() { if (image == null) { System.out.println("image is null"); return; } redraw(); } }); } } /** * Resource bundle. */ private ResourceBundle rb = null; /** * Fit image into panel area. */ private boolean fillArea = false; /** * Frames requesting frequency. */ private double frequency = 5; // FPS /** * Is frames requesting frequency limited? If true, images will be fetched * in configured time intervals. If false, images will be fetched as fast as * camera can serve them. */ private boolean frequencyLimit = false; /** * Display FPS. */ private boolean frequencyDisplayed = false; /** * Webcam object used to fetch images. */ private Webcam webcam = null; /** * Image currently being displayed. */ private Image image = null; /** * Repainter is used to fetch images from camera and force panel repaint * when image is ready. */ private volatile ImageUpdater updater = null; /** * Webcam is currently starting. */ private volatile boolean starting = false; /** * Painting is paused. */ private volatile boolean paused = false; /** * Is there any problem with webcam? */ private volatile boolean errored = false; /** * Webcam has been started. */ private AtomicBoolean started = new AtomicBoolean(false); // /** // * Painter used to draw image in panel. // * // * @see #setPainter(Painter) // * @see #getPainter() // */ // private Painter painter = new DefaultPainter(); /** * Preferred panel size. */ private Dimension size = null; public WebcamComposite(Composite parent, int style) { super(parent, style); setLayout(new FillLayout(SWT.HORIZONTAL)); setSize(640, 480); setVisible(true); setBackground(new Color(Display.getDefault(), new RGB(124, 23, 56))); } public void setWebcam(Webcam webcam) { if (webcam == null) { throw new IllegalArgumentException("Webcam cannot be null"); } this.webcam = webcam; } @Override public void dispose() { if (image != null) { image.dispose(); } super.dispose(); } public static void main(String[] args) { Display display = Display.getDefault(); Shell shell = new Shell(display); shell.setLayout(new FillLayout()); shell.setText("Test"); shell.setSize(640, 480); shell.setBackground(new Color(Display.getDefault(), new RGB(65, 120, 45))); WebcamComposite wc = new WebcamComposite(shell, SWT.EMBEDDED); wc.setWebcam(Webcam.getDefault()); wc.start(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } System.out.println("done"); wc.dispose(); display.dispose(); } @Override public void paintControl(PaintEvent e) { e.gc.drawImage(image, 0, 0); } @Override public void webcamOpen(WebcamEvent we) { // start image updater (i.e. start panel repainting) if (updater == null) { updater = new ImageUpdater(); updater.start(); } // copy size from webcam only if default size has not been provided if (size == null) { Dimension resolution = webcam.getViewSize(); setSize(resolution.width, resolution.height); } } @Override public void webcamClosed(WebcamEvent we) { stop(); } @Override public void webcamDisposed(WebcamEvent we) { webcamClosed(we); } @Override public void webcamImageObtained(WebcamEvent we) { // do nothing } /** * Open webcam and start rendering. */ public void start() { if (!started.compareAndSet(false, true)) { return; } LOG.debug("Starting panel rendering and trying to open attached webcam"); starting = true; if (updater == null) { updater = new ImageUpdater(); } updater.start(); try { errored = !webcam.open(); } catch (WebcamException e) { errored = true; Display.getDefault().syncExec(new Runnable() { @Override public void run() { redraw(); } }); throw e; } finally { starting = false; } } /** * Stop rendering and close webcam. */ public void stop() { if (!started.compareAndSet(true, false)) { return; } LOG.debug("Stopping panel rendering and closing attached webcam"); updater.stop(); updater = null; image = null; try { errored = !webcam.close(); } catch (WebcamException e) { errored = true; Display.getDefault().syncExec(new Runnable() { @Override public void run() { redraw(); } }); throw e; } } /** * Pause rendering. */ public void pause() { if (paused) { return; } LOG.debug("Pausing panel rendering"); paused = true; } /** * Resume rendering. */ public void resume() { if (!paused) { return; } LOG.debug("Resuming panel rendering"); paused = false; } /** * Is frequency limit enabled? * * @return True or false */ public boolean isFPSLimited() { return frequencyLimit; } /** * Enable or disable frequency limit. Frequency limit should be used for * <b>all IP cameras working in pull mode</b> (to save number of HTTP * requests). If true, images will be fetched in configured time intervals. * If false, images will be fetched as fast as camera can serve them. * * @param frequencyLimit */ public void setFPSLimited(boolean frequencyLimit) { this.frequencyLimit = frequencyLimit; } /** * Get rendering frequency in FPS (equivalent to Hz). * * @return Rendering frequency */ public double getFPS() { return frequency; } /** * Set rendering frequency (in Hz or FPS). Minimum frequency is 0.016 (1 * frame per minute) and maximum is 25 (25 frames per second). * * @param frequency the frequency */ public void setFPS(double frequency) { if (frequency > MAX_FREQUENCY) { frequency = MAX_FREQUENCY; } if (frequency < MIN_FREQUENCY) { frequency = MIN_FREQUENCY; } this.frequency = frequency; } public boolean isFPSDisplayed() { return frequencyDisplayed; } public void setFPSDisplayed(boolean displayed) { this.frequencyDisplayed = displayed; } /** * Is webcam panel repainting starting. * * @return True if panel is starting */ public boolean isStarting() { return starting; } /** * Is webcam panel repainting started. * * @return True if panel repainting has been started */ public boolean isStarted() { return started.get(); } /** * Image will be resized to fill panel area if true. If false then image * will be rendered as it was obtained from webcam instance. * * @param fillArea shall image be resided to fill panel area */ public void setFillArea(boolean fillArea) { this.fillArea = fillArea; } /** * Get value of fill area setting. Image will be resized to fill panel area * if true. If false then image will be rendered as it was obtained from * webcam instance. * * @return True if image is being resized, false otherwise */ public boolean isFillArea() { return fillArea; } }