some more optimizing!
now runs much better with lower power systems as we refactored code to firstly rule out double invokelaters, and we optimized the capture loop iteslf! and we introduced a ShowError class which will be refactored and used quite thoroughly! Signed-off-by: rattatwinko <seppmutterman@gmail.com>
This commit is contained in:
@@ -3,17 +3,19 @@ package io.swtc.proccessing;
|
|||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enhanced CameraPanel with support for custom image processors
|
* Enhanced CameraPanel with support for custom image processors
|
||||||
*/
|
*/
|
||||||
public class CameraPanel extends JPanel {
|
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<BufferedImage, BufferedImage> imageProcessor;
|
private Function<BufferedImage, BufferedImage> imageProcessor;
|
||||||
|
private final AtomicBoolean repaintScheduled = new AtomicBoolean(false);
|
||||||
|
|
||||||
public CameraPanel() {
|
public CameraPanel() {
|
||||||
setBackground(Color.BLACK);
|
setBackground(Color.BLACK);
|
||||||
@@ -21,78 +23,47 @@ public class CameraPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setImage(BufferedImage img) {
|
public void setImage(BufferedImage img) {
|
||||||
this.currentImage = img;
|
sourceImage = img;
|
||||||
processImage();
|
|
||||||
repaint();
|
if (repaintScheduled.compareAndSet(false, true)) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
repaintScheduled.set(false);
|
||||||
|
repaint();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImageProcessor(Function<BufferedImage, BufferedImage> processor) {
|
public void setImageProcessor(Function<BufferedImage, BufferedImage> processor) {
|
||||||
this.imageProcessor = 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
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
super.paintComponent(g);
|
super.paintComponent(g);
|
||||||
|
|
||||||
if (processedImage != null) {
|
BufferedImage src = sourceImage;
|
||||||
Graphics2D g2d = (Graphics2D) g;
|
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() {
|
public BufferedImage getCurrentProcessedImage() {
|
||||||
return processedImage;
|
return processedImage;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,76 +2,84 @@ package io.swtc.proccessing;
|
|||||||
|
|
||||||
import com.github.sarxos.webcam.Webcam;
|
import com.github.sarxos.webcam.Webcam;
|
||||||
import com.github.sarxos.webcam.WebcamException;
|
import com.github.sarxos.webcam.WebcamException;
|
||||||
|
import com.github.sarxos.webcam.WebcamResolution;
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import io.swtc.proccessing.ui.ShowError;
|
||||||
|
|
||||||
public class WebcamCaptureLoop {
|
public class WebcamCaptureLoop {
|
||||||
private final Webcam webcam;
|
private final Webcam webcam;
|
||||||
private final Consumer<BufferedImage> onFrameCaptured;
|
private final Consumer<BufferedImage> onFrameCaptured;
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
private final AtomicBoolean cleanedUp = new AtomicBoolean(false);
|
private final AtomicBoolean cleanedUp = new AtomicBoolean(false);
|
||||||
|
private final int targetframes = 20;
|
||||||
|
|
||||||
public WebcamCaptureLoop(Webcam webcam, Consumer<BufferedImage> onFrameCaptured) {
|
public WebcamCaptureLoop(Webcam webcam, Consumer<BufferedImage> onFrameCaptured) {
|
||||||
this.webcam = webcam;
|
this.webcam = webcam;
|
||||||
this.onFrameCaptured = onFrameCaptured;
|
this.onFrameCaptured = onFrameCaptured;
|
||||||
|
|
||||||
// this is some of the most stupid shit ive seen in years, this is needed for opening the stream.
|
Dimension vga = WebcamResolution.VGA.getSize();
|
||||||
// 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.
|
if (isSizeSupported(webcam, vga)) { // we dont do stupid shit anymore! cause we are smarter than before...
|
||||||
// so this freaks out and doesnt want to cooperate.
|
webcam.setViewSize(vga);
|
||||||
if (!webcam.getName().toLowerCase().contains("ipcam")) {
|
} else {
|
||||||
Dimension[] sizes = webcam.getViewSizes();
|
Dimension[] dimensions = webcam.getViewSizes();
|
||||||
webcam.setViewSize(sizes[sizes.length - 1]);
|
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() {
|
public void start() {
|
||||||
if (running) return;
|
if (running) return;
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
Thread captureThread = new Thread(() -> {
|
Thread captureThread = new Thread(() -> {
|
||||||
// this is where we open it. if the res isnt known then it fucks up.
|
|
||||||
try {
|
try {
|
||||||
webcam.open();
|
if (!webcam.isOpen()) webcam.open(); // open if not
|
||||||
} catch (WebcamException e) {
|
|
||||||
JOptionPane.showMessageDialog(
|
|
||||||
null,
|
|
||||||
"WebcamException" + e.getMessage(),
|
|
||||||
"WebcamException",
|
|
||||||
JOptionPane.ERROR_MESSAGE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (running) {
|
long frameDurationLimitns = 1_000_000_000L / targetframes; // we use NanoSeconds cause its more accurate
|
||||||
BufferedImage img = webcam.getImage();
|
|
||||||
if (img != null) {
|
while (running) {
|
||||||
//System.err.println(img);
|
long startTime = System.nanoTime();
|
||||||
onFrameCaptured.accept(img);
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
try {
|
ShowError.warning(
|
||||||
Thread.sleep(15);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
webcam.close();
|
|
||||||
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// show a error message from javax.swing.awt.JOptionPane
|
|
||||||
JOptionPane.showMessageDialog(
|
|
||||||
null,
|
null,
|
||||||
"IllegalStateException@"+e,
|
"Exception" + e,
|
||||||
"IllegalStateException",
|
"Exception"
|
||||||
JOptionPane.ERROR_MESSAGE
|
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
cleanup();
|
||||||
}
|
}
|
||||||
});
|
}, "cam_cap_thread");
|
||||||
captureThread.setName("cam_cap_thread");
|
|
||||||
captureThread.start();
|
captureThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +119,5 @@ public class WebcamCaptureLoop {
|
|||||||
*/
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
cleanup();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
38
src/main/java/io/swtc/proccessing/ui/ShowError.java
Normal file
38
src/main/java/io/swtc/proccessing/ui/ShowError.java
Normal file
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user