testing some new stuff!
This commit is contained in:
1
pom.xml
1
pom.xml
@@ -122,5 +122,6 @@
|
||||
<artifactId>jcodec-javase</artifactId>
|
||||
<version>0.2.5</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,11 +1,29 @@
|
||||
package io.swtc;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.out.println("Arg " + i + ": " + args[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
//UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
|
||||
UIManager.setLookAndFeel(
|
||||
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
|
||||
);
|
||||
} catch (Exception e) {
|
||||
JOptionPane.showMessageDialog(
|
||||
null,
|
||||
"Method not supported, Application will run anyways but might look different, and feel different!",
|
||||
"SWTCCTVWarning",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
SwingCCTVManager.main(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@Deprecated
|
||||
public class SwingCameraWindow {
|
||||
private final JFrame frame;
|
||||
private final CameraPanel cameraPanel;
|
||||
|
||||
@@ -3,44 +3,45 @@ package io.swtc;
|
||||
import com.github.sarxos.webcam.Webcam;
|
||||
import io.swtc.proccessing.WebcamCaptureLoop;
|
||||
import io.swtc.proccessing.CameraPanel;
|
||||
import io.swtc.proccessing.FilterPanel;
|
||||
import io.swtc.recording.VideoRecorder;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.TitledBorder;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.geom.CubicCurve2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/*
|
||||
*
|
||||
* This file is basically just UI, its boring the interesting stuff is in the utilities section!
|
||||
*
|
||||
* */
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Blender-style UI where the Effects panel is triggered by a Tab click
|
||||
*/
|
||||
public class SwingIFrame {
|
||||
private final JFrame mainFrame;
|
||||
private final JDesktopPane desktopPane;
|
||||
private final BlenderDesktopPane desktopPane;
|
||||
private boolean fullscreen = false;
|
||||
private Rectangle windowedBounds;
|
||||
private boolean blackbg = false;
|
||||
private final Color defDesktopBg;
|
||||
private final Color bgcolor;
|
||||
|
||||
public SwingIFrame() {
|
||||
mainFrame = new JFrame("viewer");
|
||||
mainFrame.setSize(1280,720);
|
||||
mainFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
private final Map<JInternalFrame, EffectsPanelFrame> cameraToEffects = new HashMap<>();
|
||||
|
||||
public SwingIFrame() {
|
||||
mainFrame = new JFrame("Viewer");
|
||||
mainFrame.setSize(1280, 720);
|
||||
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
|
||||
// this is good on the eyes for long periods of times,
|
||||
// i would have used #4B9EA0, which is also easy on the eyes
|
||||
bgcolor = Color.decode("#1e1e1e");
|
||||
|
||||
desktopPane = new JDesktopPane();
|
||||
desktopPane = new BlenderDesktopPane(cameraToEffects);
|
||||
desktopPane.setBackground(Color.WHITE);
|
||||
defDesktopBg = desktopPane.getBackground();
|
||||
mainFrame.add(desktopPane, BorderLayout.CENTER);
|
||||
@@ -50,7 +51,7 @@ public class SwingIFrame {
|
||||
}
|
||||
|
||||
public void addCameraInternalFrame(Webcam webcam) {
|
||||
JInternalFrame iframe = new JInternalFrame(
|
||||
JInternalFrame cameraFrame = new JInternalFrame(
|
||||
webcam.getName(),
|
||||
true, true, true, true
|
||||
);
|
||||
@@ -61,326 +62,152 @@ public class SwingIFrame {
|
||||
);
|
||||
|
||||
JPanel contentPanel = new JPanel(new BorderLayout());
|
||||
|
||||
JTabbedPane tabbedPane = new JTabbedPane();
|
||||
|
||||
tabbedPane.addTab("View", cameraPanel);
|
||||
tabbedPane.addTab("Capture", createCapturePanel(cameraPanel, webcam));
|
||||
tabbedPane.addTab("Effects", createEffectsPanel(cameraPanel));
|
||||
// Add a placeholder tab that acts as a button
|
||||
tabbedPane.addTab("Open Effect Panel", new JPanel());
|
||||
|
||||
contentPanel.add(tabbedPane, BorderLayout.CENTER);
|
||||
|
||||
iframe.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
|
||||
// Logic to open/show the Effects Frame when the tab is clicked
|
||||
tabbedPane.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) {
|
||||
// if we dont call this the camera stays open until the procces dies.
|
||||
captureLoop.stop();
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
if (tabbedPane.getSelectedIndex() == 2) { // "Effects +" tab index
|
||||
// Revert selection back to "View" so the tab doesn't stay stuck on the empty panel
|
||||
tabbedPane.setSelectedIndex(0);
|
||||
|
||||
EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame);
|
||||
if (effectsFrame != null) {
|
||||
effectsFrame.setVisible(true);
|
||||
try {
|
||||
effectsFrame.setSelected(true);
|
||||
} catch (java.beans.PropertyVetoException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
iframe.add(contentPanel);
|
||||
iframe.setSize(600, 500);
|
||||
cameraFrame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() {
|
||||
@Override
|
||||
public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) {
|
||||
captureLoop.stop();
|
||||
EffectsPanelFrame effectsFrame = cameraToEffects.get(cameraFrame);
|
||||
if (effectsFrame != null) {
|
||||
effectsFrame.dispose();
|
||||
cameraToEffects.remove(cameraFrame);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cameraFrame.add(contentPanel);
|
||||
cameraFrame.setSize(600, 500);
|
||||
|
||||
int offset = desktopPane.getAllFrames().length * 30;
|
||||
iframe.setLocation(offset, offset);
|
||||
cameraFrame.setLocation(50 + offset, 50 + offset);
|
||||
|
||||
desktopPane.add(iframe);
|
||||
iframe.setVisible(true);
|
||||
// Pre-create the effects frame but keep it hidden
|
||||
EffectsPanelFrame effectsFrame = new EffectsPanelFrame(
|
||||
"Effects - " + webcam.getName(),
|
||||
cameraPanel,
|
||||
cameraFrame
|
||||
);
|
||||
effectsFrame.setSize(350, 600);
|
||||
effectsFrame.setLocation(700 + offset, 50 + offset);
|
||||
effectsFrame.setVisible(false); // Hidden by default
|
||||
|
||||
cameraToEffects.put(cameraFrame, effectsFrame);
|
||||
addLinkageListeners(cameraFrame, effectsFrame);
|
||||
|
||||
desktopPane.add(cameraFrame);
|
||||
desktopPane.add(effectsFrame);
|
||||
|
||||
cameraFrame.setVisible(true);
|
||||
captureLoop.start();
|
||||
}
|
||||
|
||||
private void addLinkageListeners(JInternalFrame cameraFrame, EffectsPanelFrame effectsFrame) {
|
||||
java.awt.event.ComponentAdapter linker = new java.awt.event.ComponentAdapter() {
|
||||
@Override
|
||||
public void componentMoved(java.awt.event.ComponentEvent e) {
|
||||
desktopPane.repaint();
|
||||
}
|
||||
@Override
|
||||
public void componentResized(java.awt.event.ComponentEvent e) {
|
||||
desktopPane.repaint();
|
||||
}
|
||||
};
|
||||
cameraFrame.addComponentListener(linker);
|
||||
effectsFrame.addComponentListener(linker);
|
||||
}
|
||||
|
||||
// --- Standard UI Setup Methods ---
|
||||
|
||||
private void setupBlackBg() {
|
||||
JRootPane root = mainFrame.getRootPane();
|
||||
|
||||
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
|
||||
.put(KeyStroke.getKeyStroke("B"), "toggleBlackBg");
|
||||
|
||||
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("B"), "toggleBlackBg");
|
||||
root.getActionMap().put("toggleBlackBg", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
setbgblack();
|
||||
}
|
||||
@Override public void actionPerformed(ActionEvent e) { setbgblack(); }
|
||||
});
|
||||
}
|
||||
/* Setup F11 for Fullscreen */
|
||||
|
||||
private void setupFullscreenToggle() {
|
||||
JRootPane root = mainFrame.getRootPane();
|
||||
|
||||
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
|
||||
.put(KeyStroke.getKeyStroke("F11"), "toggleFullscreen");
|
||||
|
||||
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F11"), "toggleFullscreen");
|
||||
root.getActionMap().put("toggleFullscreen", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
toggleFullscreen();
|
||||
}
|
||||
@Override public void actionPerformed(ActionEvent e) { toggleFullscreen(); }
|
||||
});
|
||||
}
|
||||
|
||||
private void setbgblack() {
|
||||
if (!blackbg) {
|
||||
// easy stuff here, just setting the bg to the private color
|
||||
desktopPane.setBackground(bgcolor);
|
||||
updateInternalFrameBg(bgcolor);
|
||||
} else {
|
||||
desktopPane.setBackground(defDesktopBg);
|
||||
updateInternalFrameBg(null);
|
||||
}
|
||||
blackbg = !blackbg;
|
||||
}
|
||||
|
||||
private void updateInternalFrameBg(Color bg) {
|
||||
for (JInternalFrame frame : desktopPane.getAllFrames()) {
|
||||
Container content = frame.getContentPane();
|
||||
if (bg != null) {
|
||||
content.setBackground(bg);
|
||||
} else content.setBackground(null); // restore default
|
||||
content.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleFullscreen() {
|
||||
if (!fullscreen) {
|
||||
// We set the window to borderless windowed mode, so it doesnt
|
||||
// lag like shit when we drag windows around, which is annoying
|
||||
windowedBounds = mainFrame.getBounds();
|
||||
mainFrame.dispose();
|
||||
mainFrame.setUndecorated(true);
|
||||
mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
|
||||
mainFrame.setVisible(true);
|
||||
} else {
|
||||
// do the opposite
|
||||
mainFrame.dispose();
|
||||
mainFrame.setUndecorated(false);
|
||||
mainFrame.setExtendedState(JFrame.NORMAL);
|
||||
mainFrame.setBounds(windowedBounds);
|
||||
mainFrame.setVisible(true);
|
||||
}
|
||||
|
||||
fullscreen = !fullscreen;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private JPanel createCapturePanel(CameraPanel cameraPanel, Webcam webcam) {
|
||||
JPanel panel = new JPanel(new BorderLayout(10, 10));
|
||||
panel.setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
|
||||
JPanel mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
|
||||
|
||||
// Screenshot section
|
||||
JPanel screenshotPanel = new JPanel();
|
||||
screenshotPanel.setLayout(new BoxLayout(screenshotPanel, BoxLayout.Y_AXIS));
|
||||
screenshotPanel.setBorder(BorderFactory.createTitledBorder(
|
||||
BorderFactory.createEtchedBorder(), "Screenshot",
|
||||
TitledBorder.LEFT, TitledBorder.TOP));
|
||||
|
||||
JPanel screenshotPathPanel = new JPanel(new BorderLayout(5, 5));
|
||||
JTextField screenshotPath = new JTextField(System.getProperty("user.home") + File.separator + "screenshots");
|
||||
JButton browseSS = new JButton("...");
|
||||
browseSS.setPreferredSize(new Dimension(40, 25));
|
||||
browseSS.addActionListener(e -> browseDirectory(screenshotPath, panel));
|
||||
screenshotPathPanel.add(screenshotPath, BorderLayout.CENTER);
|
||||
screenshotPathPanel.add(browseSS, BorderLayout.EAST);
|
||||
|
||||
JButton takeScreenshot = new JButton("Take Screenshot (S)");
|
||||
takeScreenshot.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
takeScreenshot.addActionListener(e -> saveSnapshot(cameraPanel, webcam, screenshotPath.getText(), panel));
|
||||
|
||||
screenshotPanel.add(screenshotPathPanel);
|
||||
screenshotPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
screenshotPanel.add(takeScreenshot);
|
||||
|
||||
// Recording section
|
||||
JPanel recordPanel = new JPanel();
|
||||
recordPanel.setLayout(new BoxLayout(recordPanel, BoxLayout.Y_AXIS));
|
||||
recordPanel.setBorder(BorderFactory.createTitledBorder(
|
||||
BorderFactory.createEtchedBorder(), "Recording",
|
||||
TitledBorder.LEFT, TitledBorder.TOP));
|
||||
|
||||
JPanel recordPathPanel = new JPanel(new BorderLayout(5, 5));
|
||||
JTextField recordPath = new JTextField(System.getProperty("user.home") + File.separator + "recordings");
|
||||
JButton browseRec = new JButton("...");
|
||||
browseRec.setPreferredSize(new Dimension(40, 25));
|
||||
browseRec.addActionListener(e -> browseDirectory(recordPath, panel));
|
||||
recordPathPanel.add(recordPath, BorderLayout.CENTER);
|
||||
recordPathPanel.add(browseRec, BorderLayout.EAST);
|
||||
|
||||
VideoRecorder recorder = new VideoRecorder();
|
||||
JButton recordButton = new JButton("Start Recording (R)");
|
||||
JLabel recordingStatus = new JLabel("Ready");
|
||||
recordingStatus.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
|
||||
recordButton.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
recordButton.addActionListener(e -> {
|
||||
if (!recorder.isRecording()) {
|
||||
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||
String filename = "recording_" + webcam.getName().replaceAll("[^a-zA-Z0-9]", "_")
|
||||
+ "_" + timestamp + ".mp4";
|
||||
File dir = new File(recordPath.getText());
|
||||
dir.mkdirs();
|
||||
|
||||
try {
|
||||
recorder.startRecording(cameraPanel, new File(dir, filename));
|
||||
recordButton.setText("Stop Recording");
|
||||
recordingStatus.setText("Recording...");
|
||||
recordingStatus.setForeground(Color.RED);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(panel,
|
||||
"Error starting recording: " + ex.getMessage(),
|
||||
"Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
File saved = recorder.stopRecording();
|
||||
recordButton.setText("Start Recording (R)");
|
||||
recordingStatus.setText("Saved: " + saved.getName());
|
||||
recordingStatus.setForeground(Color.BLACK);
|
||||
} catch (Exception ex) {
|
||||
recordButton.setText("Start Recording (R)");
|
||||
recordingStatus.setText("Error saving");
|
||||
recordingStatus.setForeground(Color.RED);
|
||||
JOptionPane.showMessageDialog(panel,
|
||||
"Error saving recording: " + ex.getMessage(),
|
||||
"Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
recordPanel.add(recordPathPanel);
|
||||
recordPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
recordPanel.add(recordButton);
|
||||
recordPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
recordPanel.add(recordingStatus);
|
||||
|
||||
mainPanel.add(screenshotPanel);
|
||||
mainPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
||||
mainPanel.add(recordPanel);
|
||||
mainPanel.add(Box.createVerticalGlue());
|
||||
|
||||
panel.add(mainPanel, BorderLayout.NORTH);
|
||||
JButton takeScreenshot = new JButton("Take Screenshot");
|
||||
takeScreenshot.addActionListener(e -> saveSnapshot(cameraPanel, webcam, System.getProperty("user.home"), panel));
|
||||
|
||||
panel.add(takeScreenshot, BorderLayout.NORTH);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createEffectsPanel(CameraPanel cameraPanel) {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
panel.setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
|
||||
JPanel transformPanel = new JPanel(new GridLayout(0, 1, 5, 5));
|
||||
transformPanel.setBorder(BorderFactory.createTitledBorder(
|
||||
BorderFactory.createEtchedBorder(), "Transform",
|
||||
TitledBorder.LEFT, TitledBorder.TOP));
|
||||
|
||||
JCheckBox mirrorCheck = new JCheckBox("Mirror Horizontal");
|
||||
JCheckBox flipCheck = new JCheckBox("Flip Vertical");
|
||||
JCheckBox rotateCheck = new JCheckBox("Rotate 180°");
|
||||
|
||||
mirrorCheck.addActionListener(e -> cameraPanel.setMirror(mirrorCheck.isSelected()));
|
||||
flipCheck.addActionListener(e -> cameraPanel.setFlip(flipCheck.isSelected()));
|
||||
rotateCheck.addActionListener(e -> cameraPanel.setRotate(rotateCheck.isSelected()));
|
||||
|
||||
transformPanel.add(mirrorCheck);
|
||||
transformPanel.add(flipCheck);
|
||||
transformPanel.add(rotateCheck);
|
||||
|
||||
JPanel colorPanel = new JPanel();
|
||||
colorPanel.setLayout(new BoxLayout(colorPanel, BoxLayout.Y_AXIS));
|
||||
colorPanel.setBorder(BorderFactory.createTitledBorder(
|
||||
BorderFactory.createEtchedBorder(), "Color",
|
||||
TitledBorder.LEFT, TitledBorder.TOP));
|
||||
|
||||
JCheckBox grayscaleCheck = new JCheckBox("Grayscale");
|
||||
JCheckBox invertCheck = new JCheckBox("Invert Colors");
|
||||
|
||||
grayscaleCheck.addActionListener(e -> cameraPanel.setGrayscale(grayscaleCheck.isSelected()));
|
||||
invertCheck.addActionListener(e -> cameraPanel.setInvert(invertCheck.isSelected()));
|
||||
|
||||
colorPanel.add(grayscaleCheck);
|
||||
colorPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
colorPanel.add(invertCheck);
|
||||
|
||||
JPanel brightnessPanel = new JPanel();
|
||||
brightnessPanel.setLayout(new BoxLayout(brightnessPanel, BoxLayout.Y_AXIS));
|
||||
brightnessPanel.setBorder(BorderFactory.createTitledBorder(
|
||||
BorderFactory.createEtchedBorder(), "Adjustments",
|
||||
TitledBorder.LEFT, TitledBorder.TOP));
|
||||
|
||||
JPanel brightnessSliderPanel = new JPanel(new BorderLayout());
|
||||
JLabel brightnessLabel = new JLabel("Brightness: 0");
|
||||
JSlider brightnessSlider = new JSlider(-100, 100, 0);
|
||||
brightnessSlider.addChangeListener(e -> {
|
||||
int value = brightnessSlider.getValue();
|
||||
brightnessLabel.setText("Brightness: " + value);
|
||||
cameraPanel.setBrightness(value);
|
||||
});
|
||||
brightnessSliderPanel.add(brightnessLabel, BorderLayout.NORTH);
|
||||
brightnessSliderPanel.add(brightnessSlider, BorderLayout.CENTER);
|
||||
|
||||
JPanel contrastSliderPanel = new JPanel(new BorderLayout());
|
||||
JLabel contrastLabel = new JLabel("Contrast: 1.0");
|
||||
JSlider contrastSlider = new JSlider(0, 200, 100);
|
||||
contrastSlider.addChangeListener(e -> {
|
||||
float value = contrastSlider.getValue() / 100f;
|
||||
contrastLabel.setText("Contrast: " + String.format("%.1f", value));
|
||||
cameraPanel.setContrast(value);
|
||||
});
|
||||
contrastSliderPanel.add(contrastLabel, BorderLayout.NORTH);
|
||||
contrastSliderPanel.add(contrastSlider, BorderLayout.CENTER);
|
||||
|
||||
brightnessPanel.add(brightnessSliderPanel);
|
||||
brightnessPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
brightnessPanel.add(contrastSliderPanel);
|
||||
|
||||
JButton resetButton = new JButton("Reset All Effects");
|
||||
resetButton.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
resetButton.addActionListener(e -> {
|
||||
mirrorCheck.setSelected(false);
|
||||
flipCheck.setSelected(false);
|
||||
rotateCheck.setSelected(false);
|
||||
grayscaleCheck.setSelected(false);
|
||||
invertCheck.setSelected(false);
|
||||
brightnessSlider.setValue(0);
|
||||
contrastSlider.setValue(100);
|
||||
cameraPanel.resetEffects();
|
||||
});
|
||||
|
||||
panel.add(transformPanel);
|
||||
panel.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
panel.add(colorPanel);
|
||||
panel.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
panel.add(brightnessPanel);
|
||||
panel.add(Box.createRigidArea(new Dimension(0, 15)));
|
||||
panel.add(resetButton);
|
||||
panel.add(Box.createVerticalGlue());
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void browseDirectory(JTextField field, Component parent) {
|
||||
JFileChooser chooser = new JFileChooser(field.getText());
|
||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
|
||||
field.setText(chooser.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveSnapshot(CameraPanel cameraPanel, Webcam webcam, String directory, Component parent) {
|
||||
BufferedImage img = cameraPanel.getCurrentProcessedImage();
|
||||
if (img != null) {
|
||||
try {
|
||||
File dir = new File(directory);
|
||||
dir.mkdirs();
|
||||
|
||||
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||
String filename = "screenshot_" + webcam.getName().replaceAll("[^a-zA-Z0-9]", "_")
|
||||
+ "_" + timestamp + ".png";
|
||||
File file = new File(dir, filename);
|
||||
File file = new File(directory, "snap.png");
|
||||
ImageIO.write(img, "PNG", file);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(parent,
|
||||
"Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,7 +220,69 @@ public class SwingIFrame {
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
SwingIFrame dashboard = new SwingIFrame();
|
||||
// Example usage: dashboard.addCameraInternalFrame(Webcam.getDefault());
|
||||
dashboard.show();
|
||||
});
|
||||
}
|
||||
|
||||
// --- Inner Classes for Blender Styling ---
|
||||
|
||||
static class BlenderDesktopPane extends JDesktopPane {
|
||||
private final Map<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);
|
||||
add(new FilterPanel(cameraPanel));
|
||||
|
||||
// Repaint curves when moving or iconifying
|
||||
addComponentListener(new java.awt.event.ComponentAdapter() {
|
||||
@Override public void componentMoved(java.awt.event.ComponentEvent e) {
|
||||
if(getDesktopPane() != null) getDesktopPane().repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,21 @@
|
||||
package io.swtc.proccessing;
|
||||
|
||||
/*
|
||||
*
|
||||
* Soon to be deprecated!
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Gray World Algorithm.
|
||||
*
|
||||
* <p>
|
||||
* This class is an implementation of the Gray World algorithm, an automatic
|
||||
* white balancing method.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* See:
|
||||
* <a href="https://acorn.stanford.edu/psych221/projects/2000/trek/GWimages.html">
|
||||
* Stanford explanation
|
||||
* </a>
|
||||
* </p>
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
public class AutoGainProcessor {
|
||||
|
||||
public float[] calculateAutoGains(int[] pixels) {
|
||||
|
||||
@@ -3,16 +3,16 @@ package io.swtc.proccessing;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.function.Function;
|
||||
|
||||
/*
|
||||
*
|
||||
* Now here its getting rather interesting! this class processes some
|
||||
* important stuff!
|
||||
*
|
||||
* */
|
||||
|
||||
/**
|
||||
* Enhanced CameraPanel with support for custom image processors
|
||||
*/
|
||||
public class CameraPanel extends JPanel {
|
||||
private BufferedImage currentImage;
|
||||
private BufferedImage processedImage;
|
||||
|
||||
// Basic transform effects
|
||||
private boolean mirror = false;
|
||||
private boolean flip = false;
|
||||
private boolean rotate = false;
|
||||
@@ -21,150 +21,100 @@ public class CameraPanel extends JPanel {
|
||||
private int brightness = 0;
|
||||
private float contrast = 1.0f;
|
||||
|
||||
// Custom processor for advanced effects
|
||||
private Function<BufferedImage, BufferedImage> imageProcessor;
|
||||
|
||||
public CameraPanel() {
|
||||
setBackground(Color.BLACK);
|
||||
setPreferredSize(new Dimension(640, 480));
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage img) {
|
||||
this.currentImage = img;
|
||||
this.repaint();
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public BufferedImage getCurrentImage() {
|
||||
return currentImage;
|
||||
public void setImageProcessor(Function<BufferedImage, BufferedImage> processor) {
|
||||
this.imageProcessor = processor;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public BufferedImage getCurrentProcessedImage() {
|
||||
private void processImage() {
|
||||
if (currentImage == null) {
|
||||
return null;
|
||||
processedImage = null;
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedImage processed = currentImage;
|
||||
BufferedImage result = currentImage;
|
||||
|
||||
// apply color effects
|
||||
if (grayscale || invert || brightness != 0 || contrast != 1.0f) {
|
||||
processed = applyColorEffects(processed);
|
||||
// Apply basic transforms first
|
||||
result = applyBasicEffects(result);
|
||||
|
||||
// Apply custom processor if set
|
||||
if (imageProcessor != null) {
|
||||
result = imageProcessor.apply(result);
|
||||
}
|
||||
|
||||
// apply transform.
|
||||
if (mirror || flip || rotate) {
|
||||
processed = applyTransforms(processed);
|
||||
}
|
||||
|
||||
return processed;
|
||||
processedImage = result;
|
||||
}
|
||||
|
||||
/* Helper Methods ... its the same boilerplate shit over and over again, im sick of this. */
|
||||
public void setMirror(boolean mirror) {
|
||||
this.mirror = mirror;
|
||||
this.repaint();
|
||||
}
|
||||
private BufferedImage applyBasicEffects(BufferedImage img) {
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
public void setFlip(boolean flip) {
|
||||
this.flip = flip;
|
||||
this.repaint();
|
||||
}
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
// Handle mirror/flip/rotate
|
||||
int srcX = x;
|
||||
int srcY = y;
|
||||
|
||||
public void setRotate(boolean rotate) {
|
||||
this.rotate = rotate;
|
||||
this.repaint();
|
||||
}
|
||||
if (mirror) srcX = width - 1 - x;
|
||||
if (flip) srcY = height - 1 - y;
|
||||
if (rotate) {
|
||||
int temp = srcX;
|
||||
srcX = width - 1 - srcY;
|
||||
srcY = temp;
|
||||
}
|
||||
|
||||
public void setGrayscale(boolean grayscale) {
|
||||
this.grayscale = grayscale;
|
||||
this.repaint();
|
||||
}
|
||||
// Ensure coordinates are in bounds
|
||||
srcX = Math.max(0, Math.min(width - 1, srcX));
|
||||
srcY = Math.max(0, Math.min(height - 1, srcY));
|
||||
|
||||
public void setInvert(boolean invert) {
|
||||
this.invert = invert;
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
public void setBrightness(int brightness) {
|
||||
this.brightness = brightness;
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
public void setContrast(float contrast) {
|
||||
this.contrast = contrast;
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
public void resetEffects() {
|
||||
mirror = flip = rotate = grayscale = invert = false;
|
||||
brightness = 0;
|
||||
contrast = 1.0f;
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
if (currentImage != null) {
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
|
||||
BufferedImage processedImage = currentImage;
|
||||
|
||||
// effects
|
||||
if (grayscale || invert || brightness != 0 || contrast != 1.0f) {
|
||||
processedImage = applyColorEffects(processedImage);
|
||||
}
|
||||
|
||||
// transforms
|
||||
int w = getWidth(), h = getHeight();
|
||||
|
||||
if (rotate) {
|
||||
g2.translate(w / 2, h / 2);
|
||||
g2.rotate(Math.PI);
|
||||
g2.translate(-w / 2, -h / 2);
|
||||
}
|
||||
|
||||
|
||||
// here we have if, cause we need to do the stuff, yk?
|
||||
if (mirror && flip) {
|
||||
g2.drawImage(processedImage, w, h, -w, -h, null);
|
||||
} else if (mirror) {
|
||||
g2.drawImage(processedImage, w, 0, -w, h, null);
|
||||
} else if (flip) {
|
||||
g2.drawImage(processedImage, 0, h, w, -h, null);
|
||||
} else {
|
||||
g2.drawImage(processedImage, 0, 0, w, h, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage applyColorEffects(BufferedImage img) {
|
||||
BufferedImage result = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
for (int y = 0; y < img.getHeight(); y++) {
|
||||
for (int x = 0; x < img.getWidth(); x++) {
|
||||
int rgb = img.getRGB(x, y);
|
||||
int rgb = img.getRGB(srcX, srcY);
|
||||
int r = (rgb >> 16) & 0xFF;
|
||||
int g = (rgb >> 8) & 0xFF;
|
||||
int b = rgb & 0xFF;
|
||||
|
||||
// Apply grayscale
|
||||
if (grayscale) {
|
||||
int gray = (r + g + b) / 3;
|
||||
r = g = b = gray;
|
||||
}
|
||||
|
||||
// this is fun, this regulates the brightness or the contrast!
|
||||
// this is some real java, the other stuff is just UI design, which i am bad at,
|
||||
// but this! This is some real shit
|
||||
r = (int) ((r - 128) * contrast + 128 + brightness);
|
||||
g = (int) ((g - 128) * contrast + 128 + brightness);
|
||||
b = (int) ((b - 128) * contrast + 128 + brightness);
|
||||
// Apply brightness
|
||||
if (brightness != 0) {
|
||||
r = clamp(r + brightness);
|
||||
g = clamp(g + brightness);
|
||||
b = clamp(b + brightness);
|
||||
}
|
||||
|
||||
// invert the colors!
|
||||
// Apply contrast
|
||||
if (contrast != 1.0f) {
|
||||
r = clamp((int)((r - 128) * contrast + 128));
|
||||
g = clamp((int)((g - 128) * contrast + 128));
|
||||
b = clamp((int)((b - 128) * contrast + 128));
|
||||
}
|
||||
|
||||
// Apply invert
|
||||
if (invert) {
|
||||
r = 255 - r;
|
||||
g = 255 - g;
|
||||
b = 255 - b;
|
||||
}
|
||||
|
||||
// clamp so we dont get into weird color grades, or any weird looking spaces
|
||||
// if we dont do this we cant really do stuff which looks bearable
|
||||
r = Math.max(0, Math.min(255, r));
|
||||
g = Math.max(0, Math.min(255, g));
|
||||
b = Math.max(0, Math.min(255, b));
|
||||
|
||||
result.setRGB(x, y, (r << 16) | (g << 8) | b);
|
||||
}
|
||||
}
|
||||
@@ -172,33 +122,95 @@ public class CameraPanel extends JPanel {
|
||||
return result;
|
||||
}
|
||||
|
||||
private BufferedImage applyTransforms(BufferedImage img) {
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
private int clamp(int value) {
|
||||
return Math.max(0, Math.min(255, value));
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int sourceX = x;
|
||||
int sourceY = y;
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
||||
if (mirror) {
|
||||
sourceX = width - 1 - x;
|
||||
}
|
||||
if (flip) {
|
||||
sourceY = height - 1 - y;
|
||||
}
|
||||
if (rotate) {
|
||||
int tempX = width - 1 - sourceX;
|
||||
int tempY = height - 1 - sourceY;
|
||||
sourceX = tempX;
|
||||
sourceY = tempY;
|
||||
}
|
||||
if (processedImage != null) {
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
|
||||
result.setRGB(x, y, img.getRGB(sourceX, sourceY));
|
||||
}
|
||||
// Scale image to fit panel while maintaining aspect ratio
|
||||
int panelWidth = getWidth();
|
||||
int panelHeight = getHeight();
|
||||
int imgWidth = processedImage.getWidth();
|
||||
int imgHeight = processedImage.getHeight();
|
||||
|
||||
double scaleX = (double) panelWidth / imgWidth;
|
||||
double scaleY = (double) panelHeight / imgHeight;
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
int scaledWidth = (int) (imgWidth * scale);
|
||||
int scaledHeight = (int) (imgHeight * scale);
|
||||
|
||||
int x = (panelWidth - scaledWidth) / 2;
|
||||
int y = (panelHeight - scaledHeight) / 2;
|
||||
|
||||
g2d.drawImage(processedImage, x, y, scaledWidth, scaledHeight, null);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
public BufferedImage getCurrentProcessedImage() {
|
||||
return processedImage;
|
||||
}
|
||||
|
||||
// Basic effect setters
|
||||
public void setMirror(boolean mirror) {
|
||||
this.mirror = mirror;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setFlip(boolean flip) {
|
||||
this.flip = flip;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setRotate(boolean rotate) {
|
||||
this.rotate = rotate;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setGrayscale(boolean grayscale) {
|
||||
this.grayscale = grayscale;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setInvert(boolean invert) {
|
||||
this.invert = invert;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setBrightness(int brightness) {
|
||||
this.brightness = brightness;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setContrast(float contrast) {
|
||||
this.contrast = contrast;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void resetEffects() {
|
||||
this.mirror = false;
|
||||
this.flip = false;
|
||||
this.rotate = false;
|
||||
this.grayscale = false;
|
||||
this.invert = false;
|
||||
this.brightness = 0;
|
||||
this.contrast = 1.0f;
|
||||
this.imageProcessor = null;
|
||||
processImage();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
81
src/main/java/io/swtc/proccessing/ConnectionOverlay.java
Normal file
81
src/main/java/io/swtc/proccessing/ConnectionOverlay.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package io.swtc.proccessing;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.CubicCurve2D;
|
||||
|
||||
/**
|
||||
* Acts as a transparent layer over the entire window to draw connections.
|
||||
*/
|
||||
public class ConnectionOverlay extends JComponent {
|
||||
private final Component source;
|
||||
private final Component target;
|
||||
private final Color connectionColor;
|
||||
|
||||
public ConnectionOverlay(Component source, Component target, Color color) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.connectionColor = color;
|
||||
setOpaque(false); // Make sure we can see through it
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
if (!source.isShowing() || !target.isShowing()) return;
|
||||
|
||||
Graphics2D g2d = (Graphics2D) g.create();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// 1. Get absolute positions relative to this Overlay
|
||||
Point p1 = SwingUtilities.convertPoint(source, 0, 0, this);
|
||||
Point p2 = SwingUtilities.convertPoint(target, 0, 0, this);
|
||||
|
||||
int x1, y1, x2, y2, ctrl1X, ctrl2X;
|
||||
|
||||
// Calculate vertical centers
|
||||
y1 = p1.y + (source.getHeight() / 2);
|
||||
y2 = p2.y + (target.getHeight() / 2);
|
||||
|
||||
// 2. Logic to determine Left/Right orientation
|
||||
// If source is to the left of target
|
||||
if (p1.x + source.getWidth() < p2.x) {
|
||||
x1 = p1.x + source.getWidth(); // Right edge of source
|
||||
x2 = p2.x; // Left edge of target
|
||||
}
|
||||
// If source is to the right of target
|
||||
else if (p1.x > p2.x + target.getWidth()) {
|
||||
x1 = p1.x; // Left edge of source
|
||||
x2 = p2.x + target.getWidth(); // Right edge of target
|
||||
}
|
||||
// If they are overlapping horizontally, use centers
|
||||
else {
|
||||
x1 = p1.x + (source.getWidth() / 2);
|
||||
x2 = p2.x + (target.getWidth() / 2);
|
||||
}
|
||||
|
||||
// 3. Dynamic Curve "Stiffness"
|
||||
// The horizontal distance between the two points determines how far the curve pulls
|
||||
int horizontalDist = Math.abs(x1 - x2);
|
||||
int offset = Math.max(horizontalDist / 2, 20); // Minimum 20px pull for short distances
|
||||
|
||||
if (x1 < x2) {
|
||||
ctrl1X = x1 + offset;
|
||||
ctrl2X = x2 - offset;
|
||||
} else {
|
||||
ctrl1X = x1 - offset;
|
||||
ctrl2X = x2 + offset;
|
||||
}
|
||||
|
||||
// 4. Draw the Curve
|
||||
g2d.setColor(connectionColor);
|
||||
g2d.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
|
||||
// We keep the Y coordinates for controls the same as the endpoints
|
||||
// to create that "horizontal entry/exit" look.
|
||||
CubicCurve2D curve = new CubicCurve2D.Float(x1, y1, ctrl1X, y1, ctrl2X, y2, x2, y2);
|
||||
g2d.draw(curve);
|
||||
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
25
src/main/java/io/swtc/proccessing/EffectState.java
Normal file
25
src/main/java/io/swtc/proccessing/EffectState.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package io.swtc.proccessing;
|
||||
|
||||
public class EffectState {
|
||||
public final boolean awbEnabled, dnrEnabled, edgeEnhance;
|
||||
public final int awbStrength, dnrSpatial, dnrTemporal;
|
||||
public final int temperature, tint, saturation, shadows, highlights, sharpness;
|
||||
|
||||
public EffectState(boolean awbEnabled, int awbStrength, boolean dnrEnabled,
|
||||
int dnrSpatial, int dnrTemporal, int temperature, int tint,
|
||||
int saturation, int shadows, int highlights,
|
||||
int sharpness, boolean edgeEnhance) {
|
||||
this.awbEnabled = awbEnabled;
|
||||
this.awbStrength = awbStrength;
|
||||
this.dnrEnabled = dnrEnabled;
|
||||
this.dnrSpatial = dnrSpatial;
|
||||
this.dnrTemporal = dnrTemporal;
|
||||
this.temperature = temperature;
|
||||
this.tint = tint;
|
||||
this.saturation = saturation;
|
||||
this.shadows = shadows;
|
||||
this.highlights = highlights;
|
||||
this.sharpness = sharpness;
|
||||
this.edgeEnhance = edgeEnhance;
|
||||
}
|
||||
}
|
||||
273
src/main/java/io/swtc/proccessing/FilterPanel.java
Normal file
273
src/main/java/io/swtc/proccessing/FilterPanel.java
Normal file
@@ -0,0 +1,273 @@
|
||||
package io.swtc.proccessing;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.TitledBorder;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Random;
|
||||
|
||||
public class FilterPanel extends JPanel {
|
||||
|
||||
private final CameraPanel cameraPanel;
|
||||
private final AutoGainProcessor autoGainProcessor;
|
||||
private final PresetLibrary presetLibrary;
|
||||
private final Color connectionColor;
|
||||
|
||||
private JCheckBox awbEnabled, dnrEnabled, edgeEnhance;
|
||||
private JSlider awbStrength, dnrStrength, dnrTemporal, sharpness;
|
||||
private JSlider saturation, temperature, tint, shadows, highlights;
|
||||
private JComboBox<String> presetCombo;
|
||||
|
||||
private float[] currentGains = {1f, 1f, 1f};
|
||||
|
||||
public FilterPanel(CameraPanel cameraPanel) {
|
||||
this.cameraPanel = cameraPanel;
|
||||
this.autoGainProcessor = new AutoGainProcessor();
|
||||
this.presetLibrary = new PresetLibrary();
|
||||
|
||||
// FIX: Randomize color with high saturation so it stands out
|
||||
Random rand = new Random();
|
||||
this.connectionColor = Color.getHSBColor(rand.nextFloat(), 0.9f, 0.9f);
|
||||
|
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
|
||||
buildUI();
|
||||
initListeners(); // Initialize anti-ghosting listeners
|
||||
}
|
||||
|
||||
|
||||
private void buildUI() {
|
||||
add(createPresetPanel());
|
||||
add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
|
||||
JPanel container = new JPanel();
|
||||
container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
|
||||
|
||||
container.add(createAWBPanel());
|
||||
container.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
container.add(createDNRPanel());
|
||||
container.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
container.add(createColorPanel());
|
||||
container.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
container.add(createDetailPanel());
|
||||
|
||||
JScrollPane scroll = new JScrollPane(container);
|
||||
scroll.setBorder(null);
|
||||
scroll.getVerticalScrollBar().setUnitIncrement(16);
|
||||
add(scroll);
|
||||
|
||||
add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
add(createActionPanel());
|
||||
}
|
||||
|
||||
public EffectState getCurrentState() {
|
||||
return new EffectState(
|
||||
awbEnabled.isSelected(), awbStrength.getValue(),
|
||||
dnrEnabled.isSelected(), dnrStrength.getValue(), dnrTemporal.getValue(),
|
||||
temperature.getValue(), tint.getValue(), saturation.getValue(),
|
||||
shadows.getValue(), highlights.getValue(),
|
||||
sharpness.getValue(), edgeEnhance.isSelected()
|
||||
);
|
||||
}
|
||||
|
||||
// Inside AdvancedEffectsPanel.java
|
||||
|
||||
private void initListeners() {
|
||||
ComponentAdapter repaintListener = new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
updateOverlay();
|
||||
}
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
updateOverlay();
|
||||
}
|
||||
};
|
||||
|
||||
this.addComponentListener(repaintListener);
|
||||
if (this.cameraPanel != null) {
|
||||
this.cameraPanel.addComponentListener(repaintListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOverlay() {
|
||||
RootPaneContainer root = (RootPaneContainer) SwingUtilities.getWindowAncestor(this);
|
||||
if (root != null) {
|
||||
root.getGlassPane().repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyToCamera() {
|
||||
EffectState state = getCurrentState();
|
||||
cameraPanel.setImageProcessor(img ->
|
||||
ImageEffectEngine.applyEffects(img, state, currentGains)
|
||||
);
|
||||
}
|
||||
|
||||
private JPanel createPresetPanel() {
|
||||
JPanel panel = new JPanel(new BorderLayout(5, 5));
|
||||
panel.setBorder(createTitledBorder("Presets"));
|
||||
|
||||
presetCombo = new JComboBox<>(new String[]{"Custom", "Natural", "Vivid", "Portrait", "Low Light", "Cinematic"});
|
||||
JButton saveBtn = new JButton("Save");
|
||||
|
||||
presetCombo.addActionListener(e -> {
|
||||
String selected = (String) presetCombo.getSelectedItem();
|
||||
EffectState state = presetLibrary.get(selected);
|
||||
if (state != null) applyPresetToUI(state);
|
||||
});
|
||||
|
||||
saveBtn.addActionListener(e -> {
|
||||
String name = JOptionPane.showInputDialog(this, "Preset Name:");
|
||||
if (name != null) {
|
||||
presetLibrary.savePreset(name, getCurrentState());
|
||||
presetCombo.addItem(name);
|
||||
}
|
||||
});
|
||||
|
||||
panel.add(presetCombo, BorderLayout.CENTER);
|
||||
panel.add(saveBtn, BorderLayout.EAST);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createDNRPanel() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
panel.setBorder(createTitledBorder("3D Denoise (DNR)"));
|
||||
|
||||
dnrEnabled = new JCheckBox("Enable Temporal Denoise");
|
||||
dnrStrength = addSliderToPanel(panel, "Spatial Strength", 0, 100, 30, "%");
|
||||
dnrTemporal = addSliderToPanel(panel, "Temporal Strength", 0, 100, 50, "%");
|
||||
|
||||
dnrEnabled.addActionListener(e -> {
|
||||
boolean enabled = dnrEnabled.isSelected();
|
||||
dnrStrength.setEnabled(enabled);
|
||||
dnrTemporal.setEnabled(enabled);
|
||||
applyToCamera();
|
||||
});
|
||||
|
||||
dnrStrength.setEnabled(false);
|
||||
dnrTemporal.setEnabled(false);
|
||||
|
||||
panel.add(dnrEnabled, 0);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createDetailPanel() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
panel.setBorder(createTitledBorder("Detail & Sharpness"));
|
||||
|
||||
sharpness = addSliderToPanel(panel, "Sharpness", 0, 200, 100, "%");
|
||||
edgeEnhance = new JCheckBox("Edge Enhancement");
|
||||
|
||||
edgeEnhance.addActionListener(e -> applyToCamera());
|
||||
|
||||
panel.add(edgeEnhance);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createAWBPanel() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
panel.setBorder(createTitledBorder("White Balance"));
|
||||
|
||||
awbEnabled = new JCheckBox("Enable AWB");
|
||||
JPanel sPanel = createSliderPanel("Strength", 0, 100, 100, "%");
|
||||
awbStrength = (JSlider) sPanel.getComponent(1);
|
||||
JButton balanceBtn = new JButton("Balance Now");
|
||||
|
||||
awbEnabled.addActionListener(e -> {
|
||||
boolean active = awbEnabled.isSelected();
|
||||
awbStrength.setEnabled(active);
|
||||
balanceBtn.setEnabled(active);
|
||||
if (active) performOneTimeBalance();
|
||||
else { currentGains = new float[]{1f, 1f, 1f}; applyToCamera(); }
|
||||
});
|
||||
|
||||
awbStrength.addChangeListener(e -> { if(!awbStrength.getValueIsAdjusting()) applyToCamera(); });
|
||||
balanceBtn.addActionListener(e -> performOneTimeBalance());
|
||||
|
||||
panel.add(awbEnabled);
|
||||
panel.add(sPanel);
|
||||
panel.add(balanceBtn);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createColorPanel() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
panel.setBorder(createTitledBorder("Color Grading"));
|
||||
|
||||
temperature = addSliderToPanel(panel, "Temperature", -100, 100, 0, "");
|
||||
tint = addSliderToPanel(panel, "Tint", -100, 100, 0, "");
|
||||
saturation = addSliderToPanel(panel, "Saturation", 0, 200, 100, "%");
|
||||
shadows = addSliderToPanel(panel, "Shadows", -100, 100, 0, "");
|
||||
highlights = addSliderToPanel(panel, "Highlights", -100, 100, 0, "");
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void performOneTimeBalance() {
|
||||
BufferedImage img = cameraPanel.getCurrentProcessedImage();
|
||||
if (img != null) {
|
||||
int[] pixels = img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
|
||||
currentGains = autoGainProcessor.calculateAutoGains(pixels);
|
||||
applyToCamera();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyPresetToUI(EffectState s) {
|
||||
awbEnabled.setSelected(s.awbEnabled);
|
||||
awbStrength.setValue(s.awbStrength);
|
||||
dnrEnabled.setSelected(s.dnrEnabled);
|
||||
dnrStrength.setValue(s.dnrSpatial);
|
||||
temperature.setValue(s.temperature);
|
||||
tint.setValue(s.tint);
|
||||
saturation.setValue(s.saturation);
|
||||
shadows.setValue(s.shadows);
|
||||
highlights.setValue(s.highlights);
|
||||
sharpness.setValue(s.sharpness);
|
||||
edgeEnhance.setSelected(s.edgeEnhance);
|
||||
applyToCamera();
|
||||
}
|
||||
|
||||
private JSlider addSliderToPanel(JPanel parent, String label, int min, int max, int val, String unit) {
|
||||
JPanel p = createSliderPanel(label, min, max, val, unit);
|
||||
JSlider s = (JSlider) p.getComponent(1);
|
||||
s.addChangeListener(e -> { if(!s.getValueIsAdjusting()) applyToCamera(); });
|
||||
parent.add(p);
|
||||
parent.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
return s;
|
||||
}
|
||||
|
||||
private JPanel createSliderPanel(String label, int min, int max, int initial, String unit) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
JLabel title = new JLabel(label + ": " + initial + unit);
|
||||
JSlider slider = new JSlider(min, max, initial);
|
||||
slider.addChangeListener(e -> title.setText(label + ": " + slider.getValue() + unit));
|
||||
panel.add(title, BorderLayout.NORTH);
|
||||
panel.add(slider, BorderLayout.CENTER);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private TitledBorder createTitledBorder(String title) {
|
||||
return BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), title);
|
||||
}
|
||||
|
||||
private JPanel createActionPanel() {
|
||||
JPanel panel = new JPanel(new GridLayout(1, 2, 10, 0));
|
||||
JButton reset = new JButton("Reset All");
|
||||
reset.addActionListener(e -> resetUI());
|
||||
panel.add(reset);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void resetUI() {
|
||||
applyPresetToUI(new EffectState(false, 100, false, 30, 50, 0, 0, 100, 0, 0, 100, false));
|
||||
}
|
||||
}
|
||||
147
src/main/java/io/swtc/proccessing/ImageEffectEngine.java
Normal file
147
src/main/java/io/swtc/proccessing/ImageEffectEngine.java
Normal file
@@ -0,0 +1,147 @@
|
||||
package io.swtc.proccessing;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class ImageEffectEngine {
|
||||
|
||||
public static BufferedImage applyEffects(BufferedImage img, EffectState state, float[] currentGains) {
|
||||
if (img == null) return null;
|
||||
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int rgb = img.getRGB(x, y);
|
||||
int r = (rgb >> 16) & 0xFF;
|
||||
int g = (rgb >> 8) & 0xFF;
|
||||
int b = rgb & 0xFF;
|
||||
|
||||
// 1. AWB
|
||||
if (state.awbEnabled) {
|
||||
float s = state.awbStrength / 100f;
|
||||
r = (int) Math.min(255, r * (1 + (currentGains[0] - 1) * s));
|
||||
g = (int) Math.min(255, g * (1 + (currentGains[1] - 1) * s));
|
||||
b = (int) Math.min(255, b * (1 + (currentGains[2] - 1) * s));
|
||||
}
|
||||
|
||||
// 2. Temp & Tint
|
||||
if (state.temperature != 0) {
|
||||
float factor = state.temperature / 100f;
|
||||
r = clamp(r + (int)(factor * 30));
|
||||
b = clamp(b - (int)(factor * 30));
|
||||
}
|
||||
if (state.tint != 0) {
|
||||
g = clamp(g + (int)((state.tint / 100f) * 20));
|
||||
}
|
||||
|
||||
// 3. Saturation
|
||||
if (state.saturation != 100) {
|
||||
float factor = state.saturation / 100f;
|
||||
float gray = (r + g + b) / 3f;
|
||||
r = clamp((int)(gray + (r - gray) * factor));
|
||||
g = clamp((int)(gray + (g - gray) * factor));
|
||||
b = clamp((int)(gray + (b - gray) * factor));
|
||||
}
|
||||
|
||||
// 4. Shadows/Highlights
|
||||
float lum = (r + g + b) / 3f / 255f;
|
||||
if (lum < 0.5f && state.shadows != 0) {
|
||||
int adj = (int)((state.shadows / 100f) * (1 - lum * 2) * 50);
|
||||
r = clamp(r + adj); g = clamp(g + adj); b = clamp(b + adj);
|
||||
} else if (lum > 0.5f && state.highlights != 0) {
|
||||
int adj = (int)((state.highlights / 100f) * (lum * 2 - 1) * 50);
|
||||
r = clamp(r + adj); g = clamp(g + adj); b = clamp(b + adj);
|
||||
}
|
||||
|
||||
result.setRGB(x, y, (r << 16) | (g << 8) | b);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.sharpness != 100 || state.edgeEnhance) {
|
||||
result = applySharpness(result, state.sharpness / 100f, state.edgeEnhance);
|
||||
}
|
||||
|
||||
if (state.dnrEnabled) {
|
||||
result = applyDenoise(result, state.dnrSpatial / 100f);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static BufferedImage applySharpness(BufferedImage img, float amount, boolean edgeEnhance) {
|
||||
if (amount == 1f && !edgeEnhance) return img;
|
||||
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
float[][] kernel = edgeEnhance ?
|
||||
new float[][]{{0, -1, 0}, {-1, 5, -1}, {0, -1, 0}} :
|
||||
new float[][]{{-1, -1, -1}, {-1, 9, -1}, {-1, -1, -1}};
|
||||
|
||||
for (int y = 1; y < height - 1; y++) {
|
||||
for (int x = 1; x < width - 1; x++) {
|
||||
float r = 0, g = 0, b = 0;
|
||||
|
||||
for (int ky = -1; ky <= 1; ky++) {
|
||||
for (int kx = -1; kx <= 1; kx++) {
|
||||
int rgb = img.getRGB(x + kx, y + ky);
|
||||
float weight = kernel[ky + 1][kx + 1] * (amount - 1) / 8f;
|
||||
if (kx == 0 && ky == 0) weight += 1;
|
||||
|
||||
r += ((rgb >> 16) & 0xFF) * weight;
|
||||
g += ((rgb >> 8) & 0xFF) * weight;
|
||||
b += (rgb & 0xFF) * weight;
|
||||
}
|
||||
}
|
||||
|
||||
result.setRGB(x, y, (clamp((int)r) << 16) | (clamp((int)g) << 8) | clamp((int)b));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static BufferedImage applyDenoise(BufferedImage img, float strength) {
|
||||
if (strength == 0) return img;
|
||||
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
int radius = (int)(strength * 2) + 1;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int rSum = 0, gSum = 0, bSum = 0, count = 0;
|
||||
|
||||
for (int dy = -radius; dy <= radius; dy++) {
|
||||
for (int dx = -radius; dx <= radius; dx++) {
|
||||
int nx = Math.min(width - 1, Math.max(0, x + dx));
|
||||
int ny = Math.min(height - 1, Math.max(0, y + dy));
|
||||
|
||||
int rgb = img.getRGB(nx, ny);
|
||||
rSum += (rgb >> 16) & 0xFF;
|
||||
gSum += (rgb >> 8) & 0xFF;
|
||||
bSum += rgb & 0xFF;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
int r = rSum / count;
|
||||
int g = gSum / count;
|
||||
int b = bSum / count;
|
||||
|
||||
result.setRGB(x, y, (r << 16) | (g << 8) | b);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int clamp(int val) {
|
||||
return Math.max(0, Math.min(255, val));
|
||||
}
|
||||
}
|
||||
46
src/main/java/io/swtc/proccessing/PresetLibrary.java
Normal file
46
src/main/java/io/swtc/proccessing/PresetLibrary.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package io.swtc.proccessing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PresetLibrary {
|
||||
private final Map<String, EffectState> 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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user