From ee7a596d53f9ead382ec602333978f529d1738f3 Mon Sep 17 00:00:00 2001 From: johan Date: Wed, 21 Aug 2013 21:50:17 +0200 Subject: [PATCH] - HSV transform added - Moved transform of led values to before the device write so transform changes are taken into account --- config/hyperion.config.json | 7 +- config/hyperion.schema.json | 47 ++++-- include/hyperion/Hyperion.h | 17 ++- include/hyperion/ImageProcessor.h | 3 +- libsrc/hyperion/CMakeLists.txt | 2 + libsrc/hyperion/HsvTransform.cpp | 134 ++++++++++++++++++ libsrc/hyperion/HsvTransform.h | 33 +++++ libsrc/hyperion/Hyperion.cpp | 55 +++++-- libsrc/jsonserver/JsonClientConnection.cpp | 12 ++ .../jsonserver/schema/schema-transform.json | 10 ++ src/hyperion-remote/CMakeLists.txt | 2 +- src/hyperion-remote/JsonConnection.cpp | 12 +- src/hyperion-remote/JsonConnection.h | 8 +- src/hyperion-remote/hyperion-remote.cpp | 13 +- 14 files changed, 317 insertions(+), 38 deletions(-) create mode 100644 libsrc/hyperion/HsvTransform.cpp create mode 100644 libsrc/hyperion/HsvTransform.h diff --git a/config/hyperion.config.json b/config/hyperion.config.json index 448d93e3..d094622a 100644 --- a/config/hyperion.config.json +++ b/config/hyperion.config.json @@ -12,11 +12,14 @@ }, "color" : { + "hsv" : { + "saturationGain" : 1.0, + "valueGain" : 1.0 + }, "red" : { "threshold" : 0.0, "gamma" : 1.0, - "adjust" : 1.0, "blacklevel" : 0.0, "whitelevel" : 1.0 }, @@ -24,7 +27,6 @@ { "threshold" : 0.0, "gamma" : 1.0, - "adjust" : 1.0, "blacklevel" : 0.0, "whitelevel" : 1.0 }, @@ -32,7 +34,6 @@ { "threshold" : 0.0, "gamma" : 1.0, - "adjust" : 1.0, "blacklevel" : 0.0, "whitelevel" : 1.0 } diff --git a/config/hyperion.schema.json b/config/hyperion.schema.json index 4fa3a255..482f591a 100644 --- a/config/hyperion.schema.json +++ b/config/hyperion.schema.json @@ -33,6 +33,23 @@ "type":"object", "required":true, "properties": { + "hsv" : { + "type" : "object", + "required" : true, + "properties" : { + "saturationGain" : { + "type" : "number", + "required" : true, + "minimum" : 0.0 + }, + "valueGain" : { + "type" : "number", + "required" : true, + "minimum" : 0.0 + } + }, + "additionalProperties" : false + }, "red": { "type":"object", "required":true, @@ -41,10 +58,6 @@ "type":"number", "required":true }, - "adjust": { - "type":"number", - "required":true - }, "blacklevel": { "type":"number", "required":true @@ -55,7 +68,9 @@ }, "threshold": { "type":"number", - "required":true + "required":true, + "minimum" : 0.0, + "maximum" : 1.0 } } }, @@ -67,13 +82,19 @@ "type":"number", "required":true }, - "adjust": { - "type":"number", - "required":true - }, "blacklevel": { "type":"number", "required":true + }, + "whitelevel": { + "type":"number", + "required":true + }, + "threshold": { + "type":"number", + "required":true, + "minimum" : 0.0, + "maximum" : 1.0 } } }, @@ -85,13 +106,19 @@ "type":"number", "required":true }, - "adjust": { + "whitelevel": { "type":"number", "required":true }, "blacklevel": { "type":"number", "required":true + }, + "threshold": { + "type":"number", + "required":true, + "minimum" : 0.0, + "maximum" : 1.0 } } } diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 03edec7e..1945b566 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -16,8 +16,10 @@ #include // Forward class declaration -namespace hyperion { class ColorTransform; } - +namespace hyperion { + class HsvTransform; + class ColorTransform; +} class Hyperion : public QObject { @@ -27,12 +29,12 @@ public: enum Color { - RED, GREEN, BLUE + RED, GREEN, BLUE, INVALID }; enum Transform { - THRESHOLD, GAMMA, BLACKLEVEL, WHITELEVEL + SATURATION_GAIN, VALUE_GAIN, THRESHOLD, GAMMA, BLACKLEVEL, WHITELEVEL }; static LedString createLedString(const Json::Value& ledsConfig); @@ -69,9 +71,10 @@ private: PriorityMuxer _muxer; - hyperion::ColorTransform* _redTransform; - hyperion::ColorTransform* _greenTransform; - hyperion::ColorTransform* _blueTransform; + hyperion::HsvTransform * _hsvTransform; + hyperion::ColorTransform * _redTransform; + hyperion::ColorTransform * _greenTransform; + hyperion::ColorTransform * _blueTransform; LedDevice* _device; diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index 46e13864..3999b053 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -8,8 +8,7 @@ #include // Forward class declaration -namespace hyperion { class ImageToLedsMap; - class ColorTransform; } +namespace hyperion { class ImageToLedsMap; } /** * The ImageProcessor translates an RGB-image to RGB-values for the leds. The processing is diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index ed76ce33..d00fa54e 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -19,6 +19,7 @@ SET(Hyperion_HEADERS ${CURRENT_SOURCE_DIR}/LedDeviceTest.h ${CURRENT_SOURCE_DIR}/ImageToLedsMap.h ${CURRENT_SOURCE_DIR}/ColorTransform.h + ${CURRENT_SOURCE_DIR}/HsvTransform.h ) SET(Hyperion_SOURCES @@ -32,6 +33,7 @@ SET(Hyperion_SOURCES ${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp ${CURRENT_SOURCE_DIR}/ImageToLedsMap.cpp ${CURRENT_SOURCE_DIR}/ColorTransform.cpp + ${CURRENT_SOURCE_DIR}/HsvTransform.cpp ) QT4_WRAP_CPP(Hyperion_HEADERS_MOC ${Hyperion_QT_HEADERS}) diff --git a/libsrc/hyperion/HsvTransform.cpp b/libsrc/hyperion/HsvTransform.cpp new file mode 100644 index 00000000..650b8699 --- /dev/null +++ b/libsrc/hyperion/HsvTransform.cpp @@ -0,0 +1,134 @@ +#include "HsvTransform.h" + +using namespace hyperion; + +HsvTransform::HsvTransform() : + _saturationGain(1.0), + _valueGain(1.0) +{ +} + +HsvTransform::HsvTransform(double saturationGain, double valueGain) : + _saturationGain(saturationGain), + _valueGain(valueGain) +{ +} + +HsvTransform::~HsvTransform() +{ +} + +void HsvTransform::setSaturationGain(double saturationGain) +{ + _saturationGain = saturationGain; +} + +double HsvTransform::getSaturationGain() const +{ + return _saturationGain; +} + +void HsvTransform::setValueGain(double valueGain) +{ + _valueGain = valueGain; +} + +double HsvTransform::getValueGain() const +{ + return _valueGain; +} + +void HsvTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue) const +{ + if (_saturationGain != 1.0 || _valueGain != 1.0) + { + uint8_t hue, saturation, value; + rgb2hsv(red, green, blue, hue, saturation, value); + + int s = saturation * _saturationGain; + if (s > 255) + saturation = 255; + else + saturation = s; + + int v = value * _valueGain; + if (v > 255) + value = 255; + else + value = v; + + hsv2rgb(hue, saturation, value, red, green, blue); + } +} + +void HsvTransform::rgb2hsv(uint8_t red, uint8_t green, uint8_t blue, uint8_t & hue, uint8_t & saturation, uint8_t & value) +{ + uint8_t rgbMin, rgbMax; + + rgbMin = red < green ? (red < blue ? red : blue) : (green < blue ? green : blue); + rgbMax = red > green ? (red > blue ? red : blue) : (green > blue ? green : blue); + + value = rgbMax; + if (value == 0) + { + hue = 0; + saturation = 0; + return; + } + + saturation = 255 * long(rgbMax - rgbMin) / value; + if (saturation == 0) + { + hue = 0; + return; + } + + if (rgbMax == red) + hue = 0 + 43 * (green - blue) / (rgbMax - rgbMin); + else if (rgbMax == green) + hue = 85 + 43 * (blue - red) / (rgbMax - rgbMin); + else + hue = 171 + 43 * (red - green) / (rgbMax - rgbMin); +} + +void HsvTransform::hsv2rgb(uint8_t hue, uint8_t saturation, uint8_t value, uint8_t & red, uint8_t & green, uint8_t & blue) +{ + uint8_t region, remainder, p, q, t; + + if (saturation == 0) + { + red = value; + green = value; + blue = value; + return; + } + + region = hue / 43; + remainder = (hue - (region * 43)) * 6; + + p = (value * (255 - saturation)) >> 8; + q = (value * (255 - ((saturation * remainder) >> 8))) >> 8; + t = (value * (255 - ((saturation * (255 - remainder)) >> 8))) >> 8; + + switch (region) + { + case 0: + red = value; green = t; blue = p; + break; + case 1: + red = q; green = value; blue = p; + break; + case 2: + red = p; green = value; blue = t; + break; + case 3: + red = p; green = q; blue = value; + break; + case 4: + red = t; green = p; blue = value; + break; + default: + red = value; green = p; blue = q; + break; + } +} diff --git a/libsrc/hyperion/HsvTransform.h b/libsrc/hyperion/HsvTransform.h new file mode 100644 index 00000000..5f67b7fe --- /dev/null +++ b/libsrc/hyperion/HsvTransform.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace hyperion +{ + + class HsvTransform + { + public: + HsvTransform(); + HsvTransform(double saturationGain, double valueGain); + ~HsvTransform(); + + void setSaturationGain(double saturationGain); + double getSaturationGain() const; + + void setValueGain(double valueGain); + double getValueGain() const; + + void transform(uint8_t & red, uint8_t & green, uint8_t & blue) const; + + private: + // integer version of the conversion are faster, but a little less accurate + static void rgb2hsv(uint8_t red, uint8_t green, uint8_t blue, uint8_t & hue, uint8_t & saturation, uint8_t & value); + static void hsv2rgb(uint8_t hue, uint8_t saturation, uint8_t value, uint8_t & red, uint8_t & green, uint8_t & blue); + + private: + double _saturationGain; + double _valueGain; + }; + +} // namespace hyperion diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index fc166b47..cf39a915 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -15,6 +15,7 @@ #include "LedDeviceWs2801.h" #include "LedDeviceTest.h" #include "ColorTransform.h" +#include "HsvTransform.h" using namespace hyperion; @@ -45,6 +46,11 @@ LedDevice* constructDevice(const Json::Value& deviceConfig) return device; } +HsvTransform * createHsvTransform(const Json::Value & hsvConfig) +{ + return new HsvTransform(hsvConfig["saturationGain"].asDouble(), hsvConfig["valueGain"].asDouble()); +} + ColorTransform* createColorTransform(const Json::Value& colorConfig) { const double threshold = colorConfig["threshold"].asDouble(); @@ -78,6 +84,7 @@ LedString Hyperion::createLedString(const Json::Value& ledsConfig) Hyperion::Hyperion(const Json::Value &jsonConfig) : _ledString(createLedString(jsonConfig["leds"])), _muxer(_ledString.leds().size()), + _hsvTransform(createHsvTransform(jsonConfig["color"]["hsv"])), _redTransform(createColorTransform(jsonConfig["color"]["red"])), _greenTransform(createColorTransform(jsonConfig["color"]["green"])), _blueTransform(createColorTransform(jsonConfig["color"]["blue"])), @@ -99,6 +106,9 @@ Hyperion::~Hyperion() // Delete the Led-String delete _device; + // delete he hsv transform + delete _hsvTransform; + // Delete the color-transform delete _blueTransform; delete _greenTransform; @@ -121,14 +131,6 @@ void Hyperion::setColor(int priority, RgbColor & color, const int timeout_ms) void Hyperion::setColors(int priority, std::vector& ledColors, const int timeout_ms) { - // Apply the transform to each led and color-channel - for (RgbColor& color : ledColors) - { - color.red = _redTransform->transform(color.red); - color.green = _greenTransform->transform(color.green); - color.blue = _blueTransform->transform(color.blue); - } - if (timeout_ms > 0) { const uint64_t timeoutTime = QDateTime::currentMSecsSinceEpoch() + timeout_ms; @@ -161,27 +163,40 @@ void Hyperion::setTransform(Hyperion::Transform transform, Hyperion::Color color t = _blueTransform; break; default: - assert(false); + break; } // set transform value switch (transform) { + case SATURATION_GAIN: + _hsvTransform->setSaturationGain(value); + break; + case VALUE_GAIN: + _hsvTransform->setValueGain(value); + break; case THRESHOLD: + assert (t != nullptr); t->setThreshold(value); break; case GAMMA: + assert (t != nullptr); t->setGamma(value); break; case BLACKLEVEL: + assert (t != nullptr); t->setBlacklevel(value); break; case WHITELEVEL: + assert (t != nullptr); t->setWhitelevel(value); break; default: assert(false); } + + // update the led output + update(); } void Hyperion::clear(int priority) @@ -222,19 +237,27 @@ double Hyperion::getTransform(Hyperion::Transform transform, Hyperion::Color col t = _blueTransform; break; default: - assert(false); + break; } // set transform value switch (transform) { + case SATURATION_GAIN: + return _hsvTransform->getSaturationGain(); + case VALUE_GAIN: + return _hsvTransform->getValueGain(); case THRESHOLD: + assert (t != nullptr); return t->getThreshold(); case GAMMA: + assert (t != nullptr); return t->getGamma(); case BLACKLEVEL: + assert (t != nullptr); return t->getBlacklevel(); case WHITELEVEL: + assert (t != nullptr); return t->getWhitelevel(); default: assert(false); @@ -262,8 +285,18 @@ void Hyperion::update() int priority = _muxer.getCurrentPriority(); const PriorityMuxer::InputInfo & priorityInfo = _muxer.getInputInfo(priority); + // Apply the transform to each led and color-channel + std::vector ledColors(priorityInfo.ledColors); + for (RgbColor& color : ledColors) + { + _hsvTransform->transform(color.red, color.green, color.blue); + color.red = _redTransform->transform(color.red); + color.green = _greenTransform->transform(color.green); + color.blue = _blueTransform->transform(color.blue); + } + // Write the data to the device - _device->write(priorityInfo.ledColors); + _device->write(ledColors); // Start the timeout-timer if (priorityInfo.timeoutTime_ms == -1) diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 7eaa8b10..74dc6e9a 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -171,6 +171,8 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &message) // collect transform information Json::Value & transform = info["transform"]; + transform["saturationGain"] = _hyperion->getTransform(Hyperion::SATURATION_GAIN, Hyperion::INVALID); + transform["valueGain"] = _hyperion->getTransform(Hyperion::VALUE_GAIN, Hyperion::INVALID); Json::Value & threshold = transform["threshold"]; threshold.append(_hyperion->getTransform(Hyperion::THRESHOLD, Hyperion::RED)); threshold.append(_hyperion->getTransform(Hyperion::THRESHOLD, Hyperion::GREEN)); @@ -217,6 +219,16 @@ void JsonClientConnection::handleTransformCommand(const Json::Value &message) { const Json::Value & transform = message["transform"]; + if (transform.isMember("saturationGain")) + { + _hyperion->setTransform(Hyperion::SATURATION_GAIN, Hyperion::INVALID, transform["saturationGain"].asDouble()); + } + + if (transform.isMember("valueGain")) + { + _hyperion->setTransform(Hyperion::VALUE_GAIN, Hyperion::INVALID, transform["valueGain"].asDouble()); + } + if (transform.isMember("threshold")) { const Json::Value & threshold = transform["threshold"]; diff --git a/libsrc/jsonserver/schema/schema-transform.json b/libsrc/jsonserver/schema/schema-transform.json index 781c989d..26fdf787 100644 --- a/libsrc/jsonserver/schema/schema-transform.json +++ b/libsrc/jsonserver/schema/schema-transform.json @@ -11,6 +11,16 @@ "type": "object", "required": true, "properties": { + "saturationGain" : { + "type" : "double", + "required" : false, + "minimum" : 0.0 + }, + "valueGain" : { + "type" : "double", + "required" : false, + "minimum" : 0.0 + }, "threshold": { "type": "array", "required": false, diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt index 4a52a29d..2efe938d 100644 --- a/src/hyperion-remote/CMakeLists.txt +++ b/src/hyperion-remote/CMakeLists.txt @@ -30,5 +30,5 @@ qt4_use_modules(hyperion-remote Network) target_link_libraries(hyperion-remote - hyperion-utils + jsoncpp getoptPlusPlus) diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index 41814d27..d742daaa 100644 --- a/src/hyperion-remote/JsonConnection.cpp +++ b/src/hyperion-remote/JsonConnection.cpp @@ -160,7 +160,7 @@ void JsonConnection::clearAll() parseReply(reply); } -void JsonConnection::setTransform(ColorTransformValues *threshold, ColorTransformValues *gamma, ColorTransformValues *blacklevel, ColorTransformValues *whitelevel) +void JsonConnection::setTransform(double * saturation, double * value, ColorTransformValues *threshold, ColorTransformValues *gamma, ColorTransformValues *blacklevel, ColorTransformValues *whitelevel) { std::cout << "Set color transforms" << std::endl; @@ -169,6 +169,16 @@ void JsonConnection::setTransform(ColorTransformValues *threshold, ColorTransfor command["command"] = "transform"; Json::Value & transform = command["transform"]; + if (saturation != nullptr) + { + transform["saturationGain"] = *saturation; + } + + if (value != nullptr) + { + transform["valueGain"] = *value; + } + if (threshold != nullptr) { Json::Value & v = transform["threshold"]; diff --git a/src/hyperion-remote/JsonConnection.h b/src/hyperion-remote/JsonConnection.h index da4a7dc3..025d7e4a 100644 --- a/src/hyperion-remote/JsonConnection.h +++ b/src/hyperion-remote/JsonConnection.h @@ -39,7 +39,13 @@ public: /// Set the color transform of the leds /// Note that providing a NULL will leave the settings on the server unchanged - void setTransform(ColorTransformValues * threshold, ColorTransformValues * gamma, ColorTransformValues * blacklevel, ColorTransformValues * whitelevel); + void setTransform( + double * saturation, + double * value, + ColorTransformValues * threshold, + ColorTransformValues * gamma, + ColorTransformValues * blacklevel, + ColorTransformValues * whitelevel); private: /// Send a json command message and receive its reply diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index 16c833b9..df819add 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -42,9 +42,11 @@ int main(int argc, char * argv[]) IntParameter & argDuration = parameters.add ('d', "duration" , "Specify how long the leds should be switched on in millseconds [default: infinity]"); ColorParameter & argColor = parameters.add ('c', "color" , "Set all leds to a constant color (either RRGGBB hex value or a color name)"); ImageParameter & argImage = parameters.add ('i', "image" , "Set the leds to the colors according to the given image file"); - SwitchParameter<> & argServerInfo = parameters.add >('s', "info" , "List server info"); + SwitchParameter<> & argServerInfo = parameters.add >('l', "list" , "List server info"); SwitchParameter<> & argClear = parameters.add >('x', "clear" , "Clear data for the priority channel provided by the -p option"); SwitchParameter<> & argClearAll = parameters.add >(0x0, "clearall" , "Clear data for all active priority channels"); + DoubleParameter & argSaturation = parameters.add ('s', "saturation", "Set the HSV saturation gain of the leds"); + DoubleParameter & argValue = parameters.add ('v', "value ", "Set the HSV value gain of the leds"); TransformParameter & argGamma = parameters.add('g', "gamma" , "Set the gamma of the leds (requires 3 space seperated values)"); TransformParameter & argThreshold = parameters.add('t', "threshold" , "Set the threshold of the leds (requires 3 space seperated values between 0.0 and 1.0)"); TransformParameter & argBlacklevel = parameters.add('b', "blacklevel", "Set the blacklevel of the leds (requires 3 space seperated values which are normally between 0.0 and 1.0)"); @@ -68,7 +70,7 @@ int main(int argc, char * argv[]) } // check if at least one of the available color transforms is set - bool colorTransform = argThreshold.isSet() || argGamma.isSet() || argBlacklevel.isSet() || argWhitelevel.isSet(); + bool colorTransform = argSaturation.isSet() || argValue.isSet() || argThreshold.isSet() || argGamma.isSet() || argBlacklevel.isSet() || argWhitelevel.isSet(); // check that exactly one command was given int commandCount = count({argColor.isSet(), argImage.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform}); @@ -81,6 +83,8 @@ int main(int argc, char * argv[]) std::cerr << " " << argClear.usageLine() << std::endl; std::cerr << " " << argClearAll.usageLine() << std::endl; std::cerr << "or one or more of the available color transformations:" << std::endl; + std::cerr << " " << argSaturation.usageLine() << std::endl; + std::cerr << " " << argValue.usageLine() << std::endl; std::cerr << " " << argThreshold.usageLine() << std::endl; std::cerr << " " << argGamma.usageLine() << std::endl; std::cerr << " " << argBlacklevel.usageLine() << std::endl; @@ -115,14 +119,19 @@ int main(int argc, char * argv[]) } else if (colorTransform) { + double saturation, value; ColorTransformValues threshold, gamma, blacklevel, whitelevel; + if (argSaturation.isSet()) saturation = argSaturation.getValue(); + if (argValue.isSet()) value = argValue.getValue(); if (argThreshold.isSet()) threshold = argThreshold.getValue(); if (argGamma.isSet()) gamma = argGamma.getValue(); if (argBlacklevel.isSet()) blacklevel = argBlacklevel.getValue(); if (argWhitelevel.isSet()) whitelevel = argWhitelevel.getValue(); connection.setTransform( + argSaturation.isSet() ? &saturation : nullptr, + argValue.isSet() ? &value : nullptr, argThreshold.isSet() ? &threshold : nullptr, argGamma.isSet() ? &gamma : nullptr, argBlacklevel.isSet() ? &blacklevel : nullptr,