cameraToEffects = new HashMap<>();
+
private boolean fullscreen = false;
private Rectangle windowedBounds;
private boolean blackbg = false;
- private final Color defDesktopBg;
- private final Color bgcolor;
+ 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.DO_NOTHING_ON_CLOSE);
+ 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.setBackground(Color.WHITE);
- defDesktopBg = desktopPane.getBackground();
+ desktopPane = new DesktopPane(cameraToEffects);
+ desktopPane.setBackground(defDesktopBg);
mainFrame.add(desktopPane, BorderLayout.CENTER);
setupFullscreenToggle();
setupBlackBg();
+ initPopupMenu();
+
+ desktopPane.addMouseListener(popupListener());
}
public void addCameraInternalFrame(Webcam webcam) {
- JInternalFrame iframe = 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());
+ int offset = desktopPane.getAllFrames().length * 15;
+ cameraFrame.setLocation(50 + offset, 50 + offset);
+ effectsFrame.setLocation(700 + offset, 50 + offset);
+ effectsFrame.setVisible(false);
- JTabbedPane tabbedPane = new JTabbedPane();
- tabbedPane.addTab("View", cameraPanel);
- tabbedPane.addTab("Capture", createCapturePanel(cameraPanel, webcam));
- tabbedPane.addTab("Effects", createEffectsPanel(cameraPanel));
-
- contentPanel.add(tabbedPane, BorderLayout.CENTER);
-
- iframe.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
+ cameraFrame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
@Override
public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) {
- // if we dont call this the camera stays open until the procces dies.
- captureLoop.stop();
+ EffectsPanelFrame ef = cameraToEffects.remove(cameraFrame);
+ if (ef != null) ef.dispose();
+ desktopPane.forgetFrame(cameraFrame);
+ cameraFrame.dispose();
}
});
- iframe.add(contentPanel);
- iframe.setSize(600, 500);
+ desktopPane.add(cameraFrame);
+ desktopPane.add(effectsFrame);
- int offset = desktopPane.getAllFrames().length * 30;
- iframe.setLocation(offset, offset);
+ // Attach popup menu to frames and content
+ MouseAdapter popup = popupListener();
+ cameraFrame.addMouseListener(popup);
+ cameraFrame.getContentPane().addMouseListener(popup);
+ effectsFrame.addMouseListener(popup);
+ effectsFrame.getContentPane().addMouseListener(popup);
- desktopPane.add(iframe);
- iframe.setVisible(true);
- captureLoop.start();
+ cameraFrame.setVisible(true);
+ }
+
+ private void handleEffectsRequest(CameraInternalFrame source) {
+ EffectsPanelFrame effectsFrame = cameraToEffects.get(source);
+ if (effectsFrame != null) {
+ effectsFrame.setVisible(true);
+ try {
+ effectsFrame.setSelected(true);
+ effectsFrame.toFront();
+ } 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) {
- setbgblack();
+ toggleBackground();
}
});
}
- /* Setup F11 for Fullscreen */
+
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) {
@@ -116,284 +144,32 @@ public class SwingIFrame {
});
}
- 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);
- }
+ private void toggleBackground() {
+ desktopPane.setBackground(blackbg ? defDesktopBg : bgcolor);
blackbg = !blackbg;
+ desktopPane.repaint();
}
- 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();
- }
- }
-
+ /** Toggle fullscreen mode */
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);
-
- 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);
- ImageIO.write(img, "PNG", file);
- } catch (Exception ex) {
- JOptionPane.showMessageDialog(parent,
- "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
- }
- }
- }
-
public void show() {
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
}
-
- public static void main(String[] args) {
- SwingUtilities.invokeLater(() -> {
- SwingIFrame dashboard = new SwingIFrame();
- dashboard.show();
- });
- }
-}
\ 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 74%
rename from src/main/java/io/swtc/proccessing/AutoGainProcessor.java
rename to src/main/java/io/swtc/proccessing/AWBProccessor.java
index 7138dcd..d563092 100644
--- a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java
+++ b/src/main/java/io/swtc/proccessing/AWBProccessor.java
@@ -1,13 +1,22 @@
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 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 fda37a7..e27851b 100644
--- a/src/main/java/io/swtc/proccessing/CameraPanel.java
+++ b/src/main/java/io/swtc/proccessing/CameraPanel.java
@@ -3,202 +3,67 @@ 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;
-/*
-*
-* 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 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;
+
+ private volatile BufferedImage sourceImage;
+ private volatile BufferedImage processedImage;
+
+ private Function imageProcessor;
+ private final AtomicBoolean repaintScheduled = new AtomicBoolean(false);
+
+ public CameraPanel() {
+ setBackground(Color.BLACK);
+ setPreferredSize(new Dimension(640, 480));
+ }
public void setImage(BufferedImage img) {
- this.currentImage = img;
- this.repaint();
- }
+ sourceImage = img;
- public BufferedImage getCurrentImage() {
- return currentImage;
- }
-
- public BufferedImage getCurrentProcessedImage() {
- if (currentImage == null) {
- return null;
+ if (repaintScheduled.compareAndSet(false, true)) {
+ SwingUtilities.invokeLater(() -> {
+ repaintScheduled.set(false);
+ repaint();
+ });
}
-
- BufferedImage processed = currentImage;
-
- // apply color effects
- if (grayscale || invert || brightness != 0 || contrast != 1.0f) {
- processed = applyColorEffects(processed);
- }
-
- // apply transform.
- if (mirror || flip || rotate) {
- processed = applyTransforms(processed);
- }
-
- return processed;
}
- /* 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();
- }
-
- public void setFlip(boolean flip) {
- this.flip = flip;
- this.repaint();
- }
-
- public void setRotate(boolean rotate) {
- this.rotate = rotate;
- this.repaint();
- }
-
- public void setGrayscale(boolean grayscale) {
- this.grayscale = grayscale;
- this.repaint();
- }
-
- 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();
+ public void setImageProcessor(Function processor) {
+ this.imageProcessor = processor;
}
@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;
+ BufferedImage src = sourceImage;
+ if (src == null) return;
- // effects
- if (grayscale || invert || brightness != 0 || contrast != 1.0f) {
- processedImage = applyColorEffects(processedImage);
- }
+ BufferedImage img = src;
- // 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 r = (rgb >> 16) & 0xFF;
- int g = (rgb >> 8) & 0xFF;
- int b = rgb & 0xFF;
-
- 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);
-
- // invert the colors!
- 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);
- }
+ 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);
}
- return result;
+ processedImage = img;
+
+ Graphics2D g2d = (Graphics2D) g;
+ g2d.setRenderingHint(
+ RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
+ );
+
+ g2d.drawImage(img, 0, 0, getWidth(), getHeight(), null);
}
- private BufferedImage applyTransforms(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++) {
- int sourceX = x;
- int sourceY = y;
-
- 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;
- }
-
- result.setRGB(x, y, img.getRGB(sourceX, sourceY));
- }
- }
-
- return result;
+ public BufferedImage getCurrentProcessedImage() {
+ return processedImage;
}
-}
\ 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/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
new file mode 100644
index 0000000..c436ef5
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/EffectState.java
@@ -0,0 +1,6 @@
+package io.swtc.proccessing;
+
+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
new file mode 100644
index 0000000..c1417c4
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/FilterPanel.java
@@ -0,0 +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 java.awt.*;
+
+/**
+ * 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 AWBProccessor awbProcessor;
+ private final PresetLibrary presetLibrary;
+
+ 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.awbProcessor = new AWBProccessor();
+ this.presetLibrary = new PresetLibrary();
+
+ initializePanel();
+ buildUI();
+ initGlobalListeners();
+ }
+
+ private void initializePanel() {
+ setLayout(new BorderLayout());
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+ }
+
+ private void buildUI() {
+ PresetSection presetSection = new PresetSection(presetLibrary, this::applyPresetToUI, this::getCurrentState);
+ add(presetSection, BorderLayout.NORTH);
+
+ JPanel scrollContainer = new JPanel();
+ scrollContainer.setLayout(new BoxLayout(scrollContainer, BoxLayout.Y_AXIS));
+
+ awbSection = new WhiteBalanceSection(this::performOneTimeBalance, this::applyToCamera);
+ dnrSection = new DNRSection(this::applyToCamera);
+ colorSection = new ColorSection(this::applyToCamera);
+ detailSection = new DetailSection(this::applyToCamera);
+
+ 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(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(
+ awbSection.isEnabled(), awbSection.getStrength(),
+ dnrSection.isEnabled(), dnrSection.getSpatial(), dnrSection.getTemporal(),
+ colorSection.getTemp(), colorSection.getTint(), colorSection.getSaturation(),
+ colorSection.getShadows(), colorSection.getHighlights(),
+ detailSection.getSharpness(), detailSection.isEdgeEnhanceEnabled()
+ );
+ }
+
+ private void applyToCamera() {
+ if (cameraPanel == null) return;
+
+ EffectState state = getCurrentState();
+ cameraPanel.setImageProcessor(img ->
+ ImageEffectEngine.applyEffects(img, state, currentGains)
+ );
+ }
+
+ /**
+ * call awb stuff
+ */
+ private void performOneTimeBalance() {
+ 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 = awbProcessor.calculateAutoGains(pixels);
+ applyToCamera();
+ }
+ }
+
+ /**
+ * Update for a specified state
+ * */
+ private void applyPresetToUI(EffectState s) {
+ 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 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
new file mode 100644
index 0000000..1a763ff
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ImageEffectEngine.java
@@ -0,0 +1,40 @@
+package io.swtc.proccessing;
+
+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();
+ int[] pixels = ImageUtils.getPixels(img);
+
+ // 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
+
+ // 2. Apply Color Pipeline (In-Place)
+ colorProcessor.process(workingPixels, state, currentGains);
+
+ // 3. Apply Sharpness (Returns new array if applied)
+ if (state.sharpness() != 100 || state.edgeEnhance()) {
+ workingPixels = sharpnessProcessor.process(workingPixels, width, height, state.sharpness(), state.edgeEnhance());
+ }
+
+ // 4. Apply Denoise (Returns new array if applied)
+ if (state.dnrEnabled()) {
+ workingPixels = denoiseProcessor.process(workingPixels, width, height, state.dnrSpatial());
+ }
+
+ // 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/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
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..10f0470 100644
--- a/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java
+++ b/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java
@@ -2,67 +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.
- webcam.open();
-
- while (running) {
- BufferedImage img = webcam.getImage();
- if (img != null) {
- //System.err.println(img);
- onFrameCaptured.accept(img);
- }
-
- try {
- Thread.sleep(15);
- } catch (InterruptedException e) {
- break;
- }
- }
try {
- webcam.close();
+ if (!webcam.isOpen()) webcam.open(); // open if not
- } catch (IllegalStateException e) {
- // show a error message from javax.swing.awt.JOptionPane
- JOptionPane.showMessageDialog(
+ long frameDurationLimitns = 1_000_000_000L / targetframes; // we use NanoSeconds cause its more accurate
+
+ while (running) {
+ long startTime = System.nanoTime();
+
+ BufferedImage img = webcam.getImage();
+ if (img != null)
+ // there is no need for a invoke later swing wise!
+ onFrameCaptured.accept(img);
+
+ long timespent = System.nanoTime() - startTime;
+ long sleeptime = frameDurationLimitns - timespent;
+
+ if (sleeptime > 0) {
+ LockSupport.parkNanos(sleeptime);
+ } else {
+ // do a yield to prevent ui freezes
+ Thread.yield();
+ }
+ }
+ } catch (Exception e) {
+ ShowError.warning(
null,
- "IllegalStateException@"+e,
- "IllegalStateException",
- JOptionPane.ERROR_MESSAGE
+ "Exception" + e,
+ "Exception"
);
+ } finally {
+ cleanup();
}
- });
- captureThread.setName("cam_cap_thread");
+ }, "cam_cap_thread");
captureThread.start();
}
@@ -102,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/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/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
+ );
+ }
+}
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/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..9fc933d
--- /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/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..08f73c2
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/sections/DNRSection.java
@@ -0,0 +1,56 @@
+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, "%");
+
+ 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);
+ updateEnabledStates();
+ }
+
+ private void updateEnabledStates() {
+ boolean active = enabled.isSelected();
+ spatial.getSlider().setEnabled(active);
+ temporal.getSlider().setEnabled(active);
+ }
+
+ @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);
+ 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..98d3f18
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/sections/DetailSection.java
@@ -0,0 +1,38 @@
+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 != null && 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..e0e77c8
--- /dev/null
+++ b/src/main/java/io/swtc/proccessing/ui/sections/WhiteBalanceSection.java
@@ -0,0 +1,55 @@
+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);
+ }
+
+ @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);
+ 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
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