From ccc3d264f759f970b48ac81ae3a80403cc882d35 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Tue, 13 Jan 2026 20:52:32 +0100 Subject: [PATCH] a new windowing system! add: + CameraPanel ; To make life easier for coders + SwingIFrame ; Which is now our main UI component + VideoRecorder ; A helper Class for SwingIFrame to record cameras! changed: / SwingCCTVManager ; changes for the new UI Component deprecation: /-/ AutoGainProcessor ; cause it isnt needed anymore, back then this was needed cause we opened the webcams manually (color wise) --- rattatwinko --- src/main/java/io/swtc/SwingCCTVManager.java | 89 +++-- src/main/java/io/swtc/SwingIFrame.java | 311 ++++++++++++++++++ .../swtc/proccessing/AutoGainProcessor.java | 7 + .../java/io/swtc/proccessing/CameraPanel.java | 204 ++++++++++++ .../java/io/swtc/recording/VideoRecorder.java | 114 +++++++ 5 files changed, 698 insertions(+), 27 deletions(-) create mode 100644 src/main/java/io/swtc/SwingIFrame.java create mode 100644 src/main/java/io/swtc/proccessing/CameraPanel.java create mode 100644 src/main/java/io/swtc/recording/VideoRecorder.java diff --git a/src/main/java/io/swtc/SwingCCTVManager.java b/src/main/java/io/swtc/SwingCCTVManager.java index 76d8501..89cd90a 100644 --- a/src/main/java/io/swtc/SwingCCTVManager.java +++ b/src/main/java/io/swtc/SwingCCTVManager.java @@ -31,22 +31,37 @@ public class SwingCCTVManager { private final JFrame frame; private final JTable deviceTable; private final DefaultTableModel tableModel; + private final SwingIFrame IFrame; public SwingCCTVManager() { frame = new JFrame("dashboard"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(1000, 600); + this.IFrame = new SwingIFrame(); + this.IFrame.show(); + String[] columns = {"Status", "Device Name", "Type", "Resolution", "Address"}; tableModel = new DefaultTableModel(columns, 0) { - @Override public boolean isCellEditable(int r, int c) { return false; } + @Override + public boolean isCellEditable(int r, int c) { + return false; + } }; deviceTable = new JTable(tableModel); + + deviceTable.setRowSelectionAllowed(true); + deviceTable.setFocusable(true); + deviceTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + setupTableAppearance(); deviceTable.addMouseListener(new MouseAdapter() { + @Override public void mousePressed(MouseEvent e) { + deviceTable.requestFocusInWindow(); + if (e.getClickCount() == 2 && deviceTable.getSelectedRow() != -1) { launchSelected(); } @@ -74,29 +89,31 @@ public class SwingCCTVManager { } private void setupTableAppearance() { - deviceTable.getColumnModel().getColumn(0).setMaxWidth(80); // Status column + deviceTable.getColumnModel().getColumn(0).setMaxWidth(80); deviceTable.setRowHeight(30); - // Custom Renderer for Status Colors - deviceTable.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() { - @Override - public Component getTableCellRendererComponent(JTable t, Object v, boolean s, boolean f, int r, int c) { - Component comp = super.getTableCellRendererComponent(t, v, s, f, r, c); - if ("ONLINE".equals(v)) comp.setForeground(new Color(0, 150, 0)); - else comp.setForeground(Color.RED); - setHorizontalAlignment(JLabel.CENTER); - return comp; - } - }); + deviceTable.getColumnModel().getColumn(0) + .setCellRenderer(new DefaultTableCellRenderer() { + @Override + public Component getTableCellRendererComponent( + JTable t, Object v, boolean s, boolean f, int r, int c) { + + Component comp = super.getTableCellRendererComponent(t, v, s, f, r, c); + setHorizontalAlignment(JLabel.CENTER); + comp.setForeground("ONLINE".equals(v) + ? new Color(0, 150, 0) + : Color.RED); + return comp; + } + }); } private void startAutoRefresh() { - Timer autoRefreshTimer = new Timer(5000, e -> refreshTable()); - autoRefreshTimer.start(); + new Timer(5000, e -> refreshTable()).start(); } private void refreshTable() { - int selectedRow = deviceTable.getSelectedRow(); + int[] selectedRows = deviceTable.getSelectedRows(); tableModel.setRowCount(0); List webcams = Webcam.getWebcams(); @@ -114,14 +131,18 @@ public class SwingCCTVManager { }); } - if (selectedRow != -1 && selectedRow < tableModel.getRowCount()) { - deviceTable.setRowSelectionInterval(selectedRow, selectedRow); + for (int r : selectedRows) { + if (r < tableModel.getRowCount()) { + deviceTable.addRowSelectionInterval(r, r); + } } } private void showContextMenu(MouseEvent e) { int row = deviceTable.rowAtPoint(e.getPoint()); - deviceTable.setRowSelectionInterval(row, row); + if (row >= 0) { + deviceTable.setRowSelectionInterval(row, row); + } JPopupMenu menu = new JPopupMenu(); JMenuItem launch = new JMenuItem("Launch Live Stream"); @@ -143,12 +164,16 @@ public class SwingCCTVManager { String name = (String) tableModel.getValueAt(row, 1); Webcam.getWebcams().stream() .filter(w -> w.getName().equals(name)) - .findFirst().ifPresent(selected -> new Thread(() -> new SwingCameraWindow(selected).open()).start()); - + .findFirst() + .ifPresent(cam -> + new Thread(() -> IFrame.addCameraInternalFrame(cam)).start() + ); } private void deleteSelected() { int row = deviceTable.getSelectedRow(); + if (row == -1) return; + String name = (String) tableModel.getValueAt(row, 1); if (name.toLowerCase().contains("usb")) return; @@ -160,11 +185,15 @@ public class SwingCCTVManager { private static void loadSavedCameras() { for (CameraConfig config : CameraSettings.load()) { try { - IpCamDeviceRegistry.register(config.getName(), config.getUrl(), IpCamMode.PUSH); + IpCamDeviceRegistry.register( + config.getName(), + config.getUrl(), + IpCamMode.PUSH + ); } catch (MalformedURLException e) { JOptionPane.showMessageDialog( null, - "Malformed URL\n"+e.getMessage(), + "Malformed URL\n" + e.getMessage(), "Malformed URL", JOptionPane.ERROR_MESSAGE ); @@ -176,10 +205,16 @@ public class SwingCCTVManager { JPanel p = new JPanel(new GridLayout(2, 2, 5, 5)); JTextField n = new JTextField(); JTextField u = new JTextField(); - p.add(new JLabel("Name:")); p.add(n); - p.add(new JLabel("URL:")); p.add(u); - int result = JOptionPane.showConfirmDialog(frame, p, "Register IP Camera", JOptionPane.OK_CANCEL_OPTION); + p.add(new JLabel("Name:")); + p.add(n); + p.add(new JLabel("URL:")); + p.add(u); + + int result = JOptionPane.showConfirmDialog( + frame, p, "Register IP Camera", JOptionPane.OK_CANCEL_OPTION + ); + if (result == JOptionPane.OK_OPTION) { try { IpCamDeviceRegistry.register(n.getText(), u.getText(), IpCamMode.PUSH); @@ -199,4 +234,4 @@ public class SwingCCTVManager { public static void main(String[] args) { SwingUtilities.invokeLater(() -> new SwingCCTVManager().open()); } -} \ No newline at end of file +} diff --git a/src/main/java/io/swtc/SwingIFrame.java b/src/main/java/io/swtc/SwingIFrame.java new file mode 100644 index 0000000..a788ff1 --- /dev/null +++ b/src/main/java/io/swtc/SwingIFrame.java @@ -0,0 +1,311 @@ +package io.swtc; + +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 javax.swing.border.TitledBorder; +import java.awt.*; +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! +* +* */ + +public class SwingIFrame { + private final JFrame mainFrame; + private final JDesktopPane desktopPane; + + public SwingIFrame() { + mainFrame = new JFrame("viewer"); + mainFrame.setSize(1280,720); + mainFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + desktopPane = new JDesktopPane(); + desktopPane.setBackground(Color.WHITE); + mainFrame.add(desktopPane, BorderLayout.CENTER); + } + + public void addCameraInternalFrame(Webcam webcam) { + JInternalFrame iframe = new JInternalFrame( + webcam.getName(), + true, true, true, true + ); + + CameraPanel cameraPanel = new CameraPanel(); + WebcamCaptureLoop captureLoop = new WebcamCaptureLoop(webcam, (BufferedImage img) -> + SwingUtilities.invokeLater(() -> cameraPanel.setImage(img)) + ); + + 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)); + + contentPanel.add(tabbedPane, BorderLayout.CENTER); + + iframe.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter() { + @Override + public void internalFrameClosing(javax.swing.event.InternalFrameEvent e) { + // if we dont call this the camera stays open until the procces dies. + captureLoop.stop(); + } + }); + + iframe.add(contentPanel); + iframe.setSize(600, 500); + + int offset = desktopPane.getAllFrames().length * 30; + iframe.setLocation(offset, offset); + + desktopPane.add(iframe); + iframe.setVisible(true); + captureLoop.start(); + } + + private JPanel createCapturePanel(CameraPanel cameraPanel, Webcam webcam) { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(new EmptyBorder(15, 15, 15, 15)); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + // Screenshot section + JPanel screenshotPanel = new JPanel(); + screenshotPanel.setLayout(new BoxLayout(screenshotPanel, BoxLayout.Y_AXIS)); + screenshotPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Screenshot", + TitledBorder.LEFT, TitledBorder.TOP)); + + JPanel screenshotPathPanel = new JPanel(new BorderLayout(5, 5)); + JTextField screenshotPath = new JTextField(System.getProperty("user.home") + File.separator + "screenshots"); + JButton browseSS = new JButton("..."); + browseSS.setPreferredSize(new Dimension(40, 25)); + browseSS.addActionListener(e -> browseDirectory(screenshotPath, panel)); + screenshotPathPanel.add(screenshotPath, BorderLayout.CENTER); + screenshotPathPanel.add(browseSS, BorderLayout.EAST); + + JButton takeScreenshot = new JButton("Take Screenshot (S)"); + takeScreenshot.setAlignmentX(Component.CENTER_ALIGNMENT); + takeScreenshot.addActionListener(e -> saveSnapshot(cameraPanel, webcam, screenshotPath.getText(), panel)); + + screenshotPanel.add(screenshotPathPanel); + screenshotPanel.add(Box.createRigidArea(new Dimension(0, 10))); + screenshotPanel.add(takeScreenshot); + + // Recording section + JPanel recordPanel = new JPanel(); + recordPanel.setLayout(new BoxLayout(recordPanel, BoxLayout.Y_AXIS)); + recordPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Recording", + TitledBorder.LEFT, TitledBorder.TOP)); + + JPanel recordPathPanel = new JPanel(new BorderLayout(5, 5)); + JTextField recordPath = new JTextField(System.getProperty("user.home") + File.separator + "recordings"); + JButton browseRec = new JButton("..."); + browseRec.setPreferredSize(new Dimension(40, 25)); + browseRec.addActionListener(e -> browseDirectory(recordPath, panel)); + recordPathPanel.add(recordPath, BorderLayout.CENTER); + recordPathPanel.add(browseRec, BorderLayout.EAST); + + VideoRecorder recorder = new VideoRecorder(); + JButton recordButton = new JButton("Start Recording (R)"); + JLabel recordingStatus = new JLabel("Ready"); + recordingStatus.setAlignmentX(Component.CENTER_ALIGNMENT); + + recordButton.setAlignmentX(Component.CENTER_ALIGNMENT); + recordButton.addActionListener(e -> { + if (!recorder.isRecording()) { + String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String filename = "recording_" + webcam.getName().replaceAll("[^a-zA-Z0-9]", "_") + + "_" + timestamp + ".mp4"; + File dir = new File(recordPath.getText()); + dir.mkdirs(); + + try { + recorder.startRecording(cameraPanel, new File(dir, filename)); + recordButton.setText("Stop Recording"); + recordingStatus.setText("Recording..."); + recordingStatus.setForeground(Color.RED); + } catch (Exception ex) { + JOptionPane.showMessageDialog(panel, + "Error starting recording: " + ex.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + } else { + try { + File saved = recorder.stopRecording(); + recordButton.setText("Start Recording (R)"); + recordingStatus.setText("Saved: " + saved.getName()); + recordingStatus.setForeground(Color.BLACK); + } catch (Exception ex) { + recordButton.setText("Start Recording (R)"); + recordingStatus.setText("Error saving"); + recordingStatus.setForeground(Color.RED); + JOptionPane.showMessageDialog(panel, + "Error saving recording: " + ex.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + } + }); + + recordPanel.add(recordPathPanel); + recordPanel.add(Box.createRigidArea(new Dimension(0, 10))); + recordPanel.add(recordButton); + recordPanel.add(Box.createRigidArea(new Dimension(0, 5))); + recordPanel.add(recordingStatus); + + mainPanel.add(screenshotPanel); + mainPanel.add(Box.createRigidArea(new Dimension(0, 15))); + mainPanel.add(recordPanel); + mainPanel.add(Box.createVerticalGlue()); + + panel.add(mainPanel, BorderLayout.NORTH); + + return panel; + } + + private JPanel createEffectsPanel(CameraPanel cameraPanel) { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.setBorder(new EmptyBorder(15, 15, 15, 15)); + + JPanel transformPanel = new JPanel(new GridLayout(0, 1, 5, 5)); + transformPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Transform", + TitledBorder.LEFT, TitledBorder.TOP)); + + JCheckBox mirrorCheck = new JCheckBox("Mirror Horizontal"); + JCheckBox flipCheck = new JCheckBox("Flip Vertical"); + JCheckBox rotateCheck = new JCheckBox("Rotate 180°"); + + mirrorCheck.addActionListener(e -> cameraPanel.setMirror(mirrorCheck.isSelected())); + flipCheck.addActionListener(e -> cameraPanel.setFlip(flipCheck.isSelected())); + rotateCheck.addActionListener(e -> cameraPanel.setRotate(rotateCheck.isSelected())); + + transformPanel.add(mirrorCheck); + transformPanel.add(flipCheck); + transformPanel.add(rotateCheck); + + JPanel colorPanel = new JPanel(); + colorPanel.setLayout(new BoxLayout(colorPanel, BoxLayout.Y_AXIS)); + colorPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Color", + TitledBorder.LEFT, TitledBorder.TOP)); + + JCheckBox grayscaleCheck = new JCheckBox("Grayscale"); + JCheckBox invertCheck = new JCheckBox("Invert Colors"); + + grayscaleCheck.addActionListener(e -> cameraPanel.setGrayscale(grayscaleCheck.isSelected())); + invertCheck.addActionListener(e -> cameraPanel.setInvert(invertCheck.isSelected())); + + colorPanel.add(grayscaleCheck); + colorPanel.add(Box.createRigidArea(new Dimension(0, 5))); + colorPanel.add(invertCheck); + + JPanel brightnessPanel = new JPanel(); + brightnessPanel.setLayout(new BoxLayout(brightnessPanel, BoxLayout.Y_AXIS)); + brightnessPanel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createEtchedBorder(), "Adjustments", + TitledBorder.LEFT, TitledBorder.TOP)); + + JPanel brightnessSliderPanel = new JPanel(new BorderLayout()); + JLabel brightnessLabel = new JLabel("Brightness: 0"); + JSlider brightnessSlider = new JSlider(-100, 100, 0); + brightnessSlider.addChangeListener(e -> { + int value = brightnessSlider.getValue(); + brightnessLabel.setText("Brightness: " + value); + cameraPanel.setBrightness(value); + }); + brightnessSliderPanel.add(brightnessLabel, BorderLayout.NORTH); + brightnessSliderPanel.add(brightnessSlider, BorderLayout.CENTER); + + JPanel contrastSliderPanel = new JPanel(new BorderLayout()); + JLabel contrastLabel = new JLabel("Contrast: 1.0"); + JSlider contrastSlider = new JSlider(0, 200, 100); + contrastSlider.addChangeListener(e -> { + float value = contrastSlider.getValue() / 100f; + contrastLabel.setText("Contrast: " + String.format("%.1f", value)); + cameraPanel.setContrast(value); + }); + contrastSliderPanel.add(contrastLabel, BorderLayout.NORTH); + contrastSliderPanel.add(contrastSlider, BorderLayout.CENTER); + + brightnessPanel.add(brightnessSliderPanel); + brightnessPanel.add(Box.createRigidArea(new Dimension(0, 10))); + brightnessPanel.add(contrastSliderPanel); + + JButton resetButton = new JButton("Reset All Effects"); + resetButton.setAlignmentX(Component.CENTER_ALIGNMENT); + resetButton.addActionListener(e -> { + mirrorCheck.setSelected(false); + flipCheck.setSelected(false); + rotateCheck.setSelected(false); + grayscaleCheck.setSelected(false); + invertCheck.setSelected(false); + brightnessSlider.setValue(0); + contrastSlider.setValue(100); + cameraPanel.resetEffects(); + }); + + panel.add(transformPanel); + panel.add(Box.createRigidArea(new Dimension(0, 10))); + panel.add(colorPanel); + panel.add(Box.createRigidArea(new Dimension(0, 10))); + panel.add(brightnessPanel); + panel.add(Box.createRigidArea(new Dimension(0, 15))); + panel.add(resetButton); + panel.add(Box.createVerticalGlue()); + + return panel; + } + + private void browseDirectory(JTextField field, Component parent) { + JFileChooser chooser = new JFileChooser(field.getText()); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { + field.setText(chooser.getSelectedFile().getAbsolutePath()); + } + } + + private void saveSnapshot(CameraPanel cameraPanel, Webcam webcam, String directory, Component parent) { + BufferedImage img = cameraPanel.getCurrentProcessedImage(); + if (img != null) { + try { + File dir = new File(directory); + dir.mkdirs(); + + String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String filename = "screenshot_" + webcam.getName().replaceAll("[^a-zA-Z0-9]", "_") + + "_" + timestamp + ".png"; + File file = new File(dir, filename); + ImageIO.write(img, "PNG", file); + } catch (Exception ex) { + JOptionPane.showMessageDialog(parent, + "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + } + + public void show() { + mainFrame.setLocationRelativeTo(null); + mainFrame.setVisible(true); + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + SwingIFrame dashboard = new SwingIFrame(); + dashboard.show(); + }); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java b/src/main/java/io/swtc/proccessing/AutoGainProcessor.java index 1b007e3..7138dcd 100644 --- a/src/main/java/io/swtc/proccessing/AutoGainProcessor.java +++ b/src/main/java/io/swtc/proccessing/AutoGainProcessor.java @@ -1,5 +1,12 @@ package io.swtc.proccessing; +/* +* +* Soon to be deprecated! +* +* */ + +@Deprecated public class AutoGainProcessor { public float[] calculateAutoGains(int[] pixels) { diff --git a/src/main/java/io/swtc/proccessing/CameraPanel.java b/src/main/java/io/swtc/proccessing/CameraPanel.java new file mode 100644 index 0000000..fda37a7 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/CameraPanel.java @@ -0,0 +1,204 @@ +package io.swtc.proccessing; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; + +/* +* +* Now here its getting rather interesting! this class processes some +* important stuff! +* +* */ + +public class CameraPanel extends JPanel { + private BufferedImage currentImage; + private boolean mirror = false; + private boolean flip = false; + private boolean rotate = false; + private boolean grayscale = false; + private boolean invert = false; + private int brightness = 0; + private float contrast = 1.0f; + + public void setImage(BufferedImage img) { + this.currentImage = img; + this.repaint(); + } + + public BufferedImage getCurrentImage() { + return currentImage; + } + + public BufferedImage getCurrentProcessedImage() { + if (currentImage == null) { + return null; + } + + BufferedImage processed = currentImage; + + // apply color effects + if (grayscale || invert || brightness != 0 || contrast != 1.0f) { + processed = applyColorEffects(processed); + } + + // apply transform. + if (mirror || flip || rotate) { + processed = applyTransforms(processed); + } + + return processed; + } + + /* Helper Methods ... its the same boilerplate shit over and over again, im sick of this. */ + public void setMirror(boolean mirror) { + this.mirror = mirror; + this.repaint(); + } + + public void setFlip(boolean flip) { + this.flip = flip; + this.repaint(); + } + + public void setRotate(boolean rotate) { + this.rotate = rotate; + this.repaint(); + } + + public void setGrayscale(boolean grayscale) { + this.grayscale = grayscale; + this.repaint(); + } + + public void setInvert(boolean invert) { + this.invert = invert; + this.repaint(); + } + + public void setBrightness(int brightness) { + this.brightness = brightness; + this.repaint(); + } + + public void setContrast(float contrast) { + this.contrast = contrast; + this.repaint(); + } + + public void resetEffects() { + mirror = flip = rotate = grayscale = invert = false; + brightness = 0; + contrast = 1.0f; + this.repaint(); + } + + @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 r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + if (grayscale) { + int gray = (r + g + b) / 3; + r = g = b = gray; + } + + // this is fun, this regulates the brightness or the contrast! + // this is some real java, the other stuff is just UI design, which i am bad at, + // but this! This is some real shit + r = (int) ((r - 128) * contrast + 128 + brightness); + g = (int) ((g - 128) * contrast + 128 + brightness); + b = (int) ((b - 128) * contrast + 128 + brightness); + + // invert the colors! + if (invert) { + r = 255 - r; + g = 255 - g; + b = 255 - b; + } + + // clamp so we dont get into weird color grades, or any weird looking spaces + // if we dont do this we cant really do stuff which looks bearable + r = Math.max(0, Math.min(255, r)); + g = Math.max(0, Math.min(255, g)); + b = Math.max(0, Math.min(255, b)); + + result.setRGB(x, y, (r << 16) | (g << 8) | b); + } + } + + 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); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int sourceX = x; + int sourceY = y; + + if (mirror) { + sourceX = width - 1 - x; + } + if (flip) { + sourceY = height - 1 - y; + } + if (rotate) { + int tempX = width - 1 - sourceX; + int tempY = height - 1 - sourceY; + sourceX = tempX; + sourceY = tempY; + } + + result.setRGB(x, y, img.getRGB(sourceX, sourceY)); + } + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/recording/VideoRecorder.java b/src/main/java/io/swtc/recording/VideoRecorder.java new file mode 100644 index 0000000..de09f07 --- /dev/null +++ b/src/main/java/io/swtc/recording/VideoRecorder.java @@ -0,0 +1,114 @@ +package io.swtc.recording; + +import io.swtc.proccessing.CameraPanel; +import org.jcodec.api.awt.AWTSequenceEncoder; +import org.jcodec.common.io.NIOUtils; +import org.jcodec.common.io.SeekableByteChannel; +import org.jcodec.common.model.Rational; + +import javax.swing.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +/* +* +* This is not that interesting but surely improtant for any security based people, +* here we record using jcodec, which is more efficient than me writing my own recorder, +* which i am certainly not doing. +* +* Anyways we dont do shit properly anyway. +* +* */ + +public class VideoRecorder { + private boolean recording = false; + private Timer captureTimer; + private File outputFile; + private AWTSequenceEncoder encoder; + private SeekableByteChannel channel; + private static final int FPS = 30; + + public void startRecording(CameraPanel panel, File output) throws IOException { + this.outputFile = output; + this.recording = true; + + channel = NIOUtils.writableFileChannel(String.valueOf(outputFile)); + encoder = new AWTSequenceEncoder(channel, Rational.R(FPS, 1)); + + captureTimer = new Timer(1000 / FPS, e -> { + try { + BufferedImage img = panel.getCurrentProcessedImage(); + if (img != null) { + BufferedImage rgbImage = convertToRGB(img); + encoder.encodeImage(rgbImage); + } + } catch (IOException ex) { + JOptionPane.showMessageDialog( + null, + "IOException\n" + ex, + "IOException in Recorder", + JOptionPane.ERROR_MESSAGE + ); + try { + stopRecording(); + } catch (IOException stopEx) { + JOptionPane.showMessageDialog( + null, + "IOException@stopEx\n" + stopEx, + "IOException in Recorder@stopEx", + JOptionPane.ERROR_MESSAGE + ); + + } + } + }); + captureTimer.start(); + } + + public File stopRecording() throws IOException { + recording = false; + + /* some helper methods, i swear its always the same? */ + if (captureTimer != null) { + captureTimer.stop(); + } + + if (encoder != null) { + encoder.finish(); + } + + if (channel != null) { + channel.close(); + } + + return outputFile; + } + + public boolean isRecording() { + return recording; + } + + // maybe i should move this to a component, some useless conversion right here, + // (performance wise) + // yes, capitain? + private BufferedImage convertToRGB(BufferedImage source) { + if (source.getType() == BufferedImage.TYPE_INT_RGB) { + return source; + } + + BufferedImage rgb = new BufferedImage( + source.getWidth(), + source.getHeight(), + BufferedImage.TYPE_INT_RGB + ); + + for (int y = 0; y < source.getHeight(); y++) { + for (int x = 0; x < source.getWidth(); x++) { + rgb.setRGB(x, y, source.getRGB(x, y) & 0xFFFFFF); + } + } + + return rgb; + } +} \ No newline at end of file