]> git.basschouten.com Git - openhab-addons.git/blob
e04e43fa49f9601497dc982925b9fa99835b1aec
[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 package org.openhab.binding.nanoleaf.internal.layout.shape;
14
15 import java.awt.Color;
16 import java.awt.Paint;
17 import java.awt.PaintContext;
18 import java.awt.Rectangle;
19 import java.awt.RenderingHints;
20 import java.awt.geom.AffineTransform;
21 import java.awt.geom.Rectangle2D;
22 import java.awt.image.ColorModel;
23 import java.awt.image.DataBufferInt;
24 import java.awt.image.PackedColorModel;
25 import java.awt.image.Raster;
26 import java.awt.image.WritableRaster;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.nanoleaf.internal.layout.ImagePoint2D;
31
32 /**
33  * Paint for triangles with one color in each corner. Used to make gradients between the colors when
34  * dividing a hexagon into 6 triangles.
35  *
36  * https://codeplea.com/triangular-interpolation is instructive for the math.
37  *
38  * Inspired by
39  * https://github.com/hageldave/JPlotter/blob/9c92731f3b29a2cdb14f3dfdeeed6fffde37eee4/jplotter/src/main/java/hageldave/jplotter/util/BarycentricGradientPaint.java,
40  * for how to integrate it into Java AWT but kept so simple that I could understand it. It was however far too big to
41  * use as a dependency.
42  *
43  * @author Jørgen Austvik - Initial contribution
44  */
45 @NonNullByDefault
46 public class BarycentricTriangleGradient implements Paint {
47
48     private final Color color1;
49     private final Color color2;
50     private final Color color3;
51
52     private final ImagePoint2D corner1;
53     private final ImagePoint2D corner2;
54     private final ImagePoint2D corner3;
55
56     public BarycentricTriangleGradient(ImagePoint2D corner1, Color color1, ImagePoint2D corner2, Color color2,
57             ImagePoint2D corner3, Color color3) {
58         this.corner1 = corner1;
59         this.corner2 = corner2;
60         this.corner3 = corner3;
61         this.color1 = color1;
62         this.color2 = color2;
63         this.color3 = color3;
64     }
65
66     @Override
67     public @Nullable PaintContext createContext(@Nullable ColorModel cm, @Nullable Rectangle deviceBounds,
68             @Nullable Rectangle2D userBounds, @Nullable AffineTransform xform, @Nullable RenderingHints hints) {
69         return new BarycentricTriangleGradientContext(corner1, color1, corner2, color2, corner3, color3);
70     }
71
72     @Override
73     public int getTransparency() {
74         return OPAQUE;
75     }
76
77     private class BarycentricTriangleGradientContext implements PaintContext {
78
79         private final Color color1;
80         private final Color color2;
81         private final Color color3;
82
83         private final ImagePoint2D corner1;
84         private final ImagePoint2D corner2;
85         private final ImagePoint2D corner3;
86
87         private final PackedColorModel colorModel = (PackedColorModel) ColorModel.getRGBdefault();
88
89         public BarycentricTriangleGradientContext(ImagePoint2D corner1, Color color1, ImagePoint2D corner2,
90                 Color color2, ImagePoint2D corner3, Color color3) {
91             this.corner1 = corner1;
92             this.corner2 = corner2;
93             this.corner3 = corner3;
94             this.color1 = color1;
95             this.color2 = color2;
96             this.color3 = color3;
97         }
98
99         @Override
100         public void dispose() {
101         }
102
103         @Override
104         public @Nullable ColorModel getColorModel() {
105             return colorModel;
106         }
107
108         @Override
109         public Raster getRaster(int x, int y, int w, int h) {
110             int[] data = new int[h * w];
111             DataBufferInt buffer = new DataBufferInt(data, w * h);
112             WritableRaster raster = Raster.createPackedRaster(buffer, w, h, w, colorModel.getMasks(), null);
113
114             float denominator = 1f / (((corner2.getY() - corner3.getY()) * (corner1.getX() - corner3.getX()))
115                     + ((corner3.getX() - corner2.getX()) * (corner1.getY() - corner3.getY())));
116
117             for (int yPos = 0; yPos < h; yPos++) {
118                 int imageY = y + yPos;
119                 for (int xPos = 0; xPos < w; xPos++) {
120                     int imageX = xPos + x;
121
122                     float weight1 = (((corner2.getY() - corner3.getY()) * (imageX - corner3.getX()))
123                             + ((corner3.getX() - corner2.getX()) * (imageY - corner3.getY()))) * denominator;
124                     float weight2 = (((corner3.getY() - corner1.getY()) * (imageX - corner3.getX()))
125                             + ((corner1.getX() - corner3.getX()) * (imageY - corner3.getY()))) * denominator;
126                     float weight3 = 1 - weight1 - weight2;
127
128                     if (weight1 < 0 || weight2 < 0 || weight3 < 0) {
129                         // Outside of triangle
130                         data[yPos * w + xPos] = 0;
131                     } else {
132                         Color c = mergeColors(weight1, color1, weight2, color2, weight3, color3);
133                         data[yPos * w + xPos] = c.getRGB();
134                     }
135                 }
136             }
137
138             return raster;
139         }
140
141         private Color mergeColors(float weight1, Color color1, float weight2, Color color2, float weight3,
142                 Color color3) {
143             float normalize = 1f / (weight1 + weight2 + weight3);
144             float r = (color1.getRed() * weight1 + color2.getRed() * weight2 + color3.getRed() * weight3) * normalize;
145             float g = (color1.getGreen() * weight1 + color2.getGreen() * weight2 + color3.getGreen() * weight3)
146                     * normalize;
147             float b = (color1.getBlue() * weight1 + color2.getBlue() * weight2 + color3.getBlue() * weight3)
148                     * normalize;
149             return new Color((int) r, (int) g, (int) b);
150         }
151     }
152 }