From c393e05bb182e34ab2edccecb9e753f2e7a096f9 Mon Sep 17 00:00:00 2001
From: rattatwinko
Date: Sun, 18 Jan 2026 21:00:24 +0100
Subject: [PATCH 1/5] testing some new stuff!
---
pom.xml | 1 +
src/main/java/io/swtc/Main.java | 18 +
src/main/java/io/swtc/SwingCameraWindow.java | 1 +
src/main/java/io/swtc/SwingIFrame.java | 427 +++++++-----------
.../swtc/proccessing/AutoGainProcessor.java | 21 +-
.../java/io/swtc/proccessing/CameraPanel.java | 292 ++++++------
.../swtc/proccessing/ConnectionOverlay.java | 81 ++++
.../java/io/swtc/proccessing/EffectState.java | 25 +
.../java/io/swtc/proccessing/FilterPanel.java | 273 +++++++++++
.../swtc/proccessing/ImageEffectEngine.java | 147 ++++++
.../io/swtc/proccessing/PresetLibrary.java | 46 ++
11 files changed, 917 insertions(+), 415 deletions(-)
create mode 100644 src/main/java/io/swtc/proccessing/ConnectionOverlay.java
create mode 100644 src/main/java/io/swtc/proccessing/EffectState.java
create mode 100644 src/main/java/io/swtc/proccessing/FilterPanel.java
create mode 100644 src/main/java/io/swtc/proccessing/ImageEffectEngine.java
create mode 100644 src/main/java/io/swtc/proccessing/PresetLibrary.java
diff --git a/pom.xml b/pom.xml
index 16698d9..5e4fbc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -122,5 +122,6 @@
jcodec-javase
0.2.5
+
\ No newline at end of file
diff --git a/src/main/java/io/swtc/Main.java b/src/main/java/io/swtc/Main.java
index 80cd92f..a5b5d3e 100644
--- a/src/main/java/io/swtc/Main.java
+++ b/src/main/java/io/swtc/Main.java
@@ -1,11 +1,29 @@
package io.swtc;
+import javax.swing.*;
+
public class Main {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("Arg " + i + ": " + args[i]);
}
+
+ try {
+ //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
+ UIManager.setLookAndFeel(
+ "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
+ );
+ } catch (Exception e) {
+ JOptionPane.showMessageDialog(
+ null,
+ "Method not supported, Application will run anyways but might look different, and feel different!",
+ "SWTCCTVWarning",
+ JOptionPane.WARNING_MESSAGE
+ );
+ }
+
+
SwingCCTVManager.main(null);
}
}
diff --git a/src/main/java/io/swtc/SwingCameraWindow.java b/src/main/java/io/swtc/SwingCameraWindow.java
index de3af66..0ac69fc 100644
--- a/src/main/java/io/swtc/SwingCameraWindow.java
+++ b/src/main/java/io/swtc/SwingCameraWindow.java
@@ -9,6 +9,7 @@ import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
+@Deprecated
public class SwingCameraWindow {
private final JFrame frame;
private final CameraPanel cameraPanel;
diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java
index f2f76b2..1b4416b 100644
--- a/src/main/java/io/swtc/SwingIFrame.java
+++ b/src/main/java/io/swtc/SwingIFrame.java
@@ -3,44 +3,45 @@ package io.swtc;
import com.github.sarxos.webcam.Webcam;
import io.swtc.proccessing.WebcamCaptureLoop;
import io.swtc.proccessing.CameraPanel;
+import io.swtc.proccessing.FilterPanel;
import io.swtc.recording.VideoRecorder;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
+import java.awt.geom.CubicCurve2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.text.SimpleDateFormat;
-import java.util.Date;
-
-/*
-*
-* This file is basically just UI, its boring the interesting stuff is in the utilities section!
-*
-* */
+import java.util.*;
+/**
+ * Blender-style UI where the Effects panel is triggered by a Tab click
+ */
public class SwingIFrame {
private final JFrame mainFrame;
- private final JDesktopPane desktopPane;
+ private final BlenderDesktopPane desktopPane;
private boolean fullscreen = false;
private Rectangle windowedBounds;
private boolean blackbg = false;
private final Color defDesktopBg;
private final Color bgcolor;
- public SwingIFrame() {
- mainFrame = new JFrame("viewer");
- mainFrame.setSize(1280,720);
- mainFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ private final Map cameraToEffects = new HashMap<>();
+
+ public SwingIFrame() {
+ mainFrame = new JFrame("Viewer");
+ mainFrame.setSize(1280, 720);
+ mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- // this is good on the eyes for long periods of times,
- // i would have used #4B9EA0, which is also easy on the eyes
bgcolor = Color.decode("#1e1e1e");
- desktopPane = new JDesktopPane();
+ desktopPane = new BlenderDesktopPane(cameraToEffects);
desktopPane.setBackground(Color.WHITE);
defDesktopBg = desktopPane.getBackground();
mainFrame.add(desktopPane, BorderLayout.CENTER);
@@ -50,7 +51,7 @@ public class SwingIFrame {
}
public void addCameraInternalFrame(Webcam webcam) {
- JInternalFrame iframe = new JInternalFrame(
+ JInternalFrame cameraFrame = new JInternalFrame(
webcam.getName(),
true, true, true, true
);
@@ -61,326 +62,152 @@ public class SwingIFrame {
);
JPanel contentPanel = new JPanel(new BorderLayout());
-
JTabbedPane tabbedPane = new JTabbedPane();
+
tabbedPane.addTab("View", cameraPanel);
tabbedPane.addTab("Capture", createCapturePanel(cameraPanel, webcam));
- tabbedPane.addTab("Effects", createEffectsPanel(cameraPanel));
+ // Add a placeholder tab that acts as a button
+ tabbedPane.addTab("Open Effect Panel", new JPanel());
contentPanel.add(tabbedPane, BorderLayout.CENTER);
- iframe.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
+ // Logic to open/show the Effects Frame when the tab is clicked
+ tabbedPane.addChangeListener(new ChangeListener() {
@Override
- public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) {
- // if we dont call this the camera stays open until the procces dies.
- captureLoop.stop();
+ public void stateChanged(ChangeEvent e) {
+ if (tabbedPane.getSelectedIndex() == 2) { // "Effects +" tab index
+ // Revert selection back to "View" so the tab doesn't stay stuck on the empty panel
+ tabbedPane.setSelectedIndex(0);
+
+ EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame);
+ if (effectsFrame != null) {
+ effectsFrame.setVisible(true);
+ try {
+ effectsFrame.setSelected(true);
+ } catch (java.beans.PropertyVetoException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
}
});
- iframe.add(contentPanel);
- iframe.setSize(600, 500);
+ cameraFrame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
+ @Override
+ public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) {
+ captureLoop.stop();
+ EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame);
+ if (effectsFrame != null) {
+ effectsFrame.dispose();
+ cameraToEffects.remove(cameraFrame);
+ }
+ }
+ });
+
+ cameraFrame.add(contentPanel);
+ cameraFrame.setSize(600, 500);
int offset = desktopPane.getAllFrames().length * 30;
- iframe.setLocation(offset, offset);
+ cameraFrame.setLocation(50 + offset, 50 + offset);
- desktopPane.add(iframe);
- iframe.setVisible(true);
+ // Pre-create the effects frame but keep it hidden
+ EffectsPanelFrame effectsFrame = new EffectsPanelFrame(
+ "Effects - " + webcam.getName(),
+ cameraPanel,
+ cameraFrame
+ );
+ effectsFrame.setSize(350, 600);
+ effectsFrame.setLocation(700 + offset, 50 + offset);
+ effectsFrame.setVisible(false); // Hidden by default
+
+ cameraToEffects.put(cameraFrame, effectsFrame);
+ addLinkageListeners(cameraFrame, effectsFrame);
+
+ desktopPane.add(cameraFrame);
+ desktopPane.add(effectsFrame);
+
+ cameraFrame.setVisible(true);
captureLoop.start();
}
+ private void addLinkageListeners(JInternalFrame cameraFrame, EffectsPanelFrame effectsFrame) {
+ java.awt.event.ComponentAdapter linker = new java.awt.event.ComponentAdapter() {
+ @Override
+ public void componentMoved(java.awt.event.ComponentEvent e) {
+ desktopPane.repaint();
+ }
+ @Override
+ public void componentResized(java.awt.event.ComponentEvent e) {
+ desktopPane.repaint();
+ }
+ };
+ cameraFrame.addComponentListener(linker);
+ effectsFrame.addComponentListener(linker);
+ }
+
+ // --- Standard UI Setup Methods ---
+
private void setupBlackBg() {
JRootPane root = mainFrame.getRootPane();
-
- root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
- .put(KeyStroke.getKeyStroke("B"), "toggleBlackBg");
-
+ root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("B"), "toggleBlackBg");
root.getActionMap().put("toggleBlackBg", new AbstractAction() {
- @Override
- public void actionPerformed(ActionEvent e) {
- setbgblack();
- }
+ @Override public void actionPerformed(ActionEvent e) { setbgblack(); }
});
}
- /* Setup F11 for Fullscreen */
+
private void setupFullscreenToggle() {
JRootPane root = mainFrame.getRootPane();
-
- root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
- .put(KeyStroke.getKeyStroke("F11"), "toggleFullscreen");
-
+ 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();
- }
+ @Override public void actionPerformed(ActionEvent e) { toggleFullscreen(); }
});
}
private void setbgblack() {
if (!blackbg) {
- // easy stuff here, just setting the bg to the private color
desktopPane.setBackground(bgcolor);
- updateInternalFrameBg(bgcolor);
} else {
desktopPane.setBackground(defDesktopBg);
- updateInternalFrameBg(null);
}
blackbg = !blackbg;
}
- private void updateInternalFrameBg(Color bg) {
- for (JInternalFrame frame : desktopPane.getAllFrames()) {
- Container content = frame.getContentPane();
- if (bg != null) {
- content.setBackground(bg);
- } else content.setBackground(null); // restore default
- content.repaint();
- }
- }
-
private void toggleFullscreen() {
if (!fullscreen) {
- // We set the window to borderless windowed mode, so it doesnt
- // lag like shit when we drag windows around, which is annoying
windowedBounds = mainFrame.getBounds();
mainFrame.dispose();
mainFrame.setUndecorated(true);
mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
mainFrame.setVisible(true);
} else {
- // do the opposite
mainFrame.dispose();
mainFrame.setUndecorated(false);
mainFrame.setExtendedState(JFrame.NORMAL);
mainFrame.setBounds(windowedBounds);
mainFrame.setVisible(true);
}
-
fullscreen = !fullscreen;
}
-
-
private JPanel createCapturePanel(CameraPanel cameraPanel, Webcam webcam) {
JPanel panel = new JPanel(new BorderLayout(10, 10));
panel.setBorder(new EmptyBorder(15, 15, 15, 15));
- JPanel mainPanel = new JPanel();
- mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
-
- // Screenshot section
- JPanel screenshotPanel = new JPanel();
- screenshotPanel.setLayout(new BoxLayout(screenshotPanel, BoxLayout.Y_AXIS));
- screenshotPanel.setBorder(BorderFactory.createTitledBorder(
- BorderFactory.createEtchedBorder(), "Screenshot",
- TitledBorder.LEFT, TitledBorder.TOP));
-
- JPanel screenshotPathPanel = new JPanel(new BorderLayout(5, 5));
- JTextField screenshotPath = new JTextField(System.getProperty("user.home") + File.separator + "screenshots");
- JButton browseSS = new JButton("...");
- browseSS.setPreferredSize(new Dimension(40, 25));
- browseSS.addActionListener(e -> browseDirectory(screenshotPath, panel));
- screenshotPathPanel.add(screenshotPath, BorderLayout.CENTER);
- screenshotPathPanel.add(browseSS, BorderLayout.EAST);
-
- JButton takeScreenshot = new JButton("Take Screenshot (S)");
- takeScreenshot.setAlignmentX(Component.CENTER_ALIGNMENT);
- takeScreenshot.addActionListener(e -> saveSnapshot(cameraPanel, webcam, screenshotPath.getText(), panel));
-
- screenshotPanel.add(screenshotPathPanel);
- screenshotPanel.add(Box.createRigidArea(new Dimension(0, 10)));
- screenshotPanel.add(takeScreenshot);
-
- // Recording section
- JPanel recordPanel = new JPanel();
- recordPanel.setLayout(new BoxLayout(recordPanel, BoxLayout.Y_AXIS));
- recordPanel.setBorder(BorderFactory.createTitledBorder(
- BorderFactory.createEtchedBorder(), "Recording",
- TitledBorder.LEFT, TitledBorder.TOP));
-
- JPanel recordPathPanel = new JPanel(new BorderLayout(5, 5));
- JTextField recordPath = new JTextField(System.getProperty("user.home") + File.separator + "recordings");
- JButton browseRec = new JButton("...");
- browseRec.setPreferredSize(new Dimension(40, 25));
- browseRec.addActionListener(e -> browseDirectory(recordPath, panel));
- recordPathPanel.add(recordPath, BorderLayout.CENTER);
- recordPathPanel.add(browseRec, BorderLayout.EAST);
-
- VideoRecorder recorder = new VideoRecorder();
- JButton recordButton = new JButton("Start Recording (R)");
- JLabel recordingStatus = new JLabel("Ready");
- recordingStatus.setAlignmentX(Component.CENTER_ALIGNMENT);
-
- recordButton.setAlignmentX(Component.CENTER_ALIGNMENT);
- recordButton.addActionListener(e -> {
- if (!recorder.isRecording()) {
- String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
- String filename = "recording_" + webcam.getName().replaceAll("[^a-zA-Z0-9]", "_")
- + "_" + timestamp + ".mp4";
- File dir = new File(recordPath.getText());
- dir.mkdirs();
-
- try {
- recorder.startRecording(cameraPanel, new File(dir, filename));
- recordButton.setText("Stop Recording");
- recordingStatus.setText("Recording...");
- recordingStatus.setForeground(Color.RED);
- } catch (Exception ex) {
- JOptionPane.showMessageDialog(panel,
- "Error starting recording: " + ex.getMessage(),
- "Error", JOptionPane.ERROR_MESSAGE);
- }
- } else {
- try {
- File saved = recorder.stopRecording();
- recordButton.setText("Start Recording (R)");
- recordingStatus.setText("Saved: " + saved.getName());
- recordingStatus.setForeground(Color.BLACK);
- } catch (Exception ex) {
- recordButton.setText("Start Recording (R)");
- recordingStatus.setText("Error saving");
- recordingStatus.setForeground(Color.RED);
- JOptionPane.showMessageDialog(panel,
- "Error saving recording: " + ex.getMessage(),
- "Error", JOptionPane.ERROR_MESSAGE);
- }
- }
- });
-
- recordPanel.add(recordPathPanel);
- recordPanel.add(Box.createRigidArea(new Dimension(0, 10)));
- recordPanel.add(recordButton);
- recordPanel.add(Box.createRigidArea(new Dimension(0, 5)));
- recordPanel.add(recordingStatus);
-
- mainPanel.add(screenshotPanel);
- mainPanel.add(Box.createRigidArea(new Dimension(0, 15)));
- mainPanel.add(recordPanel);
- mainPanel.add(Box.createVerticalGlue());
-
- panel.add(mainPanel, BorderLayout.NORTH);
+ JButton takeScreenshot = new JButton("Take Screenshot");
+ takeScreenshot.addActionListener(e -> saveSnapshot(cameraPanel, webcam, System.getProperty("user.home"), panel));
+ panel.add(takeScreenshot, BorderLayout.NORTH);
return panel;
}
- private JPanel createEffectsPanel(CameraPanel cameraPanel) {
- JPanel panel = new JPanel();
- panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
- panel.setBorder(new EmptyBorder(15, 15, 15, 15));
-
- JPanel transformPanel = new JPanel(new GridLayout(0, 1, 5, 5));
- transformPanel.setBorder(BorderFactory.createTitledBorder(
- BorderFactory.createEtchedBorder(), "Transform",
- TitledBorder.LEFT, TitledBorder.TOP));
-
- JCheckBox mirrorCheck = new JCheckBox("Mirror Horizontal");
- JCheckBox flipCheck = new JCheckBox("Flip Vertical");
- JCheckBox rotateCheck = new JCheckBox("Rotate 180°");
-
- mirrorCheck.addActionListener(e -> cameraPanel.setMirror(mirrorCheck.isSelected()));
- flipCheck.addActionListener(e -> cameraPanel.setFlip(flipCheck.isSelected()));
- rotateCheck.addActionListener(e -> cameraPanel.setRotate(rotateCheck.isSelected()));
-
- transformPanel.add(mirrorCheck);
- transformPanel.add(flipCheck);
- transformPanel.add(rotateCheck);
-
- JPanel colorPanel = new JPanel();
- colorPanel.setLayout(new BoxLayout(colorPanel, BoxLayout.Y_AXIS));
- colorPanel.setBorder(BorderFactory.createTitledBorder(
- BorderFactory.createEtchedBorder(), "Color",
- TitledBorder.LEFT, TitledBorder.TOP));
-
- JCheckBox grayscaleCheck = new JCheckBox("Grayscale");
- JCheckBox invertCheck = new JCheckBox("Invert Colors");
-
- grayscaleCheck.addActionListener(e -> cameraPanel.setGrayscale(grayscaleCheck.isSelected()));
- invertCheck.addActionListener(e -> cameraPanel.setInvert(invertCheck.isSelected()));
-
- colorPanel.add(grayscaleCheck);
- colorPanel.add(Box.createRigidArea(new Dimension(0, 5)));
- colorPanel.add(invertCheck);
-
- JPanel brightnessPanel = new JPanel();
- brightnessPanel.setLayout(new BoxLayout(brightnessPanel, BoxLayout.Y_AXIS));
- brightnessPanel.setBorder(BorderFactory.createTitledBorder(
- BorderFactory.createEtchedBorder(), "Adjustments",
- TitledBorder.LEFT, TitledBorder.TOP));
-
- JPanel brightnessSliderPanel = new JPanel(new BorderLayout());
- JLabel brightnessLabel = new JLabel("Brightness: 0");
- JSlider brightnessSlider = new JSlider(-100, 100, 0);
- brightnessSlider.addChangeListener(e -> {
- int value = brightnessSlider.getValue();
- brightnessLabel.setText("Brightness: " + value);
- cameraPanel.setBrightness(value);
- });
- brightnessSliderPanel.add(brightnessLabel, BorderLayout.NORTH);
- brightnessSliderPanel.add(brightnessSlider, BorderLayout.CENTER);
-
- JPanel contrastSliderPanel = new JPanel(new BorderLayout());
- JLabel contrastLabel = new JLabel("Contrast: 1.0");
- JSlider contrastSlider = new JSlider(0, 200, 100);
- contrastSlider.addChangeListener(e -> {
- float value = contrastSlider.getValue() / 100f;
- contrastLabel.setText("Contrast: " + String.format("%.1f", value));
- cameraPanel.setContrast(value);
- });
- contrastSliderPanel.add(contrastLabel, BorderLayout.NORTH);
- contrastSliderPanel.add(contrastSlider, BorderLayout.CENTER);
-
- brightnessPanel.add(brightnessSliderPanel);
- brightnessPanel.add(Box.createRigidArea(new Dimension(0, 10)));
- brightnessPanel.add(contrastSliderPanel);
-
- JButton resetButton = new JButton("Reset All Effects");
- resetButton.setAlignmentX(Component.CENTER_ALIGNMENT);
- resetButton.addActionListener(e -> {
- mirrorCheck.setSelected(false);
- flipCheck.setSelected(false);
- rotateCheck.setSelected(false);
- grayscaleCheck.setSelected(false);
- invertCheck.setSelected(false);
- brightnessSlider.setValue(0);
- contrastSlider.setValue(100);
- cameraPanel.resetEffects();
- });
-
- panel.add(transformPanel);
- panel.add(Box.createRigidArea(new Dimension(0, 10)));
- panel.add(colorPanel);
- panel.add(Box.createRigidArea(new Dimension(0, 10)));
- panel.add(brightnessPanel);
- panel.add(Box.createRigidArea(new Dimension(0, 15)));
- panel.add(resetButton);
- panel.add(Box.createVerticalGlue());
-
- return panel;
- }
-
- private void browseDirectory(JTextField field, Component parent) {
- JFileChooser chooser = new JFileChooser(field.getText());
- chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
- if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
- field.setText(chooser.getSelectedFile().getAbsolutePath());
- }
- }
-
private void saveSnapshot(CameraPanel cameraPanel, Webcam webcam, String directory, Component parent) {
BufferedImage img = cameraPanel.getCurrentProcessedImage();
if (img != null) {
try {
- File dir = new File(directory);
- dir.mkdirs();
-
- String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
- String filename = "screenshot_" + webcam.getName().replaceAll("[^a-zA-Z0-9]", "_")
- + "_" + timestamp + ".png";
- File file = new File(dir, filename);
+ File file = new File(directory, "snap.png");
ImageIO.write(img, "PNG", file);
} catch (Exception ex) {
- JOptionPane.showMessageDialog(parent,
- "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ ex.printStackTrace();
}
}
}
@@ -393,7 +220,69 @@ public class SwingIFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SwingIFrame dashboard = new SwingIFrame();
+ // Example usage: dashboard.addCameraInternalFrame(Webcam.getDefault());
dashboard.show();
});
}
+
+ // --- Inner Classes for Blender Styling ---
+
+ static class BlenderDesktopPane extends JDesktopPane {
+ private final Map connections;
+
+ public BlenderDesktopPane(Map connections) {
+ this.connections = connections;
+ }
+
+ @Override
+ protected void paintChildren(Graphics g) {
+ super.paintChildren(g);
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ for (Map.Entry entry : connections.entrySet()) {
+ JInternalFrame camera = entry.getKey();
+ EffectsPanelFrame effects = entry.getValue();
+
+ if (camera.isVisible() && effects.isVisible() && !camera.isIcon() && !effects.isIcon()) {
+ drawBezierConnection(g2d, camera, effects);
+ }
+ }
+ g2d.dispose();
+ }
+
+ private void drawBezierConnection(Graphics2D g2d, JInternalFrame from, JInternalFrame to) {
+ Rectangle f = from.getBounds();
+ Rectangle t = to.getBounds();
+
+ int x1 = f.x + f.width;
+ int y1 = f.y + f.height / 2;
+ int x2 = t.x;
+ int y2 = t.y + t.height / 2;
+
+ 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.setColor(new Color(100, 200, 255, 200));
+ g2d.draw(curve);
+
+ g2d.fillOval(x1 - 5, y1 - 5, 10, 10);
+ g2d.fillOval(x2 - 5, y2 - 5, 10, 10);
+ }
+ }
+
+ static class EffectsPanelFrame extends JInternalFrame {
+ public EffectsPanelFrame(String title, CameraPanel cameraPanel, JInternalFrame cameraFrame) {
+ super(title, true, true, true, true);
+ add(new FilterPanel(cameraPanel));
+
+ // Repaint curves when moving or iconifying
+ addComponentListener(new java.awt.event.ComponentAdapter() {
+ @Override public void componentMoved(java.awt.event.ComponentEvent e) {
+ if(getDesktopPane() != null) getDesktopPane().repaint();
+ }
+ });
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java b/src/main/java/io/swtc/proccessing/AutoGainProcessor.java
index 7138dcd..473ba4c 100644
--- a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java
+++ b/src/main/java/io/swtc/proccessing/AutoGainProcessor.java
@@ -1,12 +1,21 @@
package io.swtc.proccessing;
-/*
-*
-* Soon to be deprecated!
-*
-* */
+/**
+ * Gray World Algorithm.
+ *
+ *
+ * This class is an implementation of the Gray World algorithm, an automatic
+ * white balancing method.
+ *
+ *
+ *
+ * See:
+ *
+ * Stanford explanation
+ *
+ *
+ */
-@Deprecated
public class AutoGainProcessor {
public float[] calculateAutoGains(int[] pixels) {
diff --git a/src/main/java/io/swtc/proccessing/CameraPanel.java b/src/main/java/io/swtc/proccessing/CameraPanel.java
index fda37a7..f0d5620 100644
--- a/src/main/java/io/swtc/proccessing/CameraPanel.java
+++ b/src/main/java/io/swtc/proccessing/CameraPanel.java
@@ -3,16 +3,16 @@ package io.swtc.proccessing;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
+import java.util.function.Function;
-/*
-*
-* Now here its getting rather interesting! this class processes some
-* important stuff!
-*
-* */
-
+/**
+ * Enhanced CameraPanel with support for custom image processors
+ */
public class CameraPanel extends JPanel {
private BufferedImage currentImage;
+ private BufferedImage processedImage;
+
+ // Basic transform effects
private boolean mirror = false;
private boolean flip = false;
private boolean rotate = false;
@@ -21,150 +21,100 @@ public class CameraPanel extends JPanel {
private int brightness = 0;
private float contrast = 1.0f;
+ // Custom processor for advanced effects
+ private Function imageProcessor;
+
+ public CameraPanel() {
+ setBackground(Color.BLACK);
+ setPreferredSize(new Dimension(640, 480));
+ }
+
public void setImage(BufferedImage img) {
this.currentImage = img;
- this.repaint();
+ processImage();
+ repaint();
}
- public BufferedImage getCurrentImage() {
- return currentImage;
+ public void setImageProcessor(Function processor) {
+ this.imageProcessor = processor;
+ processImage();
+ repaint();
}
- public BufferedImage getCurrentProcessedImage() {
+ private void processImage() {
if (currentImage == null) {
- return null;
+ processedImage = null;
+ return;
}
- BufferedImage processed = currentImage;
+ BufferedImage result = currentImage;
- // apply color effects
- if (grayscale || invert || brightness != 0 || contrast != 1.0f) {
- processed = applyColorEffects(processed);
+ // Apply basic transforms first
+ result = applyBasicEffects(result);
+
+ // Apply custom processor if set
+ if (imageProcessor != null) {
+ result = imageProcessor.apply(result);
}
- // apply transform.
- if (mirror || flip || rotate) {
- processed = applyTransforms(processed);
- }
-
- return processed;
+ processedImage = result;
}
- /* Helper Methods ... its the same boilerplate shit over and over again, im sick of this. */
- public void setMirror(boolean mirror) {
- this.mirror = mirror;
- this.repaint();
- }
+ private BufferedImage applyBasicEffects(BufferedImage img) {
+ int width = img.getWidth();
+ int height = img.getHeight();
+ BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- public void setFlip(boolean flip) {
- this.flip = flip;
- this.repaint();
- }
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ // Handle mirror/flip/rotate
+ int srcX = x;
+ int srcY = y;
- public void setRotate(boolean rotate) {
- this.rotate = rotate;
- this.repaint();
- }
+ if (mirror) srcX = width - 1 - x;
+ if (flip) srcY = height - 1 - y;
+ if (rotate) {
+ int temp = srcX;
+ srcX = width - 1 - srcY;
+ srcY = temp;
+ }
- public void setGrayscale(boolean grayscale) {
- this.grayscale = grayscale;
- this.repaint();
- }
+ // Ensure coordinates are in bounds
+ srcX = Math.max(0, Math.min(width - 1, srcX));
+ srcY = Math.max(0, Math.min(height - 1, srcY));
- public void setInvert(boolean invert) {
- this.invert = invert;
- this.repaint();
- }
-
- public void setBrightness(int brightness) {
- this.brightness = brightness;
- this.repaint();
- }
-
- public void setContrast(float contrast) {
- this.contrast = contrast;
- this.repaint();
- }
-
- public void resetEffects() {
- mirror = flip = rotate = grayscale = invert = false;
- brightness = 0;
- contrast = 1.0f;
- this.repaint();
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
- if (currentImage != null) {
- Graphics2D g2 = (Graphics2D) g;
- g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-
- BufferedImage processedImage = currentImage;
-
- // effects
- if (grayscale || invert || brightness != 0 || contrast != 1.0f) {
- processedImage = applyColorEffects(processedImage);
- }
-
- // transforms
- int w = getWidth(), h = getHeight();
-
- if (rotate) {
- g2.translate(w / 2, h / 2);
- g2.rotate(Math.PI);
- g2.translate(-w / 2, -h / 2);
- }
-
-
- // here we have if, cause we need to do the stuff, yk?
- if (mirror && flip) {
- g2.drawImage(processedImage, w, h, -w, -h, null);
- } else if (mirror) {
- g2.drawImage(processedImage, w, 0, -w, h, null);
- } else if (flip) {
- g2.drawImage(processedImage, 0, h, w, -h, null);
- } else {
- g2.drawImage(processedImage, 0, 0, w, h, null);
- }
- }
- }
-
- private BufferedImage applyColorEffects(BufferedImage img) {
- BufferedImage result = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
-
- for (int y = 0; y < img.getHeight(); y++) {
- for (int x = 0; x < img.getWidth(); x++) {
- int rgb = img.getRGB(x, y);
+ int rgb = img.getRGB(srcX, srcY);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
+ // Apply grayscale
if (grayscale) {
int gray = (r + g + b) / 3;
r = g = b = gray;
}
- // this is fun, this regulates the brightness or the contrast!
- // this is some real java, the other stuff is just UI design, which i am bad at,
- // but this! This is some real shit
- r = (int) ((r - 128) * contrast + 128 + brightness);
- g = (int) ((g - 128) * contrast + 128 + brightness);
- b = (int) ((b - 128) * contrast + 128 + brightness);
+ // Apply brightness
+ if (brightness != 0) {
+ r = clamp(r + brightness);
+ g = clamp(g + brightness);
+ b = clamp(b + brightness);
+ }
- // invert the colors!
+ // Apply contrast
+ if (contrast != 1.0f) {
+ r = clamp((int)((r - 128) * contrast + 128));
+ g = clamp((int)((g - 128) * contrast + 128));
+ b = clamp((int)((b - 128) * contrast + 128));
+ }
+
+ // Apply invert
if (invert) {
r = 255 - r;
g = 255 - g;
b = 255 - b;
}
- // clamp so we dont get into weird color grades, or any weird looking spaces
- // if we dont do this we cant really do stuff which looks bearable
- r = Math.max(0, Math.min(255, r));
- g = Math.max(0, Math.min(255, g));
- b = Math.max(0, Math.min(255, b));
-
result.setRGB(x, y, (r << 16) | (g << 8) | b);
}
}
@@ -172,33 +122,95 @@ public class CameraPanel extends JPanel {
return result;
}
- private BufferedImage applyTransforms(BufferedImage img) {
- int width = img.getWidth();
- int height = img.getHeight();
- BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ private int clamp(int value) {
+ return Math.max(0, Math.min(255, value));
+ }
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- int sourceX = x;
- int sourceY = y;
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
- if (mirror) {
- sourceX = width - 1 - x;
- }
- if (flip) {
- sourceY = height - 1 - y;
- }
- if (rotate) {
- int tempX = width - 1 - sourceX;
- int tempY = height - 1 - sourceY;
- sourceX = tempX;
- sourceY = tempY;
- }
+ if (processedImage != null) {
+ Graphics2D g2d = (Graphics2D) g;
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- result.setRGB(x, y, img.getRGB(sourceX, sourceY));
- }
+ // Scale image to fit panel while maintaining aspect ratio
+ int panelWidth = getWidth();
+ int panelHeight = getHeight();
+ int imgWidth = processedImage.getWidth();
+ int imgHeight = processedImage.getHeight();
+
+ double scaleX = (double) panelWidth / imgWidth;
+ double scaleY = (double) panelHeight / imgHeight;
+ double scale = Math.min(scaleX, scaleY);
+
+ int scaledWidth = (int) (imgWidth * scale);
+ int scaledHeight = (int) (imgHeight * scale);
+
+ int x = (panelWidth - scaledWidth) / 2;
+ int y = (panelHeight - scaledHeight) / 2;
+
+ g2d.drawImage(processedImage, x, y, scaledWidth, scaledHeight, null);
}
+ }
- return result;
+ public BufferedImage getCurrentProcessedImage() {
+ return processedImage;
+ }
+
+ // Basic effect setters
+ public void setMirror(boolean mirror) {
+ this.mirror = mirror;
+ processImage();
+ repaint();
+ }
+
+ public void setFlip(boolean flip) {
+ this.flip = flip;
+ processImage();
+ repaint();
+ }
+
+ public void setRotate(boolean rotate) {
+ this.rotate = rotate;
+ processImage();
+ repaint();
+ }
+
+ public void setGrayscale(boolean grayscale) {
+ this.grayscale = grayscale;
+ processImage();
+ repaint();
+ }
+
+ public void setInvert(boolean invert) {
+ this.invert = invert;
+ processImage();
+ repaint();
+ }
+
+ public void setBrightness(int brightness) {
+ this.brightness = brightness;
+ processImage();
+ repaint();
+ }
+
+ public void setContrast(float contrast) {
+ this.contrast = contrast;
+ processImage();
+ repaint();
+ }
+
+ public void resetEffects() {
+ this.mirror = false;
+ this.flip = false;
+ this.rotate = false;
+ this.grayscale = false;
+ this.invert = false;
+ this.brightness = 0;
+ this.contrast = 1.0f;
+ this.imageProcessor = null;
+ processImage();
+ repaint();
}
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ConnectionOverlay.java b/src/main/java/io/swtc/proccessing/ConnectionOverlay.java
new file mode 100644
index 0000000..9be93fc
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ConnectionOverlay.java
@@ -0,0 +1,81 @@
+package io.swtc.proccessing;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.CubicCurve2D;
+
+/**
+ * Acts as a transparent layer over the entire window to draw connections.
+ */
+public class ConnectionOverlay extends JComponent {
+ private final Component source;
+ private final Component target;
+ private final Color connectionColor;
+
+ public ConnectionOverlay(Component source, Component target, Color color) {
+ this.source = source;
+ this.target = target;
+ this.connectionColor = color;
+ setOpaque(false); // Make sure we can see through it
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ if (!source.isShowing() || !target.isShowing()) return;
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // 1. Get absolute positions relative to this Overlay
+ Point p1 = SwingUtilities.convertPoint(source, 0, 0, this);
+ Point p2 = SwingUtilities.convertPoint(target, 0, 0, this);
+
+ int x1, y1, x2, y2, ctrl1X, ctrl2X;
+
+ // Calculate vertical centers
+ y1 = p1.y + (source.getHeight() / 2);
+ y2 = p2.y + (target.getHeight() / 2);
+
+ // 2. Logic to determine Left/Right orientation
+ // If source is to the left of target
+ if (p1.x + source.getWidth() < p2.x) {
+ x1 = p1.x + source.getWidth(); // Right edge of source
+ x2 = p2.x; // Left edge of target
+ }
+ // If source is to the right of target
+ else if (p1.x > p2.x + target.getWidth()) {
+ x1 = p1.x; // Left edge of source
+ x2 = p2.x + target.getWidth(); // Right edge of target
+ }
+ // If they are overlapping horizontally, use centers
+ else {
+ x1 = p1.x + (source.getWidth() / 2);
+ x2 = p2.x + (target.getWidth() / 2);
+ }
+
+ // 3. Dynamic Curve "Stiffness"
+ // The horizontal distance between the two points determines how far the curve pulls
+ int horizontalDist = Math.abs(x1 - x2);
+ int offset = Math.max(horizontalDist / 2, 20); // Minimum 20px pull for short distances
+
+ if (x1 < x2) {
+ ctrl1X = x1 + offset;
+ ctrl2X = x2 - offset;
+ } else {
+ ctrl1X = x1 - offset;
+ ctrl2X = x2 + offset;
+ }
+
+ // 4. Draw the Curve
+ g2d.setColor(connectionColor);
+ g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+
+ // We keep the Y coordinates for controls the same as the endpoints
+ // to create that "horizontal entry/exit" look.
+ CubicCurve2D curve = new CubicCurve2D.Float(x1, y1, ctrl1X, y1, ctrl2X, y2, x2, y2);
+ g2d.draw(curve);
+
+ g2d.dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/EffectState.java b/src/main/java/io/swtc/proccessing/EffectState.java
new file mode 100644
index 0000000..c0d45ce
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/EffectState.java
@@ -0,0 +1,25 @@
+package io.swtc.proccessing;
+
+public class EffectState {
+ public final boolean awbEnabled, dnrEnabled, edgeEnhance;
+ public final int awbStrength, dnrSpatial, dnrTemporal;
+ public final int temperature, tint, saturation, shadows, highlights, sharpness;
+
+ public EffectState(boolean awbEnabled, int awbStrength, boolean dnrEnabled,
+ int dnrSpatial, int dnrTemporal, int temperature, int tint,
+ int saturation, int shadows, int highlights,
+ int sharpness, boolean edgeEnhance) {
+ this.awbEnabled = awbEnabled;
+ this.awbStrength = awbStrength;
+ this.dnrEnabled = dnrEnabled;
+ this.dnrSpatial = dnrSpatial;
+ this.dnrTemporal = dnrTemporal;
+ this.temperature = temperature;
+ this.tint = tint;
+ this.saturation = saturation;
+ this.shadows = shadows;
+ this.highlights = highlights;
+ this.sharpness = sharpness;
+ this.edgeEnhance = edgeEnhance;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/FilterPanel.java b/src/main/java/io/swtc/proccessing/FilterPanel.java
new file mode 100644
index 0000000..2258e42
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/FilterPanel.java
@@ -0,0 +1,273 @@
+package io.swtc.proccessing;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.image.BufferedImage;
+import java.util.Random;
+
+public class FilterPanel extends JPanel {
+
+ private final CameraPanel cameraPanel;
+ private final AutoGainProcessor autoGainProcessor;
+ private final PresetLibrary presetLibrary;
+ private final Color connectionColor;
+
+ private JCheckBox awbEnabled, dnrEnabled, edgeEnhance;
+ private JSlider awbStrength, dnrStrength, dnrTemporal, sharpness;
+ private JSlider saturation, temperature, tint, shadows, highlights;
+ private JComboBox presetCombo;
+
+ private float[] currentGains = {1f, 1f, 1f};
+
+ public FilterPanel(CameraPanel cameraPanel) {
+ this.cameraPanel = cameraPanel;
+ this.autoGainProcessor = new AutoGainProcessor();
+ this.presetLibrary = new PresetLibrary();
+
+ // FIX: Randomize color with high saturation so it stands out
+ Random rand = new Random();
+ this.connectionColor = Color.getHSBColor(rand.nextFloat(), 0.9f, 0.9f);
+
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ buildUI();
+ initListeners(); // Initialize anti-ghosting listeners
+ }
+
+
+ private void buildUI() {
+ add(createPresetPanel());
+ add(Box.createRigidArea(new Dimension(0, 10)));
+
+ JPanel container = new JPanel();
+ container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
+
+ container.add(createAWBPanel());
+ container.add(Box.createRigidArea(new Dimension(0, 10)));
+ container.add(createDNRPanel());
+ container.add(Box.createRigidArea(new Dimension(0, 10)));
+ container.add(createColorPanel());
+ container.add(Box.createRigidArea(new Dimension(0, 10)));
+ container.add(createDetailPanel());
+
+ JScrollPane scroll = new JScrollPane(container);
+ scroll.setBorder(null);
+ scroll.getVerticalScrollBar().setUnitIncrement(16);
+ add(scroll);
+
+ add(Box.createRigidArea(new Dimension(0, 10)));
+ add(createActionPanel());
+ }
+
+ public EffectState getCurrentState() {
+ return new EffectState(
+ awbEnabled.isSelected(), awbStrength.getValue(),
+ dnrEnabled.isSelected(), dnrStrength.getValue(), dnrTemporal.getValue(),
+ temperature.getValue(), tint.getValue(), saturation.getValue(),
+ shadows.getValue(), highlights.getValue(),
+ sharpness.getValue(), edgeEnhance.isSelected()
+ );
+ }
+
+ // Inside AdvancedEffectsPanel.java
+
+ private void initListeners() {
+ ComponentAdapter repaintListener = new ComponentAdapter() {
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ updateOverlay();
+ }
+ @Override
+ public void componentResized(ComponentEvent e) {
+ updateOverlay();
+ }
+ };
+
+ this.addComponentListener(repaintListener);
+ if (this.cameraPanel != null) {
+ this.cameraPanel.addComponentListener(repaintListener);
+ }
+ }
+
+ private void updateOverlay() {
+ RootPaneContainer root = (RootPaneContainer) SwingUtilities.getWindowAncestor(this);
+ if (root != null) {
+ root.getGlassPane().repaint();
+ }
+ }
+
+ private void applyToCamera() {
+ EffectState state = getCurrentState();
+ cameraPanel.setImageProcessor(img ->
+ ImageEffectEngine.applyEffects(img, state, currentGains)
+ );
+ }
+
+ private JPanel createPresetPanel() {
+ JPanel panel = new JPanel(new BorderLayout(5, 5));
+ panel.setBorder(createTitledBorder("Presets"));
+
+ presetCombo = new JComboBox<>(new String[]{"Custom", "Natural", "Vivid", "Portrait", "Low Light", "Cinematic"});
+ JButton saveBtn = new JButton("Save");
+
+ presetCombo.addActionListener(e -> {
+ String selected = (String) presetCombo.getSelectedItem();
+ EffectState state = presetLibrary.get(selected);
+ if (state != null) applyPresetToUI(state);
+ });
+
+ saveBtn.addActionListener(e -> {
+ String name = JOptionPane.showInputDialog(this, "Preset Name:");
+ if (name != null) {
+ presetLibrary.savePreset(name, getCurrentState());
+ presetCombo.addItem(name);
+ }
+ });
+
+ panel.add(presetCombo, BorderLayout.CENTER);
+ panel.add(saveBtn, BorderLayout.EAST);
+ return panel;
+ }
+
+ private JPanel createDNRPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(createTitledBorder("3D Denoise (DNR)"));
+
+ dnrEnabled = new JCheckBox("Enable Temporal Denoise");
+ dnrStrength = addSliderToPanel(panel, "Spatial Strength", 0, 100, 30, "%");
+ dnrTemporal = addSliderToPanel(panel, "Temporal Strength", 0, 100, 50, "%");
+
+ dnrEnabled.addActionListener(e -> {
+ boolean enabled = dnrEnabled.isSelected();
+ dnrStrength.setEnabled(enabled);
+ dnrTemporal.setEnabled(enabled);
+ applyToCamera();
+ });
+
+ dnrStrength.setEnabled(false);
+ dnrTemporal.setEnabled(false);
+
+ panel.add(dnrEnabled, 0);
+ return panel;
+ }
+
+ private JPanel createDetailPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(createTitledBorder("Detail & Sharpness"));
+
+ sharpness = addSliderToPanel(panel, "Sharpness", 0, 200, 100, "%");
+ edgeEnhance = new JCheckBox("Edge Enhancement");
+
+ edgeEnhance.addActionListener(e -> applyToCamera());
+
+ panel.add(edgeEnhance);
+ return panel;
+ }
+
+ private JPanel createAWBPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(createTitledBorder("White Balance"));
+
+ awbEnabled = new JCheckBox("Enable AWB");
+ JPanel sPanel = createSliderPanel("Strength", 0, 100, 100, "%");
+ awbStrength = (JSlider) sPanel.getComponent(1);
+ JButton balanceBtn = new JButton("Balance Now");
+
+ awbEnabled.addActionListener(e -> {
+ boolean active = awbEnabled.isSelected();
+ awbStrength.setEnabled(active);
+ balanceBtn.setEnabled(active);
+ if (active) performOneTimeBalance();
+ else { currentGains = new float[]{1f, 1f, 1f}; applyToCamera(); }
+ });
+
+ awbStrength.addChangeListener(e -> { if(!awbStrength.getValueIsAdjusting()) applyToCamera(); });
+ balanceBtn.addActionListener(e -> performOneTimeBalance());
+
+ panel.add(awbEnabled);
+ panel.add(sPanel);
+ panel.add(balanceBtn);
+ return panel;
+ }
+
+ private JPanel createColorPanel() {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(createTitledBorder("Color Grading"));
+
+ temperature = addSliderToPanel(panel, "Temperature", -100, 100, 0, "");
+ tint = addSliderToPanel(panel, "Tint", -100, 100, 0, "");
+ saturation = addSliderToPanel(panel, "Saturation", 0, 200, 100, "%");
+ shadows = addSliderToPanel(panel, "Shadows", -100, 100, 0, "");
+ highlights = addSliderToPanel(panel, "Highlights", -100, 100, 0, "");
+
+ return panel;
+ }
+
+ private void performOneTimeBalance() {
+ BufferedImage img = cameraPanel.getCurrentProcessedImage();
+ if (img != null) {
+ int[] pixels = img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
+ currentGains = autoGainProcessor.calculateAutoGains(pixels);
+ applyToCamera();
+ }
+ }
+
+ private void applyPresetToUI(EffectState s) {
+ awbEnabled.setSelected(s.awbEnabled);
+ awbStrength.setValue(s.awbStrength);
+ dnrEnabled.setSelected(s.dnrEnabled);
+ dnrStrength.setValue(s.dnrSpatial);
+ temperature.setValue(s.temperature);
+ tint.setValue(s.tint);
+ saturation.setValue(s.saturation);
+ shadows.setValue(s.shadows);
+ highlights.setValue(s.highlights);
+ sharpness.setValue(s.sharpness);
+ edgeEnhance.setSelected(s.edgeEnhance);
+ applyToCamera();
+ }
+
+ private JSlider addSliderToPanel(JPanel parent, String label, int min, int max, int val, String unit) {
+ JPanel p = createSliderPanel(label, min, max, val, unit);
+ JSlider s = (JSlider) p.getComponent(1);
+ s.addChangeListener(e -> { if(!s.getValueIsAdjusting()) applyToCamera(); });
+ parent.add(p);
+ parent.add(Box.createRigidArea(new Dimension(0, 5)));
+ return s;
+ }
+
+ private JPanel createSliderPanel(String label, int min, int max, int initial, String unit) {
+ JPanel panel = new JPanel(new BorderLayout());
+ JLabel title = new JLabel(label + ": " + initial + unit);
+ JSlider slider = new JSlider(min, max, initial);
+ slider.addChangeListener(e -> title.setText(label + ": " + slider.getValue() + unit));
+ panel.add(title, BorderLayout.NORTH);
+ panel.add(slider, BorderLayout.CENTER);
+ return panel;
+ }
+
+ private TitledBorder createTitledBorder(String title) {
+ return BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), title);
+ }
+
+ private JPanel createActionPanel() {
+ JPanel panel = new JPanel(new GridLayout(1, 2, 10, 0));
+ JButton reset = new JButton("Reset All");
+ reset.addActionListener(e -> resetUI());
+ panel.add(reset);
+ return panel;
+ }
+
+ private void resetUI() {
+ applyPresetToUI(new EffectState(false, 100, false, 30, 50, 0, 0, 100, 0, 0, 100, false));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ImageEffectEngine.java b/src/main/java/io/swtc/proccessing/ImageEffectEngine.java
new file mode 100644
index 0000000..65eb22f
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ImageEffectEngine.java
@@ -0,0 +1,147 @@
+package io.swtc.proccessing;
+
+import java.awt.image.BufferedImage;
+
+public class ImageEffectEngine {
+
+ public static BufferedImage applyEffects(BufferedImage img, EffectState state, float[] currentGains) {
+ if (img == null) return null;
+
+ 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++) {
+ int rgb = img.getRGB(x, y);
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+
+ // 1. AWB
+ if (state.awbEnabled) {
+ float s = state.awbStrength / 100f;
+ r = (int) Math.min(255, r * (1 + (currentGains[0] - 1) * s));
+ g = (int) Math.min(255, g * (1 + (currentGains[1] - 1) * s));
+ b = (int) Math.min(255, b * (1 + (currentGains[2] - 1) * s));
+ }
+
+ // 2. Temp & Tint
+ if (state.temperature != 0) {
+ float factor = state.temperature / 100f;
+ r = clamp(r + (int)(factor * 30));
+ b = clamp(b - (int)(factor * 30));
+ }
+ if (state.tint != 0) {
+ g = clamp(g + (int)((state.tint / 100f) * 20));
+ }
+
+ // 3. Saturation
+ if (state.saturation != 100) {
+ float factor = state.saturation / 100f;
+ float gray = (r + g + b) / 3f;
+ r = clamp((int)(gray + (r - gray) * factor));
+ g = clamp((int)(gray + (g - gray) * factor));
+ b = clamp((int)(gray + (b - gray) * factor));
+ }
+
+ // 4. Shadows/Highlights
+ float lum = (r + g + b) / 3f / 255f;
+ if (lum < 0.5f && state.shadows != 0) {
+ int adj = (int)((state.shadows / 100f) * (1 - lum * 2) * 50);
+ r = clamp(r + adj); g = clamp(g + adj); b = clamp(b + adj);
+ } else if (lum > 0.5f && state.highlights != 0) {
+ int adj = (int)((state.highlights / 100f) * (lum * 2 - 1) * 50);
+ r = clamp(r + adj); g = clamp(g + adj); b = clamp(b + adj);
+ }
+
+ result.setRGB(x, y, (r << 16) | (g << 8) | b);
+ }
+ }
+
+ if (state.sharpness != 100 || state.edgeEnhance) {
+ result = applySharpness(result, state.sharpness / 100f, state.edgeEnhance);
+ }
+
+ if (state.dnrEnabled) {
+ result = applyDenoise(result, state.dnrSpatial / 100f);
+ }
+
+ return result;
+ }
+
+ private static BufferedImage applySharpness(BufferedImage img, float amount, boolean edgeEnhance) {
+ if (amount == 1f && !edgeEnhance) return img;
+
+ int width = img.getWidth();
+ int height = img.getHeight();
+ BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ float[][] kernel = edgeEnhance ?
+ new float[][]{{0, -1, 0}, {-1, 5, -1}, {0, -1, 0}} :
+ new float[][]{{-1, -1, -1}, {-1, 9, -1}, {-1, -1, -1}};
+
+ for (int y = 1; y < height - 1; y++) {
+ for (int x = 1; x < width - 1; x++) {
+ float r = 0, g = 0, b = 0;
+
+ for (int ky = -1; ky <= 1; ky++) {
+ for (int kx = -1; kx <= 1; kx++) {
+ int rgb = img.getRGB(x + kx, y + ky);
+ float weight = kernel[ky + 1][kx + 1] * (amount - 1) / 8f;
+ if (kx == 0 && ky == 0) weight += 1;
+
+ r += ((rgb >> 16) & 0xFF) * weight;
+ g += ((rgb >> 8) & 0xFF) * weight;
+ b += (rgb & 0xFF) * weight;
+ }
+ }
+
+ result.setRGB(x, y, (clamp((int)r) << 16) | (clamp((int)g) << 8) | clamp((int)b));
+ }
+ }
+
+ return result;
+ }
+
+ private static BufferedImage applyDenoise(BufferedImage img, float strength) {
+ if (strength == 0) return img;
+
+ int width = img.getWidth();
+ int height = img.getHeight();
+ BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ int radius = (int)(strength * 2) + 1;
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int rSum = 0, gSum = 0, bSum = 0, count = 0;
+
+ for (int dy = -radius; dy <= radius; dy++) {
+ for (int dx = -radius; dx <= radius; dx++) {
+ int nx = Math.min(width - 1, Math.max(0, x + dx));
+ int ny = Math.min(height - 1, Math.max(0, y + dy));
+
+ int rgb = img.getRGB(nx, ny);
+ rSum += (rgb >> 16) & 0xFF;
+ gSum += (rgb >> 8) & 0xFF;
+ bSum += rgb & 0xFF;
+ count++;
+ }
+ }
+
+ int r = rSum / count;
+ int g = gSum / count;
+ int b = bSum / count;
+
+ result.setRGB(x, y, (r << 16) | (g << 8) | b);
+ }
+ }
+
+ return result;
+ }
+
+ private static int clamp(int val) {
+ return Math.max(0, Math.min(255, val));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/PresetLibrary.java b/src/main/java/io/swtc/proccessing/PresetLibrary.java
new file mode 100644
index 0000000..56f81ca
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/PresetLibrary.java
@@ -0,0 +1,46 @@
+package io.swtc.proccessing;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PresetLibrary {
+ private final Map presets = new HashMap<>();
+
+ public PresetLibrary() {
+ presets.put("Natural", new EffectState(
+ false, 100, false, 20, 40, 0, 0, 100, 0, 0, 100, false
+ ));
+
+ presets.put("Vivid", new EffectState(
+ true, 100, false, 15, 30, 10, 5, 130, 0, 0, 120, true
+ ));
+
+ presets.put("Portrait", new EffectState(
+ true, 80, true, 40, 50, -5, 10, 95, 10, -5, 90, false
+ ));
+
+ presets.put("Low Light", new EffectState(
+ true, 100, true, 60, 70, 0, 0, 110, 20, -10, 80, false
+ ));
+
+ presets.put("High Contrast", new EffectState(
+ false, 100, false, 25, 35, 0, 0, 120, -20, 20, 130, true
+ ));
+
+ presets.put("Cinematic", new EffectState(
+ true, 70, true, 30, 45, -15, -5, 90, -10, 5, 110, false
+ ));
+ }
+
+ public void savePreset(String name, EffectState state) {
+ presets.put(name, state);
+ }
+
+ public EffectState get(String name) {
+ return presets.get(name);
+ }
+
+ public String[] getPresetNames() {
+ return presets.keySet().toArray(new String[0]);
+ }
+}
\ No newline at end of file
From f6ee3e915eb2864d0607f7a14d93207d2b13d9e6 Mon Sep 17 00:00:00 2001
From: rattatwinko
Date: Mon, 19 Jan 2026 12:00:50 +0100
Subject: [PATCH 2/5] refactored shitty unusable code
Signed-off-by: rattatwinko
---
src/main/java/io/swtc/Main.java | 15 +-
src/main/java/io/swtc/SwingCameraWindow.java | 80 -----
src/main/java/io/swtc/SwingIFrame.java | 54 ++--
.../java/io/swtc/networking/CameraConfig.java | 1 -
...oGainProcessor.java => AWBProccessor.java} | 2 +-
.../java/io/swtc/proccessing/CameraPanel.java | 122 +-------
.../io/swtc/proccessing/ColorProccessor.java | 62 ++++
.../swtc/proccessing/ConnectionOverlay.java | 81 -----
.../swtc/proccessing/DenoiseProccessor.java | 51 +++
.../java/io/swtc/proccessing/EffectState.java | 25 +-
.../java/io/swtc/proccessing/FilterPanel.java | 296 +++++-------------
.../swtc/proccessing/ImageEffectEngine.java | 147 ++-------
.../java/io/swtc/proccessing/ImageUtils.java | 36 +++
.../swtc/proccessing/SharpnessProccessor.java | 70 +++++
.../swtc/proccessing/WebcamCaptureLoop.java | 13 +-
.../io/swtc/proccessing/ui/FilterSection.java | 29 ++
.../io/swtc/proccessing/ui/LabeledSlider.java | 45 +++
.../io/swtc/proccessing/ui/UIFactory.java | 21 ++
.../proccessing/ui/sections/ColorSection.java | 37 +++
.../proccessing/ui/sections/DNRSection.java | 48 +++
.../ui/sections/DetailSection.java | 33 ++
.../ui/sections/PresetSection.java | 39 +++
.../ui/sections/WhiteBalanceSection.java | 49 +++
...cessorTest.java => AWBProccessorTest.java} | 8 +-
24 files changed, 684 insertions(+), 680 deletions(-)
delete mode 100644 src/main/java/io/swtc/SwingCameraWindow.java
rename src/main/java/io/swtc/proccessing/{AutoGainProcessor.java => AWBProccessor.java} (97%)
create mode 100644 src/main/java/io/swtc/proccessing/ColorProccessor.java
delete mode 100644 src/main/java/io/swtc/proccessing/ConnectionOverlay.java
create mode 100644 src/main/java/io/swtc/proccessing/DenoiseProccessor.java
create mode 100644 src/main/java/io/swtc/proccessing/ImageUtils.java
create mode 100644 src/main/java/io/swtc/proccessing/SharpnessProccessor.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/FilterSection.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/LabeledSlider.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/UIFactory.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/sections/ColorSection.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/sections/PresetSection.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java
rename src/test/java/{AutoGainProcessorTest.java => AWBProccessorTest.java} (94%)
diff --git a/src/main/java/io/swtc/Main.java b/src/main/java/io/swtc/Main.java
index a5b5d3e..84009e5 100644
--- a/src/main/java/io/swtc/Main.java
+++ b/src/main/java/io/swtc/Main.java
@@ -1,3 +1,4 @@
+
package io.swtc;
import javax.swing.*;
@@ -5,25 +6,23 @@ import javax.swing.*;
public class Main {
public static void main(String[] args) {
- for (int i = 0; i < args.length; i++) {
- System.out.println("Arg " + i + ": " + args[i]);
- }
try {
- //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
- "Method not supported, Application will run anyways but might look different, and feel different!",
- "SWTCCTVWarning",
+ "LaF not available",
+ "LaF-Warning",
JOptionPane.WARNING_MESSAGE
);
}
-
- SwingCCTVManager.main(null);
+ // For some reason we need to invoke Later for LaF to work!
+ SwingUtilities.invokeLater(() -> {
+ SwingCCTVManager.main(null);
+ });
}
}
diff --git a/src/main/java/io/swtc/SwingCameraWindow.java b/src/main/java/io/swtc/SwingCameraWindow.java
deleted file mode 100644
index 0ac69fc..0000000
--- a/src/main/java/io/swtc/SwingCameraWindow.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package io.swtc;
-
-import com.github.sarxos.webcam.Webcam;
-import io.swtc.proccessing.WebcamCaptureLoop;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.awt.image.BufferedImage;
-
-@Deprecated
-public class SwingCameraWindow {
- private final JFrame frame;
- private final CameraPanel cameraPanel;
- private final WebcamCaptureLoop captureLoop;
-
- public SwingCameraWindow(Webcam webcam) {
- this.frame = new JFrame("scctv@" + webcam.getName());
- this.frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
- this.frame.addWindowListener(new WindowAdapter() {
- @Override
- public void windowClosing(WindowEvent e) {
- // clean shit up
- frame.dispose();
- captureLoop.stop(); // be sure to call this! otherwise the camera will stay open!
- }
- });
-
- this.cameraPanel = new CameraPanel();
- this.frame.add(cameraPanel);
- this.frame.pack();
- this.frame.setSize(640, 480);
-
- this.captureLoop = new WebcamCaptureLoop(webcam, (BufferedImage img) -> SwingUtilities.invokeLater(() -> cameraPanel.setImage(img)));
-
- }
-
- public void open() {
- frame.setVisible(true);
- captureLoop.start();
- }
-
- private static class CameraPanel extends JPanel {
- private BufferedImage currentImage;
-
- public void setImage(BufferedImage img) {
- this.currentImage = img;
- this.repaint(); // Triggers paintComponent
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
- if (currentImage != null) {
- Graphics2D g2 = (Graphics2D) g;
- g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- g2.drawImage(currentImage, 0, 0, getWidth(), getHeight(), null);
- }
- }
- }
-
- public static void main(String[] args) {
- SwingUtilities.invokeLater(() -> {
- // init in edt
- Webcam webcam = Webcam.getDefault();
- if (webcam != null) {
- SwingCameraWindow window = new SwingCameraWindow(webcam);
- window.open();
- } else {
- JOptionPane.showMessageDialog(
- null,
- "No Webcam found!",
- "Error",
- JOptionPane.WARNING_MESSAGE
- );
- }
- });
- }
-}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java
index 1b4416b..ba6b382 100644
--- a/src/main/java/io/swtc/SwingIFrame.java
+++ b/src/main/java/io/swtc/SwingIFrame.java
@@ -3,26 +3,18 @@ package io.swtc;
import com.github.sarxos.webcam.Webcam;
import io.swtc.proccessing.WebcamCaptureLoop;
import io.swtc.proccessing.CameraPanel;
-import io.swtc.proccessing.FilterPanel;
-import io.swtc.recording.VideoRecorder;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
-import javax.swing.border.TitledBorder;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
+import javax.swing.event.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.geom.CubicCurve2D;
import java.awt.image.BufferedImage;
import java.io.File;
-import java.text.SimpleDateFormat;
import java.util.*;
-/**
- * Blender-style UI where the Effects panel is triggered by a Tab click
- */
public class SwingIFrame {
private final JFrame mainFrame;
private final BlenderDesktopPane desktopPane;
@@ -66,17 +58,16 @@ public class SwingIFrame {
tabbedPane.addTab("View", cameraPanel);
tabbedPane.addTab("Capture", createCapturePanel(cameraPanel, webcam));
- // Add a placeholder tab that acts as a button
- tabbedPane.addTab("Open Effect Panel", new JPanel());
+ tabbedPane.addTab("Effects", new JPanel());
contentPanel.add(tabbedPane, BorderLayout.CENTER);
+ tabbedPane.setPreferredSize(null);
- // Logic to open/show the Effects Frame when the tab is clicked
+ // where we show the effectpanel (io.swtc.proccessing.FilterPanel.java)
tabbedPane.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
- if (tabbedPane.getSelectedIndex() == 2) { // "Effects +" tab index
- // Revert selection back to "View" so the tab doesn't stay stuck on the empty panel
+ if (tabbedPane.getSelectedIndex() == 2) {
tabbedPane.setSelectedIndex(0);
EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame);
@@ -234,6 +225,8 @@ public class SwingIFrame {
this.connections = connections;
}
+
+
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
@@ -273,16 +266,41 @@ public class SwingIFrame {
}
static class EffectsPanelFrame extends JInternalFrame {
+
public EffectsPanelFrame(String title, CameraPanel cameraPanel, JInternalFrame cameraFrame) {
super(title, true, true, true, true);
- add(new FilterPanel(cameraPanel));
- // Repaint curves when moving or iconifying
+ // Hide instead of dispose so it can be reopened
+ setDefaultCloseOperation(JInternalFrame.HIDE_ON_CLOSE);
+
+ // Add your FilterPanel
+ add(new io.swtc.proccessing.FilterPanel(cameraPanel));
+
+ // Repaint desktop pane when this frame closes so the Bézier disappears
+ addInternalFrameListener(new InternalFrameAdapter() {
+ @Override
+ public void internalFrameClosing(InternalFrameEvent e) {
+ if (getDesktopPane() != null) {
+ getDesktopPane().repaint();
+ }
+ }
+ });
+
+ // Repaint curves when moving or resizing
addComponentListener(new java.awt.event.ComponentAdapter() {
- @Override public void componentMoved(java.awt.event.ComponentEvent e) {
- if(getDesktopPane() != null) getDesktopPane().repaint();
+ @Override
+ public void componentMoved(java.awt.event.ComponentEvent e) {
+ if (getDesktopPane() != null)
+ getDesktopPane().repaint();
+ }
+
+ @Override
+ public void componentResized(java.awt.event.ComponentEvent e) {
+ if (getDesktopPane() != null)
+ getDesktopPane().repaint();
}
});
}
}
+
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/networking/CameraConfig.java b/src/main/java/io/swtc/networking/CameraConfig.java
index 1716f54..d3814a0 100644
--- a/src/main/java/io/swtc/networking/CameraConfig.java
+++ b/src/main/java/io/swtc/networking/CameraConfig.java
@@ -5,7 +5,6 @@ public class CameraConfig {
public String url;
// Default constructor for Jackson
- public CameraConfig() {}
public CameraConfig(String name, String url) {
this.name = name;
diff --git a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java b/src/main/java/io/swtc/proccessing/AWBProccessor.java
similarity index 97%
rename from src/main/java/io/swtc/proccessing/AutoGainProcessor.java
rename to src/main/java/io/swtc/proccessing/AWBProccessor.java
index 473ba4c..d563092 100644
--- a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java
+++ b/src/main/java/io/swtc/proccessing/AWBProccessor.java
@@ -16,7 +16,7 @@ package io.swtc.proccessing;
*
*/
-public class AutoGainProcessor {
+public class AWBProccessor {
public float[] calculateAutoGains(int[] pixels) {
long rSum = 0, gSum = 0, bSum = 0;
diff --git a/src/main/java/io/swtc/proccessing/CameraPanel.java b/src/main/java/io/swtc/proccessing/CameraPanel.java
index f0d5620..6d64310 100644
--- a/src/main/java/io/swtc/proccessing/CameraPanel.java
+++ b/src/main/java/io/swtc/proccessing/CameraPanel.java
@@ -12,15 +12,6 @@ public class CameraPanel extends JPanel {
private BufferedImage currentImage;
private BufferedImage processedImage;
- // Basic transform effects
- private boolean mirror = false;
- private boolean flip = false;
- private boolean rotate = false;
- private boolean grayscale = false;
- private boolean invert = false;
- private int brightness = 0;
- private float contrast = 1.0f;
-
// Custom processor for advanced effects
private Function imageProcessor;
@@ -71,14 +62,6 @@ public class CameraPanel extends JPanel {
int srcX = x;
int srcY = y;
- if (mirror) srcX = width - 1 - x;
- if (flip) srcY = height - 1 - y;
- if (rotate) {
- int temp = srcX;
- srcX = width - 1 - srcY;
- srcY = temp;
- }
-
// Ensure coordinates are in bounds
srcX = Math.max(0, Math.min(width - 1, srcX));
srcY = Math.max(0, Math.min(height - 1, srcY));
@@ -88,33 +71,6 @@ public class CameraPanel extends JPanel {
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
- // Apply grayscale
- if (grayscale) {
- int gray = (r + g + b) / 3;
- r = g = b = gray;
- }
-
- // Apply brightness
- if (brightness != 0) {
- r = clamp(r + brightness);
- g = clamp(g + brightness);
- b = clamp(b + brightness);
- }
-
- // Apply contrast
- if (contrast != 1.0f) {
- r = clamp((int)((r - 128) * contrast + 128));
- g = clamp((int)((g - 128) * contrast + 128));
- b = clamp((int)((b - 128) * contrast + 128));
- }
-
- // Apply invert
- if (invert) {
- r = 255 - r;
- g = 255 - g;
- b = 255 - b;
- }
-
result.setRGB(x, y, (r << 16) | (g << 8) | b);
}
}
@@ -122,35 +78,16 @@ public class CameraPanel extends JPanel {
return result;
}
- private int clamp(int value) {
- return Math.max(0, Math.min(255, value));
- }
-
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (processedImage != null) {
Graphics2D g2d = (Graphics2D) g;
+
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- // Scale image to fit panel while maintaining aspect ratio
- int panelWidth = getWidth();
- int panelHeight = getHeight();
- int imgWidth = processedImage.getWidth();
- int imgHeight = processedImage.getHeight();
-
- double scaleX = (double) panelWidth / imgWidth;
- double scaleY = (double) panelHeight / imgHeight;
- double scale = Math.min(scaleX, scaleY);
-
- int scaledWidth = (int) (imgWidth * scale);
- int scaledHeight = (int) (imgHeight * scale);
-
- int x = (panelWidth - scaledWidth) / 2;
- int y = (panelHeight - scaledHeight) / 2;
-
- g2d.drawImage(processedImage, x, y, scaledWidth, scaledHeight, null);
+ g2d.drawImage(processedImage, 0, 0, getWidth(), getHeight(), null);
}
}
@@ -158,59 +95,4 @@ public class CameraPanel extends JPanel {
return processedImage;
}
- // Basic effect setters
- public void setMirror(boolean mirror) {
- this.mirror = mirror;
- processImage();
- repaint();
- }
-
- public void setFlip(boolean flip) {
- this.flip = flip;
- processImage();
- repaint();
- }
-
- public void setRotate(boolean rotate) {
- this.rotate = rotate;
- processImage();
- repaint();
- }
-
- public void setGrayscale(boolean grayscale) {
- this.grayscale = grayscale;
- processImage();
- repaint();
- }
-
- public void setInvert(boolean invert) {
- this.invert = invert;
- processImage();
- repaint();
- }
-
- public void setBrightness(int brightness) {
- this.brightness = brightness;
- processImage();
- repaint();
- }
-
- public void setContrast(float contrast) {
- this.contrast = contrast;
- processImage();
- repaint();
- }
-
- public void resetEffects() {
- this.mirror = false;
- this.flip = false;
- this.rotate = false;
- this.grayscale = false;
- this.invert = false;
- this.brightness = 0;
- this.contrast = 1.0f;
- this.imageProcessor = null;
- processImage();
- repaint();
- }
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ColorProccessor.java b/src/main/java/io/swtc/proccessing/ColorProccessor.java
new file mode 100644
index 0000000..5025986
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ColorProccessor.java
@@ -0,0 +1,62 @@
+package io.swtc.proccessing;
+
+import java.util.stream.IntStream;
+
+public class ColorProccessor {
+
+ public void process(int[] pixels, EffectState state, float[] awbGains) {
+ float[] effectiveGains = (state.awbEnabled() && awbGains != null) ? awbGains : new float[]{1f, 1f, 1f};
+
+ // Parallel processing for O(N) pixel operations
+ IntStream.range(0, pixels.length).parallel().forEach(i -> {
+ int rgb = pixels[i];
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+
+ if (state.awbEnabled()) {
+ float s = state.awbStrength() / 100f;
+ r = Math.min(255, (int)(r * (1 + (effectiveGains[0] - 1) * s)));
+ g = Math.min(255, (int)(g * (1 + (effectiveGains[1] - 1) * s)));
+ b = Math.min(255, (int)(b * (1 + (effectiveGains[2] - 1) * s)));
+ }
+
+ // 2. Temperature & Tint
+ if (state.temperature() != 0) {
+ float factor = state.temperature() / 100f;
+ r = ImageUtils.clamp(r + (int)(factor * 30));
+ b = ImageUtils.clamp(b - (int)(factor * 30));
+ }
+ if (state.tint() != 0) {
+ g = ImageUtils.clamp(g + (int)((state.tint() / 100f) * 20));
+ }
+
+ // 3. Saturation
+ if (state.saturation() != 100) {
+ float factor = state.saturation() / 100f;
+ float gray = (r + g + b) / 3f;
+ r = ImageUtils.clamp((int)(gray + (r - gray) * factor));
+ g = ImageUtils.clamp((int)(gray + (g - gray) * factor));
+ b = ImageUtils.clamp((int)(gray + (b - gray) * factor));
+ }
+
+ // 4. Shadows & Highlights
+ if (state.shadows() != 0 || state.highlights() != 0) {
+ float lum = (r + g + b) / 765f; // 765 = 3 * 255
+ if (lum < 0.5f && state.shadows() != 0) {
+ int adj = (int)((state.shadows() / 100f) * (1 - lum * 2) * 50);
+ r = ImageUtils.clamp(r + adj);
+ g = ImageUtils.clamp(g + adj);
+ b = ImageUtils.clamp(b + adj);
+ } else if (lum > 0.5f && state.highlights() != 0) {
+ int adj = (int)((state.highlights() / 100f) * (lum * 2 - 1) * 50);
+ r = ImageUtils.clamp(r + adj);
+ g = ImageUtils.clamp(g + adj);
+ b = ImageUtils.clamp(b + adj);
+ }
+ }
+
+ pixels[i] = (r << 16) | (g << 8) | b;
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ConnectionOverlay.java b/src/main/java/io/swtc/proccessing/ConnectionOverlay.java
deleted file mode 100644
index 9be93fc..0000000
--- a/src/main/java/io/swtc/proccessing/ConnectionOverlay.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package io.swtc.proccessing;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.geom.CubicCurve2D;
-
-/**
- * Acts as a transparent layer over the entire window to draw connections.
- */
-public class ConnectionOverlay extends JComponent {
- private final Component source;
- private final Component target;
- private final Color connectionColor;
-
- public ConnectionOverlay(Component source, Component target, Color color) {
- this.source = source;
- this.target = target;
- this.connectionColor = color;
- setOpaque(false); // Make sure we can see through it
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
- if (!source.isShowing() || !target.isShowing()) return;
-
- Graphics2D g2d = (Graphics2D) g.create();
- g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-
- // 1. Get absolute positions relative to this Overlay
- Point p1 = SwingUtilities.convertPoint(source, 0, 0, this);
- Point p2 = SwingUtilities.convertPoint(target, 0, 0, this);
-
- int x1, y1, x2, y2, ctrl1X, ctrl2X;
-
- // Calculate vertical centers
- y1 = p1.y + (source.getHeight() / 2);
- y2 = p2.y + (target.getHeight() / 2);
-
- // 2. Logic to determine Left/Right orientation
- // If source is to the left of target
- if (p1.x + source.getWidth() < p2.x) {
- x1 = p1.x + source.getWidth(); // Right edge of source
- x2 = p2.x; // Left edge of target
- }
- // If source is to the right of target
- else if (p1.x > p2.x + target.getWidth()) {
- x1 = p1.x; // Left edge of source
- x2 = p2.x + target.getWidth(); // Right edge of target
- }
- // If they are overlapping horizontally, use centers
- else {
- x1 = p1.x + (source.getWidth() / 2);
- x2 = p2.x + (target.getWidth() / 2);
- }
-
- // 3. Dynamic Curve "Stiffness"
- // The horizontal distance between the two points determines how far the curve pulls
- int horizontalDist = Math.abs(x1 - x2);
- int offset = Math.max(horizontalDist / 2, 20); // Minimum 20px pull for short distances
-
- if (x1 < x2) {
- ctrl1X = x1 + offset;
- ctrl2X = x2 - offset;
- } else {
- ctrl1X = x1 - offset;
- ctrl2X = x2 + offset;
- }
-
- // 4. Draw the Curve
- g2d.setColor(connectionColor);
- g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
-
- // We keep the Y coordinates for controls the same as the endpoints
- // to create that "horizontal entry/exit" look.
- CubicCurve2D curve = new CubicCurve2D.Float(x1, y1, ctrl1X, y1, ctrl2X, y2, x2, y2);
- g2d.draw(curve);
-
- g2d.dispose();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/DenoiseProccessor.java b/src/main/java/io/swtc/proccessing/DenoiseProccessor.java
new file mode 100644
index 0000000..27adfa2
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/DenoiseProccessor.java
@@ -0,0 +1,51 @@
+package io.swtc.proccessing;
+
+import java.util.stream.IntStream;
+
+public class DenoiseProccessor {
+
+ public int[] process(int[] srcPixels, int width, int height, float strength) {
+ if (strength <= 0) return srcPixels;
+
+ int[] dstPixels = new int[srcPixels.length];
+ int[] tempPixels = new int[srcPixels.length];
+
+ int radius = (int) (strength / 100f * 2) + 1;
+
+ // Pass 1: Horizontal
+ IntStream.range(0, height).parallel().forEach(y ->
+ blurLine(srcPixels, tempPixels, width, height, y, radius, true)
+ );
+
+ // Pass 2: Vertical
+ IntStream.range(0, width).parallel().forEach(x ->
+ blurLine(tempPixels, dstPixels, width, height, x, radius, false)
+ );
+
+ return dstPixels;
+ }
+
+ private void blurLine(int[] src, int[] dest, int w, int h, int lineIndex, int radius, boolean horizontal) {
+ int length = horizontal ? w : h;
+ int limit = length - 1;
+
+ for (int i = 0; i < length; i++) {
+ long rSum = 0, gSum = 0, bSum = 0;
+ int count = 0;
+ int start = Math.max(0, i - radius);
+ int end = Math.min(limit, i + radius);
+
+ for (int k = start; k <= end; k++) {
+ int idx = horizontal ? (lineIndex * w + k) : (k * w + lineIndex);
+ int rgb = src[idx];
+ rSum += (rgb >> 16) & 0xFF;
+ gSum += (rgb >> 8) & 0xFF;
+ bSum += rgb & 0xFF;
+ count++;
+ }
+
+ int targetIdx = horizontal ? (lineIndex * w + i) : (i * w + lineIndex);
+ dest[targetIdx] = ((int)(rSum/count) << 16) | ((int)(gSum/count) << 8) | (int)(bSum/count);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/EffectState.java b/src/main/java/io/swtc/proccessing/EffectState.java
index c0d45ce..c436ef5 100644
--- a/src/main/java/io/swtc/proccessing/EffectState.java
+++ b/src/main/java/io/swtc/proccessing/EffectState.java
@@ -1,25 +1,6 @@
package io.swtc.proccessing;
-public class EffectState {
- public final boolean awbEnabled, dnrEnabled, edgeEnhance;
- public final int awbStrength, dnrSpatial, dnrTemporal;
- public final int temperature, tint, saturation, shadows, highlights, sharpness;
-
- public EffectState(boolean awbEnabled, int awbStrength, boolean dnrEnabled,
- int dnrSpatial, int dnrTemporal, int temperature, int tint,
- int saturation, int shadows, int highlights,
- int sharpness, boolean edgeEnhance) {
- this.awbEnabled = awbEnabled;
- this.awbStrength = awbStrength;
- this.dnrEnabled = dnrEnabled;
- this.dnrSpatial = dnrSpatial;
- this.dnrTemporal = dnrTemporal;
- this.temperature = temperature;
- this.tint = tint;
- this.saturation = saturation;
- this.shadows = shadows;
- this.highlights = highlights;
- this.sharpness = sharpness;
- this.edgeEnhance = edgeEnhance;
- }
+public record EffectState(boolean awbEnabled, int awbStrength, boolean dnrEnabled, int dnrSpatial, int dnrTemporal,
+ int temperature, int tint, int saturation, int shadows, int highlights, int sharpness,
+ boolean edgeEnhance) {
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/FilterPanel.java b/src/main/java/io/swtc/proccessing/FilterPanel.java
index 2258e42..c1417c4 100644
--- a/src/main/java/io/swtc/proccessing/FilterPanel.java
+++ b/src/main/java/io/swtc/proccessing/FilterPanel.java
@@ -1,273 +1,135 @@
package io.swtc.proccessing;
+import io.swtc.proccessing.ui.UIFactory;
+import io.swtc.proccessing.ui.sections.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
-import javax.swing.border.TitledBorder;
import java.awt.*;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.image.BufferedImage;
-import java.util.Random;
+/**
+ * This is basically the UI Commander, this orchestrates the whole ui package into one useful
+ * feature
+ *
+ * Without this the code will look like ass
+ * */
public class FilterPanel extends JPanel {
private final CameraPanel cameraPanel;
- private final AutoGainProcessor autoGainProcessor;
+ private final AWBProccessor awbProcessor;
private final PresetLibrary presetLibrary;
- private final Color connectionColor;
- private JCheckBox awbEnabled, dnrEnabled, edgeEnhance;
- private JSlider awbStrength, dnrStrength, dnrTemporal, sharpness;
- private JSlider saturation, temperature, tint, shadows, highlights;
- private JComboBox presetCombo;
+ private WhiteBalanceSection awbSection;
+ private DNRSection dnrSection;
+ private ColorSection colorSection;
+ private DetailSection detailSection;
private float[] currentGains = {1f, 1f, 1f};
public FilterPanel(CameraPanel cameraPanel) {
this.cameraPanel = cameraPanel;
- this.autoGainProcessor = new AutoGainProcessor();
+ this.awbProcessor = new AWBProccessor();
this.presetLibrary = new PresetLibrary();
- // FIX: Randomize color with high saturation so it stands out
- Random rand = new Random();
- this.connectionColor = Color.getHSBColor(rand.nextFloat(), 0.9f, 0.9f);
-
- setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
- setBorder(new EmptyBorder(10, 10, 10, 10));
-
+ initializePanel();
buildUI();
- initListeners(); // Initialize anti-ghosting listeners
+ initGlobalListeners();
}
+ private void initializePanel() {
+ setLayout(new BorderLayout());
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+ }
private void buildUI() {
- add(createPresetPanel());
- add(Box.createRigidArea(new Dimension(0, 10)));
+ PresetSection presetSection = new PresetSection(presetLibrary, this::applyPresetToUI, this::getCurrentState);
+ add(presetSection, BorderLayout.NORTH);
- JPanel container = new JPanel();
- container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
+ JPanel scrollContainer = new JPanel();
+ scrollContainer.setLayout(new BoxLayout(scrollContainer, BoxLayout.Y_AXIS));
- container.add(createAWBPanel());
- container.add(Box.createRigidArea(new Dimension(0, 10)));
- container.add(createDNRPanel());
- container.add(Box.createRigidArea(new Dimension(0, 10)));
- container.add(createColorPanel());
- container.add(Box.createRigidArea(new Dimension(0, 10)));
- container.add(createDetailPanel());
+ awbSection = new WhiteBalanceSection(this::performOneTimeBalance, this::applyToCamera);
+ dnrSection = new DNRSection(this::applyToCamera);
+ colorSection = new ColorSection(this::applyToCamera);
+ detailSection = new DetailSection(this::applyToCamera);
- JScrollPane scroll = new JScrollPane(container);
- scroll.setBorder(null);
- scroll.getVerticalScrollBar().setUnitIncrement(16);
- add(scroll);
+ scrollContainer.add(awbSection);
+ scrollContainer.add(Box.createRigidArea(new Dimension(0, 10)));
+ scrollContainer.add(dnrSection);
+ scrollContainer.add(Box.createRigidArea(new Dimension(0, 10)));
+ scrollContainer.add(colorSection);
+ scrollContainer.add(Box.createRigidArea(new Dimension(0, 10)));
+ scrollContainer.add(detailSection);
- add(Box.createRigidArea(new Dimension(0, 10)));
- add(createActionPanel());
+ add(UIFactory.createTransparentScrollPane(scrollContainer), BorderLayout.CENTER);
+
+ JPanel footer = new JPanel(new GridLayout(1, 1, 5, 5));
+ footer.setBorder(new EmptyBorder(10, 0, 0, 0));
+ footer.add(UIFactory.createActionButton("Reset All Factory Settings", e -> resetUI()));
+ add(footer, BorderLayout.SOUTH);
}
public EffectState getCurrentState() {
return new EffectState(
- awbEnabled.isSelected(), awbStrength.getValue(),
- dnrEnabled.isSelected(), dnrStrength.getValue(), dnrTemporal.getValue(),
- temperature.getValue(), tint.getValue(), saturation.getValue(),
- shadows.getValue(), highlights.getValue(),
- sharpness.getValue(), edgeEnhance.isSelected()
+ awbSection.isEnabled(), awbSection.getStrength(),
+ dnrSection.isEnabled(), dnrSection.getSpatial(), dnrSection.getTemporal(),
+ colorSection.getTemp(), colorSection.getTint(), colorSection.getSaturation(),
+ colorSection.getShadows(), colorSection.getHighlights(),
+ detailSection.getSharpness(), detailSection.isEdgeEnhanceEnabled()
);
}
- // Inside AdvancedEffectsPanel.java
-
- private void initListeners() {
- ComponentAdapter repaintListener = new ComponentAdapter() {
- @Override
- public void componentMoved(ComponentEvent e) {
- updateOverlay();
- }
- @Override
- public void componentResized(ComponentEvent e) {
- updateOverlay();
- }
- };
-
- this.addComponentListener(repaintListener);
- if (this.cameraPanel != null) {
- this.cameraPanel.addComponentListener(repaintListener);
- }
- }
-
- private void updateOverlay() {
- RootPaneContainer root = (RootPaneContainer) SwingUtilities.getWindowAncestor(this);
- if (root != null) {
- root.getGlassPane().repaint();
- }
- }
-
private void applyToCamera() {
+ if (cameraPanel == null) return;
+
EffectState state = getCurrentState();
cameraPanel.setImageProcessor(img ->
ImageEffectEngine.applyEffects(img, state, currentGains)
);
}
- private JPanel createPresetPanel() {
- JPanel panel = new JPanel(new BorderLayout(5, 5));
- panel.setBorder(createTitledBorder("Presets"));
-
- presetCombo = new JComboBox<>(new String[]{"Custom", "Natural", "Vivid", "Portrait", "Low Light", "Cinematic"});
- JButton saveBtn = new JButton("Save");
-
- presetCombo.addActionListener(e -> {
- String selected = (String) presetCombo.getSelectedItem();
- EffectState state = presetLibrary.get(selected);
- if (state != null) applyPresetToUI(state);
- });
-
- saveBtn.addActionListener(e -> {
- String name = JOptionPane.showInputDialog(this, "Preset Name:");
- if (name != null) {
- presetLibrary.savePreset(name, getCurrentState());
- presetCombo.addItem(name);
- }
- });
-
- panel.add(presetCombo, BorderLayout.CENTER);
- panel.add(saveBtn, BorderLayout.EAST);
- return panel;
- }
-
- private JPanel createDNRPanel() {
- JPanel panel = new JPanel();
- panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
- panel.setBorder(createTitledBorder("3D Denoise (DNR)"));
-
- dnrEnabled = new JCheckBox("Enable Temporal Denoise");
- dnrStrength = addSliderToPanel(panel, "Spatial Strength", 0, 100, 30, "%");
- dnrTemporal = addSliderToPanel(panel, "Temporal Strength", 0, 100, 50, "%");
-
- dnrEnabled.addActionListener(e -> {
- boolean enabled = dnrEnabled.isSelected();
- dnrStrength.setEnabled(enabled);
- dnrTemporal.setEnabled(enabled);
- applyToCamera();
- });
-
- dnrStrength.setEnabled(false);
- dnrTemporal.setEnabled(false);
-
- panel.add(dnrEnabled, 0);
- return panel;
- }
-
- private JPanel createDetailPanel() {
- JPanel panel = new JPanel();
- panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
- panel.setBorder(createTitledBorder("Detail & Sharpness"));
-
- sharpness = addSliderToPanel(panel, "Sharpness", 0, 200, 100, "%");
- edgeEnhance = new JCheckBox("Edge Enhancement");
-
- edgeEnhance.addActionListener(e -> applyToCamera());
-
- panel.add(edgeEnhance);
- return panel;
- }
-
- private JPanel createAWBPanel() {
- JPanel panel = new JPanel();
- panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
- panel.setBorder(createTitledBorder("White Balance"));
-
- awbEnabled = new JCheckBox("Enable AWB");
- JPanel sPanel = createSliderPanel("Strength", 0, 100, 100, "%");
- awbStrength = (JSlider) sPanel.getComponent(1);
- JButton balanceBtn = new JButton("Balance Now");
-
- awbEnabled.addActionListener(e -> {
- boolean active = awbEnabled.isSelected();
- awbStrength.setEnabled(active);
- balanceBtn.setEnabled(active);
- if (active) performOneTimeBalance();
- else { currentGains = new float[]{1f, 1f, 1f}; applyToCamera(); }
- });
-
- awbStrength.addChangeListener(e -> { if(!awbStrength.getValueIsAdjusting()) applyToCamera(); });
- balanceBtn.addActionListener(e -> performOneTimeBalance());
-
- panel.add(awbEnabled);
- panel.add(sPanel);
- panel.add(balanceBtn);
- return panel;
- }
-
- private JPanel createColorPanel() {
- JPanel panel = new JPanel();
- panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
- panel.setBorder(createTitledBorder("Color Grading"));
-
- temperature = addSliderToPanel(panel, "Temperature", -100, 100, 0, "");
- tint = addSliderToPanel(panel, "Tint", -100, 100, 0, "");
- saturation = addSliderToPanel(panel, "Saturation", 0, 200, 100, "%");
- shadows = addSliderToPanel(panel, "Shadows", -100, 100, 0, "");
- highlights = addSliderToPanel(panel, "Highlights", -100, 100, 0, "");
-
- return panel;
- }
-
+ /**
+ * call awb stuff
+ */
private void performOneTimeBalance() {
- BufferedImage img = cameraPanel.getCurrentProcessedImage();
+ java.awt.image.BufferedImage img = cameraPanel.getCurrentProcessedImage();
if (img != null) {
int[] pixels = img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
- currentGains = autoGainProcessor.calculateAutoGains(pixels);
+ currentGains = awbProcessor.calculateAutoGains(pixels);
applyToCamera();
}
}
+ /**
+ * Update for a specified state
+ * */
private void applyPresetToUI(EffectState s) {
- awbEnabled.setSelected(s.awbEnabled);
- awbStrength.setValue(s.awbStrength);
- dnrEnabled.setSelected(s.dnrEnabled);
- dnrStrength.setValue(s.dnrSpatial);
- temperature.setValue(s.temperature);
- tint.setValue(s.tint);
- saturation.setValue(s.saturation);
- shadows.setValue(s.shadows);
- highlights.setValue(s.highlights);
- sharpness.setValue(s.sharpness);
- edgeEnhance.setSelected(s.edgeEnhance);
+ awbSection.setState(s.awbEnabled(), s.awbStrength());
+ dnrSection.setState(s.dnrEnabled(), s.dnrSpatial(), s.dnrTemporal());
+ colorSection.setState(s.temperature(), s.tint(), s.saturation(), s.shadows(), s.highlights());
+ detailSection.setState(s.sharpness(), s.edgeEnhance());
+
+ // Reset gains if AWB is disabled in the preset
+ if (!s.awbEnabled()) currentGains = new float[]{1f, 1f, 1f};
+
applyToCamera();
}
- private JSlider addSliderToPanel(JPanel parent, String label, int min, int max, int val, String unit) {
- JPanel p = createSliderPanel(label, min, max, val, unit);
- JSlider s = (JSlider) p.getComponent(1);
- s.addChangeListener(e -> { if(!s.getValueIsAdjusting()) applyToCamera(); });
- parent.add(p);
- parent.add(Box.createRigidArea(new Dimension(0, 5)));
- return s;
- }
-
- private JPanel createSliderPanel(String label, int min, int max, int initial, String unit) {
- JPanel panel = new JPanel(new BorderLayout());
- JLabel title = new JLabel(label + ": " + initial + unit);
- JSlider slider = new JSlider(min, max, initial);
- slider.addChangeListener(e -> title.setText(label + ": " + slider.getValue() + unit));
- panel.add(title, BorderLayout.NORTH);
- panel.add(slider, BorderLayout.CENTER);
- return panel;
- }
-
- private TitledBorder createTitledBorder(String title) {
- return BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), title);
- }
-
- private JPanel createActionPanel() {
- JPanel panel = new JPanel(new GridLayout(1, 2, 10, 0));
- JButton reset = new JButton("Reset All");
- reset.addActionListener(e -> resetUI());
- panel.add(reset);
- return panel;
- }
-
private void resetUI() {
applyPresetToUI(new EffectState(false, 100, false, 30, 50, 0, 0, 100, 0, 0, 100, false));
}
+
+ private void initGlobalListeners() {
+ java.awt.event.ComponentAdapter repaintListener = new java.awt.event.ComponentAdapter() {
+ @Override public void componentMoved(java.awt.event.ComponentEvent e) { updateOverlay(); }
+ @Override public void componentResized(java.awt.event.ComponentEvent e) { updateOverlay(); }
+ };
+
+ this.addComponentListener(repaintListener);
+ if (cameraPanel != null) cameraPanel.addComponentListener(repaintListener);
+ }
+
+ private void updateOverlay() {
+ RootPaneContainer root = (RootPaneContainer) SwingUtilities.getWindowAncestor(this);
+ if (root != null) root.getGlassPane().repaint();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ImageEffectEngine.java b/src/main/java/io/swtc/proccessing/ImageEffectEngine.java
index 65eb22f..1a763ff 100644
--- a/src/main/java/io/swtc/proccessing/ImageEffectEngine.java
+++ b/src/main/java/io/swtc/proccessing/ImageEffectEngine.java
@@ -4,144 +4,37 @@ import java.awt.image.BufferedImage;
public class ImageEffectEngine {
+ private static final ColorProccessor colorProcessor = new ColorProccessor();
+ private static final DenoiseProccessor denoiseProcessor = new DenoiseProccessor();
+ private static final SharpnessProccessor sharpnessProcessor = new SharpnessProccessor();
+
public static BufferedImage applyEffects(BufferedImage img, EffectState state, float[] currentGains) {
if (img == null) return null;
+ // 1. Extract raw data (High Performance)
int width = img.getWidth();
int height = img.getHeight();
- BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ int[] pixels = ImageUtils.getPixels(img);
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- int rgb = img.getRGB(x, y);
- int r = (rgb >> 16) & 0xFF;
- int g = (rgb >> 8) & 0xFF;
- int b = rgb & 0xFF;
+ // NOTE: If we want to avoid modifying the original image's backing array,
+ // we should clone 'pixels' here. If in-place modification is okay, we proceed.
+ // int[] workingPixels = pixels.clone();
+ int[] workingPixels = pixels; // Assuming in-place is fine for performance
- // 1. AWB
- if (state.awbEnabled) {
- float s = state.awbStrength / 100f;
- r = (int) Math.min(255, r * (1 + (currentGains[0] - 1) * s));
- g = (int) Math.min(255, g * (1 + (currentGains[1] - 1) * s));
- b = (int) Math.min(255, b * (1 + (currentGains[2] - 1) * s));
- }
+ // 2. Apply Color Pipeline (In-Place)
+ colorProcessor.process(workingPixels, state, currentGains);
- // 2. Temp & Tint
- if (state.temperature != 0) {
- float factor = state.temperature / 100f;
- r = clamp(r + (int)(factor * 30));
- b = clamp(b - (int)(factor * 30));
- }
- if (state.tint != 0) {
- g = clamp(g + (int)((state.tint / 100f) * 20));
- }
-
- // 3. Saturation
- if (state.saturation != 100) {
- float factor = state.saturation / 100f;
- float gray = (r + g + b) / 3f;
- r = clamp((int)(gray + (r - gray) * factor));
- g = clamp((int)(gray + (g - gray) * factor));
- b = clamp((int)(gray + (b - gray) * factor));
- }
-
- // 4. Shadows/Highlights
- float lum = (r + g + b) / 3f / 255f;
- if (lum < 0.5f && state.shadows != 0) {
- int adj = (int)((state.shadows / 100f) * (1 - lum * 2) * 50);
- r = clamp(r + adj); g = clamp(g + adj); b = clamp(b + adj);
- } else if (lum > 0.5f && state.highlights != 0) {
- int adj = (int)((state.highlights / 100f) * (lum * 2 - 1) * 50);
- r = clamp(r + adj); g = clamp(g + adj); b = clamp(b + adj);
- }
-
- result.setRGB(x, y, (r << 16) | (g << 8) | b);
- }
+ // 3. Apply Sharpness (Returns new array if applied)
+ if (state.sharpness() != 100 || state.edgeEnhance()) {
+ workingPixels = sharpnessProcessor.process(workingPixels, width, height, state.sharpness(), state.edgeEnhance());
}
- if (state.sharpness != 100 || state.edgeEnhance) {
- result = applySharpness(result, state.sharpness / 100f, state.edgeEnhance);
+ // 4. Apply Denoise (Returns new array if applied)
+ if (state.dnrEnabled()) {
+ workingPixels = denoiseProcessor.process(workingPixels, width, height, state.dnrSpatial());
}
- if (state.dnrEnabled) {
- result = applyDenoise(result, state.dnrSpatial / 100f);
- }
-
- return result;
- }
-
- private static BufferedImage applySharpness(BufferedImage img, float amount, boolean edgeEnhance) {
- if (amount == 1f && !edgeEnhance) return img;
-
- int width = img.getWidth();
- int height = img.getHeight();
- BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
-
- float[][] kernel = edgeEnhance ?
- new float[][]{{0, -1, 0}, {-1, 5, -1}, {0, -1, 0}} :
- new float[][]{{-1, -1, -1}, {-1, 9, -1}, {-1, -1, -1}};
-
- for (int y = 1; y < height - 1; y++) {
- for (int x = 1; x < width - 1; x++) {
- float r = 0, g = 0, b = 0;
-
- for (int ky = -1; ky <= 1; ky++) {
- for (int kx = -1; kx <= 1; kx++) {
- int rgb = img.getRGB(x + kx, y + ky);
- float weight = kernel[ky + 1][kx + 1] * (amount - 1) / 8f;
- if (kx == 0 && ky == 0) weight += 1;
-
- r += ((rgb >> 16) & 0xFF) * weight;
- g += ((rgb >> 8) & 0xFF) * weight;
- b += (rgb & 0xFF) * weight;
- }
- }
-
- result.setRGB(x, y, (clamp((int)r) << 16) | (clamp((int)g) << 8) | clamp((int)b));
- }
- }
-
- return result;
- }
-
- private static BufferedImage applyDenoise(BufferedImage img, float strength) {
- if (strength == 0) return img;
-
- int width = img.getWidth();
- int height = img.getHeight();
- BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
-
- int radius = (int)(strength * 2) + 1;
-
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- int rSum = 0, gSum = 0, bSum = 0, count = 0;
-
- for (int dy = -radius; dy <= radius; dy++) {
- for (int dx = -radius; dx <= radius; dx++) {
- int nx = Math.min(width - 1, Math.max(0, x + dx));
- int ny = Math.min(height - 1, Math.max(0, y + dy));
-
- int rgb = img.getRGB(nx, ny);
- rSum += (rgb >> 16) & 0xFF;
- gSum += (rgb >> 8) & 0xFF;
- bSum += rgb & 0xFF;
- count++;
- }
- }
-
- int r = rSum / count;
- int g = gSum / count;
- int b = bSum / count;
-
- result.setRGB(x, y, (r << 16) | (g << 8) | b);
- }
- }
-
- return result;
- }
-
- private static int clamp(int val) {
- return Math.max(0, Math.min(255, val));
+ // 5. Reconstruct Image
+ return ImageUtils.createFromPixels(workingPixels, width, height);
}
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ImageUtils.java b/src/main/java/io/swtc/proccessing/ImageUtils.java
new file mode 100644
index 0000000..1d8117a
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ImageUtils.java
@@ -0,0 +1,36 @@
+package io.swtc.proccessing;
+
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+
+public class ImageUtils {
+
+ public static int[] getPixels(BufferedImage img) {
+ return ((DataBufferInt) ensureIntRGB(img).getRaster().getDataBuffer()).getData();
+ }
+
+ public static BufferedImage createFromPixels(int[] pixels, int width, int height) {
+ BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ int[] dst = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
+ System.arraycopy(pixels, 0, dst, 0, pixels.length);
+ return img;
+ }
+
+ public static BufferedImage ensureIntRGB(BufferedImage img) {
+ if (img.getType() == BufferedImage.TYPE_INT_RGB) {
+ return img;
+ }
+ BufferedImage newImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
+ Graphics g = newImg.getGraphics();
+ g.drawImage(img, 0, 0, null);
+ g.dispose();
+ return newImg;
+ }
+
+ public static int clamp(int val) {
+ if (val < 0) return 0;
+ if (val > 255) return 255;
+ return val;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/SharpnessProccessor.java b/src/main/java/io/swtc/proccessing/SharpnessProccessor.java
new file mode 100644
index 0000000..4dd36cb
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/SharpnessProccessor.java
@@ -0,0 +1,70 @@
+package io.swtc.proccessing;
+
+import java.util.stream.IntStream;
+
+public class SharpnessProccessor {
+
+ public int[] process(int[] srcPixels, int width, int height, float amount, boolean edgeEnhance) {
+ if (amount == 0 && !edgeEnhance) return srcPixels;
+
+ int[] dstPixels = new int[srcPixels.length];
+
+ // Normalization setup
+ float centerWeight = edgeEnhance ? 5f : 9f;
+ float neighborWeight = -1f;
+ float strength = (amount / 100f);
+ // Adjust strength scaling to match your original "amount - 1 / 8f" logic if needed,
+ // but typically sharpness is 0.0 to 1.0.
+ // Adapting to your specific previous math:
+ float weightFactor = (amount / 100f - 1) / 8f;
+
+ // Parallel loop skipping borders
+ IntStream.range(1, height - 1).parallel().forEach(y -> {
+ int yOffset = y * width;
+ for (int x = 1; x < width - 1; x++) {
+ int i = yOffset + x;
+
+ float rAcc = 0, gAcc = 0, bAcc = 0;
+
+ // Center
+ int pC = srcPixels[i];
+ float wC = centerWeight * weightFactor + 1.0f;
+ rAcc += ((pC >> 16) & 0xFF) * wC;
+ gAcc += ((pC >> 8) & 0xFF) * wC;
+ bAcc += (pC & 0xFF) * wC;
+
+ // Neighbors (North, South, East, West)
+ int[] neighbors = {
+ srcPixels[i - width], srcPixels[i + width],
+ srcPixels[i - 1], srcPixels[i + 1]
+ };
+
+ float wN = neighborWeight * weightFactor;
+ for(int p : neighbors) {
+ rAcc += ((p >> 16) & 0xFF) * wN;
+ gAcc += ((p >> 8) & 0xFF) * wN;
+ bAcc += (p & 0xFF) * wN;
+ }
+
+ // Diagonals (only if not edge enhance mode, per your original code)
+ if (!edgeEnhance) {
+ int[] diags = {
+ srcPixels[i - width - 1], srcPixels[i - width + 1],
+ srcPixels[i + width - 1], srcPixels[i + width + 1]
+ };
+ for(int p : diags) {
+ rAcc += ((p >> 16) & 0xFF) * wN;
+ gAcc += ((p >> 8) & 0xFF) * wN;
+ bAcc += (p & 0xFF) * wN;
+ }
+ }
+
+ dstPixels[i] = (ImageUtils.clamp((int)rAcc) << 16) |
+ (ImageUtils.clamp((int)gAcc) << 8) |
+ ImageUtils.clamp((int)bAcc);
+ }
+ });
+
+ return dstPixels;
+ }
+}
\ 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 77ba110..fbac654 100644
--- a/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java
+++ b/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java
@@ -34,7 +34,18 @@ public class WebcamCaptureLoop {
Thread captureThread = new Thread(() -> {
// this is where we open it. if the res isnt known then it fucks up.
- webcam.open();
+ try {
+ webcam.open();
+ } catch (WebcamException e) {
+ JOptionPane.showMessageDialog(
+ null,
+ "WebcamException" + e.getMessage(),
+ "WebcamException",
+ JOptionPane.ERROR_MESSAGE
+ );
+ } finally {
+ webcam.open();
+ }
while (running) {
BufferedImage img = webcam.getImage();
diff --git a/src/main/java/io/swtc/proccessing/ui/FilterSection.java b/src/main/java/io/swtc/proccessing/ui/FilterSection.java
new file mode 100644
index 0000000..6eb3966
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/FilterSection.java
@@ -0,0 +1,29 @@
+package io.swtc.proccessing.ui;
+
+import javax.swing.*;
+import java.awt.*;
+
+public abstract class FilterSection extends JPanel {
+
+ public FilterSection(String title) {
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ setBorder(BorderFactory.createTitledBorder(
+ BorderFactory.createEtchedBorder(), title));
+ }
+
+ /**
+ * Helper to create and add a slider in one line
+ */
+ protected LabeledSlider addSlider(String label, int min, int max, int val, String unit) {
+ LabeledSlider ls = new LabeledSlider(label, min, max, val, unit);
+ add(ls);
+ return ls;
+ }
+
+ /**
+ * Helper to add spacing between elements
+ */
+ protected void addPadding(int height) {
+ add(Box.createRigidArea(new Dimension(0, height)));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/LabeledSlider.java b/src/main/java/io/swtc/proccessing/ui/LabeledSlider.java
new file mode 100644
index 0000000..58904f9
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/LabeledSlider.java
@@ -0,0 +1,45 @@
+package io.swtc.proccessing.ui;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+
+public class LabeledSlider extends JPanel {
+ private final JSlider slider;
+ private final JLabel label;
+ private final String title;
+ private final String unit;
+
+ public LabeledSlider(String title, int min, int max, int value, String unit) {
+ this.title = title;
+ this.unit = unit;
+ setLayout(new BorderLayout());
+
+ label = new JLabel(title + ": " + value + unit);
+ slider = new JSlider(min, max, value);
+
+ // Internal listener to update the text label as user drags
+ slider.addChangeListener(e -> updateLabel());
+
+ add(label, BorderLayout.NORTH);
+ add(slider, BorderLayout.CENTER);
+ setBorder(new EmptyBorder(5, 0, 5, 0));
+ }
+
+ private void updateLabel() {
+ label.setText(title + ": " + slider.getValue() + unit);
+ }
+
+ public int getValue() { return slider.getValue(); }
+
+ public void setValue(int val) {
+ slider.setValue(val);
+ updateLabel();
+ }
+
+ public JSlider getSlider() { return slider; }
+
+ public void addChangeListener(javax.swing.event.ChangeListener cl) {
+ slider.addChangeListener(cl);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/UIFactory.java b/src/main/java/io/swtc/proccessing/ui/UIFactory.java
new file mode 100644
index 0000000..8fa07d6
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/UIFactory.java
@@ -0,0 +1,21 @@
+package io.swtc.proccessing.ui;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+
+public class UIFactory {
+
+ public static JButton createActionButton(String text, java.awt.event.ActionListener listener) {
+ JButton btn = new JButton(text);
+ btn.addActionListener(listener);
+ return btn;
+ }
+
+ public static JScrollPane createTransparentScrollPane(Component view) {
+ JScrollPane scroll = new JScrollPane(view);
+ scroll.setBorder(null);
+ scroll.getVerticalScrollBar().setUnitIncrement(16);
+ return scroll;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/sections/ColorSection.java b/src/main/java/io/swtc/proccessing/ui/sections/ColorSection.java
new file mode 100644
index 0000000..c62e1f9
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/sections/ColorSection.java
@@ -0,0 +1,37 @@
+package io.swtc.proccessing.ui.sections;
+
+import io.swtc.proccessing.ui.FilterSection;
+import io.swtc.proccessing.ui.LabeledSlider;
+
+public class ColorSection extends FilterSection {
+ private final LabeledSlider temp, tint, sat, shadows, highlights;
+
+ public ColorSection(Runnable onUpdate) {
+ super("Color Grading");
+
+ temp = addSlider("Temperature", -100, 100, 0, "");
+ tint = addSlider("Tint", -100, 100, 0, "");
+ sat = addSlider("Saturation", 0, 200, 100, "%");
+ shadows = addSlider("Shadows", -100, 100, 0, "");
+ highlights = addSlider("Highlights", -100, 100, 0, "");
+
+ LabeledSlider[] sliders = {temp, tint, sat, shadows, highlights};
+ for (LabeledSlider s : sliders) {
+ s.addChangeListener(e -> { if(!s.getSlider().getValueIsAdjusting()) onUpdate.run(); });
+ }
+ }
+
+ public int getTemp() { return temp.getValue(); }
+ public int getTint() { return tint.getValue(); }
+ public int getSaturation() { return sat.getValue(); }
+ public int getShadows() { return shadows.getValue(); }
+ public int getHighlights() { return highlights.getValue(); }
+
+ public void setState(int t, int ti, int s, int sh, int hi) {
+ temp.setValue(t);
+ tint.setValue(ti);
+ sat.setValue(s);
+ shadows.setValue(sh);
+ highlights.setValue(hi);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java b/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java
new file mode 100644
index 0000000..ffdc94d
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java
@@ -0,0 +1,48 @@
+package io.swtc.proccessing.ui.sections;
+
+import io.swtc.proccessing.ui.FilterSection;
+import io.swtc.proccessing.ui.LabeledSlider;
+import javax.swing.*;
+
+public class DNRSection extends FilterSection {
+ private final JCheckBox enabled;
+ private final LabeledSlider spatial;
+ private final LabeledSlider temporal;
+
+ public DNRSection(Runnable onUpdate) {
+ super("3D Denoise (DNR)");
+
+ enabled = new JCheckBox("Enable Temporal Denoise");
+ spatial = addSlider("Spatial Strength", 0, 100, 30, "%");
+ temporal = addSlider("Temporal Strength", 0, 100, 50, "%");
+
+ // Logic: Disable sliders if DNR is off
+ enabled.addActionListener(e -> {
+ updateEnabledStates();
+ onUpdate.run();
+ });
+
+ spatial.addChangeListener(e -> { if(!spatial.getSlider().getValueIsAdjusting()) onUpdate.run(); });
+ temporal.addChangeListener(e -> { if(!temporal.getSlider().getValueIsAdjusting()) onUpdate.run(); });
+
+ add(enabled, 0); // Checkbox at top
+ updateEnabledStates();
+ }
+
+ private void updateEnabledStates() {
+ boolean active = enabled.isSelected();
+ spatial.getSlider().setEnabled(active);
+ temporal.getSlider().setEnabled(active);
+ }
+
+ public boolean isDnrEnabled() { return enabled.isSelected(); }
+ public int getSpatial() { return spatial.getValue(); }
+ public int getTemporal() { return temporal.getValue(); }
+
+ public void setState(boolean isEnabled, int s, int t) {
+ enabled.setSelected(isEnabled);
+ spatial.setValue(s);
+ temporal.setValue(t);
+ updateEnabledStates();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java b/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java
new file mode 100644
index 0000000..d051b24
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java
@@ -0,0 +1,33 @@
+package io.swtc.proccessing.ui.sections;
+
+import io.swtc.proccessing.ui.FilterSection;
+import io.swtc.proccessing.ui.LabeledSlider;
+import javax.swing.*;
+
+public class DetailSection extends FilterSection {
+ private final LabeledSlider sharpness;
+ private final JCheckBox edgeEnhance;
+
+ public DetailSection(Runnable onUpdate) {
+ super("Detail & Sharpness");
+
+ sharpness = addSlider("Sharpness", 0, 200, 100, "%");
+ edgeEnhance = new JCheckBox("Edge Enhancement");
+
+ sharpness.addChangeListener(e -> {
+ if (!sharpness.getSlider().getValueIsAdjusting()) onUpdate.run();
+ });
+
+ edgeEnhance.addActionListener(e -> onUpdate.run());
+
+ add(edgeEnhance);
+ }
+
+ public int getSharpness() { return sharpness.getValue(); }
+ public boolean isEdgeEnhanceEnabled() { return edgeEnhance.isSelected(); }
+
+ public void setState(int sharpVal, boolean edgeActive) {
+ sharpness.setValue(sharpVal);
+ edgeEnhance.setSelected(edgeActive);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/sections/PresetSection.java b/src/main/java/io/swtc/proccessing/ui/sections/PresetSection.java
new file mode 100644
index 0000000..9f10ccb
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/sections/PresetSection.java
@@ -0,0 +1,39 @@
+package io.swtc.proccessing.ui.sections;
+
+import io.swtc.proccessing.EffectState;
+import io.swtc.proccessing.PresetLibrary;
+import javax.swing.*;
+import java.awt.*;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class PresetSection extends JPanel {
+ private final JComboBox presetCombo;
+ private final JButton saveBtn;
+
+ public PresetSection(PresetLibrary library, Consumer onPresetSelected, Supplier stateSupplier) {
+ setLayout(new BorderLayout(5, 5));
+ setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Presets"));
+
+ presetCombo = new JComboBox<>(new String[]{"Custom", "Natural", "Vivid", "Portrait", "Low Light", "Cinematic"});
+ saveBtn = new JButton("Save");
+
+ presetCombo.addActionListener(e -> {
+ String selected = (String) presetCombo.getSelectedItem();
+ EffectState state = library.get(selected);
+ if (state != null) onPresetSelected.accept(state);
+ });
+
+ saveBtn.addActionListener(e -> {
+ String name = JOptionPane.showInputDialog(this, "Preset Name:");
+ if (name != null && !name.trim().isEmpty()) {
+ library.savePreset(name, stateSupplier.get());
+ presetCombo.addItem(name);
+ presetCombo.setSelectedItem(name);
+ }
+ });
+
+ add(presetCombo, BorderLayout.CENTER);
+ add(saveBtn, BorderLayout.EAST);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java b/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java
new file mode 100644
index 0000000..10a329a
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java
@@ -0,0 +1,49 @@
+package io.swtc.proccessing.ui.sections;
+
+import io.swtc.proccessing.ui.FilterSection;
+import io.swtc.proccessing.ui.LabeledSlider;
+import javax.swing.*;
+
+public class WhiteBalanceSection extends FilterSection {
+ private final JCheckBox enabled;
+ private final LabeledSlider strength;
+ private final JButton balanceBtn;
+
+ public WhiteBalanceSection(Runnable onBalanceNow, Runnable onUpdate) {
+ super("White Balance");
+
+ enabled = new JCheckBox("Enable AWB");
+ strength = addSlider("Strength", 0, 100, 100, "%");
+ balanceBtn = new JButton("Balance Now");
+
+ enabled.addActionListener(e -> {
+ updateEnabledStates();
+ onUpdate.run();
+ });
+
+ strength.addChangeListener(e -> {
+ if (!strength.getSlider().getValueIsAdjusting()) onUpdate.run();
+ });
+
+ balanceBtn.addActionListener(e -> onBalanceNow.run());
+
+ add(enabled, 0);
+ add(balanceBtn);
+ updateEnabledStates();
+ }
+
+ private void updateEnabledStates() {
+ boolean active = enabled.isSelected();
+ strength.getSlider().setEnabled(active);
+ balanceBtn.setEnabled(active);
+ }
+
+ public boolean isEnabled() { return enabled.isSelected(); }
+ public int getStrength() { return strength.getValue(); }
+
+ public void setState(boolean isEnabled, int str) {
+ enabled.setSelected(isEnabled);
+ strength.setValue(str);
+ updateEnabledStates();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/AutoGainProcessorTest.java b/src/test/java/AWBProccessorTest.java
similarity index 94%
rename from src/test/java/AutoGainProcessorTest.java
rename to src/test/java/AWBProccessorTest.java
index 3bc4ab5..fc85e0c 100644
--- a/src/test/java/AutoGainProcessorTest.java
+++ b/src/test/java/AWBProccessorTest.java
@@ -1,4 +1,4 @@
-import io.swtc.proccessing.AutoGainProcessor;
+import io.swtc.proccessing.AWBProccessor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -13,13 +13,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* of displaying stuff.
* */
-class AutoGainProcessorTest {
+class AWBProccessorTest {
- private AutoGainProcessor processor;
+ private AWBProccessor processor;
@BeforeEach
void setUp() {
- processor = new AutoGainProcessor();
+ processor = new AWBProccessor();
}
@Test
From 565a4f3cf3272e33115e9dae694db174965a1cac Mon Sep 17 00:00:00 2001
From: rattatwinko
Date: Mon, 19 Jan 2026 13:04:40 +0100
Subject: [PATCH 3/5] fixed some functionallity added back some which was
removed cause of refactoring ...
some new refactoring to make the UI code cleaner
Signed-off-by: rattatwinko
---
src/main/java/io/swtc/Main.java | 6 +-
src/main/java/io/swtc/SwingIFrame.java | 287 ++----------------
.../ui/iframe/CameraInternalFrame.java | 132 ++++++++
.../proccessing/ui/iframe/DesktopPane.java | 66 ++++
.../ui/iframe/EffectsPanelFrame.java | 14 +
.../proccessing/ui/iframe/RecordingPane.java | 126 ++++++++
.../proccessing/ui/sections/DNRSection.java | 18 +-
.../ui/sections/DetailSection.java | 9 +-
.../ui/sections/WhiteBalanceSection.java | 10 +-
src/test/java/CameraSettingsTest.java | 98 ------
10 files changed, 393 insertions(+), 373 deletions(-)
create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java
create mode 100644 src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java
delete mode 100644 src/test/java/CameraSettingsTest.java
diff --git a/src/main/java/io/swtc/Main.java b/src/main/java/io/swtc/Main.java
index 84009e5..8733916 100644
--- a/src/main/java/io/swtc/Main.java
+++ b/src/main/java/io/swtc/Main.java
@@ -9,7 +9,7 @@ public class Main {
try {
UIManager.setLookAndFeel(
- "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
+ "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
);
} catch (Exception e) {
JOptionPane.showMessageDialog(
@@ -21,8 +21,6 @@ public class Main {
}
// For some reason we need to invoke Later for LaF to work!
- SwingUtilities.invokeLater(() -> {
- SwingCCTVManager.main(null);
- });
+ SwingUtilities.invokeLater(() -> SwingCCTVManager.main(null));
}
}
diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java
index ba6b382..bf0e269 100644
--- a/src/main/java/io/swtc/SwingIFrame.java
+++ b/src/main/java/io/swtc/SwingIFrame.java
@@ -1,29 +1,15 @@
package io.swtc;
import com.github.sarxos.webcam.Webcam;
-import io.swtc.proccessing.WebcamCaptureLoop;
-import io.swtc.proccessing.CameraPanel;
-
-import javax.imageio.ImageIO;
+import io.swtc.proccessing.ui.iframe.*;
import javax.swing.*;
-import javax.swing.border.EmptyBorder;
-import javax.swing.event.*;
import java.awt.*;
-import java.awt.event.ActionEvent;
-import java.awt.geom.CubicCurve2D;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.util.*;
+import java.util.HashMap;
+import java.util.Map;
public class SwingIFrame {
private final JFrame mainFrame;
- private final BlenderDesktopPane desktopPane;
- private boolean fullscreen = false;
- private Rectangle windowedBounds;
- private boolean blackbg = false;
- private final Color defDesktopBg;
- private final Color bgcolor;
-
+ private final DesktopPane desktopPane;
private final Map cameraToEffects = new HashMap<>();
public SwingIFrame() {
@@ -31,175 +17,48 @@ public class SwingIFrame {
mainFrame.setSize(1280, 720);
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- bgcolor = Color.decode("#1e1e1e");
-
- desktopPane = new BlenderDesktopPane(cameraToEffects);
+ desktopPane = new DesktopPane(cameraToEffects);
desktopPane.setBackground(Color.WHITE);
- defDesktopBg = desktopPane.getBackground();
mainFrame.add(desktopPane, BorderLayout.CENTER);
-
- setupFullscreenToggle();
- setupBlackBg();
}
public void addCameraInternalFrame(Webcam webcam) {
- JInternalFrame cameraFrame = new JInternalFrame(
- webcam.getName(),
- true, true, true, true
+ CameraInternalFrame cameraFrame = new CameraInternalFrame(webcam, this::handleEffectsRequest);
+
+ EffectsPanelFrame effectsFrame = new EffectsPanelFrame(
+ "Effects - " + webcam.getName(),
+ cameraFrame.getCameraPanel()
);
- CameraPanel cameraPanel = new CameraPanel();
- WebcamCaptureLoop captureLoop = new WebcamCaptureLoop(webcam, (BufferedImage img) ->
- SwingUtilities.invokeLater(() -> cameraPanel.setImage(img))
- );
+ cameraToEffects.put(cameraFrame, effectsFrame);
- JPanel contentPanel = new JPanel(new BorderLayout());
- JTabbedPane tabbedPane = new JTabbedPane();
-
- tabbedPane.addTab("View", cameraPanel);
- tabbedPane.addTab("Capture", createCapturePanel(cameraPanel, webcam));
- tabbedPane.addTab("Effects", new JPanel());
-
- contentPanel.add(tabbedPane, BorderLayout.CENTER);
- tabbedPane.setPreferredSize(null);
-
- // where we show the effectpanel (io.swtc.proccessing.FilterPanel.java)
- tabbedPane.addChangeListener(new ChangeListener() {
- @Override
- public void stateChanged(ChangeEvent e) {
- if (tabbedPane.getSelectedIndex() == 2) {
- tabbedPane.setSelectedIndex(0);
-
- EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame);
- if (effectsFrame != null) {
- effectsFrame.setVisible(true);
- try {
- effectsFrame.setSelected(true);
- } catch (java.beans.PropertyVetoException ex) {
- ex.printStackTrace();
- }
- }
- }
- }
- });
+ int offset = desktopPane.getAllFrames().length * 15;
+ cameraFrame.setLocation(50 + offset, 50 + offset);
+ effectsFrame.setLocation(700 + offset, 50 + offset);
+ effectsFrame.setVisible(false);
cameraFrame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
@Override
public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) {
- captureLoop.stop();
- EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame);
- if (effectsFrame != null) {
- effectsFrame.dispose();
- cameraToEffects.remove(cameraFrame);
- }
+ EffectsPanelFrame ef = cameraToEffects.remove(cameraFrame);
+ if (ef != null) ef.dispose();
+ desktopPane.forgetFrame(cameraFrame);
}
});
- cameraFrame.add(contentPanel);
- cameraFrame.setSize(600, 500);
-
- int offset = desktopPane.getAllFrames().length * 30;
- cameraFrame.setLocation(50 + offset, 50 + offset);
-
- // Pre-create the effects frame but keep it hidden
- EffectsPanelFrame effectsFrame = new EffectsPanelFrame(
- "Effects - " + webcam.getName(),
- cameraPanel,
- cameraFrame
- );
- effectsFrame.setSize(350, 600);
- effectsFrame.setLocation(700 + offset, 50 + offset);
- effectsFrame.setVisible(false); // Hidden by default
-
- cameraToEffects.put(cameraFrame, effectsFrame);
- addLinkageListeners(cameraFrame, effectsFrame);
-
desktopPane.add(cameraFrame);
desktopPane.add(effectsFrame);
-
cameraFrame.setVisible(true);
- captureLoop.start();
}
- private void addLinkageListeners(JInternalFrame cameraFrame, EffectsPanelFrame effectsFrame) {
- java.awt.event.ComponentAdapter linker = new java.awt.event.ComponentAdapter() {
- @Override
- public void componentMoved(java.awt.event.ComponentEvent e) {
- desktopPane.repaint();
- }
- @Override
- public void componentResized(java.awt.event.ComponentEvent e) {
- desktopPane.repaint();
- }
- };
- cameraFrame.addComponentListener(linker);
- effectsFrame.addComponentListener(linker);
- }
-
- // --- Standard UI Setup Methods ---
-
- 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) { setbgblack(); }
- });
- }
-
- 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 setbgblack() {
- if (!blackbg) {
- desktopPane.setBackground(bgcolor);
- } else {
- desktopPane.setBackground(defDesktopBg);
- }
- blackbg = !blackbg;
- }
-
- 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;
- }
-
- private JPanel createCapturePanel(CameraPanel cameraPanel, Webcam webcam) {
- JPanel panel = new JPanel(new BorderLayout(10, 10));
- panel.setBorder(new EmptyBorder(15, 15, 15, 15));
-
- JButton takeScreenshot = new JButton("Take Screenshot");
- takeScreenshot.addActionListener(e -> saveSnapshot(cameraPanel, webcam, System.getProperty("user.home"), panel));
-
- panel.add(takeScreenshot, BorderLayout.NORTH);
- return panel;
- }
-
- private void saveSnapshot(CameraPanel cameraPanel, Webcam webcam, String directory, Component parent) {
- BufferedImage img = cameraPanel.getCurrentProcessedImage();
- if (img != null) {
+ private void handleEffectsRequest(CameraInternalFrame source) {
+ EffectsPanelFrame effectsFrame = cameraToEffects.get(source);
+ if (effectsFrame != null) {
+ effectsFrame.setVisible(true);
try {
- File file = new File(directory, "snap.png");
- ImageIO.write(img, "PNG", file);
- } catch (Exception ex) {
- ex.printStackTrace();
- }
+ effectsFrame.setSelected(true);
+ effectsFrame.toFront();
+ } catch (java.beans.PropertyVetoException ex) { JOptionPane.showMessageDialog(null,"Exception" + ex.getMessage() , "Exception", JOptionPane.ERROR_MESSAGE); }
}
}
@@ -207,100 +66,4 @@ public class SwingIFrame {
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
}
-
- public static void main(String[] args) {
- SwingUtilities.invokeLater(() -> {
- SwingIFrame dashboard = new SwingIFrame();
- // Example usage: dashboard.addCameraInternalFrame(Webcam.getDefault());
- dashboard.show();
- });
- }
-
- // --- Inner Classes for Blender Styling ---
-
- static class BlenderDesktopPane extends JDesktopPane {
- private final Map connections;
-
- public BlenderDesktopPane(Map connections) {
- this.connections = connections;
- }
-
-
-
- @Override
- protected void paintChildren(Graphics g) {
- super.paintChildren(g);
- Graphics2D g2d = (Graphics2D) g.create();
- g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-
- for (Map.Entry entry : connections.entrySet()) {
- JInternalFrame camera = entry.getKey();
- EffectsPanelFrame effects = entry.getValue();
-
- if (camera.isVisible() && effects.isVisible() && !camera.isIcon() && !effects.isIcon()) {
- drawBezierConnection(g2d, camera, effects);
- }
- }
- g2d.dispose();
- }
-
- private void drawBezierConnection(Graphics2D g2d, JInternalFrame from, JInternalFrame to) {
- Rectangle f = from.getBounds();
- Rectangle t = to.getBounds();
-
- int x1 = f.x + f.width;
- int y1 = f.y + f.height / 2;
- int x2 = t.x;
- int y2 = t.y + t.height / 2;
-
- 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.setColor(new Color(100, 200, 255, 200));
- g2d.draw(curve);
-
- g2d.fillOval(x1 - 5, y1 - 5, 10, 10);
- g2d.fillOval(x2 - 5, y2 - 5, 10, 10);
- }
- }
-
- static class EffectsPanelFrame extends JInternalFrame {
-
- public EffectsPanelFrame(String title, CameraPanel cameraPanel, JInternalFrame cameraFrame) {
- super(title, true, true, true, true);
-
- // Hide instead of dispose so it can be reopened
- setDefaultCloseOperation(JInternalFrame.HIDE_ON_CLOSE);
-
- // Add your FilterPanel
- add(new io.swtc.proccessing.FilterPanel(cameraPanel));
-
- // Repaint desktop pane when this frame closes so the Bézier disappears
- addInternalFrameListener(new InternalFrameAdapter() {
- @Override
- public void internalFrameClosing(InternalFrameEvent e) {
- if (getDesktopPane() != null) {
- getDesktopPane().repaint();
- }
- }
- });
-
- // Repaint curves when moving or resizing
- addComponentListener(new java.awt.event.ComponentAdapter() {
- @Override
- public void componentMoved(java.awt.event.ComponentEvent e) {
- if (getDesktopPane() != null)
- getDesktopPane().repaint();
- }
-
- @Override
- public void componentResized(java.awt.event.ComponentEvent e) {
- if (getDesktopPane() != null)
- getDesktopPane().repaint();
- }
- });
- }
- }
-
}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java
new file mode 100644
index 0000000..127789a
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/iframe/CameraInternalFrame.java
@@ -0,0 +1,132 @@
+package io.swtc.proccessing.ui.iframe;
+
+import com.github.sarxos.webcam.Webcam;
+import io.swtc.proccessing.WebcamCaptureLoop;
+import io.swtc.proccessing.CameraPanel;
+import io.swtc.recording.VideoRecorder;
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.function.Consumer;
+
+public class CameraInternalFrame extends JInternalFrame {
+ private final WebcamCaptureLoop captureLoop;
+ private final CameraPanel cameraPanel;
+ private final VideoRecorder videoRecorder; // Instance of the recorder
+ private JButton recordBtn;
+
+ public CameraInternalFrame(Webcam webcam, Consumer onOpenEffects) {
+ super(webcam.getName(), true, true, true, true);
+ this.cameraPanel = new CameraPanel();
+ this.videoRecorder = new VideoRecorder(); // Initialize recorder
+
+ // Initialize capture loop
+ this.captureLoop = new WebcamCaptureLoop(webcam, img ->
+ SwingUtilities.invokeLater(() -> cameraPanel.setImage(img))
+ );
+
+ setupUI(onOpenEffects);
+ captureLoop.start();
+ }
+
+ private void setupUI(Consumer onOpenEffects) {
+ JTabbedPane tabbedPane = new JTabbedPane();
+ tabbedPane.addTab("View", cameraPanel);
+ tabbedPane.addTab("Capture", new RecordingPane(cameraPanel, videoRecorder));
+ tabbedPane.addTab("Effects", new JPanel());
+
+ tabbedPane.addChangeListener(e -> {
+ if (tabbedPane.getSelectedIndex() == 2) {
+ tabbedPane.setSelectedIndex(0);
+ onOpenEffects.accept(this);
+ }
+ });
+
+ add(tabbedPane);
+ setSize(600, 500);
+ }
+
+ private JPanel createCapturePanel() {
+ JPanel panel = new JPanel(new GridLayout(2, 1, 10, 10)); // Changed to Grid for better button layout
+ panel.setBorder(new EmptyBorder(15, 15, 15, 15));
+
+ JButton screenshotBtn = new JButton("Take Screenshot");
+ screenshotBtn.addActionListener(e -> saveSnapshot());
+
+ recordBtn = new JButton("Start Recording");
+ recordBtn.addActionListener(e -> toggleRecording());
+
+ panel.add(screenshotBtn);
+ panel.add(recordBtn);
+
+ // Wrap in a wrapper to prevent buttons from stretching too much
+ JPanel wrapper = new JPanel(new BorderLayout());
+ wrapper.add(panel, BorderLayout.NORTH);
+ return wrapper;
+ }
+
+ private void toggleRecording() {
+ if (!videoRecorder.isRecording()) {
+ startVideo();
+ } else {
+ stopVideo();
+ }
+ }
+
+ private void startVideo() {
+ try {
+ File file = new File(System.getProperty("user.home"), "vid_" + System.currentTimeMillis() + ".mp4");
+ videoRecorder.startRecording(cameraPanel, file);
+
+ recordBtn.setText("Stop Recording");
+ recordBtn.setForeground(Color.RED);
+ } catch (IOException ex) {
+ showError("Failed to start recording", ex);
+ }
+ }
+
+ private void stopVideo() {
+ try {
+ File savedFile = videoRecorder.stopRecording();
+
+ recordBtn.setText("Start Recording");
+ recordBtn.setForeground(Color.BLACK);
+
+ JOptionPane.showMessageDialog(this, "Video saved to: " + savedFile.getAbsolutePath());
+ } catch (IOException ex) {
+ showError("Failed to stop recording safely", ex);
+ }
+ }
+
+ private void saveSnapshot() {
+ BufferedImage img = cameraPanel.getCurrentProcessedImage();
+ if (img != null) {
+ try {
+ File file = new File(System.getProperty("user.home"), "snap_" + System.currentTimeMillis() + ".png");
+ ImageIO.write(img, "PNG", file);
+ } catch (Exception ex) {
+ showError("Snapshot failed", ex);
+ }
+ }
+ }
+
+ private void showError(String title, Exception ex) {
+ JOptionPane.showMessageDialog(this, title + "\n" + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ }
+
+ public CameraPanel getCameraPanel() { return cameraPanel; }
+
+ @Override
+ public void dispose() {
+ // Safety check: stop recording if the window is closed
+ if (videoRecorder.isRecording()) {
+ try { videoRecorder.stopRecording(); } catch (IOException ignored) {}
+ }
+ captureLoop.stop();
+ super.dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java b/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java
new file mode 100644
index 0000000..f317395
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java
@@ -0,0 +1,66 @@
+package io.swtc.proccessing.ui.iframe;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.CubicCurve2D;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+public class DesktopPane extends JDesktopPane {
+ private final Map connections;
+ private final Map connectionColors = new HashMap<>();
+
+ public DesktopPane(Map connections) {
+ this.connections = connections;
+ }
+
+ private Color getConnectionColor(JInternalFrame frame) {
+ return connectionColors.computeIfAbsent(frame, k -> {
+ Random rand = new Random();
+ return new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256), 200);
+ });
+ }
+
+ public void forgetFrame(JInternalFrame frame) {
+ connectionColors.remove(frame);
+ }
+
+ @Override
+ protected void paintChildren(Graphics g) {
+ super.paintChildren(g);
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ for (Map.Entry entry : connections.entrySet()) {
+ JInternalFrame camera = entry.getKey();
+ EffectsPanelFrame effects = entry.getValue();
+
+ if (camera.isVisible() && effects.isVisible() && !camera.isIcon() && !effects.isIcon()) {
+ g2d.setColor(getConnectionColor(camera));
+ drawBezierConnection(g2d, camera, effects);
+ }
+ }
+ g2d.dispose();
+ }
+
+ private void drawBezierConnection(Graphics2D g2d, JInternalFrame from, JInternalFrame to) {
+ Rectangle f = from.getBounds();
+ Rectangle t = to.getBounds();
+
+ int x1 = f.x + f.width;
+ int y1 = f.y + (f.height / 2);
+ int x2 = t.x;
+ int y2 = t.y + (t.height / 2);
+
+ 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);
+
+ // Terminals
+ g2d.fillOval(x1 - 5, y1 - 5, 10, 10);
+ g2d.fillOval(x2 - 5, y2 - 5, 10, 10);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java b/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java
new file mode 100644
index 0000000..a6533c1
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/iframe/EffectsPanelFrame.java
@@ -0,0 +1,14 @@
+package io.swtc.proccessing.ui.iframe;
+
+import io.swtc.proccessing.CameraPanel;
+import io.swtc.proccessing.FilterPanel;
+import javax.swing.*;
+
+public class EffectsPanelFrame extends JInternalFrame {
+ public EffectsPanelFrame(String title, CameraPanel cameraPanel) {
+ super(title, true, true, true, true);
+ setDefaultCloseOperation(HIDE_ON_CLOSE);
+ add(new FilterPanel(cameraPanel));
+ setSize(350, 600);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java
new file mode 100644
index 0000000..22b7d93
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java
@@ -0,0 +1,126 @@
+package io.swtc.proccessing.ui.iframe;
+
+import io.swtc.proccessing.CameraPanel;
+import io.swtc.recording.VideoRecorder;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+public class RecordingPane extends JPanel {
+ private final VideoRecorder videoRecorder;
+ private final CameraPanel cameraPanel;
+
+ private JTextField pathField;
+ private JButton recordBtn;
+ private JLabel statusLabel;
+ private File outputDirectory;
+
+ public RecordingPane(CameraPanel cameraPanel, VideoRecorder videoRecorder) {
+ this.cameraPanel = cameraPanel;
+ this.videoRecorder = videoRecorder;
+ this.outputDirectory = new File(System.getProperty("user.home"));
+
+ setLayout(new GridBagLayout());
+
+ JPanel contentPanel = new JPanel();
+ contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
+ contentPanel.setPreferredSize(new Dimension(400, 250));
+
+ // Add the functional sections
+ contentPanel.add(createStoragePanel());
+ contentPanel.add(Box.createVerticalStrut(15));
+ contentPanel.add(createActionPanel());
+ contentPanel.add(Box.createVerticalStrut(15));
+ contentPanel.add(createStatusPanel());
+
+ add(contentPanel);
+ }
+
+ private JPanel createStoragePanel() {
+ JPanel panel = new JPanel(new BorderLayout(5, 5));
+ panel.setBorder(BorderFactory.createTitledBorder(
+ BorderFactory.createEtchedBorder(), "Storage Settings", TitledBorder.LEFT, TitledBorder.TOP));
+
+ pathField = new JTextField(outputDirectory.getAbsolutePath());
+ pathField.setEditable(false);
+
+ JButton browseBtn = new JButton("Browse...");
+ browseBtn.addActionListener(e -> {
+ JFileChooser chooser = new JFileChooser(outputDirectory);
+ chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+ outputDirectory = chooser.getSelectedFile();
+ pathField.setText(outputDirectory.getAbsolutePath());
+ }
+ });
+
+ panel.add(pathField, BorderLayout.CENTER);
+ panel.add(browseBtn, BorderLayout.EAST);
+ return panel;
+ }
+
+ private JPanel createActionPanel() {
+ JPanel panel = new JPanel(new GridLayout(1, 2, 10, 10));
+
+ recordBtn = new JButton("start recording");
+ recordBtn.addActionListener(e -> toggleRecording());
+
+ JButton snapBtn = new JButton("take snapshot");
+ snapBtn.addActionListener(e -> takeSnapshot());
+
+ panel.add(recordBtn);
+ panel.add(snapBtn);
+ return panel;
+ }
+
+ private JPanel createStatusPanel() {
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ statusLabel = new JLabel("");
+ statusLabel.setForeground(Color.DARK_GRAY);
+ panel.add(statusLabel);
+ return panel;
+ }
+
+ private void toggleRecording() {
+ if (!videoRecorder.isRecording()) {
+ try {
+ File file = new File(outputDirectory, "vid_" + System.currentTimeMillis() + ".mp4");
+ videoRecorder.startRecording(cameraPanel, file);
+ recordBtn.setText("stop recording");
+ statusLabel.setText("recording");
+ } catch (IOException ex) {
+ showError("Start Error", ex);
+ }
+ } else {
+ try {
+ File saved = videoRecorder.stopRecording();
+ recordBtn.setText("Start Recording");
+ statusLabel.setText("Status: Saved " + saved.getName());
+ } catch (IOException ex) {
+ showError("Stop Error", ex);
+ }
+ }
+ }
+
+ private void takeSnapshot() {
+ BufferedImage img = cameraPanel.getCurrentProcessedImage();
+ if (img != null) {
+ try {
+ File file = new File(outputDirectory, "snap_" + System.currentTimeMillis() + ".png");
+ ImageIO.write(img, "PNG", file);
+ statusLabel.setText("captured");
+ } catch (IOException ex) {
+ showError("Snapshot Error", ex);
+ }
+ }
+ }
+
+ private void showError(String title, Exception ex) {
+ JOptionPane.showMessageDialog(this, ex.getMessage(), title, JOptionPane.ERROR_MESSAGE);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java b/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java
index ffdc94d..08f73c2 100644
--- a/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java
+++ b/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java
@@ -16,7 +16,6 @@ public class DNRSection extends FilterSection {
spatial = addSlider("Spatial Strength", 0, 100, 30, "%");
temporal = addSlider("Temporal Strength", 0, 100, 50, "%");
- // Logic: Disable sliders if DNR is off
enabled.addActionListener(e -> {
updateEnabledStates();
onUpdate.run();
@@ -25,7 +24,7 @@ public class DNRSection extends FilterSection {
spatial.addChangeListener(e -> { if(!spatial.getSlider().getValueIsAdjusting()) onUpdate.run(); });
temporal.addChangeListener(e -> { if(!temporal.getSlider().getValueIsAdjusting()) onUpdate.run(); });
- add(enabled, 0); // Checkbox at top
+ add(enabled, 0);
updateEnabledStates();
}
@@ -35,9 +34,18 @@ public class DNRSection extends FilterSection {
temporal.getSlider().setEnabled(active);
}
- public boolean isDnrEnabled() { return enabled.isSelected(); }
- public int getSpatial() { return spatial.getValue(); }
- public int getTemporal() { return temporal.getValue(); }
+ @Override
+ public boolean isEnabled() {
+ return enabled.isSelected();
+ }
+
+ public int getSpatial() {
+ return enabled.isSelected() ? spatial.getValue() : 0;
+ }
+
+ public int getTemporal() {
+ return enabled.isSelected() ? temporal.getValue() : 0;
+ }
public void setState(boolean isEnabled, int s, int t) {
enabled.setSelected(isEnabled);
diff --git a/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java b/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java
index d051b24..98d3f18 100644
--- a/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java
+++ b/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java
@@ -23,8 +23,13 @@ public class DetailSection extends FilterSection {
add(edgeEnhance);
}
- public int getSharpness() { return sharpness.getValue(); }
- public boolean isEdgeEnhanceEnabled() { return edgeEnhance.isSelected(); }
+ public int getSharpness() {
+ return sharpness.getValue();
+ }
+
+ public boolean isEdgeEnhanceEnabled() {
+ return edgeEnhance != null && edgeEnhance.isSelected();
+ }
public void setState(int sharpVal, boolean edgeActive) {
sharpness.setValue(sharpVal);
diff --git a/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java b/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java
index 10a329a..e0e77c8 100644
--- a/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java
+++ b/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java
@@ -38,8 +38,14 @@ public class WhiteBalanceSection extends FilterSection {
balanceBtn.setEnabled(active);
}
- public boolean isEnabled() { return enabled.isSelected(); }
- public int getStrength() { return strength.getValue(); }
+ @Override
+ public boolean isEnabled() {
+ return enabled != null && enabled.isSelected();
+ }
+
+ public int getStrength() {
+ return enabled.isSelected() ? strength.getValue() : 0;
+ }
public void setState(boolean isEnabled, int str) {
enabled.setSelected(isEnabled);
diff --git a/src/test/java/CameraSettingsTest.java b/src/test/java/CameraSettingsTest.java
deleted file mode 100644
index 0abe0ec..0000000
--- a/src/test/java/CameraSettingsTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-import io.swtc.networking.CameraConfig;
-import io.swtc.networking.CameraSettings;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class CameraSettingsTest {
-
- // Must match the filename used in CameraSettings.java
- private final File TEST_FILE = new File("network_cameras.json");
-
- @BeforeEach
- @AfterEach
- void cleanUp() {
- // Ensure we start and end with a clean slate to avoid side effects
- if (TEST_FILE.exists()) {
- TEST_FILE.delete();
- }
- }
-
- @Test
- void testLoadReturnsEmptyListWhenNoFile() {
- // If the file doesn't exist, it should return an empty list (not null)
- List result = CameraSettings.load();
-
- assertNotNull(result, "Load should never return null");
- assertTrue(result.isEmpty(), "Should return empty list if file doesn't exist");
- }
-
- @Test
- void testSaveAndLoad() {
- // 1. Create a config (Using your actual constructor)
- CameraConfig config = new CameraConfig("FrontDoor", "http://192.168.1.100/mjpeg");
-
- // 2. Save it
- CameraSettings.save(config);
-
- // 3. Verify file creation
- assertTrue(TEST_FILE.exists(), "File should be created after save");
-
- // 4. Load it back
- List loaded = CameraSettings.load();
-
- // 5. Verify contents
- assertEquals(1, loaded.size());
- assertEquals("FrontDoor", loaded.get(0).getName());
- assertEquals("http://192.168.1.100/mjpeg", loaded.get(0).getUrl());
- }
-
- @Test
- void testSaveMultiple() {
- // Save two distinct cameras
- CameraSettings.save(new CameraConfig("Cam1", "rtsp://10.0.0.1/stream"));
- CameraSettings.save(new CameraConfig("Cam2", "rtsp://10.0.0.2/stream"));
-
- List loaded = CameraSettings.load();
-
- assertEquals(2, loaded.size());
- assertEquals("Cam1", loaded.get(0).getName());
- assertEquals("Cam2", loaded.get(1).getName());
- }
-
- @Test
- void testDelete() {
- // Setup: Save two cameras
- CameraSettings.save(new CameraConfig("Garage", "http://1.1.1.1"));
- CameraSettings.save(new CameraConfig("Garden", "http://2.2.2.2"));
-
- // Action: Delete "Garage"
- CameraSettings.delete("Garage");
-
- // Verify: Only "Garden" remains
- List result = CameraSettings.load();
- assertEquals(1, result.size());
- assertEquals("Garden", result.get(0).getName());
- }
-
- @Test
- void testLoadCorruptFile() throws IOException {
- // Manually write broken JSON to the file
- try (FileWriter writer = new FileWriter(TEST_FILE)) {
- writer.write("{ \"this is broken json\": ... ");
- }
-
- // The code catches IOException and returns empty list
- List result = CameraSettings.load();
-
- assertNotNull(result);
- assertTrue(result.isEmpty(), "Should handle corrupt JSON gracefully by returning empty list");
- }
-}
\ No newline at end of file
From b49cc8b2f045ee625b5a904b0cddcac556642858 Mon Sep 17 00:00:00 2001
From: rattatwinko
Date: Mon, 19 Jan 2026 18:33:42 +0100
Subject: [PATCH 4/5] refactors
---
src/main/java/io/swtc/SwingIFrame.java | 114 +++++++++++++++++-
.../swtc/proccessing/WebcamCaptureLoop.java | 2 -
.../proccessing/ui/iframe/DesktopPane.java | 8 +-
3 files changed, 114 insertions(+), 10 deletions(-)
diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java
index bf0e269..ff1cc03 100644
--- a/src/main/java/io/swtc/SwingIFrame.java
+++ b/src/main/java/io/swtc/SwingIFrame.java
@@ -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 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);
}
-}
\ 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 fbac654..434b2f4 100644
--- a/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java
+++ b/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java
@@ -43,8 +43,6 @@ public class WebcamCaptureLoop {
"WebcamException",
JOptionPane.ERROR_MESSAGE
);
- } finally {
- webcam.open();
}
while (running) {
diff --git a/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java b/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java
index f317395..9fc933d 100644
--- a/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java
+++ b/src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java
@@ -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);
}
}
\ No newline at end of file
From 55474092e34c6664531929fafa65c11da1d9ce81 Mon Sep 17 00:00:00 2001
From: rattatwinko
Date: Tue, 20 Jan 2026 08:25:26 +0100
Subject: [PATCH 5/5] 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
---
.../java/io/swtc/proccessing/CameraPanel.java | 91 +++++++------------
.../swtc/proccessing/WebcamCaptureLoop.java | 87 ++++++++++--------
.../io/swtc/proccessing/ui/ShowError.java | 38 ++++++++
3 files changed, 116 insertions(+), 100 deletions(-)
create mode 100644 src/main/java/io/swtc/proccessing/ui/ShowError.java
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
+ );
+ }
+}