From 98a68ef75eec463be4cd912d870dcb87bf547a15 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Tue, 30 Dec 2025 15:43:06 +0100 Subject: [PATCH] initial --- .gitignore | 44 +++++++++ pom.xml | 58 +++++++++++ src/main/java/io/swtc/CCTVManager.java | 74 ++++++++++++++ src/main/java/io/swtc/CameraWindow.java | 62 ++++++++++++ src/main/java/io/swtc/Main.java | 7 ++ .../swtc/proccessing/AutoGainProcessor.java | 35 +++++++ .../io/swtc/proccessing/CameraRenderer.java | 96 +++++++++++++++++++ .../swtc/proccessing/WebcamCaptureLoop.java | 50 ++++++++++ 8 files changed, 426 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/io/swtc/CCTVManager.java create mode 100644 src/main/java/io/swtc/CameraWindow.java create mode 100644 src/main/java/io/swtc/Main.java create mode 100644 src/main/java/io/swtc/proccessing/AutoGainProcessor.java create mode 100644 src/main/java/io/swtc/proccessing/CameraRenderer.java create mode 100644 src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10a4441 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +### Other stuff ### + +.mvn +.idea diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f834374 --- /dev/null +++ b/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + io.swtc + swtc + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + windows-x86_64 + + + + + org.eclipse.platform + org.eclipse.swt.win32.win32.x86_64 + 3.132.0 + + + + com.github.sarxos + webcam-capture + 0.3.12 + + + + + + + org.lwjgl + lwjgl + 3.3.3 + + + org.lwjgl + lwjgl-opengl + 3.3.3 + + + + org.lwjgl + lwjgl + 3.3.3 + natives-windows + + + org.lwjgl + lwjgl-opengl + 3.3.3 + natives-windows + + + \ No newline at end of file diff --git a/src/main/java/io/swtc/CCTVManager.java b/src/main/java/io/swtc/CCTVManager.java new file mode 100644 index 0000000..37529af --- /dev/null +++ b/src/main/java/io/swtc/CCTVManager.java @@ -0,0 +1,74 @@ +package io.swtc; + +import com.github.sarxos.webcam.Webcam; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.*; + +import java.util.List; + +public class CCTVManager { + public static void main(String[] args) { + Display display = new Display(); + Shell shell = new Shell(display, SWT.SHELL_TRIM | SWT.ON_TOP); + shell.setText("Dashboard"); + + List webcams = Webcam.getWebcams(); + int columnCount = webcams.size() > 3 ? 3 : Math.max(1, webcams.size()); + + GridLayout mainLayout = new GridLayout(columnCount, true); + mainLayout.marginWidth = 20; + mainLayout.marginHeight = 20; + mainLayout.horizontalSpacing = 15; + mainLayout.verticalSpacing = 15; + shell.setLayout(mainLayout); + + // Header (Spans across all columns) + Label title = new Label(shell, SWT.NONE); + title.setText("Connected Devices"); + title.setFont(new Font(display, "Segoe UI", 14, SWT.BOLD)); + GridData titleData = new GridData(SWT.FILL, SWT.CENTER, true, false, columnCount, 1); + title.setLayoutData(titleData); + + if (webcams.isEmpty()) { + Label error = new Label(shell, SWT.NONE); + error.setText("no available cameras!"); + } else { + for (Webcam webcam : webcams) { + createCameraCard(shell, display, webcam); + } + } + + shell.pack(); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + display.dispose(); + } + + private static void createCameraCard(Composite parent, Display display, Webcam webcam) { + Group card = new Group(parent, SWT.NONE); + card.setText(webcam.getName()); + card.setLayout(new GridLayout(1, false)); + card.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Button btn = new Button(card, SWT.PUSH); + btn.setText("View"); + btn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + + btn.addListener(SWT.Selection, e -> { + /** + * This is where magic happens! We start the Window Here! As arguments, we give our display and webcam index! + * */ + System.out.println("Starting: " + webcam.getName()); + CameraWindow window = new CameraWindow(display,webcam); + window.open(); + }); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/CameraWindow.java b/src/main/java/io/swtc/CameraWindow.java new file mode 100644 index 0000000..ec5b6e4 --- /dev/null +++ b/src/main/java/io/swtc/CameraWindow.java @@ -0,0 +1,62 @@ +package io.swtc; + +import com.github.sarxos.webcam.Webcam; +import io.swtc.proccessing.CameraRenderer; +import io.swtc.proccessing.WebcamCaptureLoop; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.opengl.GLData; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import java.awt.image.BufferedImage; + + +public class CameraWindow { + private final Shell shell; + private final CameraRenderer renderer; + private final WebcamCaptureLoop captureLoop; + + public CameraWindow(Display display, Webcam webcam) { + this.shell = new Shell(display); + this.shell.setText("swt-cctv@" + webcam.getName()); + this.shell.setLayout(new FillLayout()); + this.shell.setSize(640, 480); + + GLData data = new GLData(); + data.doubleBuffer = true; + this.renderer = new CameraRenderer(shell, data); + + this.captureLoop = new WebcamCaptureLoop(webcam, (BufferedImage img) -> { + if (!display.isDisposed() && !shell.isDisposed()) { + // terribly uneficcient. very stupid tbh + display.syncExec(() -> renderer.render(img)); + } + }); + + this.shell.addListener(SWT.Close, event -> { + captureLoop.stop(); + }); + } + + public void open() { + shell.open(); + renderer.getCanvas().setCurrent(); + captureLoop.start(); + } + + public static void main(String[] args) { + Display display = new Display(); + Webcam webcam = Webcam.getDefault(); + + CameraWindow window = new CameraWindow(display, webcam); + window.open(); + + while (!window.shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + display.dispose(); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/Main.java b/src/main/java/io/swtc/Main.java new file mode 100644 index 0000000..3071a17 --- /dev/null +++ b/src/main/java/io/swtc/Main.java @@ -0,0 +1,7 @@ +package io.swtc; +public class Main { + public static void main(String[] args) { + // very simple main, so that we can start the manager! + CCTVManager.main(args); + } +} \ 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 new file mode 100644 index 0000000..1b007e3 --- /dev/null +++ b/src/main/java/io/swtc/proccessing/AutoGainProcessor.java @@ -0,0 +1,35 @@ +package io.swtc.proccessing; + +public class AutoGainProcessor { + + public float[] calculateAutoGains(int[] pixels) { + long rSum = 0, gSum = 0, bSum = 0; + + int step = 8; + int sampledCount = 0; + + for (int i = 0; i < pixels.length; i += step) { + int pixel = pixels[i]; + rSum += (pixel >> 16) & 0xFF; + gSum += (pixel >> 8) & 0xFF; + bSum += pixel & 0xFF; + sampledCount++; + } + + if (sampledCount == 0) return new float[]{1f, 1f, 1f}; + + float rAvg = (float) rSum / sampledCount; + float gAvg = (float) gSum / sampledCount; + float bAvg = (float) bSum / sampledCount; + + float grayAvg = (rAvg + gAvg + bAvg) / 3.0f; + + if (grayAvg < 1.0f) return new float[]{1.0f, 1.0f, 1.0f}; + + return new float[]{ + Math.min(2.0f, grayAvg / rAvg), + Math.min(2.0f, grayAvg / gAvg), + Math.min(2.0f, grayAvg / bAvg) + }; + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/CameraRenderer.java b/src/main/java/io/swtc/proccessing/CameraRenderer.java new file mode 100644 index 0000000..fe3962c --- /dev/null +++ b/src/main/java/io/swtc/proccessing/CameraRenderer.java @@ -0,0 +1,96 @@ +package io.swtc.proccessing; + +import org.eclipse.swt.opengl.GLCanvas; +import org.eclipse.swt.widgets.Composite; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; + +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class CameraRenderer { + private final GLCanvas canvas; + private int textureId = -1; + private ByteBuffer pixelBuffer; + private final AutoGainProcessor gainProcessor; + + public CameraRenderer(Composite parent, org.eclipse.swt.opengl.GLData data) { + this.canvas = new GLCanvas(parent, 0, data); + this.gainProcessor = new AutoGainProcessor(); + + // Initialize OpenGL context immediately + this.canvas.setCurrent(); + GL.createCapabilities(); + initGL(); + } + + public GLCanvas getCanvas() { + return canvas; + } + + private void initGL() { + GL11.glEnable(GL11.GL_TEXTURE_2D); + textureId = GL11.glGenTextures(); + } + + public void render(BufferedImage img) { + if (canvas.isDisposed()) return; + + canvas.setCurrent(); + + int width = img.getWidth(); + int height = img.getHeight(); + + int[] rgbArray = new int[width * height]; + // this is hellishly unefficcient but who cares. + img.getRGB(0, 0, width, height, rgbArray, 0, width); + + float[] gains = gainProcessor.calculateAutoGains(rgbArray); + + int bufferSize = width * height * 3; + if (pixelBuffer == null || pixelBuffer.capacity() < bufferSize) { + pixelBuffer = ByteBuffer.allocateDirect(bufferSize); + pixelBuffer.order(ByteOrder.nativeOrder()); + } + pixelBuffer.clear(); + + for (int pixel : rgbArray) { + int r = (pixel >> 16) & 0xFF; + int g = (pixel >> 8) & 0xFF; + int b = pixel & 0xFF; + + r = Math.min(255, (int)(r * gains[0])); + g = Math.min(255, (int)(g * gains[1])); + b = Math.min(255, (int)(b * gains[2])); + + pixelBuffer.put((byte) r); + pixelBuffer.put((byte) g); + pixelBuffer.put((byte) b); + } + pixelBuffer.flip(); + + // this is just gl stuff + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); + GL11.glViewport(0, 0, canvas.getClientArea().width, canvas.getClientArea().height); + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, width, height, + 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, pixelBuffer); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + + GL11.glColor3f(1.0f, 1.0f, 1.0f); + GL11.glBegin(GL11.GL_QUADS); + GL11.glTexCoord2f(0, 0); GL11.glVertex2f(-1, 1); + GL11.glTexCoord2f(1, 0); GL11.glVertex2f(1, 1); + GL11.glTexCoord2f(1, 1); GL11.glVertex2f(1, -1); + GL11.glTexCoord2f(0, 1); GL11.glVertex2f(-1, -1); + GL11.glEnd(); + + canvas.swapBuffers(); + } +} \ No newline at end of file diff --git a/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java b/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java new file mode 100644 index 0000000..ad30edc --- /dev/null +++ b/src/main/java/io/swtc/proccessing/WebcamCaptureLoop.java @@ -0,0 +1,50 @@ +package io.swtc.proccessing; + +import com.github.sarxos.webcam.Webcam; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.util.function.Consumer; + +public class WebcamCaptureLoop { + private final Webcam webcam; + private final Consumer onFrameCaptured; + private volatile boolean running = false; + + public WebcamCaptureLoop(Webcam webcam, Consumer onFrameCaptured) { + this.webcam = webcam; + this.onFrameCaptured = onFrameCaptured; + + // Configure webcam + Dimension[] sizes = webcam.getViewSizes(); + webcam.setViewSize(sizes[sizes.length - 1]); + } + + public void start() { + if (running) return; + running = true; + + Thread captureThread = new Thread(() -> { + webcam.open(); + + while (running) { + BufferedImage img = webcam.getImage(); + if (img != null) { + onFrameCaptured.accept(img); + } + + try { + Thread.sleep(15); + } catch (InterruptedException e) { + break; + } + } + webcam.close(); + }); + captureThread.setName("cam_cap_thread"); + captureThread.start(); + } + + public void stop() { + running = false; + } +} \ No newline at end of file