]> git.basschouten.com Git - openhab-addons.git/blob
70724333eca8478e1251163f9c47f11ea090b9dd
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13
14 package org.openhab.binding.nanoleaf.internal.layout;
15
16 import java.awt.Color;
17 import java.awt.Graphics2D;
18 import java.awt.image.BufferedImage;
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.List;
24
25 import javax.imageio.ImageIO;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants;
29 import org.openhab.binding.nanoleaf.internal.layout.shape.Shape;
30 import org.openhab.binding.nanoleaf.internal.layout.shape.ShapeFactory;
31 import org.openhab.binding.nanoleaf.internal.model.GlobalOrientation;
32 import org.openhab.binding.nanoleaf.internal.model.Layout;
33 import org.openhab.binding.nanoleaf.internal.model.PanelLayout;
34 import org.openhab.binding.nanoleaf.internal.model.PositionDatum;
35
36 /**
37  * Renders the Nanoleaf layout to an image.
38  *
39  * @author Jørgen Austvik - Initial contribution
40  */
41 @NonNullByDefault
42 public class NanoleafLayout {
43
44     private static final Color COLOR_BACKGROUND = Color.WHITE;
45     private static final Color COLOR_PANEL = Color.BLACK;
46     private static final Color COLOR_SIDE = Color.GRAY;
47     private static final Color COLOR_TEXT = Color.BLACK;
48
49     public static byte[] render(PanelLayout panelLayout) throws IOException {
50         double rotationRadians = 0;
51         GlobalOrientation globalOrientation = panelLayout.getGlobalOrientation();
52         if (globalOrientation != null) {
53             rotationRadians = calculateRotationRadians(globalOrientation);
54         }
55
56         Layout layout = panelLayout.getLayout();
57         if (layout == null) {
58             return new byte[] {};
59         }
60
61         List<PositionDatum> panels = layout.getPositionData();
62         if (panels == null) {
63             return new byte[] {};
64         }
65
66         Point2D size[] = findSize(panels, rotationRadians);
67         final Point2D min = size[0];
68         final Point2D max = size[1];
69         Point2D prev = null;
70         Point2D first = null;
71
72         int sideCounter = 0;
73         BufferedImage image = new BufferedImage(
74                 (max.getX() - min.getX()) + 2 * NanoleafBindingConstants.LAYOUT_BORDER_WIDTH,
75                 (max.getY() - min.getY()) + 2 * NanoleafBindingConstants.LAYOUT_BORDER_WIDTH,
76                 BufferedImage.TYPE_INT_RGB);
77         Graphics2D g2 = image.createGraphics();
78
79         g2.setBackground(COLOR_BACKGROUND);
80         g2.clearRect(0, 0, image.getWidth(), image.getHeight());
81
82         for (PositionDatum panel : panels) {
83             final ShapeType shapeType = ShapeType.valueOf(panel.getShapeType());
84
85             Shape shape = ShapeFactory.CreateShape(shapeType, panel);
86             List<Point2D> outline = toPictureLayout(shape.generateOutline(), image.getHeight(), min, rotationRadians);
87             for (int i = 0; i < outline.size(); i++) {
88                 g2.setColor(COLOR_SIDE);
89                 Point2D pos = outline.get(i);
90                 Point2D nextPos = outline.get((i + 1) % outline.size());
91                 g2.drawLine(pos.getX(), pos.getY(), nextPos.getX(), nextPos.getY());
92             }
93
94             for (int i = 0; i < outline.size(); i++) {
95                 Point2D pos = outline.get(i);
96                 g2.setColor(COLOR_PANEL);
97                 g2.fillOval(pos.getX() - NanoleafBindingConstants.LAYOUT_LIGHT_RADIUS / 2,
98                         pos.getY() - NanoleafBindingConstants.LAYOUT_LIGHT_RADIUS / 2,
99                         NanoleafBindingConstants.LAYOUT_LIGHT_RADIUS, NanoleafBindingConstants.LAYOUT_LIGHT_RADIUS);
100             }
101
102             Point2D current = toPictureLayout(new Point2D(panel.getPosX(), panel.getPosY()), image.getHeight(), min,
103                     rotationRadians);
104             if (sideCounter == 0) {
105                 first = current;
106             }
107
108             g2.setColor(COLOR_SIDE);
109             final int expectedSides = shapeType.getNumSides();
110             if (shapeType.getDrawingAlgorithm() == DrawingAlgorithm.CORNER) {
111                 // Special handling of Elements Hexagon Corners, where we get 6 corners instead of 1 shape. They seem to
112                 // come after each other in the JSON, so this algorithm connects them based on the number of sides the
113                 // shape is expected to have.
114                 if (sideCounter > 0 && sideCounter != expectedSides && prev != null) {
115                     g2.drawLine(prev.getX(), prev.getY(), current.getX(), current.getY());
116                 }
117
118                 sideCounter++;
119
120                 if (sideCounter == expectedSides && first != null) {
121                     g2.drawLine(current.getX(), current.getY(), first.getX(), first.getY());
122                     sideCounter = 0;
123                 }
124             } else {
125                 sideCounter = 0;
126             }
127
128             prev = current;
129
130             g2.setColor(COLOR_TEXT);
131             Point2D textPos = shape.labelPosition(g2, outline);
132             g2.drawString(Integer.toString(panel.getPanelId()), textPos.getX(), textPos.getY());
133         }
134
135         ByteArrayOutputStream out = new ByteArrayOutputStream();
136         ImageIO.write(image, "png", out);
137         return out.toByteArray();
138     }
139
140     private static double calculateRotationRadians(GlobalOrientation globalOrientation) {
141         Integer maxObj = globalOrientation.getMax();
142         int maxValue = maxObj == null ? 360 : (int) maxObj;
143         int value = globalOrientation.getValue(); // 0 - 360 measured counter clockwise.
144         return ((double) (maxValue - value)) * (Math.PI / 180);
145     }
146
147     private static Point2D[] findSize(Collection<PositionDatum> panels, double rotationRadians) {
148         int maxX = 0;
149         int maxY = 0;
150         int minX = 0;
151         int minY = 0;
152
153         for (PositionDatum panel : panels) {
154             ShapeType shapeType = ShapeType.valueOf(panel.getShapeType());
155             Shape shape = ShapeFactory.CreateShape(shapeType, panel);
156             for (Point2D point : shape.generateOutline()) {
157                 var rotated = point.rotate(rotationRadians);
158                 maxX = Math.max(rotated.getX(), maxX);
159                 maxY = Math.max(rotated.getY(), maxY);
160                 minX = Math.min(rotated.getX(), minX);
161                 minY = Math.min(rotated.getY(), minY);
162             }
163         }
164
165         return new Point2D[] { new Point2D(minX, minY), new Point2D(maxX, maxY) };
166     }
167
168     private static Point2D toPictureLayout(Point2D original, int imageHeight, Point2D min, double rotationRadians) {
169         Point2D rotated = original.rotate(rotationRadians);
170         Point2D translated = new Point2D(NanoleafBindingConstants.LAYOUT_BORDER_WIDTH + rotated.getX() - min.getX(),
171                 imageHeight - NanoleafBindingConstants.LAYOUT_BORDER_WIDTH - rotated.getY() + min.getY());
172         return translated;
173     }
174
175     private static List<Point2D> toPictureLayout(List<Point2D> originals, int imageHeight, Point2D min,
176             double rotationRadians) {
177         List<Point2D> result = new ArrayList<Point2D>(originals.size());
178         for (Point2D original : originals) {
179             result.add(toPictureLayout(original, imageHeight, min, rotationRadians));
180         }
181
182         return result;
183     }
184 }