5 Commits

Author SHA1 Message Date
3eaf6f0303 fix
Signed-off-by: rattatwinko <seppmutterman@gmail.com>
2026-01-20 08:37:48 +01:00
11c5aa9115 Merge branch 'experimenting'
Signed-off-by: rattatwinko <seppmutterman@gmail.com>

# Conflicts:
#	src/main/java/io/swtc/Main.java
2026-01-20 08:28:10 +01:00
55474092e3 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>
2026-01-20 08:25:26 +01:00
b49cc8b2f0 refactors 2026-01-19 18:33:42 +01:00
41fbf62757 just some theme changes, i like this one more 2026-01-18 22:10:37 +01:00
6 changed files with 242 additions and 126 deletions

View File

@@ -1,26 +1,22 @@
package io.swtc;
import javax.swing.*;
import io.swtc.proccessing.ui.ShowError;
public class Main {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"LaF not available",
"LaF-Warning",
JOptionPane.WARNING_MESSAGE
);
for (int i = 0; i < args.length; i++) {
System.out.println("Arg " + i + ": " + args[i]);
}
// For some reason we need to invoke Later for LaF to work!
SwingUtilities.invokeLater(() -> SwingCCTVManager.main(null));
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
ShowError.warning(null,"LaF Warn","LaF");
}
SwingCCTVManager.main(null);
}
}

View File

@@ -1,9 +1,12 @@
package io.swtc;
import com.github.sarxos.webcam.Webcam;
import io.swtc.proccessing.ui.iframe.*;
import io.swtc.proccessing.ui.iframe.*; // Your custom frames
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
@@ -12,14 +15,28 @@ public class SwingIFrame {
private final DesktopPane desktopPane;
private final Map<JInternalFrame, EffectsPanelFrame> cameraToEffects = new HashMap<>();
private boolean fullscreen = false;
private Rectangle windowedBounds;
private boolean blackbg = false;
private final Color bgcolor = Color.decode("#336B6A");
private final Color defDesktopBg = Color.WHITE;
private final JPopupMenu popupMenu = new JPopupMenu();
public SwingIFrame() {
mainFrame = new JFrame("Viewer");
mainFrame.setSize(1280, 720);
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
desktopPane = new DesktopPane(cameraToEffects);
desktopPane.setBackground(Color.WHITE);
desktopPane.setBackground(defDesktopBg);
mainFrame.add(desktopPane, BorderLayout.CENTER);
setupFullscreenToggle();
setupBlackBg();
initPopupMenu();
desktopPane.addMouseListener(popupListener());
}
public void addCameraInternalFrame(Webcam webcam) {
@@ -43,11 +60,20 @@ public class SwingIFrame {
EffectsPanelFrame ef = cameraToEffects.remove(cameraFrame);
if (ef != null) ef.dispose();
desktopPane.forgetFrame(cameraFrame);
cameraFrame.dispose();
}
});
desktopPane.add(cameraFrame);
desktopPane.add(effectsFrame);
// Attach popup menu to frames and content
MouseAdapter popup = popupListener();
cameraFrame.addMouseListener(popup);
cameraFrame.getContentPane().addMouseListener(popup);
effectsFrame.addMouseListener(popup);
effectsFrame.getContentPane().addMouseListener(popup);
cameraFrame.setVisible(true);
}
@@ -58,12 +84,92 @@ public class SwingIFrame {
try {
effectsFrame.setSelected(true);
effectsFrame.toFront();
} catch (java.beans.PropertyVetoException ex) { JOptionPane.showMessageDialog(null,"Exception" + ex.getMessage() , "Exception", JOptionPane.ERROR_MESSAGE); }
} catch (java.beans.PropertyVetoException ex) {
JOptionPane.showMessageDialog(null, "Focus Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
}
private MouseAdapter popupListener() {
return new MouseAdapter() {
private void showPopup(MouseEvent e) {
if (e.isPopupTrigger()) { // cross-platform trigger
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
@Override public void mousePressed(MouseEvent e) { showPopup(e); }
@Override public void mouseReleased(MouseEvent e) { showPopup(e); }
};
}
private void initPopupMenu() {
popupMenu.removeAll(); // clean slate
JCheckBoxMenuItem fullscreenItem = new JCheckBoxMenuItem("Fullscreen");
fullscreenItem.addActionListener(e -> toggleFullscreen());
popupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent e) {
fullscreenItem.setState(fullscreen);
}
@Override public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent e) {}
@Override public void popupMenuCanceled(javax.swing.event.PopupMenuEvent e) {}
});
popupMenu.add(fullscreenItem);
}
private void setupBlackBg() {
JRootPane root = mainFrame.getRootPane();
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke("B"), "toggleBlackBg");
root.getActionMap().put("toggleBlackBg", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
toggleBackground();
}
});
}
private void setupFullscreenToggle() {
JRootPane root = mainFrame.getRootPane();
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke("F11"), "toggleFullscreen");
root.getActionMap().put("toggleFullscreen", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
toggleFullscreen();
}
});
}
private void toggleBackground() {
desktopPane.setBackground(blackbg ? defDesktopBg : bgcolor);
blackbg = !blackbg;
desktopPane.repaint();
}
/** Toggle fullscreen mode */
private void toggleFullscreen() {
if (!fullscreen) {
windowedBounds = mainFrame.getBounds();
mainFrame.dispose();
mainFrame.setUndecorated(true);
mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
mainFrame.setVisible(true);
} else {
mainFrame.dispose();
mainFrame.setUndecorated(false);
mainFrame.setExtendedState(JFrame.NORMAL);
mainFrame.setBounds(windowedBounds);
mainFrame.setVisible(true);
}
fullscreen = !fullscreen;
}
public void show() {
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
}
}
}

View File

@@ -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<BufferedImage, BufferedImage> 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<BufferedImage, BufferedImage> 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;
}
}
}

View File

@@ -2,78 +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<BufferedImage> onFrameCaptured;
private volatile boolean running = false;
private final AtomicBoolean cleanedUp = new AtomicBoolean(false);
private final int targetframes = 20;
public WebcamCaptureLoop(Webcam webcam, Consumer<BufferedImage> 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(
if (!webcam.isOpen()) webcam.open(); // open if not
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();
}
}
} catch (Exception e) {
ShowError.warning(
null,
"WebcamException" + e.getMessage(),
"WebcamException",
JOptionPane.ERROR_MESSAGE
"Exception" + e,
"Exception"
);
} finally {
webcam.open();
cleanup();
}
while (running) {
BufferedImage img = webcam.getImage();
if (img != null) {
//System.err.println(img);
onFrameCaptured.accept(img);
}
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(
null,
"IllegalStateException@"+e,
"IllegalStateException",
JOptionPane.ERROR_MESSAGE
);
}
});
captureThread.setName("cam_cap_thread");
}, "cam_cap_thread");
captureThread.start();
}
@@ -113,6 +119,5 @@ public class WebcamCaptureLoop {
*/
public void stop() {
running = false;
cleanup();
}
}

View 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
);
}
}

View File

@@ -56,11 +56,11 @@ public class DesktopPane extends JDesktopPane {
int ctrlOffset = Math.min(Math.abs(x2 - x1) / 2, 150);
CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x1 + ctrlOffset, y1, x2 - ctrlOffset, y2, x2, y2);
g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(curve);
//g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
//g2d.draw(curve);
// Terminals
g2d.fillOval(x1 - 5, y1 - 5, 10, 10);
g2d.fillOval(x2 - 5, y2 - 5, 10, 10);
//g2d.fillOval(x1 - 5, y1 - 5, 10, 10);
//g2d.fillOval(x2 - 5, y2 - 5, 10, 10);
}
}