Added a LED controller that consumes LED frames from the modes
[kaka/cakelight.git] / src / kaka / cakelight / Frame.java
1 package kaka.cakelight;
2
3 import javafx.scene.paint.Color;
4 import org.opencv.core.CvType;
5 import org.opencv.core.Mat;
6 import org.opencv.core.Size;
7 import org.opencv.imgproc.Imgproc;
8
9 import static kaka.cakelight.Main.saveFile;
10 import static kaka.cakelight.Main.timeIt;
11
12 public class Frame {
13     private byte[] data;
14     private Configuration config;
15     private Mat colImage;
16     private Mat rowImage;
17     private Mat converted;
18
19     private Frame(byte[] data) {
20         this.data = data;
21     }
22
23     public static Frame of(byte[] data, Configuration config) {
24         Frame frame = new Frame(data);
25         frame.config = config;
26         frame.convert();
27         return frame;
28     }
29
30     private void convert() {
31         /* TODO: how to do this?
32         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.
33         2) Resize to 16x9 and use 2 pixels of depth (or maybe 3) and interpolate for each led.
34         3) Resize to 2 images where each led uses 2 pixels:
35             vertical   - 16 x <#leds>
36             horizontal - <#leds> x 9
37          */
38         Mat src = new Mat(config.video.height, config.video.width, CvType.CV_8UC2); // 8-bit, unsigned, 2 channels
39         src.put(0, 0, data);
40
41 //        Mat converted = new Mat();
42 //        Mat resized = new Mat();
43 //
44 //        timeIt("total", () -> {
45 //            timeIt("yuyv2rgb", () -> Imgproc.cvtColor(src, converted, Imgproc.COLOR_YUV2RGB_YUYV)); // 3.5 - 4.0 ms
46 //            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)
47 //        });
48
49         Mat cropped = src.submat(
50                 config.video.crop.top,
51                 config.video.height - config.video.crop.bottom,
52                 config.video.crop.left,
53                 config.video.width - config.video.crop.right
54         );
55         converted = new Mat();
56         Imgproc.cvtColor(cropped, converted, Imgproc.COLOR_YUV2RGB_YUYV);
57 //        timeIt("model 1", () -> model1(converted, Imgproc.INTER_AREA));
58 //        timeIt("model 2", () -> model2(converted, Imgproc.INTER_AREA));
59         timeIt("model 3", () -> model3(converted, Imgproc.INTER_AREA));
60 //        save(converted, "/home/kaka/test-converted.data");
61 //        save(resized, "/home/kaka/test-resized.data");
62         src.release();
63         cropped.release();
64     }
65
66     private void model1(Mat src, int interpolation) {
67         Mat resized = new Mat();
68         Imgproc.resize(src, resized, new Size(config.leds.cols, config.leds.rows), 0, 0, interpolation);
69     }
70
71     private void model2(Mat src, int interpolation) {
72         Mat resized = new Mat();
73         Imgproc.resize(src, resized, new Size(16, 9), 0, 0, interpolation);
74     }
75
76     private void model3(Mat src, int interpolation) {
77         colImage = new Mat();
78         rowImage = new Mat();
79         Imgproc.resize(src, colImage, new Size(config.leds.cols, 9), 0, 0, interpolation);
80         Imgproc.resize(src, rowImage, new Size(16, config.leds.rows), 0, 0, interpolation);
81     }
82
83     public Color getLedColor(ListPosition listPosition, int xy) {
84         switch (listPosition) {
85             case LEFT:
86                 return interpolatedRowColor(xy, 0, 1, 2);
87             case RIGHT:
88                 return interpolatedRowColor(xy, 15, 14, 13);
89             case TOP:
90                 return interpolatedColColor(xy, 0, 1, 2);
91             case BOTTOM:
92                 return interpolatedColColor(xy, 8, 7, 6);
93         }
94         return null;
95     }
96
97     private Color interpolatedRowColor(int y, int x1, int x2, int x3) {
98         return pixelToColor(rowImage, x3, y).interpolate(pixelToColor(rowImage, x2, y), 0.65).interpolate(pixelToColor(rowImage, x1, y), 0.65);
99     }
100
101     private Color interpolatedColColor(int x, int y1, int y2, int y3) {
102         return pixelToColor(colImage, x, y3).interpolate(pixelToColor(colImage, x, y2), 0.65).interpolate(pixelToColor(colImage, x, y1), 0.65);
103     }
104
105     private Color pixelToColor(Mat image, int x, int y) {
106         byte[] rgb = new byte[3];
107         image.get(y, x, rgb);
108         return Color.rgb(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff);
109     }
110
111     private void save(Mat mat, String filepath) {
112         byte[] data = new byte[mat.cols() * mat.rows() * mat.channels()];
113         mat.get(0, 0, data);
114         saveFile(data, filepath);
115     }
116
117     public byte[] getData() {
118         byte[] buff = new byte[(int) (converted.total() * converted.channels())];
119         converted.get(0, 0, buff);
120         return buff;
121     }
122
123     public Mat getColImage() {
124         return colImage;
125     }
126
127     public Mat getRowImage() {
128         return rowImage;
129     }
130
131     public Mat getConvertedImage() {
132         return converted;
133     }
134
135     /**
136      * Creates a LED frame going clockwise from the bottom-left corner, sans the corners.
137      */
138     public LedFrame getLedFrame() {
139         LedFrame frame = LedFrame.from(config);
140         int led = 0;
141         for (int i = config.leds.rows - 1; i >= 0; i--) frame.setLedColor(led++, getLedColor(ListPosition.LEFT, i));
142         for (int i = 0; i < config.leds.cols; i++)      frame.setLedColor(led++, getLedColor(ListPosition.TOP, i));
143         for (int i = 0; i < config.leds.rows; i++)      frame.setLedColor(led++, getLedColor(ListPosition.RIGHT, i));
144         for (int i = config.leds.cols - 1; i >= 0; i--) frame.setLedColor(led++, getLedColor(ListPosition.BOTTOM, i));
145         return frame;
146     }
147 }