diff --git a/src/main/java/io/swtc/proccessing/CameraPanel.java b/src/main/java/io/swtc/proccessing/CameraPanel.java index 6d64310..e27851b 100644 --- a/src/main/java/io/swtc/proccessing/CameraPanel.java +++ b/src/main/java/io/swtc/proccessing/CameraPanel.java @@ -3,17 +3,19 @@ package io.swtc.proccessing; import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; /** * Enhanced CameraPanel with support for custom image processors */ public class CameraPanel extends JPanel { - private BufferedImage currentImage; - private BufferedImage processedImage; - // Custom processor for advanced effects + private volatile BufferedImage sourceImage; + private volatile BufferedImage processedImage; + private Function imageProcessor; + private final AtomicBoolean repaintScheduled = new AtomicBoolean(false); public CameraPanel() { setBackground(Color.BLACK); @@ -21,78 +23,47 @@ public class CameraPanel extends JPanel { } public void setImage(BufferedImage img) { - this.currentImage = img; - processImage(); - repaint(); + sourceImage = img; + + if (repaintScheduled.compareAndSet(false, true)) { + SwingUtilities.invokeLater(() -> { + repaintScheduled.set(false); + repaint(); + }); + } } public void setImageProcessor(Function processor) { this.imageProcessor = processor; - processImage(); - repaint(); - } - - private void processImage() { - if (currentImage == null) { - processedImage = null; - return; - } - - BufferedImage result = currentImage; - - // Apply basic transforms first - result = applyBasicEffects(result); - - // Apply custom processor if set - if (imageProcessor != null) { - result = imageProcessor.apply(result); - } - - processedImage = result; - } - - private BufferedImage applyBasicEffects(BufferedImage img) { - int width = img.getWidth(); - int height = img.getHeight(); - BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - // Handle mirror/flip/rotate - int srcX = x; - int srcY = y; - - // Ensure coordinates are in bounds - srcX = Math.max(0, Math.min(width - 1, srcX)); - srcY = Math.max(0, Math.min(height - 1, srcY)); - - int rgb = img.getRGB(srcX, srcY); - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = rgb & 0xFF; - - result.setRGB(x, y, (r << 16) | (g << 8) | b); - } - } - - return result; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); - if (processedImage != null) { - Graphics2D g2d = (Graphics2D) g; + BufferedImage src = sourceImage; + if (src == null) return; - g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + BufferedImage img = src; - g2d.drawImage(processedImage, 0, 0, getWidth(), getHeight(), null); + if (imageProcessor != null) { + // we only apply the proccessor if it is present + // that is due too CPU Usage ; This project was built for a DUAL Core CPU, not a quad or even 6 Core CPU + img = imageProcessor.apply(src); } + + processedImage = img; + + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR + ); + + g2d.drawImage(img, 0, 0, getWidth(), getHeight(), null); } public BufferedImage getCurrentProcessedImage() { return processedImage; } - -} \ No newline at end of file +} diff --git a/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java b/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java index 434b2f4..10f0470 100644 --- a/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java +++ b/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java @@ -2,76 +2,84 @@ package io.swtc.proccessing; import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.WebcamException; +import com.github.sarxos.webcam.WebcamResolution; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; import javax.swing.*; +import io.swtc.proccessing.ui.ShowError; + public class WebcamCaptureLoop { private final Webcam webcam; private final Consumer onFrameCaptured; private volatile boolean running = false; private final AtomicBoolean cleanedUp = new AtomicBoolean(false); + private final int targetframes = 20; public WebcamCaptureLoop(Webcam webcam, Consumer onFrameCaptured) { this.webcam = webcam; this.onFrameCaptured = onFrameCaptured; - // this is some of the most stupid shit ive seen in years, this is needed for opening the stream. - // the webcam package may not know the res before its opened and well this is before we open it. that is below in the thread. - // so this freaks out and doesnt want to cooperate. - if (!webcam.getName().toLowerCase().contains("ipcam")) { - Dimension[] sizes = webcam.getViewSizes(); - webcam.setViewSize(sizes[sizes.length - 1]); + Dimension vga = WebcamResolution.VGA.getSize(); + if (isSizeSupported(webcam, vga)) { // we dont do stupid shit anymore! cause we are smarter than before... + webcam.setViewSize(vga); + } else { + Dimension[] dimensions = webcam.getViewSizes(); + webcam.setViewSize(dimensions[dimensions.length - 1]); } } + private boolean isSizeSupported(Webcam webcam, Dimension vga) { + for (Dimension d: webcam.getViewSizes()) { + if ( + d.width == vga.width && d.height == vga.height + ) return true; // this should return 640x480 :) + } + return false; + } + public void start() { if (running) return; running = true; Thread captureThread = new Thread(() -> { - // this is where we open it. if the res isnt known then it fucks up. try { - webcam.open(); - } catch (WebcamException e) { - JOptionPane.showMessageDialog( - null, - "WebcamException" + e.getMessage(), - "WebcamException", - JOptionPane.ERROR_MESSAGE - ); - } + if (!webcam.isOpen()) webcam.open(); // open if not - while (running) { - BufferedImage img = webcam.getImage(); - if (img != null) { - //System.err.println(img); - onFrameCaptured.accept(img); + long frameDurationLimitns = 1_000_000_000L / targetframes; // we use NanoSeconds cause its more accurate + + while (running) { + long startTime = System.nanoTime(); + + BufferedImage img = webcam.getImage(); + if (img != null) + // there is no need for a invoke later swing wise! + onFrameCaptured.accept(img); + + long timespent = System.nanoTime() - startTime; + long sleeptime = frameDurationLimitns - timespent; + + if (sleeptime > 0) { + LockSupport.parkNanos(sleeptime); + } else { + // do a yield to prevent ui freezes + Thread.yield(); + } } - - try { - Thread.sleep(15); - } catch (InterruptedException e) { - break; - } - } - try { - webcam.close(); - - } catch (IllegalStateException e) { - // show a error message from javax.swing.awt.JOptionPane - JOptionPane.showMessageDialog( + } catch (Exception e) { + ShowError.warning( null, - "IllegalStateException@"+e, - "IllegalStateException", - JOptionPane.ERROR_MESSAGE + "Exception" + e, + "Exception" ); + } finally { + cleanup(); } - }); - captureThread.setName("cam_cap_thread"); + }, "cam_cap_thread"); captureThread.start(); } @@ -111,6 +119,5 @@ public class WebcamCaptureLoop { */ public void stop() { running = false; - cleanup(); } } \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/ui/ShowError.java b/src/main/java/io/swtc/proccessing/ui/ShowError.java new file mode 100644 index 0000000..5efca5a --- /dev/null +++ b/src/main/java/io/swtc/proccessing/ui/ShowError.java @@ -0,0 +1,38 @@ +package io.swtc.proccessing.ui; + +import javax.swing.*; +import java.awt.*; + +public final class ShowError { + + private ShowError() { + // we dont instantiate cause it causes some errors + } + + public static void error(Component parent, String title, String message) { + JOptionPane.showMessageDialog( + parent, + message, + title, + JOptionPane.ERROR_MESSAGE + ); + } + + public static void warning(Component parent, String title, String message) { + JOptionPane.showMessageDialog( + parent, + message, + title, + JOptionPane.WARNING_MESSAGE + ); + } + + public static void info(Component parent, String title, String message) { + JOptionPane.showMessageDialog( + parent, + message, + title, + JOptionPane.INFORMATION_MESSAGE + ); + } +}