2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
14 package org.openhab.binding.nanoleaf.internal.layout;
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;
25 import javax.imageio.ImageIO;
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;
37 * Renders the Nanoleaf layout to an image.
39 * @author Jørgen Austvik - Initial contribution
42 public class NanoleafLayout {
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;
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);
56 Layout layout = panelLayout.getLayout();
61 List<PositionDatum> panels = layout.getPositionData();
66 Point2D size[] = findSize(panels, rotationRadians);
67 final Point2D min = size[0];
68 final Point2D max = size[1];
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();
79 g2.setBackground(COLOR_BACKGROUND);
80 g2.clearRect(0, 0, image.getWidth(), image.getHeight());
82 for (PositionDatum panel : panels) {
83 final ShapeType shapeType = ShapeType.valueOf(panel.getShapeType());
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());
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);
102 Point2D current = toPictureLayout(new Point2D(panel.getPosX(), panel.getPosY()), image.getHeight(), min,
104 if (sideCounter == 0) {
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());
120 if (sideCounter == expectedSides && first != null) {
121 g2.drawLine(current.getX(), current.getY(), first.getX(), first.getY());
130 g2.setColor(COLOR_TEXT);
131 Point2D textPos = shape.labelPosition(g2, outline);
132 g2.drawString(Integer.toString(panel.getPanelId()), textPos.getX(), textPos.getY());
135 ByteArrayOutputStream out = new ByteArrayOutputStream();
136 ImageIO.write(image, "png", out);
137 return out.toByteArray();
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);
147 private static Point2D[] findSize(Collection<PositionDatum> panels, double rotationRadians) {
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);
165 return new Point2D[] { new Point2D(minX, minY), new Point2D(maxX, maxY) };
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());
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));