# default settings
-video.device=/dev/video0
+video.device=/dev/video2
video.width=720
video.height=576
video.bpp=2
+video.crop.left=28
+video.crop.right=22
+video.crop.top=8
+video.crop.bottom=120
+
leds.cols=32
leds.rows=18
public int width;
public int height;
public int bpp;
+ public CropConfiguration crop;
private VideoConfiguration(Properties prop) {
device = get(prop, "video.device", "/dev/video0");
width = Integer.parseInt(get(prop, "video.width", "720"));
height = Integer.parseInt(get(prop, "video.height", "576"));
bpp = Integer.parseInt(get(prop, "video.bpp", "2"));
+ crop = new CropConfiguration(prop);
+ }
+
+ public class CropConfiguration {
+ public int left, right, top, bottom;
+
+ private CropConfiguration(Properties prop) {
+ left = Integer.parseInt(get(prop, "video.crop.left", "0"));
+ right = Integer.parseInt(get(prop, "video.crop.right", "0"));
+ top = Integer.parseInt(get(prop, "video.crop.top", "0"));
+ bottom = Integer.parseInt(get(prop, "video.crop.bottom", "0"));
+ }
}
}
package kaka.cakelight;
+import javafx.scene.paint.Color;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
-import java.awt.*;
-
import static kaka.cakelight.Main.saveFile;
import static kaka.cakelight.Main.timeIt;
private Configuration config;
private Mat colImage;
private Mat rowImage;
+ private Mat converted;
private Frame(byte[] data) {
this.data = data;
// timeIt("resizing", () -> Imgproc.resize(converted, resized, new Size(config.leds.cols, config.leds.rows), 0, 0, Imgproc.INTER_AREA)); // INTER_AREA is the best for shrinking, but also the slowest (~1.5 ms)
// });
- Mat converted = new Mat();
- Imgproc.cvtColor(src, converted, Imgproc.COLOR_YUV2RGB_YUYV);
- timeIt("model 1", () -> model1(converted, Imgproc.INTER_AREA));
- timeIt("model 2", () -> model2(converted, Imgproc.INTER_AREA));
+ Mat cropped = src.submat(
+ config.video.crop.top,
+ config.video.height - config.video.crop.bottom,
+ config.video.crop.left,
+ config.video.width - config.video.crop.right
+ );
+ converted = new Mat();
+ Imgproc.cvtColor(cropped, converted, Imgproc.COLOR_YUV2RGB_YUYV);
+// timeIt("model 1", () -> model1(converted, Imgproc.INTER_AREA));
+// timeIt("model 2", () -> model2(converted, Imgproc.INTER_AREA));
timeIt("model 3", () -> model3(converted, Imgproc.INTER_AREA));
// save(converted, "/home/kaka/test-converted.data");
// save(resized, "/home/kaka/test-resized.data");
- System.out.println("color: " + getPixel(ListPosition.BOTTOM, 0));
+ src.release();
+ cropped.release();
}
private void model1(Mat src, int interpolation) {
Imgproc.resize(src, rowImage, new Size(16, config.leds.rows), 0, 0, interpolation);
}
- public Color getPixel(ListPosition listPosition, int xy) {
+ public Color getLedColor(ListPosition listPosition, int xy) {
switch (listPosition) {
case LEFT:
- return pixelToColor(rowImage, 0, xy);
+ return interpolatedRowColor(xy, 0, 1, 2);
case RIGHT:
- return pixelToColor(rowImage, config.leds.cols - 1, xy);
+ return interpolatedRowColor(xy, 15, 14, 13);
case TOP:
- return pixelToColor(colImage, xy, 0);
+ return interpolatedColColor(xy, 0, 1, 2);
case BOTTOM:
- return pixelToColor(colImage, xy, config.leds.cols - 1);
+ return interpolatedColColor(xy, 8, 7, 6);
}
return null;
}
+ private Color interpolatedRowColor(int y, int x1, int x2, int x3) {
+ return pixelToColor(rowImage, x3, y).interpolate(pixelToColor(rowImage, x2, y), 0.65).interpolate(pixelToColor(rowImage, x1, y), 0.65);
+ }
+
+ private Color interpolatedColColor(int x, int y1, int y2, int y3) {
+ return pixelToColor(colImage, x, y3).interpolate(pixelToColor(colImage, x, y2), 0.65).interpolate(pixelToColor(colImage, x, y1), 0.65);
+ }
+
private Color pixelToColor(Mat image, int x, int y) {
byte[] rgb = new byte[3];
image.get(y, x, rgb);
- System.out.println("r = " + rgb[0] + ", g = " + rgb[1] + ", b = " + rgb[2]);
- return new Color(rgb[0], rgb[1], rgb[2]);
+ return Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff);
}
private void save(Mat mat, String filepath) {
}
public byte[] getData() {
- return data;
+ byte[] buff = new byte[(int) (converted.total() * converted.channels())];
+ converted.get(0, 0, buff);
+ return buff;
+ }
+
+ public Mat getColImage() {
+ return colImage;
+ }
+
+ public Mat getRowImage() {
+ return rowImage;
+ }
+
+ public Mat getConvertedImage() {
+ return converted;
}
}
--- /dev/null
+package kaka.cakelight;
+
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.input.KeyCode;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.stage.Stage;
+import org.opencv.core.Core;
+import org.opencv.core.Mat;
+
+import static kaka.cakelight.Main.log;
+
+public class GuiTest extends Application {
+ private static final int BLOCK = 45;
+ private static final int GUTTER = 3 * BLOCK;
+ private Canvas canvas;
+ private Configuration config;
+ private CakeLight cakelight;
+ private boolean paused;
+
+ public static void main(String[] args) {
+ System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
+ launch(args);
+ }
+
+ @Override
+ public void start(Stage stage) throws Exception {
+ config = Configuration.from("config.properties");
+ canvas = new Canvas(16 * BLOCK + 2 * GUTTER, 9 * BLOCK + 2 * GUTTER);
+
+ Pane root = new Pane();
+ root.getChildren().add(canvas);
+
+ Scene scene = new Scene(root);
+ scene.setOnKeyPressed(keyEvent -> {
+ if (keyEvent.getCode() == KeyCode.ESCAPE) {
+ stage.close();
+ cakelight.cleanup();
+ }
+ if (keyEvent.getCode() == KeyCode.SPACE) {
+ paused = !paused;
+ }
+ });
+
+ stage.setTitle("Cakelight");
+ stage.setScene(scene);
+ stage.setOnCloseRequest(windowEvent -> cakelight.cleanup());
+ stage.show();
+
+ setupCakeLight();
+ }
+
+ private void setupCakeLight() {
+ log("Running with config:\n" + config);
+ cakelight = new CakeLight(config);
+ VideoMode mode = new VideoMode();
+ cakelight.setMode(mode);
+ cakelight.startLoop();
+ mode.onFrame(frame -> drawFrame(canvas.getGraphicsContext2D(), frame));
+ }
+
+ private void drawFrame(GraphicsContext gc, Frame frame) {
+ if (paused) return;
+ System.out.println("Drawing a frame");
+ drawCols(gc, frame);
+ drawRows(gc, frame);
+// drawVideo(gc, frame);
+ drawBorderAndGrid(gc);
+ drawLEDs(gc, frame);
+ }
+
+ private void drawLEDs(GraphicsContext gc, Frame frame) {
+ int ledLength = GUTTER;
+ float colSize = 16f * BLOCK / config.leds.cols;
+ float rowSize = 9f * BLOCK / config.leds.rows;
+// DropShadow shadow = new DropShadow(BlurType.ONE_PASS_BOX, Color.RED, colSize * 2, colSize, 0, 0);
+ for (int x = 0; x < config.leds.cols; x++) {
+ gc.setFill(frame.getLedColor(ListPosition.TOP, x));
+ gc.fillRect(GUTTER + x * colSize, GUTTER - ledLength, colSize, ledLength);
+ gc.setFill(frame.getLedColor(ListPosition.BOTTOM, x));
+ gc.fillRect(GUTTER + x * colSize, GUTTER + 9 * BLOCK, colSize, ledLength);
+ }
+ for (int y = 0; y < config.leds.rows; y++) {
+ gc.setFill(frame.getLedColor(ListPosition.LEFT, y));
+ gc.fillRect(GUTTER - ledLength, GUTTER + y * rowSize, ledLength, rowSize);
+ gc.setFill(frame.getLedColor(ListPosition.RIGHT, y));
+ gc.fillRect(GUTTER + 16 * BLOCK, GUTTER + y * rowSize, ledLength, rowSize);
+ }
+ }
+
+ private void drawVideo(GraphicsContext gc, Frame frame) {
+ byte[] rgb = new byte[3];
+ Mat img = frame.getConvertedImage();
+ float colSize = 16 * BLOCK / (float)img.cols();
+ float rowSize = 9 * BLOCK / (float)img.rows();
+ for (int x = 0, cols = img.cols(); x < cols; x++) {
+ for (int y = 0, rows = img.rows(); y < rows; y++) {
+ img.get(y, x, rgb);
+ gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5));
+ gc.fillRect(GUTTER + x * colSize, GUTTER + y * rowSize, colSize, rowSize);
+ }
+ }
+ }
+
+ private void drawCols(GraphicsContext gc, Frame frame) {
+ byte[] rgb = new byte[3];
+ for (int x = 0; x < config.leds.cols; x++) {
+ for (int y = 0; y < 9; y++) {
+ frame.getColImage().get(y, x, rgb);
+ drawColPixel(gc, x, y, rgb);
+ }
+ }
+ }
+
+ private void drawRows(GraphicsContext gc, Frame frame) {
+ byte[] rgb = new byte[3];
+ for (int y = 0; y < config.leds.rows; y++) {
+ for (int x = 0; x < 16; x++) {
+ frame.getRowImage().get(y, x, rgb);
+ drawRowPixel(gc, x, y, rgb);
+ }
+ }
+ }
+
+ private void drawColPixel(GraphicsContext gc, int x, int y, byte[] rgb) {
+ float ledSize = 16f * BLOCK / config.leds.cols;
+ gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5));
+ gc.fillRect(GUTTER + x * ledSize, GUTTER + y * BLOCK, ledSize, BLOCK);
+ }
+
+ private void drawRowPixel(GraphicsContext gc, int x, int y, byte[] rgb) {
+ float ledSize = 9f * BLOCK / config.leds.rows;
+ gc.setFill(Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff, 0.5));
+ gc.fillRect(GUTTER + x * BLOCK, GUTTER + y * ledSize, BLOCK, ledSize);
+ }
+
+ private void drawBorderAndGrid(GraphicsContext gc) {
+ gc.setStroke(Color.BLACK);
+ gc.setLineWidth(BLOCK * 0.1);
+ gc.strokeRect(GUTTER, GUTTER, 16 * BLOCK, 9 * BLOCK);
+ gc.setLineWidth(1);
+ for (int x = 1; x < 16; x++) {
+ gc.strokeLine(GUTTER + x * BLOCK, GUTTER, GUTTER + x * BLOCK, GUTTER + 9 * BLOCK);
+ }
+ for (int y = 1; y < 9; y++) {
+ gc.strokeLine(GUTTER, GUTTER + y * BLOCK, GUTTER + 16 * BLOCK, GUTTER + y * BLOCK);
+ }
+ }
+
+ private void drawPixel(GraphicsContext gc, int x, int y, Paint paint) {
+ gc.setFill(paint);
+ gc.fillRect(GUTTER + x * BLOCK, GUTTER + y * BLOCK, BLOCK, BLOCK);
+ }
+}
import java.io.IOException;
import java.util.Optional;
+import java.util.function.Consumer;
import static kaka.cakelight.Main.log;
import static kaka.cakelight.Main.timeIt;
public class VideoMode implements Mode {
private Configuration config;
private Thread thread;
+ private Consumer<Frame> frameConsumer;
@Override
public void enter(Configuration config) {
}
private void startGrabberThread() {
+ assert frameConsumer != null;
thread = new Thread() {
public void run() {
try (FrameGrabber grabber = FrameGrabber.from(config)) {
while (!isInterrupted()) {
// Optional<Frame> frame = grabber.grabFrame();
- timeIt("frame", grabber::grabFrame);
+ grabber.grabFrame().ifPresent(frameConsumer);
+// timeIt("frame", grabber::grabFrame);
// TODO: process frame
// TODO: save where the LedController can access it
}
};
thread.start();
}
+
+ public void onFrame(Consumer<Frame> consumer) {
+ frameConsumer = consumer;
+ }
}