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