Feature/color objects color command (#302)

* added BACnetColorCommand and BACnetxyColor encoding and unit testing

* Added Color object and unit testing.

* Added Color Temperature object and Unit test

* Fix BVLC unit test warning.

* add port Makefile for extra types

* added RGB to and from CIE xy utility in sys folder, and add unit tests.

* added cmake-win32 target

* Change RP and RPM to use known property decoder.

Add color object RP and RPM decoding and printing
Fix RPM print for new reserved range above 4194303
Change default protocol-revision to 24 for Color object

* Integrate Color and Color Temperature objects into demo apps

Co-authored-by: Steve Karg <skarg@users.sourceforge.net>
This commit is contained in:
Steve Karg
2022-07-13 09:54:36 -05:00
committed by GitHub
parent 085de3c385
commit 38d213b47c
80 changed files with 5369 additions and 347 deletions
+486
View File
@@ -0,0 +1,486 @@
/**
* @file
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2022
* @brief computes sRGB to and from from CIE xy and brightness
*
* @section LICENSE
*
* Public domain algorithms from Philips and W3C
*
*/
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
#include "bacnet/basic/sys/color_rgb.h"
/**
* @brief Clamp a double precision value between two limits
* @param d - value to be clamped
* @param min - minimum value to clamp within
* @param max - maximum value to clamp within
* @return value clamped between min and max inclusive
*/
static double clamp(double d, double min, double max)
{
if (isnan(d)) {
return min;
} else {
const double t = d < min ? min : d;
return t > max ? max : t;
}
}
/**
* @brief Convert sRGB to CIE xy
* @param r - R value of sRGB 0..255
* @param g - G value of sRGB 0..255
* @param b - B value of sRGB 0..255
* @param x_coordinate - return x of CIE xy 0.0..1.0
* @param y_coordinate - return y of CIE xy 0.0..1.0
* @param brightness - return brightness of the CIE xy color 0..255
* @note http://en.wikipedia.org/wiki/Srgb
*/
void color_rgb_to_xy(uint8_t r, uint8_t g, uint8_t b,
float *x_coordinate, float *y_coordinate, uint8_t *brightness)
{
/* Get the RGB values from your color object
and convert them to be between 0 and 1.
So the RGB color (255, 0, 100) becomes (1.0, 0.0, 0.39) */
float red = (float)r;
float green = (float)g;
float blue = (float)b;
red /= 255.0f;
green /= 255.0f;
blue /= 255.0f;
/* Apply a gamma correction to the RGB values,
which makes the color more vivid and more the
like the color displayed on the screen of your device.
This gamma correction is also applied to the screen
of your computer or phone, thus we need this to create
the same color on the light as on screen. */
red = (red > 0.04045f) ?
pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) :
(red / 12.92f);
green = (green > 0.04045f) ?
pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) :
(green / 12.92f);
blue = (blue > 0.04045f) ?
pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) :
(blue / 12.92f);
/* Convert the RGB values to XYZ using the
Wide RGB D65 conversion formula */
float X = red * 0.649926f + green * 0.103455f + blue * 0.197109f;
float Y = red * 0.234327f + green * 0.743075f + blue * 0.022598f;
float Z = red * 0.0000000f + green * 0.053077f + blue * 1.035763f;
/* Calculate the xy values from the XYZ values */
float x = X / (X + Y + Z);
float y = Y / (X + Y + Z);
x = clamp(x, 0.0f, 1.0f);
y = clamp(y, 0.0f, 1.0f);
/* copy to return values if possible */
if (x_coordinate) {
*x_coordinate = x;
}
if (y_coordinate) {
*y_coordinate = y;
}
/* Use the Y value of XYZ as brightness
The Y value indicates the brightness
of the converted color. */
Y = Y*255.0f;
Y = clamp(Y, 0.0f, 255.0f);
if (brightness) {
*brightness = (uint8_t)Y;
}
}
/**
* @brief Convert sRGB from CIE xy and brightness
* @param red - return R value of sRGB
* @param green - return G value of sRGB
* @param blue - return B value of sRGB
* @param x_coordinate - x of CIE xy
* @param y_coordinate - y of CIE xy
* @param brightness - brightness of the CIE xy color
* @note http://en.wikipedia.org/wiki/Srgb
*/
void color_rgb_from_xy(uint8_t *red, uint8_t *green, uint8_t *blue,
float x_coordinate, float y_coordinate, uint8_t brightness)
{
/* Calculate XYZ values */
float x = x_coordinate;
float y = y_coordinate;
float z = 1.0f - x - y;
float Y = brightness;
Y /= 255.0f;
float X = (Y / y) * x;
float Z = (Y / y) * z;
/* Convert to RGB using Wide RGB D65 conversion
(THIS IS A D50 conversion currently) */
float r = X * 1.4628067f - Y * 0.1840623f - Z * 0.2743606f;
float g = -X * 0.5217933f + Y * 1.4472381f + Z * 0.0677227f;
float b = X * 0.0349342f - Y * 0.0968930f + Z * 1.2884099f;
/* Apply reverse gamma correction */
r = r <= 0.0031308f ? 12.92f * r :
(1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f;
g = g <= 0.0031308f ? 12.92f * g :
(1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f;
b = b <= 0.0031308f ? 12.92f * b :
(1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f;
/* Convert the RGB values to your color object
The rgb values from the above formulas are
between 0.0 and 1.0. */
r = r*255.0f;
r = clamp(r, 0.0f, 255.0f);
g = g*255;
g = clamp(g, 0.0f, 255.0f);
b = b*255;
b = clamp(b, 0.0f, 255.0f);
/* copy to return value if possible */
if (red) {
*red = (uint8_t)r;
}
if (green) {
*green = (uint8_t)g;
}
if (blue) {
*blue = (uint8_t)b;
}
}
/* table for converting RGB to and from ASCII color names */
struct css_color_rgb {
const char *name;
uint8_t red;
uint8_t green;
uint8_t blue;
};
static struct css_color_rgb CSS_Color_RGB_Table[] = {
{"aliceblue", 240, 248, 255},
{"antiquewhite", 250, 235, 215},
{"aqua", 0, 255, 255},
{"aquamarine", 127, 255, 212},
{"azure", 240, 255, 255},
{"beige", 245, 245, 220},
{"bisque", 255, 228, 196},
{"black", 0, 0, 0},
{"blanchedalmond", 255, 235, 205},
{"blue", 0, 0, 255},
{"blueviolet", 138, 43, 226},
{"brown", 165, 42, 42},
{"burlywood", 222, 184, 135},
{"cadetblue", 95, 158, 160},
{"chartreuse", 127, 255, 0},
{"chocolate", 210, 105, 30},
{"coral", 255, 127, 80},
{"cornflowerblue", 100, 149, 237},
{"cornsilk", 255, 248, 220},
{"crimson", 220, 20, 60},
{"cyan", 0, 255, 255},
{"darkblue", 0, 0, 139},
{"darkcyan", 0, 139, 139},
{"darkgoldenrod", 184, 134, 11},
{"darkgray", 169, 169, 169},
{"darkgreen", 0, 100, 0},
{"darkgrey", 169, 169, 169},
{"darkkhaki", 189, 183, 107},
{"darkmagenta", 139, 0, 139},
{"darkolivegreen", 85, 107, 47},
{"darkorange", 255, 140, 0},
{"darkorchid", 153, 50, 204},
{"darkred", 139, 0, 0},
{"darksalmon", 233, 150, 122},
{"darkseagreen", 143, 188, 143},
{"darkslateblue", 72, 61, 139},
{"darkslategray", 47, 79, 79},
{"darkslategrey", 47, 79, 79},
{"darkturquoise", 0, 206, 209},
{"darkviolet", 148, 0, 211},
{"deeppink", 255, 20, 147},
{"deepskyblue", 0, 191, 255},
{"dimgray", 105, 105, 105},
{"dimgrey", 105, 105, 105},
{"dodgerblue", 30, 144, 255},
{"firebrick", 178, 34, 34},
{"floralwhite", 255, 250, 240},
{"forestgreen", 34, 139, 34},
{"fuchsia", 255, 0, 255},
{"gainsboro", 220, 220, 220},
{"ghostwhite", 248, 248, 255},
{"gold", 255, 215, 0},
{"goldenrod", 218, 165, 32},
{"gray", 128, 128, 128},
{"green", 0, 128, 0},
{"greenyellow", 173, 255, 47},
{"grey", 128, 128, 128},
{"honeydew", 240, 255, 240},
{"hotpink", 255, 105, 180},
{"indianred", 205, 92, 92},
{"indigo", 75, 0, 130},
{"ivory", 255, 255, 240},
{"khaki", 240, 230, 140},
{"lavender", 230, 230, 250},
{"lavenderblush", 255, 240, 245},
{"lawngreen", 124, 252, 0},
{"lemonchiffon", 255, 250, 205},
{"lightblue", 173, 216, 230},
{"lightcoral", 240, 128, 128},
{"lightcyan", 224, 255, 255},
{"lightgoldenrodyellow", 250, 250, 210},
{"lightgray", 211, 211, 211},
{"lightgreen", 144, 238, 144},
{"lightgrey", 211, 211, 211},
{"lightpink", 255, 182, 193},
{"lightsalmon", 255, 160, 122},
{"lightseagreen", 32, 178, 170},
{"lightskyblue", 135, 206, 250},
{"lightslategray", 119, 136, 153},
{"lightslategrey", 119, 136, 153},
{"lightsteelblue", 176, 196, 222},
{"lightyellow", 255, 255, 224},
{"lime", 0, 255, 0},
{"limegreen", 50, 205, 50},
{"linen", 250, 240, 230},
{"magenta", 255, 0, 255},
{"maroon", 128, 0, 0},
{"mediumaquamarine", 102, 205, 170},
{"mediumblue", 0, 0, 205},
{"mediumorchid", 186, 85, 211},
{"mediumpurple", 147, 112, 219},
{"mediumseagreen", 60, 179, 113},
{"mediumslateblue", 123, 104, 238},
{"mediumspringgreen", 0, 250, 154},
{"mediumturquoise", 72, 209, 204},
{"mediumvioletred", 199, 21, 133},
{"midnightblue", 25, 25, 112},
{"mintcream", 245, 255, 250},
{"mistyrose", 255, 228, 225},
{"moccasin", 255, 228, 181},
{"navajowhite", 255, 222, 173},
{"navy", 0, 0, 128},
{"navyblue", 0, 0, 128},
{"oldlace", 253, 245, 230},
{"olive", 128, 128, 0},
{"olivedrab", 107, 142, 35},
{"orange", 255, 165, 0},
{"orangered", 255, 69, 0},
{"orchid", 218, 112, 214},
{"palegoldenrod", 238, 232, 170},
{"palegreen", 152, 251, 152},
{"paleturquoise", 175, 238, 238},
{"palevioletred", 219, 112, 147},
{"papayawhip", 255, 239, 213},
{"peachpuff", 255, 218, 185},
{"peru", 205, 133, 63},
{"pink", 255, 192, 203},
{"plum", 221, 160, 221},
{"powderblue", 176, 224, 230},
{"purple", 128, 0, 128},
{"red", 255, 0, 0},
{"rosybrown", 188, 143, 143},
{"royalblue", 65, 105, 225},
{"saddlebrown", 139, 69, 19},
{"salmon", 250, 128, 114},
{"sandybrown", 244, 164, 96},
{"seagreen", 46, 139, 87},
{"seashell", 255, 245, 238},
{"sienna", 160, 82, 45},
{"silver", 192, 192, 192},
{"skyblue", 135, 206, 235},
{"slateblue", 106, 90, 205},
{"slategray", 112, 128, 144},
{"slategrey", 112, 128, 144},
{"snow", 255, 250, 250},
{"springgreen", 0, 255, 127},
{"steelblue", 70, 130, 180},
{"tan", 210, 180, 140},
{"teal", 0, 128, 128},
{"thistle", 216, 191, 216},
{"tomato", 255, 99, 71},
{"turquoise", 64, 224, 208},
{"violet", 238, 130, 238},
{"wheat", 245, 222, 179},
{"white", 255, 255, 255},
{"whitesmoke", 245, 245, 245},
{"yellow", 255, 255, 0},
{"yellowgreen", 154, 205, 50},
{NULL, 0, 0, 0}
};
/**
* @brief Convert sRGB from CIE xy and brightness
* @param red - return R value of sRGB
* @param green - return G value of sRGB
* @param blue - return B value of sRGB
* @return name - CSS color name from W3C or "" if not found
* @note Official CSS3 colors from w3.org:
* https://www.w3.org/TR/2010/PR-css3-color-20101028/#html4
* names do not have spaces
*/
const char * color_rgb_to_ascii(uint8_t red, uint8_t green, uint8_t blue)
{
const char * name = "";
unsigned index = 0;
while (CSS_Color_RGB_Table[index].name) {
if ((red == CSS_Color_RGB_Table[index].red) &&
(green == CSS_Color_RGB_Table[index].green) &&
(blue == CSS_Color_RGB_Table[index].blue)) {
return CSS_Color_RGB_Table[index].name;
}
index++;
};
return name;
}
/**
* @brief Convert sRGB from CIE xy and brightness
* @param red - return R value of sRGB
* @param green - return G value of sRGB
* @param blue - return B value of sRGB
* @param name - CSS color name from W3C
* @return index 0..color_rgb_count(), where color_rgb_count() is not found.
*/
unsigned color_rgb_from_ascii(uint8_t *red, uint8_t *green, uint8_t *blue,
const char *name)
{
unsigned index = 0;
while (CSS_Color_RGB_Table[index].name) {
if (strcmp(CSS_Color_RGB_Table[index].name, name) == 0) {
if (red) {
*red = CSS_Color_RGB_Table[index].red;
}
if (green) {
*green = CSS_Color_RGB_Table[index].green;
}
if (blue) {
*blue = CSS_Color_RGB_Table[index].blue;
}
break;
}
index++;
};
return index;
}
/**
* @brief Convert sRGB from CIE xy and brightness
* @param red - return R value of sRGB
* @param green - return G value of sRGB
* @param blue - return B value of sRGB
* @return CSS ASCII color name from W3C or NULL if invalid index
*/
const char *color_rgb_from_index(unsigned target_index, uint8_t *red, uint8_t *green, uint8_t *blue)
{
unsigned index = 0;
while (CSS_Color_RGB_Table[index].name) {
if (target_index == index) {
if (red) {
*red = CSS_Color_RGB_Table[index].red;
}
if (green) {
*green = CSS_Color_RGB_Table[index].green;
}
if (blue) {
*blue = CSS_Color_RGB_Table[index].blue;
}
return CSS_Color_RGB_Table[index].name;
}
index++;
};
return NULL;
}
/**
* @brief Gets the number of sRGB names from CSS3 defines in W3C
* @return the number of defined RGB names
*/
unsigned color_rgb_count(void)
{
unsigned count = 0;
while (CSS_Color_RGB_Table[count].name) {
count++;
}
return count;
}
/**
* @brief Return an RGB color from a color temperature in Kelvin.
* @note This is a rough approximation based on the formula
* provided by T. Helland
* http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
*/
void color_rgb_from_temperature(
uint16_t temperature_kelvin,
uint8_t *r, uint8_t *g, uint8_t *b)
{
float red = 0, green = 0, blue = 0;
if (temperature_kelvin < 1000) {
temperature_kelvin = 1000;
} else if (temperature_kelvin > 40000) {
temperature_kelvin = 40000;
}
temperature_kelvin /= 100;
/* Calculate Red */
if (temperature_kelvin <= 66) {
/* Red values below 6600 K are always 255 */
red = 255.0;
} else {
red = temperature_kelvin - 60;
red = 329.698727446 * pow(red, -0.1332047592);
red = clamp(red, 0.0, 255.0);
}
/* Calculate Green */
if (temperature_kelvin <= 66) {
/* Green values below 6600 K */
green = temperature_kelvin;
green = 99.4708025861 * log(green) - 161.1195681661;
} else {
green = temperature_kelvin - 60;
green = 288.1221695283 * pow(green, -0.0755148492);
}
green = clamp(green, 0.0, 255.0);
/* Calculate Blue */
if (temperature_kelvin >= 66) {
/* Blue values above 6600 K */
blue = 255.0;
} else if (temperature_kelvin <= 19) {
/* Blue values below 1900 K */
blue = 0.0;
} else {
blue = temperature_kelvin - 10;
blue = 138.5177312231 * log(blue) - 305.0447927307;
blue = clamp(blue, 0, 255);
}
if (r) {
*r = red;
}
if (g) {
*g = green;
}
if (b) {
*b = blue;
}
}
+44
View File
@@ -0,0 +1,44 @@
/**
* @file
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2022
* @brief API Color sRGB to CIE xy and brightness
*/
#ifndef COLOR_RGB_H
#define COLOR_RGB_H
#include <stdint.h>
#include <stdbool.h>
#include "bacnet/bacnet_stack_exports.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
void color_rgb_to_xy(uint8_t r, uint8_t g, uint8_t b,
float *x_coordinate, float *y_coordinate, uint8_t *brightness);
BACNET_STACK_EXPORT
void color_rgb_from_xy(uint8_t *red, uint8_t *green, uint8_t *blue,
float x_coordinate, float y_coordinate, uint8_t brightness);
BACNET_STACK_EXPORT
const char * color_rgb_to_ascii(uint8_t red, uint8_t green, uint8_t blue);
BACNET_STACK_EXPORT
unsigned color_rgb_from_ascii(uint8_t *red, uint8_t *green, uint8_t *blue,
const char *name);
BACNET_STACK_EXPORT
const char *color_rgb_from_index(unsigned target_index, uint8_t *red,
uint8_t *green, uint8_t *blue);
BACNET_STACK_EXPORT
unsigned color_rgb_count(void);
BACNET_STACK_EXPORT
void color_rgb_from_temperature(
uint16_t temperature_kelvin,
uint8_t *r, uint8_t *g, uint8_t *b);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif