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 <seppmutterman@gmail.com>
This commit is contained in:
@@ -21,8 +21,6 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For some reason we need to invoke Later for LaF to work!
|
// For some reason we need to invoke Later for LaF to work!
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> SwingCCTVManager.main(null));
|
||||||
SwingCCTVManager.main(null);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,15 @@
|
|||||||
package io.swtc;
|
package io.swtc;
|
||||||
|
|
||||||
import com.github.sarxos.webcam.Webcam;
|
import com.github.sarxos.webcam.Webcam;
|
||||||
import io.swtc.proccessing.WebcamCaptureLoop;
|
import io.swtc.proccessing.ui.iframe.*;
|
||||||
import io.swtc.proccessing.CameraPanel;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.EmptyBorder;
|
|
||||||
import javax.swing.event.*;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.util.HashMap;
|
||||||
import java.awt.geom.CubicCurve2D;
|
import java.util.Map;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class SwingIFrame {
|
public class SwingIFrame {
|
||||||
private final JFrame mainFrame;
|
private final JFrame mainFrame;
|
||||||
private final BlenderDesktopPane desktopPane;
|
private final DesktopPane desktopPane;
|
||||||
private boolean fullscreen = false;
|
|
||||||
private Rectangle windowedBounds;
|
|
||||||
private boolean blackbg = false;
|
|
||||||
private final Color defDesktopBg;
|
|
||||||
private final Color bgcolor;
|
|
||||||
|
|
||||||
private final Map<JInternalFrame, EffectsPanelFrame> cameraToEffects = new HashMap<>();
|
private final Map<JInternalFrame, EffectsPanelFrame> cameraToEffects = new HashMap<>();
|
||||||
|
|
||||||
public SwingIFrame() {
|
public SwingIFrame() {
|
||||||
@@ -31,175 +17,48 @@ public class SwingIFrame {
|
|||||||
mainFrame.setSize(1280, 720);
|
mainFrame.setSize(1280, 720);
|
||||||
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||||
|
|
||||||
bgcolor = Color.decode("#1e1e1e");
|
desktopPane = new DesktopPane(cameraToEffects);
|
||||||
|
|
||||||
desktopPane = new BlenderDesktopPane(cameraToEffects);
|
|
||||||
desktopPane.setBackground(Color.WHITE);
|
desktopPane.setBackground(Color.WHITE);
|
||||||
defDesktopBg = desktopPane.getBackground();
|
|
||||||
mainFrame.add(desktopPane, BorderLayout.CENTER);
|
mainFrame.add(desktopPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
setupFullscreenToggle();
|
|
||||||
setupBlackBg();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCameraInternalFrame(Webcam webcam) {
|
public void addCameraInternalFrame(Webcam webcam) {
|
||||||
JInternalFrame cameraFrame = new JInternalFrame(
|
CameraInternalFrame cameraFrame = new CameraInternalFrame(webcam, this::handleEffectsRequest);
|
||||||
webcam.getName(),
|
|
||||||
true, true, true, true
|
EffectsPanelFrame effectsFrame = new EffectsPanelFrame(
|
||||||
|
"Effects - " + webcam.getName(),
|
||||||
|
cameraFrame.getCameraPanel()
|
||||||
);
|
);
|
||||||
|
|
||||||
CameraPanel cameraPanel = new CameraPanel();
|
cameraToEffects.put(cameraFrame, effectsFrame);
|
||||||
WebcamCaptureLoop captureLoop = new WebcamCaptureLoop(webcam, (BufferedImage img) ->
|
|
||||||
SwingUtilities.invokeLater(() -> cameraPanel.setImage(img))
|
|
||||||
);
|
|
||||||
|
|
||||||
JPanel contentPanel = new JPanel(new BorderLayout());
|
int offset = desktopPane.getAllFrames().length * 15;
|
||||||
JTabbedPane tabbedPane = new JTabbedPane();
|
cameraFrame.setLocation(50 + offset, 50 + offset);
|
||||||
|
effectsFrame.setLocation(700 + offset, 50 + offset);
|
||||||
tabbedPane.addTab("View", cameraPanel);
|
effectsFrame.setVisible(false);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cameraFrame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
|
cameraFrame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) {
|
public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) {
|
||||||
captureLoop.stop();
|
EffectsPanelFrame ef = cameraToEffects.remove(cameraFrame);
|
||||||
EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame);
|
if (ef != null) ef.dispose();
|
||||||
if (effectsFrame != null) {
|
desktopPane.forgetFrame(cameraFrame);
|
||||||
effectsFrame.dispose();
|
|
||||||
cameraToEffects.remove(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(cameraFrame);
|
||||||
desktopPane.add(effectsFrame);
|
desktopPane.add(effectsFrame);
|
||||||
|
|
||||||
cameraFrame.setVisible(true);
|
cameraFrame.setVisible(true);
|
||||||
captureLoop.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLinkageListeners(JInternalFrame cameraFrame, EffectsPanelFrame effectsFrame) {
|
private void handleEffectsRequest(CameraInternalFrame source) {
|
||||||
java.awt.event.ComponentAdapter linker = new java.awt.event.ComponentAdapter() {
|
EffectsPanelFrame effectsFrame = cameraToEffects.get(source);
|
||||||
@Override
|
if (effectsFrame != null) {
|
||||||
public void componentMoved(java.awt.event.ComponentEvent e) {
|
effectsFrame.setVisible(true);
|
||||||
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) {
|
|
||||||
try {
|
try {
|
||||||
File file = new File(directory, "snap.png");
|
effectsFrame.setSelected(true);
|
||||||
ImageIO.write(img, "PNG", file);
|
effectsFrame.toFront();
|
||||||
} catch (Exception ex) {
|
} catch (java.beans.PropertyVetoException ex) { JOptionPane.showMessageDialog(null,"Exception" + ex.getMessage() , "Exception", JOptionPane.ERROR_MESSAGE); }
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,100 +66,4 @@ public class SwingIFrame {
|
|||||||
mainFrame.setLocationRelativeTo(null);
|
mainFrame.setLocationRelativeTo(null);
|
||||||
mainFrame.setVisible(true);
|
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<JInternalFrame, EffectsPanelFrame> connections;
|
|
||||||
|
|
||||||
public BlenderDesktopPane(Map<JInternalFrame, EffectsPanelFrame> 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<JInternalFrame, EffectsPanelFrame> 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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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<CameraInternalFrame> 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<CameraInternalFrame> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java
Normal file
66
src/main/java/io/swtc/proccessing/ui/iframe/DesktopPane.java
Normal file
@@ -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<JInternalFrame, EffectsPanelFrame> connections;
|
||||||
|
private final Map<JInternalFrame, Color> connectionColors = new HashMap<>();
|
||||||
|
|
||||||
|
public DesktopPane(Map<JInternalFrame, EffectsPanelFrame> 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<JInternalFrame, EffectsPanelFrame> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java
Normal file
126
src/main/java/io/swtc/proccessing/ui/iframe/RecordingPane.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ public class DNRSection extends FilterSection {
|
|||||||
spatial = addSlider("Spatial Strength", 0, 100, 30, "%");
|
spatial = addSlider("Spatial Strength", 0, 100, 30, "%");
|
||||||
temporal = addSlider("Temporal Strength", 0, 100, 50, "%");
|
temporal = addSlider("Temporal Strength", 0, 100, 50, "%");
|
||||||
|
|
||||||
// Logic: Disable sliders if DNR is off
|
|
||||||
enabled.addActionListener(e -> {
|
enabled.addActionListener(e -> {
|
||||||
updateEnabledStates();
|
updateEnabledStates();
|
||||||
onUpdate.run();
|
onUpdate.run();
|
||||||
@@ -25,7 +24,7 @@ public class DNRSection extends FilterSection {
|
|||||||
spatial.addChangeListener(e -> { if(!spatial.getSlider().getValueIsAdjusting()) onUpdate.run(); });
|
spatial.addChangeListener(e -> { if(!spatial.getSlider().getValueIsAdjusting()) onUpdate.run(); });
|
||||||
temporal.addChangeListener(e -> { if(!temporal.getSlider().getValueIsAdjusting()) onUpdate.run(); });
|
temporal.addChangeListener(e -> { if(!temporal.getSlider().getValueIsAdjusting()) onUpdate.run(); });
|
||||||
|
|
||||||
add(enabled, 0); // Checkbox at top
|
add(enabled, 0);
|
||||||
updateEnabledStates();
|
updateEnabledStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,9 +34,18 @@ public class DNRSection extends FilterSection {
|
|||||||
temporal.getSlider().setEnabled(active);
|
temporal.getSlider().setEnabled(active);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDnrEnabled() { return enabled.isSelected(); }
|
@Override
|
||||||
public int getSpatial() { return spatial.getValue(); }
|
public boolean isEnabled() {
|
||||||
public int getTemporal() { return temporal.getValue(); }
|
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) {
|
public void setState(boolean isEnabled, int s, int t) {
|
||||||
enabled.setSelected(isEnabled);
|
enabled.setSelected(isEnabled);
|
||||||
|
|||||||
@@ -23,8 +23,13 @@ public class DetailSection extends FilterSection {
|
|||||||
add(edgeEnhance);
|
add(edgeEnhance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSharpness() { return sharpness.getValue(); }
|
public int getSharpness() {
|
||||||
public boolean isEdgeEnhanceEnabled() { return edgeEnhance.isSelected(); }
|
return sharpness.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEdgeEnhanceEnabled() {
|
||||||
|
return edgeEnhance != null && edgeEnhance.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
public void setState(int sharpVal, boolean edgeActive) {
|
public void setState(int sharpVal, boolean edgeActive) {
|
||||||
sharpness.setValue(sharpVal);
|
sharpness.setValue(sharpVal);
|
||||||
|
|||||||
@@ -38,8 +38,14 @@ public class WhiteBalanceSection extends FilterSection {
|
|||||||
balanceBtn.setEnabled(active);
|
balanceBtn.setEnabled(active);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnabled() { return enabled.isSelected(); }
|
@Override
|
||||||
public int getStrength() { return strength.getValue(); }
|
public boolean isEnabled() {
|
||||||
|
return enabled != null && enabled.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStrength() {
|
||||||
|
return enabled.isSelected() ? strength.getValue() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
public void setState(boolean isEnabled, int str) {
|
public void setState(boolean isEnabled, int str) {
|
||||||
enabled.setSelected(isEnabled);
|
enabled.setSelected(isEnabled);
|
||||||
|
|||||||
@@ -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<CameraConfig> 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<CameraConfig> 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<CameraConfig> 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<CameraConfig> 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<CameraConfig> result = CameraSettings.load();
|
|
||||||
|
|
||||||
assertNotNull(result);
|
|
||||||
assertTrue(result.isEmpty(), "Should handle corrupt JSON gracefully by returning empty list");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user