This commit is contained in:
2025-12-30 15:43:06 +01:00
commit 98a68ef75e
8 changed files with 426 additions and 0 deletions

44
.gitignore vendored Normal file
View File

@@ -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

58
pom.xml Normal file
View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.swtc</groupId>
<artifactId>swtc</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javacpp.platform>windows-x86_64</javacpp.platform>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.swt.win32.win32.x86_64</artifactId>
<version>3.132.0</version>
</dependency>
<dependency>
<groupId>com.github.sarxos</groupId>
<artifactId>webcam-capture</artifactId>
<version>0.3.12</version>
</dependency>
<!-- for gl we use lwjgl -->
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-opengl</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<version>3.3.3</version>
<classifier>natives-windows</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-opengl</artifactId>
<version>3.3.3</version>
<classifier>natives-windows</classifier>
</dependency>
</dependencies>
</project>

View File

@@ -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<Webcam> 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();
});
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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)
};
}
}

View File

@@ -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();
}
}

View File

@@ -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<BufferedImage> onFrameCaptured;
private volatile boolean running = false;
public WebcamCaptureLoop(Webcam webcam, Consumer<BufferedImage> 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;
}
}