--- /dev/null
+package kaka.cakelight;
+
+import static kaka.cakelight.Main.timeIt;
+
+public class CakeLight {
+ private Configuration config;
+ private Mode mode;
+
+ public CakeLight(Configuration config) {
+ this.config = config;
+ }
+
+ public void setMode(Mode mode) {
+ cleanup();
+ this.mode = mode;
+ mode.enter(config);
+ }
+
+ public void cleanup() {
+ if (this.mode != null) {
+ this.mode.exit();
+ }
+ }
+
+ public void startLoop() {
+ // TODO
+// FrameGrabber grabber = FrameGrabber.from(config);
+// grabber.prepare();
+// Frame frame = grabber.grabFrame();
+// double time = 0;
+// for (int i = 0; i < 100; i++) {
+// time += timeIt("frame", () -> grabber.grabFrame());
+// }
+// System.out.println("time = " + time);
+// grabber.close();
+// byte[] data = frame.getData();
+// saveFile(data, "/home/kaka/test.img");
+ }
+}
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;
public class Frame {
private byte[] data;
private Configuration config;
+ private Mat colImage;
+ private Mat rowImage;
private Frame(byte[] data) {
this.data = data;
}
private void convert() {
+ /* TODO: how to do this?
+ 1) Resize to an image with the size of the number of leds and use config to define how many pixels deep into the screen to use.
+ 2) Resize to 16x9 and use 2 pixels of depth (or maybe 3) and interpolate for each led.
+ 3) Resize to 2 images where each led uses 2 pixels:
+ vertical - 16 x <#leds>
+ horizontal - <#leds> x 9
+ */
Mat src = new Mat(config.video.height, config.video.width, CvType.CV_8UC2); // 8-bit, unsigned, 2 channels
src.put(0, 0, data);
- Mat converted = new Mat();
- Mat resized = new Mat();
+// Mat converted = new Mat();
+// Mat resized = new Mat();
+//
+// timeIt("total", () -> {
+// timeIt("yuyv2rgb", () -> Imgproc.cvtColor(src, converted, Imgproc.COLOR_YUV2RGB_YUYV)); // 3.5 - 4.0 ms
+// 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)
+// });
- timeIt("total", () -> {
- timeIt("yuyv2rgb", () -> Imgproc.cvtColor(src, converted, Imgproc.COLOR_YUV2RGB_YUYV)); // 3.5 - 4.0 ms
- 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));
+ 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));
+ }
+
+ private void model1(Mat src, int interpolation) {
+ Mat resized = new Mat();
+ Imgproc.resize(src, resized, new Size(config.leds.cols, config.leds.rows), 0, 0, interpolation);
+ }
+
+ private void model2(Mat src, int interpolation) {
+ Mat resized = new Mat();
+ Imgproc.resize(src, resized, new Size(16, 9), 0, 0, interpolation);
+ }
+
+ private void model3(Mat src, int interpolation) {
+ colImage = new Mat();
+ rowImage = new Mat();
+ Imgproc.resize(src, colImage, new Size(config.leds.cols, 9), 0, 0, interpolation);
+ Imgproc.resize(src, rowImage, new Size(16, config.leds.rows), 0, 0, interpolation);
+ }
+
+ public Color getPixel(ListPosition listPosition, int xy) {
+ switch (listPosition) {
+ case LEFT:
+ return pixelToColor(rowImage, 0, xy);
+ case RIGHT:
+ return pixelToColor(rowImage, config.leds.cols - 1, xy);
+ case TOP:
+ return pixelToColor(colImage, xy, 0);
+ case BOTTOM:
+ return pixelToColor(colImage, xy, config.leds.cols - 1);
+ }
+ return null;
+ }
+
+ 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]);
}
private void save(Mat mat, String filepath) {
package kaka.cakelight;
import java.io.*;
+import java.util.Optional;
-public class FrameGrabber {
+import static kaka.cakelight.Main.log;
+
+public class FrameGrabber implements Closeable {
private Configuration config;
private File file;
private int bytesPerFrame;
fg.config = config;
fg.file = new File(config.video.device);
fg.bytesPerFrame = config.video.width * config.video.height * config.video.bpp;
+ fg.prepare();
return fg;
}
- public boolean prepare() {
+ private boolean prepare() {
try {
fileStream = new FileInputStream(file);
return true;
}
}
- public Frame grabFrame() {
+ /**
+ * Must be run in the same thread as {@link #prepare}.
+ */
+ public Optional<Frame> grabFrame() {
try {
byte[] data = new byte[bytesPerFrame];
int count = fileStream.read(data);
- System.out.println("count = " + count);
- return Frame.of(data, config);
+ log("# of bytes read = " + count);
+ return Optional.of(Frame.of(data, config));
} catch (IOException e) {
e.printStackTrace();
}
- return null;
+ return Optional.empty();
}
- public void close() {
- try {
- fileStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
+ @Override
+ public void close() throws IOException {
+ fileStream.close();
}
}
--- /dev/null
+package kaka.cakelight;
+
+public enum ListPosition {
+ LEFT,
+ RIGHT,
+ TOP,
+ BOTTOM
+}
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.HashMap;
public class Main {
-
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Configuration config = Configuration.from("config.properties");
- System.out.println("Running with config:\n" + config);
-
- FrameGrabber grabber = FrameGrabber.from(config);
- grabber.prepare();
- Frame frame = grabber.grabFrame();
- double time = 0;
- for (int i = 0; i < 100; i++) {
- time += timeIt("frame", () -> grabber.grabFrame());
- }
- System.out.println("time = " + time);
- grabber.close();
-// byte[] data = frame.getData();
-// saveFile(data, "/home/kaka/test.img");
+ log("Running with config:\n" + config);
+
+ CakeLight cakelight = new CakeLight(config);
+ cakelight.setMode(new VideoMode());
+ cakelight.startLoop();
+// try {
+// Thread.sleep(1000);
+// } catch (InterruptedException e) {
+// e.printStackTrace();
+// }
+// cakelight.setMode(null);
+ Runtime.getRuntime().addShutdownHook(new Thread(Main::printTimeStats));
}
public static void saveFile(byte[] data, String filepath) {
}
}
+ public static void log(String msg, Object... args) {
+ System.out.println(String.format(msg, args));
+ }
+
+ private static HashMap<String, Double> timeDurations = new HashMap<>();
+ private static HashMap<String, Integer> timeCounts = new HashMap<>();
public static double timeIt(String tag, Runnable lambda) {
long start = System.nanoTime();
lambda.run();
long end = System.nanoTime();
double duration = (end - start) * 0.000001;
- System.out.println("duration (ms): " + tag + " = " + duration);
+// log("duration (ms): " + tag + " = " + duration);
+
+ if (!timeDurations.containsKey(tag)) {
+ timeDurations.put(tag, 0.0);
+ timeCounts.put(tag, 0);
+ }
+ timeDurations.put(tag, timeDurations.get(tag) + duration);
+ timeCounts.put(tag, timeCounts.get(tag) + 1);
return duration;
}
+
+ private static void printTimeStats() {
+ log("Average times in ms:");
+ timeDurations.forEach((tag, duration) -> {
+ log("%s: %s", tag, duration / timeCounts.get(tag));
+ });
+ }
}
/*
FrameGrabber läser frames asynkront
skickar frame till FrameConverter
sparas i huvudklassen
-läses av FrameProcessor/LedController
- */
\ No newline at end of file
+läses av FrameProcessor/CakeLight
+ */
--- /dev/null
+package kaka.cakelight;
+
+public interface Mode {
+ void enter(Configuration config);
+ void exit();
+}
--- /dev/null
+package kaka.cakelight;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import static kaka.cakelight.Main.log;
+import static kaka.cakelight.Main.timeIt;
+
+public class VideoMode implements Mode {
+ private Configuration config;
+ private Thread thread;
+
+ @Override
+ public void enter(Configuration config) {
+ this.config = config;
+ startGrabberThread();
+ }
+
+ @Override
+ public void exit() {
+ thread.interrupt();
+ }
+
+ private void startGrabberThread() {
+ thread = new Thread() {
+ public void run() {
+ try (FrameGrabber grabber = FrameGrabber.from(config)) {
+ while (!isInterrupted()) {
+// Optional<Frame> frame = grabber.grabFrame();
+ timeIt("frame", grabber::grabFrame);
+ // TODO: process frame
+ // TODO: save where the LedController can access it
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ thread.start();
+ }
+}